summaryrefslogtreecommitdiff
path: root/src/debug
diff options
context:
space:
mode:
Diffstat (limited to 'src/debug')
-rw-r--r--src/debug/.gitmirror1
-rw-r--r--src/debug/CMakeLists.txt6
-rw-r--r--src/debug/SetDebugTargetCoreSysARM.props25
-rw-r--r--src/debug/SetDebugTargetCoreSysX86.props23
-rw-r--r--src/debug/SetDebugTargetLocal.props30
-rw-r--r--src/debug/SetDebugTargetWinARM.props19
-rw-r--r--src/debug/SetDebugTargetWinx86.props8
-rw-r--r--src/debug/XPlatCommon.props41
-rw-r--r--src/debug/clrdbg.sln69
-rw-r--r--src/debug/daccess/.gitmirror1
-rw-r--r--src/debug/daccess/CMakeLists.txt49
-rw-r--r--src/debug/daccess/amd64/.gitmirror1
-rw-r--r--src/debug/daccess/amd64/primitives.cpp13
-rw-r--r--src/debug/daccess/arm/.gitmirror1
-rw-r--r--src/debug/daccess/arm/primitives.cpp9
-rw-r--r--src/debug/daccess/arm64/.gitmirror1
-rw-r--r--src/debug/daccess/arm64/primitives.cpp9
-rw-r--r--src/debug/daccess/daccess.cpp8673
-rw-r--r--src/debug/daccess/daccess.targets67
-rw-r--r--src/debug/daccess/dacdbiimpl.cpp7639
-rw-r--r--src/debug/daccess/dacdbiimpl.h1152
-rw-r--r--src/debug/daccess/dacdbiimpl.inl79
-rw-r--r--src/debug/daccess/dacdbiimpllocks.cpp43
-rw-r--r--src/debug/daccess/dacdbiimplstackwalk.cpp1313
-rw-r--r--src/debug/daccess/dacfn.cpp1505
-rw-r--r--src/debug/daccess/dacimpl.h4015
-rw-r--r--src/debug/daccess/datatargetadapter.cpp255
-rw-r--r--src/debug/daccess/datatargetadapter.h84
-rw-r--r--src/debug/daccess/dirs.proj19
-rw-r--r--src/debug/daccess/enummem.cpp2057
-rw-r--r--src/debug/daccess/fntableaccess.cpp461
-rw-r--r--src/debug/daccess/fntableaccess.h216
-rw-r--r--src/debug/daccess/i386/.gitmirror1
-rw-r--r--src/debug/daccess/i386/primitives.cpp11
-rw-r--r--src/debug/daccess/inspect.cpp3840
-rw-r--r--src/debug/daccess/nidump.cpp9579
-rw-r--r--src/debug/daccess/nidump.h624
-rw-r--r--src/debug/daccess/nidump.inl169
-rw-r--r--src/debug/daccess/reimpl.cpp115
-rw-r--r--src/debug/daccess/request.cpp4377
-rw-r--r--src/debug/daccess/request_svr.cpp348
-rw-r--r--src/debug/daccess/stack.cpp1434
-rw-r--r--src/debug/daccess/stdafx.cpp12
-rw-r--r--src/debug/daccess/stdafx.h111
-rw-r--r--src/debug/daccess/task.cpp5335
-rw-r--r--src/debug/dbgutil/.gitmirror1
-rw-r--r--src/debug/dbgutil/CMakeLists.txt16
-rw-r--r--src/debug/dbgutil/dbgutil.cpp426
-rw-r--r--src/debug/dbgutil/dbgutil.props14
-rw-r--r--src/debug/dbgutil/dirs.proj16
-rw-r--r--src/debug/debug-pal/.gitmirror1
-rw-r--r--src/debug/debug-pal/CMakeLists.txt31
-rw-r--r--src/debug/debug-pal/unix/.gitmirror1
-rw-r--r--src/debug/debug-pal/unix/twowaypipe.cpp181
-rw-r--r--src/debug/debug-pal/win/.gitmirror1
-rw-r--r--src/debug/debug-pal/win/twowaypipe.cpp210
-rw-r--r--src/debug/di/.gitmirror1
-rw-r--r--src/debug/di/CMakeLists.txt77
-rw-r--r--src/debug/di/DI.props86
-rw-r--r--src/debug/di/ICorDebugValueTypes.vsdbin0 -> 96768 bytes
-rw-r--r--src/debug/di/amd64/.gitmirror1
-rw-r--r--src/debug/di/amd64/FloatConversion.asm26
-rw-r--r--src/debug/di/amd64/cordbregisterset.cpp254
-rw-r--r--src/debug/di/amd64/floatconversion.S11
-rw-r--r--src/debug/di/amd64/primitives.cpp12
-rw-r--r--src/debug/di/arm/.gitmirror1
-rw-r--r--src/debug/di/arm/cordbregisterset.cpp150
-rw-r--r--src/debug/di/arm/primitives.cpp7
-rw-r--r--src/debug/di/arm64/.gitmirror1
-rw-r--r--src/debug/di/arm64/cordbregisterset.cpp145
-rw-r--r--src/debug/di/arm64/floatconversion.asm22
-rw-r--r--src/debug/di/arm64/primitives.cpp7
-rw-r--r--src/debug/di/breakpoint.cpp722
-rw-r--r--src/debug/di/classfactory.h82
-rw-r--r--src/debug/di/cordb.cpp572
-rw-r--r--src/debug/di/dbgtransportmanager.cpp231
-rw-r--r--src/debug/di/dbgtransportmanager.h87
-rw-r--r--src/debug/di/dbgtransportpipeline.cpp457
-rw-r--r--src/debug/di/dbi.sln20
-rw-r--r--src/debug/di/dbi.vcxproj143
-rw-r--r--src/debug/di/dirs.proj18
-rw-r--r--src/debug/di/divalue.cpp4564
-rw-r--r--src/debug/di/eventchannel.h264
-rw-r--r--src/debug/di/eventredirectionpipeline.cpp350
-rw-r--r--src/debug/di/eventredirectionpipeline.h145
-rw-r--r--src/debug/di/hash.cpp638
-rw-r--r--src/debug/di/helpers.h210
-rw-r--r--src/debug/di/i386/.gitmirror1
-rw-r--r--src/debug/di/i386/cordbregisterset.cpp222
-rw-r--r--src/debug/di/i386/primitives.cpp10
-rw-r--r--src/debug/di/localeventchannel.cpp499
-rw-r--r--src/debug/di/module.cpp4925
-rw-r--r--src/debug/di/nativepipeline.cpp64
-rw-r--r--src/debug/di/nativepipeline.h228
-rw-r--r--src/debug/di/platformspecific.cpp40
-rw-r--r--src/debug/di/process.cpp15235
-rw-r--r--src/debug/di/publish.cpp1282
-rw-r--r--src/debug/di/remoteeventchannel.cpp342
-rw-r--r--src/debug/di/rsappdomain.cpp1235
-rw-r--r--src/debug/di/rsassembly.cpp320
-rw-r--r--src/debug/di/rsclass.cpp1194
-rw-r--r--src/debug/di/rsenumerator.hpp361
-rw-r--r--src/debug/di/rsfunction.cpp1191
-rw-r--r--src/debug/di/rsmain.cpp2536
-rw-r--r--src/debug/di/rsmda.cpp243
-rw-r--r--src/debug/di/rspriv.h11756
-rw-r--r--src/debug/di/rspriv.inl723
-rw-r--r--src/debug/di/rsregsetcommon.cpp248
-rw-r--r--src/debug/di/rsstackwalk.cpp822
-rw-r--r--src/debug/di/rsthread.cpp11006
-rw-r--r--src/debug/di/rstype.cpp2815
-rw-r--r--src/debug/di/shared.cpp16
-rw-r--r--src/debug/di/shimcallback.cpp1317
-rw-r--r--src/debug/di/shimdatatarget.cpp92
-rw-r--r--src/debug/di/shimdatatarget.h133
-rw-r--r--src/debug/di/shimevents.cpp292
-rw-r--r--src/debug/di/shimlocaldatatarget.cpp471
-rw-r--r--src/debug/di/shimpriv.h1056
-rw-r--r--src/debug/di/shimprocess.cpp1904
-rw-r--r--src/debug/di/shimremotedatatarget.cpp349
-rw-r--r--src/debug/di/shimstackwalk.cpp2264
-rw-r--r--src/debug/di/stdafx.cpp12
-rw-r--r--src/debug/di/stdafx.h63
-rw-r--r--src/debug/di/symbolinfo.cpp1501
-rw-r--r--src/debug/di/symbolinfo.h816
-rw-r--r--src/debug/di/valuehome.cpp1062
-rw-r--r--src/debug/di/windowspipeline.cpp419
-rw-r--r--src/debug/dirs.proj23
-rw-r--r--src/debug/ee/.gitmirror1
-rw-r--r--src/debug/ee/CMakeLists.txt62
-rw-r--r--src/debug/ee/DIRS.proj20
-rw-r--r--src/debug/ee/DebuggerEE.vcproj107
-rw-r--r--src/debug/ee/EE.props60
-rw-r--r--src/debug/ee/amd64/.gitmirror1
-rw-r--r--src/debug/ee/amd64/amd64walker.cpp1181
-rw-r--r--src/debug/ee/amd64/dbghelpers.S156
-rw-r--r--src/debug/ee/amd64/dbghelpers.asm164
-rw-r--r--src/debug/ee/amd64/debuggerregdisplayhelper.cpp41
-rw-r--r--src/debug/ee/amd64/primitives.cpp13
-rw-r--r--src/debug/ee/arm/.gitmirror1
-rw-r--r--src/debug/ee/arm/armwalker.cpp407
-rw-r--r--src/debug/ee/arm/dbghelpers.S60
-rw-r--r--src/debug/ee/arm/dbghelpers.asm90
-rw-r--r--src/debug/ee/arm/primitives.cpp37
-rw-r--r--src/debug/ee/arm64/.gitmirror1
-rw-r--r--src/debug/ee/arm64/arm64walker.cpp476
-rw-r--r--src/debug/ee/arm64/dbghelpers.asm54
-rw-r--r--src/debug/ee/arm64/primitives.cpp15
-rw-r--r--src/debug/ee/canary.cpp324
-rw-r--r--src/debug/ee/canary.h80
-rw-r--r--src/debug/ee/controller.cpp8892
-rw-r--r--src/debug/ee/controller.h1979
-rw-r--r--src/debug/ee/controller.inl56
-rw-r--r--src/debug/ee/dac/.gitmirror1
-rw-r--r--src/debug/ee/dac/CMakeLists.txt6
-rw-r--r--src/debug/ee/dac/dirs.proj19
-rw-r--r--src/debug/ee/dactable.cpp87
-rw-r--r--src/debug/ee/datatest.h58
-rw-r--r--src/debug/ee/debugger.cpp17073
-rw-r--r--src/debug/ee/debugger.h3981
-rw-r--r--src/debug/ee/debugger.inl303
-rw-r--r--src/debug/ee/debuggermodule.cpp444
-rw-r--r--src/debug/ee/frameinfo.cpp2211
-rw-r--r--src/debug/ee/frameinfo.h209
-rw-r--r--src/debug/ee/funceval.cpp3984
-rw-r--r--src/debug/ee/functioninfo.cpp2472
-rw-r--r--src/debug/ee/i386/.gitmirror1
-rw-r--r--src/debug/ee/i386/dbghelpers.asm100
-rw-r--r--src/debug/ee/i386/debuggerregdisplayhelper.cpp18
-rw-r--r--src/debug/ee/i386/primitives.cpp11
-rw-r--r--src/debug/ee/i386/x86walker.cpp500
-rw-r--r--src/debug/ee/rcthread.cpp2142
-rw-r--r--src/debug/ee/shared.cpp15
-rw-r--r--src/debug/ee/stdafx.cpp12
-rw-r--r--src/debug/ee/stdafx.h39
-rw-r--r--src/debug/ee/walker.h255
-rw-r--r--src/debug/ee/wks/.gitmirror1
-rw-r--r--src/debug/ee/wks/CMakeLists.txt68
-rw-r--r--src/debug/ee/wks/wks.nativeproj43
-rw-r--r--src/debug/ildbsymlib/.gitmirror1
-rw-r--r--src/debug/ildbsymlib/CMakeLists.txt18
-rw-r--r--src/debug/ildbsymlib/classfactory.h95
-rw-r--r--src/debug/ildbsymlib/dirs.proj19
-rw-r--r--src/debug/ildbsymlib/ildbsymbols.cpp155
-rw-r--r--src/debug/ildbsymlib/ildbsymlib.props29
-rw-r--r--src/debug/ildbsymlib/ildbsymlib.vcproj213
-rw-r--r--src/debug/ildbsymlib/pch.h41
-rw-r--r--src/debug/ildbsymlib/pdbdata.h92
-rw-r--r--src/debug/ildbsymlib/symbinder.cpp163
-rw-r--r--src/debug/ildbsymlib/symbinder.h73
-rw-r--r--src/debug/ildbsymlib/symread.cpp2765
-rw-r--r--src/debug/ildbsymlib/symread.h554
-rw-r--r--src/debug/ildbsymlib/symwrite.cpp1553
-rw-r--r--src/debug/ildbsymlib/symwrite.h1226
-rw-r--r--src/debug/ildbsymlib/umisc.h69
-rw-r--r--src/debug/inc/.gitmirror1
-rw-r--r--src/debug/inc/amd64/.gitmirror1
-rw-r--r--src/debug/inc/amd64/primitives.h257
-rw-r--r--src/debug/inc/arm/.gitmirror1
-rw-r--r--src/debug/inc/arm/primitives.h179
-rw-r--r--src/debug/inc/arm64/.gitmirror1
-rw-r--r--src/debug/inc/arm64/primitives.h186
-rw-r--r--src/debug/inc/arm_primitives.h113
-rw-r--r--src/debug/inc/common.h323
-rw-r--r--src/debug/inc/coreclrremotedebugginginterfaces.h20
-rw-r--r--src/debug/inc/dacdbiinterface.h2726
-rw-r--r--src/debug/inc/dacdbistructures.h790
-rw-r--r--src/debug/inc/dacdbistructures.inl732
-rw-r--r--src/debug/inc/dbgappdomain.h388
-rw-r--r--src/debug/inc/dbgipcevents.h2360
-rw-r--r--src/debug/inc/dbgipceventtypes.h143
-rw-r--r--src/debug/inc/dbgtargetcontext.h450
-rw-r--r--src/debug/inc/dbgtransportsession.h849
-rw-r--r--src/debug/inc/dbgutil.h93
-rw-r--r--src/debug/inc/ddmarshalutil.h394
-rw-r--r--src/debug/inc/dump/.gitmirror1
-rw-r--r--src/debug/inc/dump/dumpcommon.h108
-rw-r--r--src/debug/inc/eventredirection.h84
-rw-r--r--src/debug/inc/i386/.gitmirror1
-rw-r--r--src/debug/inc/i386/primitives.h223
-rw-r--r--src/debug/inc/readonlydatatargetfacade.h98
-rw-r--r--src/debug/inc/readonlydatatargetfacade.inl139
-rw-r--r--src/debug/inc/stringcopyholder.h59
-rw-r--r--src/debug/inc/twowaypipe.h104
-rw-r--r--src/debug/shared/.gitmirror1
-rw-r--r--src/debug/shared/amd64/.gitmirror1
-rw-r--r--src/debug/shared/amd64/primitives.cpp178
-rw-r--r--src/debug/shared/arm/.gitmirror1
-rw-r--r--src/debug/shared/arm/primitives.cpp93
-rw-r--r--src/debug/shared/arm64/.gitmirror1
-rw-r--r--src/debug/shared/arm64/primitives.cpp83
-rw-r--r--src/debug/shared/dbgtransportsession.cpp2786
-rw-r--r--src/debug/shared/i386/.gitmirror1
-rw-r--r--src/debug/shared/i386/primitives.cpp127
-rw-r--r--src/debug/shared/stringcopyholder.cpp83
-rw-r--r--src/debug/shared/utils.cpp203
-rw-r--r--src/debug/shim/.gitmirror1
-rw-r--r--src/debug/shim/CMakeLists.txt15
-rw-r--r--src/debug/shim/debugshim.cpp657
-rw-r--r--src/debug/shim/debugshim.h90
-rw-r--r--src/debug/shim/debugshim.props19
-rw-r--r--src/debug/shim/dirs.proj16
242 files changed, 203707 insertions, 0 deletions
diff --git a/src/debug/.gitmirror b/src/debug/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/debug/.gitmirror
@@ -0,0 +1 @@
+Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror. \ No newline at end of file
diff --git a/src/debug/CMakeLists.txt b/src/debug/CMakeLists.txt
new file mode 100644
index 0000000000..1940aa9c79
--- /dev/null
+++ b/src/debug/CMakeLists.txt
@@ -0,0 +1,6 @@
+add_subdirectory(daccess)
+add_subdirectory(dbgutil)
+add_subdirectory(ildbsymlib)
+add_subdirectory(ee)
+add_subdirectory(di)
+add_subdirectory(shim)
diff --git a/src/debug/SetDebugTargetCoreSysARM.props b/src/debug/SetDebugTargetCoreSysARM.props
new file mode 100644
index 0000000000..cedb5aaf32
--- /dev/null
+++ b/src/debug/SetDebugTargetCoreSysARM.props
@@ -0,0 +1,25 @@
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <!--*****************************************************-->
+ <!--This MSBuild project file was automatically generated-->
+ <!--from the original SOURCES/DIRS file by the KBC tool.-->
+ <!--*****************************************************-->
+ <!--Import the settings-->
+ <!--Leaf project Properties-->
+ <PropertyGroup>
+ <FeatureDbgipcTransportDI>true</FeatureDbgipcTransportDI>
+ <FeatureDbgipcTransportVM>true</FeatureDbgipcTransportVM>
+ <FeatureIpcman>false</FeatureIpcman>
+ <FeatureInteropDebugging>false</FeatureInteropDebugging>
+
+ <DbgTargetARM>true</DbgTargetARM>
+ <CDefines>$(CDefines);DBG_TARGET_ARM=1</CDefines>
+
+ <DbgTargetCoreSystem>true</DbgTargetCoreSystem>
+ <CDefines>$(CDefines);DBG_TARGET_CORESYSTEM=1</CDefines>
+
+ <DbgTargetSameEndiannes Condition="'$(BuildArchitecture)' == 'i386' or '$(BuildProjectName)' == 'CoreCLR' or '$(BuildProjectName)' == 'CoreSys'">true</DbgTargetSameEndiannes>
+ <CDefines Condition="'$(BuildArchitecture)' == 'i386' or '$(BuildProjectName)' == 'CoreCLR' or '$(BuildProjectName)' == 'CoreSys'">$(CDefines);DBG_TARGET_SAME_ENDIANNESS=1</CDefines>
+ </PropertyGroup>
+ <!--Leaf Project Items-->
+ <!--Import the targets-->
+</Project>
diff --git a/src/debug/SetDebugTargetCoreSysX86.props b/src/debug/SetDebugTargetCoreSysX86.props
new file mode 100644
index 0000000000..594016a0d7
--- /dev/null
+++ b/src/debug/SetDebugTargetCoreSysX86.props
@@ -0,0 +1,23 @@
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <!--*****************************************************-->
+ <!--This MSBuild project file was automatically generated-->
+ <!--from the original SOURCES/DIRS file by the KBC tool.-->
+ <!--*****************************************************-->
+ <!--Import the settings-->
+ <!--Leaf project Properties-->
+ <PropertyGroup>
+ <FeatureDbgipcTransportDI>true</FeatureDbgipcTransportDI>
+ <FeatureDbgipcTransportVM>true</FeatureDbgipcTransportVM>
+ <FeatureIpcman>false</FeatureIpcman>
+ <FeatureInteropDebugging>false</FeatureInteropDebugging>
+
+ <DbgTargetX86>true</DbgTargetX86>
+ <CDefines>$(CDefines);DBG_TARGET_X86=1</CDefines>
+
+ <DbgTargetCoreSystem>true</DbgTargetCoreSystem>
+ <CDefines>$(CDefines);DBG_TARGET_CORESYSTEM=1</CDefines>
+
+ </PropertyGroup>
+ <!--Leaf Project Items-->
+ <!--Import the targets-->
+</Project>
diff --git a/src/debug/SetDebugTargetLocal.props b/src/debug/SetDebugTargetLocal.props
new file mode 100644
index 0000000000..28665150b8
--- /dev/null
+++ b/src/debug/SetDebugTargetLocal.props
@@ -0,0 +1,30 @@
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <!--*****************************************************-->
+ <!--This MSBuild project file was automatically generated-->
+ <!--from the original SOURCES/DIRS file by the KBC tool.-->
+ <!--*****************************************************-->
+ <!--Import the settings-->
+ <!--Leaf project Properties-->
+ <PropertyGroup>
+ <DbgTargetX86 Condition="('$(BuildArchitecture)' == 'i386' or '$(_BuildArch)'=='rotor_x86') and ('$(CrossTargetArchitecture)'=='' or '$(CrossTargetArchitecture)' == 'i386')">1</DbgTargetX86>
+ <CDefines Condition="('$(BuildArchitecture)' == 'i386' or '$(_BuildArch)'=='rotor_x86') and ('$(CrossTargetArchitecture)'=='' or '$(CrossTargetArchitecture)' == 'i386')">$(CDefines);DBG_TARGET_X86=1</CDefines>
+
+ <DbgTargetAmd64 Condition="'$(BuildArchitecture)' == 'amd64' and ('$(CrossTargetArchitecture)'=='' or '$(CrossTargetArchitecture)' == 'amd64')">1</DbgTargetAmd64>
+ <CDefines Condition="'$(BuildArchitecture)' == 'amd64' and ('$(CrossTargetArchitecture)'=='' or '$(CrossTargetArchitecture)' == 'amd64')">$(CDefines);DBG_TARGET_AMD64=1</CDefines>
+
+ <DbgTargetArm Condition="'$(BuildArchitecture)' == 'arm' Or '$(CrossTargetArchitecture)' == 'arm'">1</DbgTargetArm>
+ <CDefines Condition="'$(BuildArchitecture)' == 'arm' Or '$(CrossTargetArchitecture)' == 'arm'">$(CDefines);DBG_TARGET_ARM=1;</CDefines>
+
+ <DbgTargetArm64 Condition="'$(BuildArchitecture)' == 'arm64' Or '$(CrossTargetArchitecture)' == 'arm64'">1</DbgTargetArm64>
+ <CDefines Condition="'$(BuildArchitecture)' == 'arm64' Or '$(CrossTargetArchitecture)' == 'arm64'">$(CDefines);DBG_TARGET_ARM64=1;</CDefines>
+
+ <DbgTargetWin64 Condition="'$(DbgTargetAmd64)' == '1' Or'$(DbgTargetArm64)' == '1'">1</DbgTargetWin64>
+ <CDefines Condition="'$(DbgTargetAmd64)' == '1' Or'$(DbgTargetArm64)' == '1' ">$(CDefines);DBG_TARGET_WIN64=1</CDefines>
+
+ <DbgTarget64bit Condition="'$(DbgTargetWin64)' == '1'">1</DbgTarget64bit>
+ <CDefines Condition="'$(DbgTargetWin64)' == '1'">$(CDefines);DBG_TARGET_64BIT=1</CDefines>
+
+ </PropertyGroup>
+ <!--Leaf Project Items-->
+ <!--Import the targets-->
+</Project>
diff --git a/src/debug/SetDebugTargetWinARM.props b/src/debug/SetDebugTargetWinARM.props
new file mode 100644
index 0000000000..02240f12ca
--- /dev/null
+++ b/src/debug/SetDebugTargetWinARM.props
@@ -0,0 +1,19 @@
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <!--*****************************************************-->
+ <!--This MSBuild project file was automatically generated-->
+ <!--from the original SOURCES/DIRS file by the KBC tool.-->
+ <!--*****************************************************-->
+ <!--Import the settings-->
+ <!--Leaf project Properties-->
+ <PropertyGroup>
+ <FeatureDbgipcTransportDI>false</FeatureDbgipcTransportDI>
+ <FeatureDbgipcTransportVM>false</FeatureDbgipcTransportVM>
+ <FeatureIpcman>false</FeatureIpcman>
+ <FeatureInteropDebugging>false</FeatureInteropDebugging>
+
+ <DbgTargetArm>true</DbgTargetArm>
+ <CDefines>$(CDefines);DBG_TARGET_ARM=1</CDefines>
+ </PropertyGroup>
+ <!--Leaf Project Items-->
+ <!--Import the targets-->
+</Project>
diff --git a/src/debug/SetDebugTargetWinx86.props b/src/debug/SetDebugTargetWinx86.props
new file mode 100644
index 0000000000..bd90a410ab
--- /dev/null
+++ b/src/debug/SetDebugTargetWinx86.props
@@ -0,0 +1,8 @@
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+
+ <DbgTargetX86>1</DbgTargetX86>
+ <CDefines>$(CDefines);DBG_TARGET_X86=1</CDefines>
+
+ </PropertyGroup>
+</Project>
diff --git a/src/debug/XPlatCommon.props b/src/debug/XPlatCommon.props
new file mode 100644
index 0000000000..e7913f8380
--- /dev/null
+++ b/src/debug/XPlatCommon.props
@@ -0,0 +1,41 @@
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+
+ <!--Usage: optionally import a clr\xplat\SetHostXXX.props or SetTargetXXX.props first, then this props file -->
+
+
+ <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\xplat\utility.props"/>
+
+ <PropertyGroup>
+
+ <!-- Is DBI based OOP debugging supported -->
+ <FeatureDbiOopDebugging Condition="'$(XPlatHostBuildDir)'=='HostWinx86'">$(FeatureDbiOopDebugging_HostWindowsx86)</FeatureDbiOopDebugging>
+ <FeatureDbiOopDebugging Condition="'$(XPlatHostBuildDir)'=='HostOneCorex86'">$(FeatureDbiOopDebugging_HostOneCorex86)</FeatureDbiOopDebugging>
+ <FeatureDbiOopDebugging Condition="'$(XPlatHostBuildDir)'=='HostWinAmd64'">$(FeatureDbiOopDebugging_HostWindowsamd64)</FeatureDbiOopDebugging>
+ <FeatureDbiOopDebugging Condition="'$(XPlatHostBuildDir)'=='HostOneCoreAmd64'">$(FeatureDbiOopDebugging_HostOneCoreamd64)</FeatureDbiOopDebugging>
+ <FeatureDbiOopDebugging Condition="'$(XPlatHostBuildDir)'=='HostLocal'">$(FeatureDbiOopDebugging_HostLocal)</FeatureDbiOopDebugging>
+
+ </PropertyGroup>
+
+ <!-- Additional properties used when DBI based OOP debugging is used -->
+ <PropertyGroup Condition="'$(FeatureDbiOopDebugging)'=='true'">
+
+ <!-- These parameters set up the naming convention for CLRDEBUGINFO resources that are added to clr.dll, these resources have PE information for specific builds
+ of DAC and DBI. The naming scheme started with CLRDEBUGINFO only, but with the advent of x-platform debugging had to be expanded. It is now the general form
+ CLRDEBUGINFO$(DebugInfoResourceHostOS)$(DebugInfoResourceHostArch). To keep back-compat if host==target and target is windows that maps to an empty suffix. These values must be
+ in all capital letters because of weird restrictions on win32 resource names -->
+ <DebugInfoResourceHostOS>UNKNOWN</DebugInfoResourceHostOS>
+ <DebugInfoResourceHostOS Condition="'$(HostMachineOS)'=='windows'">WINDOWS</DebugInfoResourceHostOS>
+ <DebugInfoResourceHostOS Condition="'$(HostMachineOS)'=='OneCore'">CORESYS</DebugInfoResourceHostOS>
+
+ <DebugInfoResourceHostArch>UNKNOWN</DebugInfoResourceHostArch>
+ <DebugInfoResourceHostArch Condition="'$(HostMachineArch)'=='x86'">X86</DebugInfoResourceHostArch>
+ <DebugInfoResourceHostArch Condition="'$(HostMachineArch)'=='arm'">ARM</DebugInfoResourceHostArch>
+ <DebugInfoResourceHostArch Condition="'$(HostMachineArch)'=='amd64'">AMD64</DebugInfoResourceHostArch>
+
+ <DebugInfoResourceSuffixValid Condition="'$(DebugInfoResourceHostArch)'!='UNKNOWN' and '$(DebugInfoResourceHostOS)'!='UNKNOWN'">true</DebugInfoResourceSuffixValid>
+ <DebugInfoResourceSuffix Condition=" '$(TargetMachineOS)'=='windows' and '$(XPlatHostMatchesEnvironment)'=='true' "></DebugInfoResourceSuffix>
+ <DebugInfoResourceSuffix Condition="!('$(TargetMachineOS)'=='windows' and '$(XPlatHostMatchesEnvironment)'=='true')">$(DebugInfoResourceHostOS)$(DebugInfoResourceHostArch)</DebugInfoResourceSuffix>
+
+ </PropertyGroup>
+
+</Project>
diff --git a/src/debug/clrdbg.sln b/src/debug/clrdbg.sln
new file mode 100644
index 0000000000..2bee15258e
--- /dev/null
+++ b/src/debug/clrdbg.sln
@@ -0,0 +1,69 @@
+
+Microsoft Visual Studio Solution File, Format Version 11.00
+# Rascal (2010 Version)
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mscordacwks", "..\mscordacwks.vcxproj", "{C5716445-C233-4491-85A4-31B75731DD95}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mscordbi", "..\mscordbi.vcxproj", "{95A6AE03-EC45-4450-93DB-9B21890F79E7}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UtilCode", "..\Utilcode\UtilCode.vcxproj", "{82B95BDA-224E-49B4-8BB7-03E7B9EEFF3B}"
+EndProject
+Global
+ GlobalSection(TeamFoundationVersionControl) = preSolution
+ SccNumberOfProjects = 2
+ SccEnterpriseProvider = {4CA58AB2-18FA-4F8D-95D4-32DDF27D184C}
+ SccTeamFoundationServer = http://vstfdevdiv:8080/
+ SccProjectUniqueName0 = ..\\mscordbi.vcxproj
+ SccProjectName0 = ../
+ SccAuxPath0 = http://vstfdevdiv:8080
+ SccLocalPath0 = ..
+ SccProvider0 = {4CA58AB2-18FA-4F8D-95D4-32DDF27D184C}
+ SccProjectUniqueName1 = ..\\Utilcode\\UtilCode.vcxproj
+ SccProjectName1 = ../Utilcode
+ SccAuxPath1 = http://vstfdevdiv:8080
+ SccLocalPath1 = ..\\Utilcode
+ SccProvider1 = {4CA58AB2-18FA-4F8D-95D4-32DDF27D184C}
+ EndGlobalSection
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ _x86chk|Any Platform = _x86chk|Any Platform
+ _x86dbg|Any Platform = _x86dbg|Any Platform
+ _x86ret|Any Platform = _x86ret|Any Platform
+ amd64chk|Any Platform = amd64chk|Any Platform
+ amd64dbg|Any Platform = amd64dbg|Any Platform
+ amd64ret|Any Platform = amd64ret|Any Platform
+ x86corechk|Any Platform = x86corechk|Any Platform
+ x86coredbg|Any Platform = x86coredbg|Any Platform
+ x86coreret|Any Platform = x86coreret|Any Platform
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {C5716445-C233-4491-85A4-31B75731DD95}._x86chk|Any Platform.ActiveCfg = x86chk|Win32
+ {C5716445-C233-4491-85A4-31B75731DD95}._x86dbg|Any Platform.ActiveCfg = x86dbg|Win32
+ {C5716445-C233-4491-85A4-31B75731DD95}._x86ret|Any Platform.ActiveCfg = x86ret|Win32
+ {C5716445-C233-4491-85A4-31B75731DD95}.amd64chk|Any Platform.ActiveCfg = amd64chk|Win32
+ {C5716445-C233-4491-85A4-31B75731DD95}.amd64dbg|Any Platform.ActiveCfg = amd64dbg|Win32
+ {C5716445-C233-4491-85A4-31B75731DD95}.amd64ret|Any Platform.ActiveCfg = amd64ret|Win32
+ {C5716445-C233-4491-85A4-31B75731DD95}.x86corechk|Any Platform.ActiveCfg = x86corechk|Win32
+ {C5716445-C233-4491-85A4-31B75731DD95}.x86coredbg|Any Platform.ActiveCfg = x86coredbg|Win32
+ {C5716445-C233-4491-85A4-31B75731DD95}.x86coreret|Any Platform.ActiveCfg = x86coreret|Win32
+ {95A6AE03-EC45-4450-93DB-9B21890F79E7}._x86chk|Any Platform.ActiveCfg = x86chk|Win32
+ {95A6AE03-EC45-4450-93DB-9B21890F79E7}._x86dbg|Any Platform.ActiveCfg = x86dbg|Win32
+ {95A6AE03-EC45-4450-93DB-9B21890F79E7}._x86ret|Any Platform.ActiveCfg = x86ret|Win32
+ {95A6AE03-EC45-4450-93DB-9B21890F79E7}.amd64chk|Any Platform.ActiveCfg = amd64chk|Win32
+ {95A6AE03-EC45-4450-93DB-9B21890F79E7}.amd64dbg|Any Platform.ActiveCfg = amd64dbg|Win32
+ {95A6AE03-EC45-4450-93DB-9B21890F79E7}.amd64ret|Any Platform.ActiveCfg = amd64ret|Win32
+ {95A6AE03-EC45-4450-93DB-9B21890F79E7}.x86corechk|Any Platform.ActiveCfg = x86corechk|Win32
+ {95A6AE03-EC45-4450-93DB-9B21890F79E7}.x86coredbg|Any Platform.ActiveCfg = x86coredbg|Win32
+ {95A6AE03-EC45-4450-93DB-9B21890F79E7}.x86coreret|Any Platform.ActiveCfg = x86coreret|Win32
+ {82B95BDA-224E-49B4-8BB7-03E7B9EEFF3B}._x86chk|Any Platform.ActiveCfg = x86chk|Win32
+ {82B95BDA-224E-49B4-8BB7-03E7B9EEFF3B}._x86dbg|Any Platform.ActiveCfg = x86dbg|Win32
+ {82B95BDA-224E-49B4-8BB7-03E7B9EEFF3B}._x86ret|Any Platform.ActiveCfg = x86ret|Win32
+ {82B95BDA-224E-49B4-8BB7-03E7B9EEFF3B}.amd64chk|Any Platform.ActiveCfg = amd64chk|Win32
+ {82B95BDA-224E-49B4-8BB7-03E7B9EEFF3B}.amd64dbg|Any Platform.ActiveCfg = amd64dbg|Win32
+ {82B95BDA-224E-49B4-8BB7-03E7B9EEFF3B}.amd64ret|Any Platform.ActiveCfg = amd64ret|Win32
+ {82B95BDA-224E-49B4-8BB7-03E7B9EEFF3B}.x86corechk|Any Platform.ActiveCfg = x86corechk|Win32
+ {82B95BDA-224E-49B4-8BB7-03E7B9EEFF3B}.x86coredbg|Any Platform.ActiveCfg = x86coredbg|Win32
+ {82B95BDA-224E-49B4-8BB7-03E7B9EEFF3B}.x86coreret|Any Platform.ActiveCfg = x86coreret|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/src/debug/daccess/.gitmirror b/src/debug/daccess/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/debug/daccess/.gitmirror
@@ -0,0 +1 @@
+Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror. \ No newline at end of file
diff --git a/src/debug/daccess/CMakeLists.txt b/src/debug/daccess/CMakeLists.txt
new file mode 100644
index 0000000000..30068450b4
--- /dev/null
+++ b/src/debug/daccess/CMakeLists.txt
@@ -0,0 +1,49 @@
+
+include(${CLR_DIR}/dac.cmake)
+
+add_definitions(-DFEATURE_NO_HOST)
+
+include_directories(BEFORE ${VM_DIR})
+include_directories(BEFORE ${VM_DIR}/${ARCH_SOURCES_DIR})
+include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR})
+include_directories(${CLR_DIR}/src/debug/ee)
+include_directories(${CLR_DIR}/src/gc)
+include_directories(${CLR_DIR}/src/gcdump)
+
+if(CLR_CMAKE_PLATFORM_UNIX)
+ include_directories(${GENERATED_INCLUDE_DIR})
+ add_compile_options(-fPIC)
+endif(CLR_CMAKE_PLATFORM_UNIX)
+
+set(DACCESS_SOURCES
+ dacdbiimpl.cpp
+ dacdbiimpllocks.cpp
+ dacdbiimplstackwalk.cpp
+ daccess.cpp
+ dacfn.cpp
+ enummem.cpp
+ fntableaccess.cpp
+ inspect.cpp
+ reimpl.cpp
+ request.cpp
+ request_svr.cpp
+ stack.cpp
+ task.cpp
+ nidump.cpp
+ datatargetadapter.cpp
+)
+
+include_directories(${ARCH_SOURCES_DIR})
+ list(APPEND DACCESS_SOURCES
+ ${ARCH_SOURCES_DIR}/primitives.cpp
+ )
+
+convert_to_absolute_path(DACCESS_SOURCES ${DACCESS_SOURCES})
+
+add_precompiled_header(stdafx.h stdafx.cpp DACCESS_SOURCES)
+
+add_library_clr(daccess ${DACCESS_SOURCES})
+
+if(CLR_CMAKE_PLATFORM_UNIX)
+ add_dependencies(daccess coreclr)
+endif(CLR_CMAKE_PLATFORM_UNIX)
diff --git a/src/debug/daccess/amd64/.gitmirror b/src/debug/daccess/amd64/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/debug/daccess/amd64/.gitmirror
@@ -0,0 +1 @@
+Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror. \ No newline at end of file
diff --git a/src/debug/daccess/amd64/primitives.cpp b/src/debug/daccess/amd64/primitives.cpp
new file mode 100644
index 0000000000..055b75d120
--- /dev/null
+++ b/src/debug/daccess/amd64/primitives.cpp
@@ -0,0 +1,13 @@
+// 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 "stdafx.h"
+
+#include "../../shared/amd64/primitives.cpp"
+
+
diff --git a/src/debug/daccess/arm/.gitmirror b/src/debug/daccess/arm/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/debug/daccess/arm/.gitmirror
@@ -0,0 +1 @@
+Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror. \ No newline at end of file
diff --git a/src/debug/daccess/arm/primitives.cpp b/src/debug/daccess/arm/primitives.cpp
new file mode 100644
index 0000000000..ce8263ad49
--- /dev/null
+++ b/src/debug/daccess/arm/primitives.cpp
@@ -0,0 +1,9 @@
+// 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 "stdafx.h"
+
+#include "../../shared/arm/primitives.cpp"
diff --git a/src/debug/daccess/arm64/.gitmirror b/src/debug/daccess/arm64/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/debug/daccess/arm64/.gitmirror
@@ -0,0 +1 @@
+Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror. \ No newline at end of file
diff --git a/src/debug/daccess/arm64/primitives.cpp b/src/debug/daccess/arm64/primitives.cpp
new file mode 100644
index 0000000000..a2e52138ac
--- /dev/null
+++ b/src/debug/daccess/arm64/primitives.cpp
@@ -0,0 +1,9 @@
+// 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 "stdafx.h"
+
+#include "../../shared/arm64/primitives.cpp"
diff --git a/src/debug/daccess/daccess.cpp b/src/debug/daccess/daccess.cpp
new file mode 100644
index 0000000000..ba3995b1f7
--- /dev/null
+++ b/src/debug/daccess/daccess.cpp
@@ -0,0 +1,8673 @@
+// 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.
+//*****************************************************************************
+// File: daccess.cpp
+//
+
+//
+// ClrDataAccess implementation.
+//
+//*****************************************************************************
+
+#include "stdafx.h"
+#include <clrdata.h>
+#include "typestring.h"
+#include "holder.h"
+#include "debuginfostore.h"
+#include "peimagelayout.inl"
+#include "datatargetadapter.h"
+#include "readonlydatatargetfacade.h"
+#include "metadataexports.h"
+#include "excep.h"
+#include "debugger.h"
+#include "dwreport.h"
+#include "primitives.h"
+#include "dbgutil.h"
+#ifdef FEATURE_PAL
+#include <dactablerva.h>
+#endif
+
+#include "dwbucketmanager.hpp"
+
+// To include definiton of IsThrowableThreadAbortException
+// #include <exstatecommon.h>
+
+CRITICAL_SECTION g_dacCritSec;
+ClrDataAccess* g_dacImpl;
+HINSTANCE g_thisModule;
+
+extern VOID STDMETHODCALLTYPE TLS_FreeMasterSlotIndex();
+
+EXTERN_C BOOL WINAPI
+DllMain(HANDLE instance, DWORD reason, LPVOID reserved)
+{
+ static bool g_procInitialized = false;
+
+ switch(reason)
+ {
+ case DLL_PROCESS_ATTACH:
+ {
+ if (g_procInitialized)
+ {
+#ifdef FEATURE_PAL
+ // Double initialization can happen on Unix
+ // in case of manual load of DAC shared lib and calling DllMain
+ // not a big deal, we just ignore it.
+ return TRUE;
+#else
+ return FALSE;
+#endif
+ }
+
+#ifdef FEATURE_PAL
+ int err = PAL_InitializeDLL();
+ if(err != 0)
+ {
+ return FALSE;
+ }
+#endif
+ InitializeCriticalSection(&g_dacCritSec);
+
+ // Save the module handle.
+ g_thisModule = (HINSTANCE)instance;
+
+ g_procInitialized = true;
+ break;
+ }
+
+ case DLL_PROCESS_DETACH:
+ // It's possible for this to be called without ATTACH completing (eg. if it failed)
+ if (g_procInitialized)
+ {
+ DeleteCriticalSection(&g_dacCritSec);
+ }
+#ifndef FEATURE_PAL
+ TLS_FreeMasterSlotIndex();
+#endif
+ g_procInitialized = false;
+ break;
+ }
+
+ return TRUE;
+}
+
+HINSTANCE
+GetModuleInst(void)
+{
+ return g_thisModule;
+}
+
+HRESULT
+ConvertUtf8(__in LPCUTF8 utf8,
+ ULONG32 bufLen,
+ ULONG32* nameLen,
+ __out_ecount_part_opt(bufLen, *nameLen) PWSTR buffer)
+{
+ if (nameLen)
+ {
+ *nameLen = WszMultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0);
+ if (!*nameLen)
+ {
+ return HRESULT_FROM_GetLastError();
+ }
+ }
+
+ if (buffer && bufLen)
+ {
+ if (!WszMultiByteToWideChar(CP_UTF8, 0, utf8, -1, buffer, bufLen))
+ {
+ return HRESULT_FROM_GetLastError();
+ }
+ }
+
+ return S_OK;
+}
+
+HRESULT
+AllocUtf8(__in_opt LPCWSTR wstr,
+ ULONG32 srcChars,
+ __deref_out LPUTF8* utf8)
+{
+ ULONG32 chars = WszWideCharToMultiByte(CP_UTF8, 0, wstr, srcChars,
+ NULL, 0, NULL, NULL);
+ if (!chars)
+ {
+ return HRESULT_FROM_GetLastError();
+ }
+
+ // Make sure the converted string is always terminated.
+ if (srcChars != (ULONG32)-1)
+ {
+ if (!ClrSafeInt<ULONG32>::addition(chars, 1, chars))
+ {
+ return HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW);
+ }
+ }
+
+ char* mem = new (nothrow) char[chars];
+ if (!mem)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ if (!WszWideCharToMultiByte(CP_UTF8, 0, wstr, srcChars,
+ mem, chars, NULL, NULL))
+ {
+ HRESULT hr = HRESULT_FROM_GetLastError();
+ delete [] mem;
+ return hr;
+ }
+
+ if (srcChars != (ULONG32)-1)
+ {
+ mem[chars - 1] = 0;
+ }
+
+ *utf8 = mem;
+ return S_OK;
+}
+
+HRESULT
+GetFullClassNameFromMetadata(IMDInternalImport* mdImport,
+ mdTypeDef classToken,
+ ULONG32 bufferChars,
+ __inout_ecount(bufferChars) LPUTF8 buffer)
+{
+ HRESULT hr;
+ LPCUTF8 baseName, namespaceName;
+
+ IfFailRet(mdImport->GetNameOfTypeDef(classToken, &baseName, &namespaceName));
+ return ns::MakePath(buffer, bufferChars, namespaceName, baseName) ?
+ S_OK : E_OUTOFMEMORY;
+}
+
+HRESULT
+GetFullMethodNameFromMetadata(IMDInternalImport* mdImport,
+ mdMethodDef methodToken,
+ ULONG32 bufferChars,
+ __inout_ecount(bufferChars) LPUTF8 buffer)
+{
+ HRESULT status;
+ HRESULT hr;
+ mdTypeDef classToken;
+ size_t len;
+
+ if (mdImport->GetParentToken(methodToken, &classToken) == S_OK)
+ {
+ if ((status =
+ GetFullClassNameFromMetadata(mdImport, classToken,
+ bufferChars, buffer)) != S_OK)
+ {
+ return status;
+ }
+
+ len = strlen(buffer);
+ buffer += len;
+ bufferChars -= static_cast<ULONG32>(len) + 1;
+
+ if (!bufferChars)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ *buffer++ = NAMESPACE_SEPARATOR_CHAR;
+ }
+
+ LPCUTF8 methodName;
+ IfFailRet(mdImport->GetNameOfMethodDef(methodToken, &methodName));
+// Review conversion of size_t to ULONG32.
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable:4267)
+#endif
+ len = strlen(methodName);
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+ if (len >= bufferChars)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ strcpy_s(buffer, bufferChars, methodName);
+ return S_OK;
+}
+
+HRESULT
+SplitFullName(__in_z __in PCWSTR fullName,
+ SplitSyntax syntax,
+ ULONG32 memberDots,
+ __deref_out_opt LPUTF8* namespaceName,
+ __deref_out_opt LPUTF8* typeName,
+ __deref_out_opt LPUTF8* memberName,
+ __deref_out_opt LPUTF8* params)
+{
+ HRESULT status;
+ PCWSTR paramsStart, memberStart, memberEnd, typeStart;
+
+ if (!*fullName)
+ {
+ return E_INVALIDARG;
+ }
+
+ //
+ // Split off parameters.
+ //
+
+ paramsStart = wcschr(fullName, W('('));
+ if (paramsStart)
+ {
+ if (syntax != SPLIT_METHOD ||
+ paramsStart == fullName)
+ {
+ return E_INVALIDARG;
+ }
+
+ if ((status = AllocUtf8(paramsStart, (ULONG32)-1, params)) != S_OK)
+ {
+ return status;
+ }
+
+ memberEnd = paramsStart - 1;
+ }
+ else
+ {
+ *params = NULL;
+ memberEnd = fullName + (wcslen(fullName) - 1);
+ }
+
+ if (syntax != SPLIT_TYPE)
+ {
+ //
+ // Split off member name.
+ //
+
+ memberStart = memberEnd;
+
+ for (;;)
+ {
+ while (memberStart >= fullName &&
+ *memberStart != W('.'))
+ {
+ memberStart--;
+ }
+
+ // Some member names (e.g. .ctor and .dtor) have
+ // dots, so go back to the first dot.
+ while (memberStart > fullName &&
+ memberStart[-1] == W('.'))
+ {
+ memberStart--;
+ }
+
+ if (memberStart <= fullName)
+ {
+ if (memberDots > 0)
+ {
+ // Caller expected dots in the
+ // member name and they weren't found.
+ status = E_INVALIDARG;
+ goto DelParams;
+ }
+
+ break;
+ }
+ else if (memberDots == 0)
+ {
+ break;
+ }
+
+ memberStart--;
+ memberDots--;
+ }
+
+ memberStart++;
+ if (memberStart > memberEnd)
+ {
+ status = E_INVALIDARG;
+ goto DelParams;
+ }
+
+ if ((status = AllocUtf8(memberStart, (ULONG32)
+ (memberEnd - memberStart) + 1,
+ memberName)) != S_OK)
+ {
+ goto DelParams;
+ }
+ }
+ else
+ {
+ *memberName = NULL;
+ memberStart = memberEnd + 2;
+ }
+
+ //
+ // Split off type name.
+ //
+
+ if (memberStart > fullName)
+ {
+ // Must have at least one character for the type
+ // name. If there was a member name, there must
+ // also be a separator.
+ if (memberStart < fullName + 2)
+ {
+ status = E_INVALIDARG;
+ goto DelMember;
+ }
+
+ typeStart = memberStart - 2;
+ while (typeStart >= fullName &&
+ *typeStart != W('.'))
+ {
+ typeStart--;
+ }
+ typeStart++;
+
+ if ((status = AllocUtf8(typeStart, (ULONG32)
+ (memberStart - typeStart) - 1,
+ typeName)) != S_OK)
+ {
+ goto DelMember;
+ }
+ }
+ else
+ {
+ *typeName = NULL;
+ typeStart = fullName;
+ }
+
+ //
+ // Namespace must be the rest.
+ //
+
+ if (typeStart > fullName)
+ {
+ if ((status = AllocUtf8(fullName, (ULONG32)
+ (typeStart - fullName) - 1,
+ namespaceName)) != S_OK)
+ {
+ goto DelType;
+ }
+ }
+ else
+ {
+ *namespaceName = NULL;
+ }
+
+ return S_OK;
+
+ DelType:
+ delete [] (*typeName);
+ DelMember:
+ delete [] (*memberName);
+ DelParams:
+ delete [] (*params);
+ return status;
+}
+
+int
+CompareUtf8(__in LPCUTF8 str1, __in LPCUTF8 str2, __in ULONG32 nameFlags)
+{
+ if (nameFlags & CLRDATA_BYNAME_CASE_INSENSITIVE)
+ {
+ // XXX Microsoft - Convert to Unicode?
+ return SString::_stricmp(str1, str2);
+ }
+
+ return strcmp(str1, str2);
+}
+
+//----------------------------------------------------------------------------
+//
+// MetaEnum.
+//
+//----------------------------------------------------------------------------
+
+HRESULT
+MetaEnum::Start(IMDInternalImport* mdImport, ULONG32 kind,
+ mdToken container)
+{
+ HRESULT status;
+
+ switch(kind)
+ {
+ case mdtTypeDef:
+ status = mdImport->EnumTypeDefInit(&m_enum);
+ break;
+ case mdtMethodDef:
+ case mdtFieldDef:
+ status = mdImport->EnumInit(kind, container, &m_enum);
+ break;
+ default:
+ return E_INVALIDARG;
+ }
+ if (status != S_OK)
+ {
+ return status;
+ }
+
+ m_mdImport = mdImport;
+ m_kind = kind;
+
+ return S_OK;
+}
+
+void
+MetaEnum::End(void)
+{
+ if (!m_mdImport)
+ {
+ return;
+ }
+
+ switch(m_kind)
+ {
+ case mdtTypeDef:
+ m_mdImport->EnumTypeDefClose(&m_enum);
+ break;
+ case mdtMethodDef:
+ case mdtFieldDef:
+ m_mdImport->EnumClose(&m_enum);
+ break;
+ }
+
+ Clear();
+}
+
+HRESULT
+MetaEnum::NextToken(mdToken* token,
+ __deref_opt_out_opt LPCUTF8* namespaceName,
+ __deref_opt_out_opt LPCUTF8* name)
+{
+ HRESULT hr;
+ if (!m_mdImport)
+ {
+ return E_INVALIDARG;
+ }
+
+ switch(m_kind)
+ {
+ case mdtTypeDef:
+ if (!m_mdImport->EnumTypeDefNext(&m_enum, token))
+ {
+ return S_FALSE;
+ }
+ m_lastToken = *token;
+ if (namespaceName || name)
+ {
+ LPCSTR _name, _namespaceName;
+
+ IfFailRet(m_mdImport->GetNameOfTypeDef(*token, &_name, &_namespaceName));
+ if (namespaceName)
+ {
+ *namespaceName = _namespaceName;
+ }
+ if (name)
+ {
+ *name = _name;
+ }
+ }
+ return S_OK;
+
+ case mdtMethodDef:
+ if (!m_mdImport->EnumNext(&m_enum, token))
+ {
+ return S_FALSE;
+ }
+ m_lastToken = *token;
+ if (namespaceName)
+ {
+ *namespaceName = NULL;
+ }
+ if (name != NULL)
+ {
+ IfFailRet(m_mdImport->GetNameOfMethodDef(*token, name));
+ }
+ return S_OK;
+
+ case mdtFieldDef:
+ if (!m_mdImport->EnumNext(&m_enum, token))
+ {
+ return S_FALSE;
+ }
+ m_lastToken = *token;
+ if (namespaceName)
+ {
+ *namespaceName = NULL;
+ }
+ if (name != NULL)
+ {
+ IfFailRet(m_mdImport->GetNameOfFieldDef(*token, name));
+ }
+ return S_OK;
+
+ default:
+ return E_INVALIDARG;
+ }
+}
+
+HRESULT
+MetaEnum::NextDomainToken(AppDomain** appDomain,
+ mdToken* token)
+{
+ HRESULT status;
+
+ if (m_appDomain)
+ {
+ // Use only the caller-provided app domain.
+ *appDomain = m_appDomain;
+ return NextToken(token, NULL, NULL);
+ }
+
+ //
+ // Splay tokens across all app domains.
+ //
+
+ for (;;)
+ {
+ if (m_lastToken == mdTokenNil)
+ {
+ // Need to fetch a token.
+ if ((status = NextToken(token, NULL, NULL)) != S_OK)
+ {
+ return status;
+ }
+
+ m_domainIter.Init();
+ }
+
+ if (m_domainIter.Next())
+ {
+ break;
+ }
+
+ m_lastToken = mdTokenNil;
+ }
+
+ *appDomain = m_domainIter.GetDomain();
+ *token = m_lastToken;
+
+ return S_OK;
+}
+
+HRESULT
+MetaEnum::NextTokenByName(__in_opt LPCUTF8 namespaceName,
+ __in_opt LPCUTF8 name,
+ ULONG32 nameFlags,
+ mdToken* token)
+{
+ HRESULT status;
+ LPCUTF8 tokNamespace, tokName;
+
+ for (;;)
+ {
+ if ((status = NextToken(token, &tokNamespace, &tokName)) != S_OK)
+ {
+ return status;
+ }
+
+ if (namespaceName &&
+ (!tokNamespace ||
+ CompareUtf8(namespaceName, tokNamespace, nameFlags) != 0))
+ {
+ continue;
+ }
+ if (name &&
+ (!tokName ||
+ CompareUtf8(name, tokName, nameFlags) != 0))
+ {
+ continue;
+ }
+
+ return S_OK;
+ }
+}
+
+HRESULT
+MetaEnum::NextDomainTokenByName(__in_opt LPCUTF8 namespaceName,
+ __in_opt LPCUTF8 name,
+ ULONG32 nameFlags,
+ AppDomain** appDomain, mdToken* token)
+{
+ HRESULT status;
+
+ if (m_appDomain)
+ {
+ // Use only the caller-provided app domain.
+ *appDomain = m_appDomain;
+ return NextTokenByName(namespaceName, name, nameFlags, token);
+ }
+
+ //
+ // Splay tokens across all app domains.
+ //
+
+ for (;;)
+ {
+ if (m_lastToken == mdTokenNil)
+ {
+ // Need to fetch a token.
+ if ((status = NextTokenByName(namespaceName, name, nameFlags,
+ token)) != S_OK)
+ {
+ return status;
+ }
+
+ m_domainIter.Init();
+ }
+
+ if (m_domainIter.Next())
+ {
+ break;
+ }
+
+ m_lastToken = mdTokenNil;
+ }
+
+ *appDomain = m_domainIter.GetDomain();
+ *token = m_lastToken;
+
+ return S_OK;
+}
+
+HRESULT
+MetaEnum::New(Module* mod,
+ ULONG32 kind,
+ mdToken container,
+ IXCLRDataAppDomain* pubAppDomain,
+ MetaEnum** metaEnumRet,
+ CLRDATA_ENUM* handle)
+{
+ HRESULT status;
+ MetaEnum* metaEnum;
+
+ if (handle)
+ {
+ *handle = TO_CDENUM(NULL);
+ }
+
+ if (!mod->GetFile()->HasMetadata())
+ {
+ return S_FALSE;
+ }
+
+ metaEnum = new (nothrow) MetaEnum;
+ if (!metaEnum)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ if ((status = metaEnum->
+ Start(mod->GetMDImport(), kind, container)) != S_OK)
+ {
+ delete metaEnum;
+ return status;
+ }
+
+ if (pubAppDomain)
+ {
+ metaEnum->m_appDomain =
+ ((ClrDataAppDomain*)pubAppDomain)->GetAppDomain();
+ }
+
+ if (metaEnumRet)
+ {
+ *metaEnumRet = metaEnum;
+ }
+ if (handle)
+ {
+ *handle = TO_CDENUM(metaEnum);
+ }
+ return S_OK;
+}
+
+//----------------------------------------------------------------------------
+//
+// SplitName
+//
+//----------------------------------------------------------------------------
+
+SplitName::SplitName(SplitSyntax syntax, ULONG32 nameFlags,
+ ULONG32 memberDots)
+{
+ m_syntax = syntax;
+ m_nameFlags = nameFlags;
+ m_memberDots = memberDots;
+
+ Clear();
+}
+
+void
+SplitName::Delete(void)
+{
+ delete [] m_namespaceName;
+ m_namespaceName = NULL;
+ delete [] m_typeName;
+ m_typeName = NULL;
+ delete [] m_memberName;
+ m_memberName = NULL;
+ delete [] m_params;
+ m_params = NULL;
+}
+
+void
+SplitName::Clear(void)
+{
+ m_namespaceName = NULL;
+ m_typeName = NULL;
+ m_typeToken = mdTypeDefNil;
+ m_memberName = NULL;
+ m_memberToken = mdTokenNil;
+ m_params = NULL;
+
+ m_tlsThread = NULL;
+ m_metaEnum.m_appDomain = NULL;
+ m_module = NULL;
+ m_lastField = NULL;
+}
+
+HRESULT
+SplitName::SplitString(__in_opt PCWSTR fullName)
+{
+ if (m_syntax == SPLIT_NO_NAME)
+ {
+ if (fullName)
+ {
+ return E_INVALIDARG;
+ }
+
+ return S_OK;
+ }
+ else if (!fullName)
+ {
+ return E_INVALIDARG;
+ }
+
+ return SplitFullName(fullName,
+ m_syntax,
+ m_memberDots,
+ &m_namespaceName,
+ &m_typeName,
+ &m_memberName,
+ &m_params);
+}
+
+FORCEINLINE
+WCHAR* wcrscan(LPCWSTR beg, LPCWSTR end, WCHAR ch)
+{
+ //_ASSERTE(beg <= end);
+ WCHAR *p;
+ for (p = (WCHAR*)end; p >= beg; --p)
+ {
+ if (*p == ch)
+ break;
+ }
+ return p;
+}
+
+// This functions allocates a new UTF8 string that contains the classname
+// lying between the current sepName and the previous sepName. E.g. for a
+// class name of "Outer+middler+inner" when sepName points to the NULL
+// terminator this function will return "inner" in pResult and will update
+// sepName to point to the second '+' character in the string. When sepName
+// points to the first '+' character this function will return "Outer" in
+// pResult and sepName will point one WCHAR before fullName.
+HRESULT NextEnclosingClasName(LPCWSTR fullName, __deref_inout LPWSTR& sepName, __deref_out LPUTF8 *pResult)
+{
+ if (sepName < fullName)
+ {
+ return E_FAIL;
+ }
+ //_ASSERTE(*sepName == W('\0') || *sepName == W('+') || *sepName == W('/'));
+
+ LPWSTR origInnerName = sepName-1;
+ if ((sepName = wcrscan(fullName, origInnerName, W('+'))) < fullName)
+ {
+ sepName = wcrscan(fullName, origInnerName, W('/'));
+ }
+
+ return AllocUtf8(sepName+1, static_cast<ULONG32>(origInnerName-sepName), pResult);
+}
+
+bool
+SplitName::FindType(IMDInternalImport* mdInternal)
+{
+ if (m_typeToken != mdTypeDefNil)
+ {
+ return true;
+ }
+
+ if (!m_typeName)
+ {
+ return false;
+ }
+
+ if ((m_namespaceName == NULL || m_namespaceName[0] == '\0')
+ && (CompareUtf8(COR_MODULE_CLASS, m_typeName, m_nameFlags)==0))
+ {
+ m_typeToken = TokenFromRid(1, mdtTypeDef); // <Module> class always has a RID of 1.
+ return true;
+ }
+
+ MetaEnum metaEnum;
+
+ if (metaEnum.Start(mdInternal, mdtTypeDef, mdTypeDefNil) != S_OK)
+ {
+ return false;
+ }
+
+ LPUTF8 curClassName;
+
+ ULONG32 length;
+ WCHAR wszName[MAX_CLASS_NAME];
+ ConvertUtf8(m_typeName, MAX_CLASS_NAME, &length, wszName);
+
+ WCHAR *pHead;
+
+Retry:
+
+ pHead = wszName + length;
+
+ if (FAILED(NextEnclosingClasName(wszName, pHead, &curClassName)))
+ {
+ return false;
+ }
+
+ // an inner class has an empty namespace associated with it
+ HRESULT hr = metaEnum.NextTokenByName((pHead < wszName) ? m_namespaceName : "",
+ curClassName,
+ m_nameFlags,
+ &m_typeToken);
+ delete[] curClassName;
+
+ if (hr != S_OK)
+ {
+ // if we didn't find a token with the given name
+ return false;
+ }
+ else if (pHead < wszName)
+ {
+ // if we did find a token, *and* the class name given
+ // does not specify any enclosing class, that's it
+ return true;
+ }
+ else
+ {
+ // restart with innermost class
+ pHead = wszName + length;
+ mdTypeDef tkInner = m_typeToken;
+ mdTypeDef tkOuter;
+ BOOL bRetry = FALSE;
+ LPUTF8 utf8Name;
+
+ while (
+ !bRetry
+ && SUCCEEDED(NextEnclosingClasName(wszName, pHead, &utf8Name))
+ )
+ {
+ if (mdInternal->GetNestedClassProps(tkInner, &tkOuter) != S_OK)
+ tkOuter = mdTypeDefNil;
+
+ LPCSTR szName, szNS;
+ if (FAILED(mdInternal->GetNameOfTypeDef(tkInner, &szName, &szNS)))
+ {
+ return false;
+ }
+ bRetry = (CompareUtf8(utf8Name, szName, m_nameFlags) != 0);
+ if (!bRetry)
+ {
+ // if this is outermost class we need to compare namespaces too
+ if (tkOuter == mdTypeDefNil)
+ {
+ // is this the outermost in the class name, too?
+ if (pHead < wszName
+ && CompareUtf8(m_namespaceName ? m_namespaceName : "", szNS, m_nameFlags) == 0)
+ {
+ delete[] utf8Name;
+ return true;
+ }
+ else
+ {
+ bRetry = TRUE;
+ }
+ }
+ }
+ delete[] utf8Name;
+ tkInner = tkOuter;
+ }
+
+ goto Retry;
+ }
+
+}
+
+bool
+SplitName::FindMethod(IMDInternalImport* mdInternal)
+{
+ if (m_memberToken != mdTokenNil)
+ {
+ return true;
+ }
+
+ if (m_typeToken == mdTypeDefNil ||
+ !m_memberName)
+ {
+ return false;
+ }
+
+ ULONG32 EmptySig = 0;
+
+ // XXX Microsoft - Compare using signature when available.
+ if (mdInternal->FindMethodDefUsingCompare(m_typeToken,
+ m_memberName,
+ (PCCOR_SIGNATURE)&EmptySig,
+ sizeof(EmptySig),
+ NULL,
+ NULL,
+ &m_memberToken) != S_OK)
+ {
+ m_memberToken = mdTokenNil;
+ return false;
+ }
+
+ return true;
+}
+
+bool
+SplitName::FindField(IMDInternalImport* mdInternal)
+{
+ if (m_memberToken != mdTokenNil)
+ {
+ return true;
+ }
+
+ if (m_typeToken == mdTypeDefNil ||
+ !m_memberName ||
+ m_params)
+ {
+ // Can't have params with a field.
+ return false;
+ }
+
+ MetaEnum metaEnum;
+
+ if (metaEnum.Start(mdInternal, mdtFieldDef, m_typeToken) != S_OK)
+ {
+ return false;
+ }
+
+ return metaEnum.NextTokenByName(NULL,
+ m_memberName,
+ m_nameFlags,
+ &m_memberToken) == S_OK;
+}
+
+HRESULT
+SplitName::AllocAndSplitString(__in_opt PCWSTR fullName,
+ SplitSyntax syntax,
+ ULONG32 nameFlags,
+ ULONG32 memberDots,
+ SplitName** split)
+{
+ HRESULT status;
+
+ if (nameFlags & ~(CLRDATA_BYNAME_CASE_SENSITIVE |
+ CLRDATA_BYNAME_CASE_INSENSITIVE))
+ {
+ return E_INVALIDARG;
+ }
+
+ *split = new (nothrow) SplitName(syntax, nameFlags, memberDots);
+ if (!*split)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ if ((status = (*split)->SplitString(fullName)) != S_OK)
+ {
+ delete (*split);
+ return status;
+ }
+
+ return S_OK;
+}
+
+HRESULT
+SplitName::CdStartMethod(__in_opt PCWSTR fullName,
+ ULONG32 nameFlags,
+ Module* mod,
+ mdTypeDef typeToken,
+ AppDomain* appDomain,
+ IXCLRDataAppDomain* pubAppDomain,
+ SplitName** splitRet,
+ CLRDATA_ENUM* handle)
+{
+ HRESULT status;
+ SplitName* split;
+ ULONG methDots = 0;
+
+ *handle = TO_CDENUM(NULL);
+
+ Retry:
+ if ((status = SplitName::
+ AllocAndSplitString(fullName, SPLIT_METHOD, nameFlags,
+ methDots, &split)) != S_OK)
+ {
+ return status;
+ }
+
+ if (typeToken == mdTypeDefNil)
+ {
+ if (!split->FindType(mod->GetMDImport()))
+ {
+ bool hasNamespace = split->m_namespaceName != NULL;
+
+ delete split;
+
+ //
+ // We may have a case where there's an
+ // explicitly implemented method which
+ // has dots in the name. If it's possible
+ // to move the method name dot split
+ // back, go ahead and retry that way.
+ //
+
+ if (hasNamespace)
+ {
+ methDots++;
+ goto Retry;
+ }
+
+ return E_INVALIDARG;
+ }
+
+ typeToken = split->m_typeToken;
+ }
+ else
+ {
+ if (split->m_namespaceName || split->m_typeName)
+ {
+ delete split;
+ return E_INVALIDARG;
+ }
+ }
+
+ if ((status = split->m_metaEnum.
+ Start(mod->GetMDImport(), mdtMethodDef, typeToken)) != S_OK)
+ {
+ delete split;
+ return status;
+ }
+
+ split->m_metaEnum.m_appDomain = appDomain;
+ if (pubAppDomain)
+ {
+ split->m_metaEnum.m_appDomain =
+ ((ClrDataAppDomain*)pubAppDomain)->GetAppDomain();
+ }
+ split->m_module = mod;
+
+ *handle = TO_CDENUM(split);
+ if (splitRet)
+ {
+ *splitRet = split;
+ }
+ return S_OK;
+}
+
+HRESULT
+SplitName::CdNextMethod(CLRDATA_ENUM* handle,
+ mdMethodDef* token)
+{
+ SplitName* split = FROM_CDENUM(SplitName, *handle);
+ if (!split)
+ {
+ return E_INVALIDARG;
+ }
+
+ return split->m_metaEnum.
+ NextTokenByName(NULL, split->m_memberName, split->m_nameFlags,
+ token);
+}
+
+HRESULT
+SplitName::CdNextDomainMethod(CLRDATA_ENUM* handle,
+ AppDomain** appDomain,
+ mdMethodDef* token)
+{
+ SplitName* split = FROM_CDENUM(SplitName, *handle);
+ if (!split)
+ {
+ return E_INVALIDARG;
+ }
+
+ return split->m_metaEnum.
+ NextDomainTokenByName(NULL, split->m_memberName, split->m_nameFlags,
+ appDomain, token);
+}
+
+HRESULT
+SplitName::CdStartField(__in_opt PCWSTR fullName,
+ ULONG32 nameFlags,
+ ULONG32 fieldFlags,
+ IXCLRDataTypeInstance* fromTypeInst,
+ TypeHandle typeHandle,
+ Module* mod,
+ mdTypeDef typeToken,
+ ULONG64 objBase,
+ Thread* tlsThread,
+ IXCLRDataTask* pubTlsThread,
+ AppDomain* appDomain,
+ IXCLRDataAppDomain* pubAppDomain,
+ SplitName** splitRet,
+ CLRDATA_ENUM* handle)
+{
+ HRESULT status;
+ SplitName* split;
+
+ *handle = TO_CDENUM(NULL);
+
+ if ((status = SplitName::
+ AllocAndSplitString(fullName,
+ fullName ? SPLIT_FIELD : SPLIT_NO_NAME,
+ nameFlags, 0,
+ &split)) != S_OK)
+ {
+ return status;
+ }
+
+ if (typeHandle.IsNull())
+ {
+ if (typeToken == mdTypeDefNil)
+ {
+ if (!split->FindType(mod->GetMDImport()))
+ {
+ status = E_INVALIDARG;
+ goto Fail;
+ }
+
+ typeToken = split->m_typeToken;
+ }
+ else
+ {
+ if (split->m_namespaceName || split->m_typeName)
+ {
+ status = E_INVALIDARG;
+ goto Fail;
+ }
+ }
+
+ // With phased class loading, this may return a partially-loaded type
+ // @todo : does this matter?
+ typeHandle = mod->LookupTypeDef(split->m_typeToken);
+ if (typeHandle.IsNull())
+ {
+ status = E_UNEXPECTED;
+ goto Fail;
+ }
+ }
+
+ if ((status = InitFieldIter(&split->m_fieldEnum,
+ typeHandle,
+ true,
+ fieldFlags,
+ fromTypeInst)) != S_OK)
+ {
+ goto Fail;
+ }
+
+ split->m_objBase = objBase;
+ split->m_tlsThread = tlsThread;
+ if (pubTlsThread)
+ {
+ split->m_tlsThread = ((ClrDataTask*)pubTlsThread)->GetThread();
+ }
+ split->m_metaEnum.m_appDomain = appDomain;
+ if (pubAppDomain)
+ {
+ split->m_metaEnum.m_appDomain =
+ ((ClrDataAppDomain*)pubAppDomain)->GetAppDomain();
+ }
+ split->m_module = mod;
+
+ *handle = TO_CDENUM(split);
+ if (splitRet)
+ {
+ *splitRet = split;
+ }
+ return S_OK;
+
+ Fail:
+ delete split;
+ return status;
+}
+
+HRESULT
+SplitName::CdNextField(ClrDataAccess* dac,
+ CLRDATA_ENUM* handle,
+ IXCLRDataTypeDefinition** fieldType,
+ ULONG32* fieldFlags,
+ IXCLRDataValue** value,
+ ULONG32 nameBufRetLen,
+ ULONG32* nameLenRet,
+ __out_ecount_part_opt(nameBufRetLen, *nameLenRet) WCHAR nameBufRet[ ],
+ IXCLRDataModule** tokenScopeRet,
+ mdFieldDef* tokenRet)
+{
+ HRESULT status;
+
+ SplitName* split = FROM_CDENUM(SplitName, *handle);
+ if (!split)
+ {
+ return E_INVALIDARG;
+ }
+
+ FieldDesc* fieldDesc;
+
+ while ((fieldDesc = split->m_fieldEnum.Next()))
+ {
+ if (split->m_syntax != SPLIT_NO_NAME)
+ {
+ LPCUTF8 fieldName;
+ if (FAILED(fieldDesc->GetName_NoThrow(&fieldName)) ||
+ (split->Compare(split->m_memberName, fieldName) != 0))
+ {
+ continue;
+ }
+ }
+
+ split->m_lastField = fieldDesc;
+
+ if (fieldFlags != NULL)
+ {
+ *fieldFlags =
+ GetTypeFieldValueFlags(fieldDesc->GetFieldTypeHandleThrowing(),
+ fieldDesc,
+ split->m_fieldEnum.
+ IsFieldFromParentClass() ?
+ CLRDATA_FIELD_IS_INHERITED : 0,
+ false);
+ }
+
+ if ((nameBufRetLen != 0) || (nameLenRet != NULL))
+ {
+ LPCUTF8 szFieldName;
+ status = fieldDesc->GetName_NoThrow(&szFieldName);
+ if (status != S_OK)
+ {
+ return status;
+ }
+
+ status = ConvertUtf8(
+ szFieldName,
+ nameBufRetLen,
+ nameLenRet,
+ nameBufRet);
+ if (status != S_OK)
+ {
+ return status;
+ }
+ }
+
+ if (tokenScopeRet && !value)
+ {
+ *tokenScopeRet = new (nothrow)
+ ClrDataModule(dac, fieldDesc->GetModule());
+ if (!*tokenScopeRet)
+ {
+ return E_OUTOFMEMORY;
+ }
+ }
+
+ if (tokenRet)
+ {
+ *tokenRet = fieldDesc->GetMemberDef();
+ }
+
+ if (fieldType)
+ {
+ TypeHandle fieldTypeHandle = fieldDesc->GetFieldTypeHandleThrowing();
+ *fieldType = new (nothrow)
+ ClrDataTypeDefinition(dac,
+ fieldTypeHandle.GetModule(),
+ fieldTypeHandle.GetMethodTable()->GetCl(),
+ fieldTypeHandle);
+ if (!*fieldType && tokenScopeRet)
+ {
+ delete (ClrDataModule*)*tokenScopeRet;
+ }
+ return *fieldType ? S_OK : E_OUTOFMEMORY;
+ }
+
+ if (value)
+ {
+ return ClrDataValue::
+ NewFromFieldDesc(dac,
+ split->m_metaEnum.m_appDomain,
+ split->m_fieldEnum.IsFieldFromParentClass() ?
+ CLRDATA_VALUE_IS_INHERITED : 0,
+ fieldDesc,
+ split->m_objBase,
+ split->m_tlsThread,
+ NULL,
+ value,
+ nameBufRetLen,
+ nameLenRet,
+ nameBufRet,
+ tokenScopeRet,
+ tokenRet);
+ }
+
+ return S_OK;
+ }
+
+ return S_FALSE;
+}
+
+HRESULT
+SplitName::CdNextDomainField(ClrDataAccess* dac,
+ CLRDATA_ENUM* handle,
+ IXCLRDataValue** value)
+{
+ HRESULT status;
+
+ SplitName* split = FROM_CDENUM(SplitName, *handle);
+ if (!split)
+ {
+ return E_INVALIDARG;
+ }
+
+ if (split->m_metaEnum.m_appDomain)
+ {
+ // Use only the caller-provided app domain.
+ return CdNextField(dac, handle, NULL, NULL, value,
+ 0, NULL, NULL, NULL, NULL);
+ }
+
+ //
+ // Splay fields across all app domains.
+ //
+
+ for (;;)
+ {
+ if (!split->m_lastField)
+ {
+ // Need to fetch a field.
+ if ((status = CdNextField(dac, handle, NULL, NULL, NULL,
+ 0, NULL, NULL, NULL, NULL)) != S_OK)
+ {
+ return status;
+ }
+
+ split->m_metaEnum.m_domainIter.Init();
+ }
+
+ if (split->m_metaEnum.m_domainIter.Next())
+ {
+ break;
+ }
+
+ split->m_lastField = NULL;
+ }
+
+ return ClrDataValue::
+ NewFromFieldDesc(dac,
+ split->m_metaEnum.m_domainIter.GetDomain(),
+ split->m_fieldEnum.IsFieldFromParentClass() ?
+ CLRDATA_VALUE_IS_INHERITED : 0,
+ split->m_lastField,
+ split->m_objBase,
+ split->m_tlsThread,
+ NULL,
+ value,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+}
+
+HRESULT
+SplitName::CdStartType(__in_opt PCWSTR fullName,
+ ULONG32 nameFlags,
+ Module* mod,
+ AppDomain* appDomain,
+ IXCLRDataAppDomain* pubAppDomain,
+ SplitName** splitRet,
+ CLRDATA_ENUM* handle)
+{
+ HRESULT status;
+ SplitName* split;
+
+ *handle = TO_CDENUM(NULL);
+
+ if ((status = SplitName::
+ AllocAndSplitString(fullName, SPLIT_TYPE, nameFlags, 0,
+ &split)) != S_OK)
+ {
+ return status;
+ }
+
+ if ((status = split->m_metaEnum.
+ Start(mod->GetMDImport(), mdtTypeDef, mdTokenNil)) != S_OK)
+ {
+ delete split;
+ return status;
+ }
+
+ split->m_metaEnum.m_appDomain = appDomain;
+ if (pubAppDomain)
+ {
+ split->m_metaEnum.m_appDomain =
+ ((ClrDataAppDomain*)pubAppDomain)->GetAppDomain();
+ }
+ split->m_module = mod;
+
+ *handle = TO_CDENUM(split);
+ if (splitRet)
+ {
+ *splitRet = split;
+ }
+ return S_OK;
+}
+
+HRESULT
+SplitName::CdNextType(CLRDATA_ENUM* handle,
+ mdTypeDef* token)
+{
+ SplitName* split = FROM_CDENUM(SplitName, *handle);
+ if (!split)
+ {
+ return E_INVALIDARG;
+ }
+
+ return split->m_metaEnum.
+ NextTokenByName(split->m_namespaceName, split->m_typeName,
+ split->m_nameFlags, token);
+}
+
+HRESULT
+SplitName::CdNextDomainType(CLRDATA_ENUM* handle,
+ AppDomain** appDomain,
+ mdTypeDef* token)
+{
+ SplitName* split = FROM_CDENUM(SplitName, *handle);
+ if (!split)
+ {
+ return E_INVALIDARG;
+ }
+
+ return split->m_metaEnum.
+ NextDomainTokenByName(split->m_namespaceName, split->m_typeName,
+ split->m_nameFlags, appDomain, token);
+}
+
+//----------------------------------------------------------------------------
+//
+// DacInstanceManager.
+//
+// Data retrieved from the target process is cached for two reasons:
+//
+// 1. It may be necessary to map from the host address back to the target
+// address. For example, if any code uses a 'this' pointer or
+// takes the address of a field the address has to be translated from
+// host to target. This requires instances to be held as long as
+// they may be referenced.
+//
+// 2. Data is often referenced multiple times so caching is an important
+// performance advantage.
+//
+// Ideally we'd like to implement a simple page cache but this is
+// complicated by the fact that user minidump memory can have
+// arbitrary granularity and also that the member operator (->)
+// needs to return a pointer to an object. That means that all of
+// the data for an object must be sequential and cannot be split
+// at page boundaries.
+//
+// Data can also be accessed with different sizes. For example,
+// a base struct can be accessed, then cast to a derived struct and
+// accessed again with the larger derived size. The cache must
+// be able to replace data to maintain the largest amount of data
+// touched.
+//
+// We keep track of each access and the recovered memory for it.
+// A hash on target address allows quick access to instance data
+// by target address. The data for each access has a header on it
+// for bookkeeping purposes, so host address to target address translation
+// is just a matter of backing up to the header and pulling the target
+// address from it. Keeping each access separately allows easy
+// replacement by larger accesses.
+//
+//----------------------------------------------------------------------------
+
+DacInstanceManager::DacInstanceManager(void)
+ : m_unusedBlock(NULL)
+{
+ InitEmpty();
+}
+
+DacInstanceManager::~DacInstanceManager(void)
+{
+ // We are stopping debugging in this case, so don't save any block of memory.
+ // Otherwise, there will be a memory leak.
+ Flush(false);
+}
+
+#if defined(DAC_HASHTABLE)
+DAC_INSTANCE*
+DacInstanceManager::Add(DAC_INSTANCE* inst)
+{
+ // Assert that we don't add NULL instances. This allows us to assert that found instances
+ // are not NULL in DacInstanceManager::Find
+ _ASSERTE(inst != NULL);
+
+ DWORD nHash = DAC_INSTANCE_HASH(inst->addr);
+ HashInstanceKeyBlock* block = m_hash[nHash];
+
+ if (!block || block->firstElement == 0)
+ {
+
+ HashInstanceKeyBlock* newBlock;
+ if (block)
+ {
+ newBlock = (HashInstanceKeyBlock*) new (nothrow) BYTE[HASH_INSTANCE_BLOCK_ALLOC_SIZE];
+ }
+ else
+ {
+ // We allocate one big memory chunk that has a block for every index of the hash table to
+ // improve data locality and reduce the number of allocs. In most cases, a hash bucket will
+ // use only one block, so improving data locality across blocks (i.e. keeping the buckets of the
+ // hash table together) should help.
+ newBlock = (HashInstanceKeyBlock*)
+ ClrVirtualAlloc(NULL, HASH_INSTANCE_BLOCK_ALLOC_SIZE*NumItems(m_hash), MEM_COMMIT, PAGE_READWRITE);
+ }
+ if (!newBlock)
+ {
+ return NULL;
+ }
+ if (block)
+ {
+ // We add the newest block to the start of the list assuming that most accesses are for
+ // recently added elements.
+ newBlock->next = block;
+ m_hash[nHash] = newBlock; // The previously allocated block
+ newBlock->firstElement = HASH_INSTANCE_BLOCK_NUM_ELEMENTS;
+ block = newBlock;
+ }
+ else
+ {
+ for (DWORD j = 0; j < NumItems(m_hash); j++)
+ {
+ m_hash[j] = newBlock;
+ newBlock->next = NULL; // The previously allocated block
+ newBlock->firstElement = HASH_INSTANCE_BLOCK_NUM_ELEMENTS;
+ newBlock = (HashInstanceKeyBlock*) (((BYTE*) newBlock) + HASH_INSTANCE_BLOCK_ALLOC_SIZE);
+ }
+ block = m_hash[nHash];
+ }
+ }
+ _ASSERTE(block->firstElement > 0);
+ block->firstElement--;
+ block->instanceKeys[block->firstElement].addr = inst->addr;
+ block->instanceKeys[block->firstElement].instance = inst;
+
+ inst->next = NULL;
+ return inst;
+}
+#else //DAC_HASHTABLE
+DAC_INSTANCE*
+DacInstanceManager::Add(DAC_INSTANCE* inst)
+{
+ _ASSERTE(inst != NULL);
+#ifdef _DEBUG
+ bool isInserted = (m_hash.find(inst->addr) == m_hash.end());
+#endif //_DEBUG
+ DAC_INSTANCE *(&target) = m_hash[inst->addr];
+ _ASSERTE(!isInserted || target == NULL);
+ if( target != NULL )
+ {
+ //This is necessary to preserve the semantics of Supersede, however, it
+ //is more or less dead code.
+ inst->next = target;
+ target = inst;
+
+ //verify descending order
+ _ASSERTE(inst->size >= target->size);
+ }
+ else
+ {
+ target = inst;
+ }
+
+ return inst;
+}
+
+#endif // #if defined(DAC_HASHTABLE)
+
+
+DAC_INSTANCE*
+DacInstanceManager::Alloc(TADDR addr, ULONG32 size, DAC_USAGE_TYPE usage)
+{
+ SUPPORTS_DAC_HOST_ONLY;
+ DAC_INSTANCE_BLOCK* block;
+ DAC_INSTANCE* inst;
+ ULONG32 fullSize;
+
+ static_assert_no_msg(sizeof(DAC_INSTANCE_BLOCK) <= DAC_INSTANCE_ALIGN);
+ static_assert_no_msg((sizeof(DAC_INSTANCE) & (DAC_INSTANCE_ALIGN - 1)) == 0);
+
+ //
+ // All allocated instances must be kept alive as long
+ // as anybody may have a host pointer for one of them.
+ // This means that we cannot delete an arbitrary instance
+ // unless we are sure no pointers exist, which currently
+ // is not possible to determine, thus we just hold everything
+ // until a Flush. This greatly simplifies instance allocation
+ // as we can then just sweep through large blocks rather
+ // than having to use a real allocator. The only
+ // complication is that we need to keep all instance
+ // data aligned. We have guaranteed that the header will
+ // preserve alignment of the data following if the header
+ // is aligned, so as long as we round up all allocations
+ // to a multiple of the alignment size everything just works.
+ //
+
+ fullSize = (size + DAC_INSTANCE_ALIGN - 1) & ~(DAC_INSTANCE_ALIGN - 1);
+ _ASSERTE(fullSize && fullSize <= 0xffffffff - 2 * sizeof(*inst));
+ fullSize += sizeof(*inst);
+
+ //
+ // Check for an existing block with space.
+ //
+
+ for (block = m_blocks; block; block = block->next)
+ {
+ if (fullSize <= block->bytesFree)
+ {
+ break;
+ }
+ }
+
+ if (!block)
+ {
+ //
+ // No existing block has enough space, so allocate a new
+ // one if necessary and link it in. We know we're allocating large
+ // blocks so directly VirtualAlloc. We save one block through a
+ // flush so that we spend less time allocating/deallocating.
+ //
+
+ ULONG32 blockSize = fullSize + DAC_INSTANCE_ALIGN;
+ if (blockSize < DAC_INSTANCE_BLOCK_ALLOCATION)
+ {
+ blockSize = DAC_INSTANCE_BLOCK_ALLOCATION;
+ }
+
+ // If we have a saved block and it's large enough, use it.
+ block = m_unusedBlock;
+ if ((block != NULL) &&
+ ((block->bytesUsed + block->bytesFree) >= blockSize))
+ {
+ m_unusedBlock = NULL;
+
+ // Right now, we're locked to DAC_INSTANCE_BLOCK_ALLOCATION but
+ // that might change in the future if we decide to do something
+ // else with the size guarantee in code:DacInstanceManager::FreeAllBlocks
+ blockSize = block->bytesUsed + block->bytesFree;
+ }
+ else
+ {
+ block = (DAC_INSTANCE_BLOCK*)
+ ClrVirtualAlloc(NULL, blockSize, MEM_COMMIT, PAGE_READWRITE);
+ }
+
+ if (!block)
+ {
+ return NULL;
+ }
+
+ // Keep the first aligned unit for the block header.
+ block->bytesUsed = DAC_INSTANCE_ALIGN;
+ block->bytesFree = blockSize - DAC_INSTANCE_ALIGN;
+
+ block->next = m_blocks;
+ m_blocks = block;
+
+ m_blockMemUsage += blockSize;
+ }
+
+ inst = (DAC_INSTANCE*)((PBYTE)block + block->bytesUsed);
+ block->bytesUsed += fullSize;
+ _ASSERTE(block->bytesFree >= fullSize);
+ block->bytesFree -= fullSize;
+
+ inst->next = NULL;
+ inst->addr = addr;
+ inst->size = size;
+ inst->sig = DAC_INSTANCE_SIG;
+ inst->usage = usage;
+ inst->enumMem = 0;
+ inst->MDEnumed = 0;
+
+ m_numInst++;
+ m_instMemUsage += fullSize;
+ return inst;
+}
+
+void
+DacInstanceManager::ReturnAlloc(DAC_INSTANCE* inst)
+{
+ SUPPORTS_DAC_HOST_ONLY;
+ DAC_INSTANCE_BLOCK* block;
+ DAC_INSTANCE_BLOCK * pPrevBlock;
+ ULONG32 fullSize;
+
+ //
+ // This special routine handles cleanup in
+ // cases where an instances has been allocated
+ // but must be returned due to a following error.
+ // The given instance must be the last instance
+ // in an existing block.
+ //
+
+ fullSize =
+ ((inst->size + DAC_INSTANCE_ALIGN - 1) & ~(DAC_INSTANCE_ALIGN - 1)) +
+ sizeof(*inst);
+
+ pPrevBlock = NULL;
+ for (block = m_blocks; block; pPrevBlock = block, block = block->next)
+ {
+ if ((PBYTE)inst == (PBYTE)block + (block->bytesUsed - fullSize))
+ {
+ break;
+ }
+ }
+
+ if (!block)
+ {
+ return;
+ }
+
+ block->bytesUsed -= fullSize;
+ block->bytesFree += fullSize;
+ m_numInst--;
+ m_instMemUsage -= fullSize;
+
+ // If the block is empty after returning the specified instance, that means this block was newly created
+ // when this instance was allocated. We have seen cases where we are asked to allocate a
+ // large chunk of memory only to fail to read the memory from a dump later on, i.e. when both the target
+ // address and the size are invalid. If we keep the allocation, we'll grow the VM size unnecessarily.
+ // Thus, release a block if it's empty and if it's not the default size (to avoid thrashing memory).
+ // See Dev10 Dbug 812112 for more information.
+ if ((block->bytesUsed == DAC_INSTANCE_ALIGN) &&
+ ((block->bytesFree + block->bytesUsed) != DAC_INSTANCE_BLOCK_ALLOCATION))
+ {
+ // The empty block is at the beginning of the list.
+ if (pPrevBlock == NULL)
+ {
+ m_blocks = block->next;
+ }
+ else
+ {
+ _ASSERTE(pPrevBlock->next == block);
+ pPrevBlock->next = block->next;
+ }
+ ClrVirtualFree(block, 0, MEM_RELEASE);
+ }
+}
+
+
+#if defined(DAC_HASHTABLE)
+DAC_INSTANCE*
+DacInstanceManager::Find(TADDR addr)
+{
+
+#if defined(DAC_MEASURE_PERF)
+ unsigned _int64 nStart, nEnd;
+ g_nFindCalls++;
+ nStart = GetCycleCount();
+#endif // #if defined(DAC_MEASURE_PERF)
+
+ HashInstanceKeyBlock* block = m_hash[DAC_INSTANCE_HASH(addr)];
+
+#if defined(DAC_MEASURE_PERF)
+ nEnd = GetCycleCount();
+ g_nFindHashTotalTime += nEnd - nStart;
+#endif // #if defined(DAC_MEASURE_PERF)
+
+ while (block)
+ {
+ DWORD nIndex = block->firstElement;
+ for (; nIndex < HASH_INSTANCE_BLOCK_NUM_ELEMENTS; nIndex++)
+ {
+ if (block->instanceKeys[nIndex].addr == addr)
+ {
+ #if defined(DAC_MEASURE_PERF)
+ nEnd = GetCycleCount();
+ g_nFindHits++;
+ g_nFindTotalTime += nEnd - nStart;
+ if (g_nStackWalk) g_nFindStackTotalTime += nEnd - nStart;
+#endif // #if defined(DAC_MEASURE_PERF)
+
+ DAC_INSTANCE* inst = block->instanceKeys[nIndex].instance;
+
+ // inst should not be NULL even if the address was superseded. We search
+ // the entries in the reverse order they were added. So we should have
+ // found the superseding entry before this one. (Of course, if a NULL instance
+ // has been added, this assert is meaningless. DacInstanceManager::Add
+ // asserts that NULL instances aren't added.)
+
+ _ASSERTE(inst != NULL);
+
+ return inst;
+ }
+ }
+ block = block->next;
+ }
+
+#if defined(DAC_MEASURE_PERF)
+ nEnd = GetCycleCount();
+ g_nFindFails++;
+ g_nFindTotalTime += nEnd - nStart;
+ if (g_nStackWalk) g_nFindStackTotalTime += nEnd - nStart;
+#endif // #if defined(DAC_MEASURE_PERF)
+
+ return NULL;
+}
+#else //DAC_HASHTABLE
+DAC_INSTANCE*
+DacInstanceManager::Find(TADDR addr)
+{
+ DacInstanceHashIterator iter = m_hash.find(addr);
+ if( iter == m_hash.end() )
+ {
+ return NULL;
+ }
+ else
+ {
+ return iter->second;
+ }
+}
+#endif // if defined(DAC_HASHTABLE)
+
+HRESULT
+DacInstanceManager::Write(DAC_INSTANCE* inst, bool throwEx)
+{
+ HRESULT status;
+
+ if (inst->usage == DAC_VPTR)
+ {
+ // Skip over the host-side vtable pointer when
+ // writing back.
+ status = DacWriteAll(inst->addr + sizeof(TADDR),
+ (PBYTE)(inst + 1) + sizeof(PVOID),
+ inst->size - sizeof(TADDR),
+ throwEx);
+ }
+ else
+ {
+ // Write the whole instance back.
+ status = DacWriteAll(inst->addr, inst + 1, inst->size, throwEx);
+ }
+
+ return status;
+}
+
+#if defined(DAC_HASHTABLE)
+void
+DacInstanceManager::Supersede(DAC_INSTANCE* inst)
+{
+ _ASSERTE(inst != NULL);
+
+ //
+ // This instance has been superseded by a larger
+ // one and so must be removed from the hash. However,
+ // code may be holding the instance pointer so it
+ // can't just be deleted. Put it on a list for
+ // later cleanup.
+ //
+
+ HashInstanceKeyBlock* block = m_hash[DAC_INSTANCE_HASH(inst->addr)];
+ while (block)
+ {
+ DWORD nIndex = block->firstElement;
+ for (; nIndex < HASH_INSTANCE_BLOCK_NUM_ELEMENTS; nIndex++)
+ {
+ if (block->instanceKeys[nIndex].instance == inst)
+ {
+ block->instanceKeys[nIndex].instance = NULL;
+ break;
+ }
+ }
+ if (nIndex < HASH_INSTANCE_BLOCK_NUM_ELEMENTS)
+ {
+ break;
+ }
+ block = block->next;
+ }
+
+ AddSuperseded(inst);
+}
+#else //DAC_HASHTABLE
+void
+DacInstanceManager::Supersede(DAC_INSTANCE* inst)
+{
+ _ASSERTE(inst != NULL);
+
+ //
+ // This instance has been superseded by a larger
+ // one and so must be removed from the hash. However,
+ // code may be holding the instance pointer so it
+ // can't just be deleted. Put it on a list for
+ // later cleanup.
+ //
+
+ DacInstanceHashIterator iter = m_hash.find(inst->addr);
+ if( iter == m_hash.end() )
+ return;
+
+ DAC_INSTANCE** bucket = &(iter->second);
+ DAC_INSTANCE* cur = *bucket;
+ DAC_INSTANCE* prev = NULL;
+ //walk through the chain looking for this particular instance
+ while (cur)
+ {
+ if (cur == inst)
+ {
+ if (!prev)
+ {
+ *bucket = inst->next;
+ }
+ else
+ {
+ prev->next = inst->next;
+ }
+ break;
+ }
+
+ prev = cur;
+ cur = cur->next;
+ }
+
+ AddSuperseded(inst);
+}
+#endif // if defined(DAC_HASHTABLE)
+
+// This is the default Flush() called when the DAC cache is invalidated,
+// e.g. when we continue the debuggee process. In this case, we want to
+// save one block of memory to avoid thrashing. See the usage of m_unusedBlock
+// for more information.
+void DacInstanceManager::Flush(void)
+{
+ Flush(true);
+}
+
+void DacInstanceManager::Flush(bool fSaveBlock)
+{
+ SUPPORTS_DAC_HOST_ONLY;
+
+ //
+ // All allocated memory is in the block
+ // list, so just free the blocks and
+ // forget all the internal pointers.
+ //
+
+ for (;;)
+ {
+ FreeAllBlocks(fSaveBlock);
+
+ DAC_INSTANCE_PUSH* push = m_instPushed;
+ if (!push)
+ {
+ break;
+ }
+
+ m_instPushed = push->next;
+ m_blocks = push->blocks;
+ delete push;
+ }
+
+ // If we are not saving any memory blocks, then clear the saved buffer block (if any) as well.
+ if (!fSaveBlock)
+ {
+ if (m_unusedBlock != NULL)
+ {
+ ClrVirtualFree(m_unusedBlock, 0, MEM_RELEASE);
+ m_unusedBlock = NULL;
+ }
+ }
+
+#if defined(DAC_HASHTABLE)
+ for (int i = NumItems(m_hash) - 1; i >= 0; i--)
+ {
+ HashInstanceKeyBlock* block = m_hash[i];
+ HashInstanceKeyBlock* next;
+ while (block)
+ {
+ next = block->next;
+ if (next)
+ {
+ delete [] block;
+ }
+ else if (i == 0)
+ {
+ ClrVirtualFree(block, 0, MEM_RELEASE);
+ }
+ block = next;
+ }
+ }
+#else //DAC_HASHTABLE
+ m_hash.clear();
+#endif //DAC_HASHTABLE
+
+ InitEmpty();
+}
+
+#if defined(DAC_HASHTABLE)
+void
+DacInstanceManager::ClearEnumMemMarker(void)
+{
+ ULONG i;
+ DAC_INSTANCE* inst;
+
+ for (i = 0; i < NumItems(m_hash); i++)
+ {
+ HashInstanceKeyBlock* block = m_hash[i];
+ while (block)
+ {
+ DWORD j;
+ for (j = block->firstElement; j < HASH_INSTANCE_BLOCK_NUM_ELEMENTS; j++)
+ {
+ inst = block->instanceKeys[j].instance;
+ if (inst != NULL)
+ {
+ inst->enumMem = 0;
+ }
+ }
+ block = block->next;
+ }
+ }
+ for (inst = m_superseded; inst; inst = inst->next)
+ {
+ inst->enumMem = 0;
+ }
+}
+#else //DAC_HASHTABLE
+void
+DacInstanceManager::ClearEnumMemMarker(void)
+{
+ ULONG i;
+ DAC_INSTANCE* inst;
+
+ DacInstanceHashIterator end = m_hash.end();
+ /* REVISIT_TODO Fri 10/20/2006
+ * This might have an issue, since it might miss chained entries off of
+ * ->next. However, ->next is going away, and for all intents and
+ * purposes, this never happens.
+*/
+ for( DacInstanceHashIterator cur = m_hash.begin(); cur != end; ++cur )
+ {
+ cur->second->enumMem = 0;
+ }
+
+ for (inst = m_superseded; inst; inst = inst->next)
+ {
+ inst->enumMem = 0;
+ }
+}
+#endif // if defined(DAC_HASHTABLE)
+
+
+#if defined(DAC_HASHTABLE)
+//
+//
+// Iterating through all of the hash entry and report the memory
+// instance to minidump
+//
+// This function returns the total number of bytes that it reported.
+//
+//
+UINT
+DacInstanceManager::DumpAllInstances(
+ ICLRDataEnumMemoryRegionsCallback *pCallBack) // memory report call back
+{
+ ULONG i;
+ DAC_INSTANCE* inst;
+ UINT cbTotal = 0;
+
+#if defined(DAC_MEASURE_PERF)
+ FILE* fp = fopen("c:\\dumpLog.txt", "a");
+ int total = 0;
+#endif // #if defined(DAC_MEASURE_PERF)
+
+ for (i = 0; i < NumItems(m_hash); i++)
+ {
+
+#if defined(DAC_MEASURE_PERF)
+ int numInBucket = 0;
+#endif // #if defined(DAC_MEASURE_PERF)
+
+ HashInstanceKeyBlock* block = m_hash[i];
+ while (block)
+ {
+ DWORD j;
+ for (j = block->firstElement; j < HASH_INSTANCE_BLOCK_NUM_ELEMENTS; j++)
+ {
+ inst = block->instanceKeys[j].instance;
+
+ // Only report those we intended to.
+ // So far, only metadata is excluded!
+ //
+ if (inst && inst->noReport == 0)
+ {
+ cbTotal += inst->size;
+ HRESULT hr = pCallBack->EnumMemoryRegion(TO_CDADDR(inst->addr), inst->size);
+ if (hr == COR_E_OPERATIONCANCELED)
+ {
+ ThrowHR(hr);
+ }
+ }
+
+#if defined(DAC_MEASURE_PERF)
+ if (inst)
+ {
+ numInBucket++;
+ }
+#endif // #if defined(DAC_MEASURE_PERF)
+ }
+ block = block->next;
+ }
+
+ #if defined(DAC_MEASURE_PERF)
+ fprintf(fp, "%4d: %4d%s", i, numInBucket, (i+1)%5? "; " : "\n");
+ total += numInBucket;
+#endif // #if defined(DAC_MEASURE_PERF)
+
+ }
+
+#if defined(DAC_MEASURE_PERF)
+ fprintf(fp, "\n\nTotal entries: %d\n\n", total);
+ fclose(fp);
+#endif // #if defined(DAC_MEASURE_PERF)
+
+ return cbTotal;
+
+}
+#else //DAC_HASHTABLE
+//
+//
+// Iterating through all of the hash entry and report the memory
+// instance to minidump
+//
+// This function returns the total number of bytes that it reported.
+//
+//
+UINT
+DacInstanceManager::DumpAllInstances(
+ ICLRDataEnumMemoryRegionsCallback *pCallBack) // memory report call back
+{
+ SUPPORTS_DAC_HOST_ONLY;
+
+ DAC_INSTANCE* inst;
+ UINT cbTotal = 0;
+
+#if defined(DAC_MEASURE_PERF)
+ FILE* fp = fopen("c:\\dumpLog.txt", "a");
+#endif // #if defined(DAC_MEASURE_PERF)
+
+#if defined(DAC_MEASURE_PERF)
+ int numInBucket = 0;
+#endif // #if defined(DAC_MEASURE_PERF)
+
+ DacInstanceHashIterator end = m_hash.end();
+ for (DacInstanceHashIterator cur = m_hash.begin(); end != cur; ++cur)
+ {
+ inst = cur->second;
+
+ // Only report those we intended to.
+ // So far, only metadata is excluded!
+ //
+ if (inst->noReport == 0)
+ {
+ cbTotal += inst->size;
+ HRESULT hr = pCallBack->EnumMemoryRegion(TO_CDADDR(inst->addr), inst->size);
+ if (hr == COR_E_OPERATIONCANCELED)
+ {
+ ThrowHR(hr);
+ }
+ }
+
+#if defined(DAC_MEASURE_PERF)
+ numInBucket++;
+#endif // #if defined(DAC_MEASURE_PERF)
+ }
+
+#if defined(DAC_MEASURE_PERF)
+ fprintf(fp, "\n\nTotal entries: %d\n\n", numInBucket);
+ fclose(fp);
+#endif // #if defined(DAC_MEASURE_PERF)
+
+ return cbTotal;
+
+}
+#endif // if defined(DAC_HASHTABLE)
+
+DAC_INSTANCE_BLOCK*
+DacInstanceManager::FindInstanceBlock(DAC_INSTANCE* inst)
+{
+ for (DAC_INSTANCE_BLOCK* block = m_blocks; block; block = block->next)
+ {
+ if ((PBYTE)inst >= (PBYTE)block &&
+ (PBYTE)inst < (PBYTE)block + block->bytesUsed)
+ {
+ return block;
+ }
+ }
+
+ return NULL;
+}
+
+// If fSaveBlock is false, free all blocks of allocated memory. Otherwise,
+// free all blocks except the one we save to avoid thrashing memory.
+// Callers very frequently flush repeatedly with little memory needed in DAC
+// so this avoids wasteful repeated allocations/deallocations.
+// There is a very unlikely case that we'll have allocated an extremely large
+// block; if this is the only block we will save none since this block will
+// remain allocated.
+void
+DacInstanceManager::FreeAllBlocks(bool fSaveBlock)
+{
+ DAC_INSTANCE_BLOCK* block;
+
+ while ((block = m_blocks))
+ {
+ m_blocks = block->next;
+
+ // If we haven't saved our single block yet and this block is the default size
+ // then we will save it instead of freeing it. This avoids saving an unnecessarily large
+ // memory block.
+ // Do *NOT* trash the byte counts. code:DacInstanceManager::Alloc
+ // depends on them being correct when checking to see if a block is large enough.
+ if (fSaveBlock &&
+ (m_unusedBlock == NULL) &&
+ ((block->bytesFree + block->bytesUsed) == DAC_INSTANCE_BLOCK_ALLOCATION))
+ {
+ // Just to avoid confusion, since we're keeping it around.
+ block->next = NULL;
+ m_unusedBlock = block;
+ }
+ else
+ {
+ ClrVirtualFree(block, 0, MEM_RELEASE);
+ }
+ }
+}
+
+//----------------------------------------------------------------------------
+//
+// DacStreamManager.
+//
+//----------------------------------------------------------------------------
+
+#ifdef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+
+namespace serialization { namespace bin {
+
+ //========================================================================
+ // Support functions for binary serialization of simple types to a buffer:
+ // - raw_size() returns the size in bytes of the binary representation
+ // of a value.
+ // - raw_serialize() copies the binary representation of a value into a
+ // buffer.
+ // - raw_deserialize() generates a value from its binary representation
+ // in a buffer.
+ // Beyond simple types the APIs below support SString instances. SStrings
+ // are stored as UTF8 strings.
+ //========================================================================
+
+ static const size_t ErrOverflow = (size_t)(-1);
+
+#ifndef FEATURE_PAL
+
+ // Template class is_blittable
+ template <typename _Ty, typename Enable = void>
+ struct is_blittable
+ : std::false_type
+ { // determines whether _Ty is blittable
+ };
+
+ template <typename _Ty>
+ struct is_blittable<_Ty, typename std::enable_if<std::is_arithmetic<_Ty>::value>::type>
+ : std::true_type
+ { // determines whether _Ty is blittable
+ };
+
+ // allow types to declare themselves blittable by including a static bool
+ // member "is_blittable".
+ template <typename _Ty>
+ struct is_blittable<_Ty, typename std::enable_if<_Ty::is_blittable>::type>
+ : std::true_type
+ { // determines whether _Ty is blittable
+ };
+
+
+ //========================================================================
+ // serialization::bin::Traits<T> enables binary serialization and
+ // deserialization of instances of T.
+ //========================================================================
+
+ //
+ // General specialization for non-blittable types - must be overridden
+ // for each specific non-blittable type.
+ //
+ template <typename T, typename Enable = void>
+ class Traits
+ {
+ public:
+ static FORCEINLINE size_t
+ raw_size(const T & val)
+ {
+ static_assert(false, "Non-blittable types need explicit specializations");
+ }
+ };
+
+ //
+ // General type trait supporting serialization/deserialization of blittable
+ // type arguments (as defined by the is_blittable<> type traits above).
+ //
+ template <typename T>
+ class Traits<T, typename std::enable_if<is_blittable<T>::value>::type>
+ {
+#else // FEATURE_PAL
+ template <typename T>
+ class Traits
+ {
+#endif // !FEATURE_PAL
+ public:
+ //
+ // raw_size() returns the size in bytes of the binary representation of a
+ // value.
+ //
+ static FORCEINLINE size_t
+ raw_size(const T & val)
+ {
+ return sizeof(T);
+ }
+
+ //
+ // raw_serialize() copies the binary representation of a value into a
+ // "dest" buffer that has "destSize" bytes available.
+ // Returns raw_size(val), or ErrOverflow if the buffer does not have
+ // enough space to accommodate "val".
+ //
+ static FORCEINLINE size_t
+ raw_serialize(BYTE* dest, size_t destSize, const T & val)
+ {
+ size_t cnt = raw_size(val);
+
+ if (destSize < cnt)
+ {
+ return ErrOverflow;
+ }
+
+ memcpy_s(dest, destSize, &val, cnt);
+
+ return cnt;
+ }
+
+ //
+ // raw_deserialize() generates a value "val" from its binary
+ // representation in a buffer "src".
+ // Returns raw_size(val), or ErrOverflow if the buffer does not have
+ // enough space to accommodate "val".
+ //
+ static FORCEINLINE size_t
+ raw_deserialize(T & val, const BYTE* src, size_t srcSize)
+ {
+ size_t cnt = raw_size(*(T*)src);
+
+ if (srcSize < cnt)
+ {
+ return ErrOverflow;
+ }
+
+ memcpy_s(&val, cnt, src, cnt);
+
+ return cnt;
+ }
+
+ };
+
+ //
+ // Specialization for UTF8 strings
+ //
+ template<>
+ class Traits<LPCUTF8>
+ {
+ public:
+ static FORCEINLINE size_t
+ raw_size(const LPCUTF8 & val)
+ {
+ return strlen(val) + 1;
+ }
+
+ static FORCEINLINE size_t
+ raw_serialize(BYTE* dest, size_t destSize, const LPCUTF8 & val)
+ {
+ size_t cnt = raw_size(val);
+
+ if (destSize < cnt)
+ {
+ return ErrOverflow;
+ }
+
+ memcpy_s(dest, destSize, &val, cnt);
+
+ return cnt;
+ }
+
+ static FORCEINLINE size_t
+ raw_deserialize(LPCUTF8 & val, const BYTE* src, size_t srcSize)
+ {
+ size_t cnt = strnlen((LPCUTF8)src, srcSize) + 1;
+
+ // assert we found a NULL terminated string at "src"
+ if (srcSize < cnt)
+ {
+ return ErrOverflow;
+ }
+
+ // we won't allocate another buffer for this string
+ val = (LPCUTF8)src;
+
+ return cnt;
+ }
+
+ };
+
+ //
+ // Specialization for SString.
+ // SString serialization/deserialization is performed to/from a UTF8
+ // string.
+ //
+ template<>
+ class Traits<SString>
+ {
+ public:
+ static FORCEINLINE size_t
+ raw_size(const SString & val)
+ {
+ StackSString s;
+ val.ConvertToUTF8(s);
+ // make sure to include the NULL terminator
+ return s.GetCount() + 1;
+ }
+
+ static FORCEINLINE size_t
+ raw_serialize(BYTE* dest, size_t destSize, const SString & val)
+ {
+ // instead of calling raw_size() we inline it here, so we can reuse
+ // the UTF8 string obtained below as an argument to memcpy.
+
+ StackSString s;
+ val.ConvertToUTF8(s);
+ // make sure to include the NULL terminator
+ size_t cnt = s.GetCount() + 1;
+
+ if (destSize < cnt)
+ {
+ return ErrOverflow;
+ }
+
+ memcpy_s(dest, destSize, s.GetUTF8NoConvert(), cnt);
+
+ return cnt;
+ }
+
+ static FORCEINLINE size_t
+ raw_deserialize(SString & val, const BYTE* src, size_t srcSize)
+ {
+ size_t cnt = strnlen((LPCUTF8)src, srcSize) + 1;
+
+ // assert we found a NULL terminated string at "src"
+ if (srcSize < cnt)
+ {
+ return ErrOverflow;
+ }
+
+ // a literal SString avoids a new allocation + copy
+ SString sUtf8(SString::Utf8Literal, (LPCUTF8) src);
+ sUtf8.ConvertToUnicode(val);
+
+ return cnt;
+ }
+
+ };
+
+#ifndef FEATURE_PAL
+ //
+ // Specialization for SString-derived classes (like SStrings)
+ //
+ template<typename T>
+ class Traits<T, typename std::enable_if<std::is_base_of<SString, T>::value>::type>
+ : public Traits<SString>
+ {
+ };
+#endif // !FEATURE_PAL
+
+ //
+ // Convenience functions to allow argument type deduction
+ //
+ template <typename T> FORCEINLINE
+ size_t raw_size(const T & val)
+ { return Traits<T>::raw_size(val); }
+
+ template <typename T> FORCEINLINE
+ size_t raw_serialize(BYTE* dest, size_t destSize, const T & val)
+ { return Traits<T>::raw_serialize(dest, destSize, val); }
+
+ template <typename T> FORCEINLINE
+ size_t raw_deserialize(T & val, const BYTE* src, size_t srcSize)
+ { return Traits<T>::raw_deserialize(val, src, srcSize); }
+
+
+ enum StreamBuffState
+ {
+ sbsOK,
+ sbsUnrecoverable,
+ sbsOOM = sbsUnrecoverable,
+ };
+
+ //
+ // OStreamBuff - Manages writing to an output buffer
+ //
+ class OStreamBuff
+ {
+ public:
+ OStreamBuff(BYTE * _buff, size_t _buffsize)
+ : buffsize(_buffsize)
+ , buff(_buff)
+ , crt(0)
+ , sbs(sbsOK)
+ { }
+
+ template <typename T>
+ OStreamBuff& operator << (const T & val)
+ {
+ if (sbs >= sbsUnrecoverable)
+ return *this;
+
+ size_t cnt = raw_serialize(buff+crt, buffsize-crt, val);
+ if (cnt == ErrOverflow)
+ {
+ sbs = sbsOOM;
+ }
+ else
+ {
+ crt += cnt;
+ }
+
+ return *this;
+ }
+
+ inline size_t GetPos() const
+ {
+ return crt;
+ }
+
+ inline BOOL operator!() const
+ {
+ return sbs >= sbsUnrecoverable;
+ }
+
+ inline StreamBuffState State() const
+ {
+ return sbs;
+ }
+
+ private:
+ size_t buffsize; // size of buffer
+ BYTE* buff; // buffer to stream to
+ size_t crt; // current offset in buffer
+ StreamBuffState sbs; // current state
+ };
+
+
+ //
+ // OStreamBuff - Manages reading from an input buffer
+ //
+ class IStreamBuff
+ {
+ public:
+ IStreamBuff(const BYTE* _buff, size_t _buffsize)
+ : buffsize(_buffsize)
+ , buff(_buff)
+ , crt(0)
+ , sbs(sbsOK)
+ { }
+
+ template <typename T>
+ IStreamBuff& operator >> (T & val)
+ {
+ if (sbs >= sbsUnrecoverable)
+ return *this;
+
+ size_t cnt = raw_deserialize(val, buff+crt, buffsize-crt);
+ if (cnt == ErrOverflow)
+ {
+ sbs = sbsOOM;
+ }
+ else
+ {
+ crt += cnt;
+ }
+
+ return *this;
+ }
+
+ inline size_t GetPos() const
+ {
+ return crt;
+ }
+
+ inline BOOL operator!() const
+ {
+ return sbs >= sbsUnrecoverable;
+ }
+
+ inline StreamBuffState State() const
+ {
+ return sbs;
+ }
+
+ private:
+ size_t buffsize; // size of buffer
+ const BYTE * buff; // buffer to read from
+ size_t crt; // current offset in buffer
+ StreamBuffState sbs; // current state
+ };
+
+} }
+
+using serialization::bin::StreamBuffState;
+using serialization::bin::IStreamBuff;
+using serialization::bin::OStreamBuff;
+
+
+// Callback function type used by DacStreamManager to coordinate
+// amount of available memory between multiple streamable data
+// structures (e.g. DacEENamesStreamable)
+typedef bool (*Reserve_Fnptr)(DWORD size, void * writeState);
+
+
+//
+// DacEENamesStreamable
+// Stores EE struct* -> Name mappings and streams them to a
+// streambuf when asked
+//
+class DacEENamesStreamable
+{
+private:
+ // the hash map storing the interesting mappings of EE* -> Names
+ MapSHash< TADDR, SString,
+ NoRemoveSHashTraits <
+ NonDacAwareSHashTraits< MapSHashTraits <TADDR, SString> >
+ > > m_hash;
+
+ Reserve_Fnptr m_reserveFn;
+ void *m_writeState;
+
+private:
+ // signature value in the header in stream
+ static const DWORD sig = 0x614e4545; // "EENa" - EE Name
+
+ // header in stream
+ struct StreamHeader
+ {
+ DWORD sig; // 0x614e4545 == "EENa"
+ DWORD cnt; // count of entries
+
+ static const bool is_blittable = true;
+ };
+
+public:
+ DacEENamesStreamable()
+ : m_reserveFn(NULL)
+ , m_writeState(NULL)
+ {}
+
+ // Ensures the instance is ready for caching data and later writing
+ // its map entries to an OStreamBuff.
+ bool PrepareStreamForWriting(Reserve_Fnptr pfn, void * writeState)
+ {
+ _ASSERTE(pfn != NULL && writeState != NULL);
+ m_reserveFn = pfn;
+ m_writeState = writeState;
+
+ DWORD size = (DWORD) sizeof(StreamHeader);
+
+ // notify owner to reserve space for a StreamHeader
+ return m_reserveFn(size, m_writeState);
+ }
+
+ // Adds a new mapping from an EE struct pointer (e.g. MethodDesc*) to
+ // its name
+ bool AddEEName(TADDR taEE, const SString & eeName)
+ {
+ _ASSERTE(m_reserveFn != NULL && m_writeState != NULL);
+
+ // as a micro-optimization convert to Utf8 here as both raw_size and
+ // raw_serialize are optimized for Utf8...
+ StackSString seeName;
+ eeName.ConvertToUTF8(seeName);
+
+ DWORD size = (DWORD)(serialization::bin::raw_size(taEE) +
+ serialization::bin::raw_size(seeName));
+
+ // notify owner of the amount of space needed in the buffer
+ if (m_reserveFn(size, m_writeState))
+ {
+ // if there's still space cache the entry in m_hash
+ m_hash.AddOrReplace(KeyValuePair<TADDR, SString>(taEE, seeName));
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ // Finds an EE name from a target address of an EE struct (e.g.
+ // MethodDesc*)
+ bool FindEEName(TADDR taEE, SString & eeName) const
+ {
+ return m_hash.Lookup(taEE, &eeName) == TRUE;
+ }
+
+ void Clear()
+ {
+ m_hash.RemoveAll();
+ }
+
+ // Writes a header and the hash entries to an OStreamBuff
+ HRESULT StreamTo(OStreamBuff &out) const
+ {
+ StreamHeader hdr;
+ hdr.sig = sig;
+ hdr.cnt = (DWORD) m_hash.GetCount();
+
+ out << hdr;
+
+ auto end = m_hash.End();
+ for (auto cur = m_hash.Begin(); end != cur; ++cur)
+ {
+ out << cur->Key() << cur->Value();
+ if (!out)
+ return E_FAIL;
+ }
+
+ return S_OK;
+ }
+
+ // Reads a header and the hash entries from an IStreamBuff
+ HRESULT StreamFrom(IStreamBuff &in)
+ {
+ StreamHeader hdr;
+
+ DWORD _sig;
+ in >> hdr; // in >> hdr.sig >> hdr.cnt;
+
+ if (hdr.sig != sig)
+ return E_FAIL;
+
+ for (size_t i = 0; i < hdr.cnt; ++i)
+ {
+ TADDR taEE;
+ SString eeName;
+ in >> taEE >> eeName;
+
+ if (!in)
+ return E_FAIL;
+
+ m_hash.AddOrReplace(KeyValuePair<TADDR, SString>(taEE, eeName));
+ }
+
+ return S_OK;
+ }
+
+};
+
+//================================================================================
+// This class enables two scenarios:
+// 1. When debugging a triage/mini-dump the class is initialized with a valid
+// buffer in taMiniMetaDataBuff. Afterwards one can call MdCacheGetEEName to
+// retrieve the name associated with a MethodDesc*.
+// 2. When generating a dump one must follow this sequence:
+// a. Initialize the DacStreamManager passing a valid (if the current
+// debugging target is a triage/mini-dump) or empty buffer (if the
+// current target is a live processa full or a heap dump)
+// b. Call PrepareStreamsForWriting() before starting enumerating any memory
+// c. Call MdCacheAddEEName() anytime we enumerate an EE structure of interest
+// d. Call EnumStreams() as the last action in the memory enumeration method.
+//
+class DacStreamManager
+{
+public:
+ enum eReadOrWrite
+ {
+ eNone, // the stream doesn't exist (target is a live process/full/heap dump)
+ eRO, // the stream exists and we've read it (target is triage/mini-dump)
+ eWO, // the stream doesn't exist but we're creating it
+ // (e.g. to save a minidump from the current debugging session)
+ eRW // the stream exists but we're generating another triage/mini-dump
+ };
+
+ static const DWORD sig = 0x6d727473; // 'strm'
+
+ struct StreamsHeader
+ {
+ DWORD dwSig; // 0x6d727473 == "strm"
+ DWORD dwTotalSize; // total size in bytes
+ DWORD dwCntStreams; // number of streams (currently 1)
+
+ static const bool is_blittable = true;
+ };
+
+ DacStreamManager(TADDR miniMetaDataBuffAddress, DWORD miniMetaDataBuffSizeMax)
+ : m_MiniMetaDataBuffAddress(miniMetaDataBuffAddress)
+ , m_MiniMetaDataBuffSizeMax(miniMetaDataBuffSizeMax)
+ , m_rawBuffer(NULL)
+ , m_cbAvailBuff(0)
+ , m_rw(eNone)
+ , m_bStreamsRead(FALSE)
+ , m_EENames()
+ {
+ Initialize();
+ }
+
+ ~DacStreamManager()
+ {
+ if (m_rawBuffer != NULL)
+ {
+ delete [] m_rawBuffer;
+ }
+ }
+
+ bool PrepareStreamsForWriting()
+ {
+ if (m_rw == eNone)
+ m_rw = eWO;
+ else if (m_rw == eRO)
+ m_rw = eRW;
+ else if (m_rw == eRW)
+ /* nothing */;
+ else // m_rw == eWO
+ {
+ // this is a second invocation from a possibly live process
+ // clean up the map since the callstacks/exceptions may be different
+ m_EENames.Clear();
+ }
+
+ // update available count based on the header and footer sizes
+ if (m_MiniMetaDataBuffSizeMax < sizeof(StreamsHeader))
+ return false;
+
+ m_cbAvailBuff = m_MiniMetaDataBuffSizeMax - sizeof(StreamsHeader);
+
+ // update available count based on each stream's initial needs
+ if (!m_EENames.PrepareStreamForWriting(&ReserveInBuffer, this))
+ return false;
+
+ return true;
+ }
+
+ bool MdCacheAddEEName(TADDR taEEStruct, const SString& name)
+ {
+ // don't cache unless we enabled "W"riting from a target that does not
+ // already have a stream yet
+ if (m_rw != eWO)
+ return false;
+
+ m_EENames.AddEEName(taEEStruct, name);
+ return true;
+ }
+
+ HRESULT EnumStreams(IN CLRDataEnumMemoryFlags flags)
+ {
+ _ASSERTE(flags == CLRDATA_ENUM_MEM_MINI || flags == CLRDATA_ENUM_MEM_TRIAGE);
+ _ASSERTE(m_rw == eWO || m_rw == eRW);
+
+ DWORD cbWritten = 0;
+
+ if (m_rw == eWO)
+ {
+ // only dump the stream is it wasn't already present in the target
+ DumpAllStreams(&cbWritten);
+ }
+ else
+ {
+ cbWritten = m_MiniMetaDataBuffSizeMax;
+ }
+
+ DacEnumMemoryRegion(m_MiniMetaDataBuffAddress, cbWritten, false);
+ DacUpdateMemoryRegion(m_MiniMetaDataBuffAddress, cbWritten, m_rawBuffer);
+
+ return S_OK;
+ }
+
+ bool MdCacheGetEEName(TADDR taEEStruct, SString & eeName)
+ {
+ if (!m_bStreamsRead)
+ {
+ ReadAllStreams();
+ }
+
+ if (m_rw == eNone || m_rw == eWO)
+ {
+ return false;
+ }
+
+ return m_EENames.FindEEName(taEEStruct, eeName);
+ }
+
+private:
+ HRESULT Initialize()
+ {
+ _ASSERTE(m_rw == eNone);
+ _ASSERTE(m_rawBuffer == NULL);
+
+ HRESULT hr = S_OK;
+
+ StreamsHeader hdr;
+ DacReadAll(dac_cast<TADDR>(m_MiniMetaDataBuffAddress),
+ &hdr, sizeof(hdr), true);
+
+ // when the DAC looks at a triage dump or minidump generated using
+ // a "minimetadata" enabled DAC, buff will point to a serialized
+ // representation of a methoddesc->method name hashmap.
+ if (hdr.dwSig == sig)
+ {
+ m_rw = eRO;
+ m_MiniMetaDataBuffSizeMax = hdr.dwTotalSize;
+ hr = S_OK;
+ }
+ else
+ // when the DAC initializes this for the case where the target is
+ // (a) a live process, or (b) a full dump, buff will point to a
+ // zero initialized memory region (allocated w/ VirtualAlloc)
+ if (hdr.dwSig == 0 && hdr.dwTotalSize == 0 && hdr.dwCntStreams == 0)
+ {
+ hr = S_OK;
+ }
+ // otherwise we may have some memory corruption. treat this as
+ // a liveprocess/full dump
+ else
+ {
+ hr = S_FALSE;
+ }
+
+ BYTE * buff = new BYTE[m_MiniMetaDataBuffSizeMax];
+ DacReadAll(dac_cast<TADDR>(m_MiniMetaDataBuffAddress),
+ buff, m_MiniMetaDataBuffSizeMax, true);
+
+ m_rawBuffer = buff;
+
+ return hr;
+ }
+
+ HRESULT DumpAllStreams(DWORD * pcbWritten)
+ {
+ _ASSERTE(m_rw == eWO);
+
+ HRESULT hr = S_OK;
+
+ OStreamBuff out(m_rawBuffer, m_MiniMetaDataBuffSizeMax);
+
+ // write header
+ StreamsHeader hdr;
+ hdr.dwSig = sig;
+ hdr.dwTotalSize = m_MiniMetaDataBuffSizeMax-m_cbAvailBuff; // will update
+ hdr.dwCntStreams = 1;
+
+ out << hdr;
+
+ // write MethodDesc->Method name map
+ hr = m_EENames.StreamTo(out);
+
+ // wrap up the buffer whether we ecountered an error or not
+ size_t cbWritten = out.GetPos();
+ cbWritten = ALIGN_UP(cbWritten, sizeof(size_t));
+
+ // patch the dwTotalSize field blitted at the beginning of the buffer
+ ((StreamsHeader*)m_rawBuffer)->dwTotalSize = (DWORD) cbWritten;
+
+ if (pcbWritten)
+ *pcbWritten = (DWORD) cbWritten;
+
+ return hr;
+ }
+
+ HRESULT ReadAllStreams()
+ {
+ _ASSERTE(!m_bStreamsRead);
+
+ if (m_rw == eNone || m_rw == eWO)
+ {
+ // no streams to read...
+ m_bStreamsRead = TRUE;
+ return S_FALSE;
+ }
+
+ HRESULT hr = S_OK;
+
+ IStreamBuff in(m_rawBuffer, m_MiniMetaDataBuffSizeMax);
+
+ // read header
+ StreamsHeader hdr;
+ in >> hdr;
+ _ASSERTE(hdr.dwSig == sig);
+ _ASSERTE(hdr.dwCntStreams == 1);
+
+ // read EE struct pointer -> EE name map
+ m_EENames.Clear();
+ hr = m_EENames.StreamFrom(in);
+
+ m_bStreamsRead = TRUE;
+
+ return hr;
+ }
+
+ static bool ReserveInBuffer(DWORD size, void * writeState)
+ {
+ DacStreamManager * pThis = reinterpret_cast<DacStreamManager*>(writeState);
+ if (size > pThis->m_cbAvailBuff)
+ {
+ return false;
+ }
+ else
+ {
+ pThis->m_cbAvailBuff -= size;
+ return true;
+ }
+ }
+
+private:
+ TADDR m_MiniMetaDataBuffAddress; // TADDR of the buffer
+ DWORD m_MiniMetaDataBuffSizeMax; // max size of buffer
+ BYTE * m_rawBuffer; // inproc copy of buffer
+ DWORD m_cbAvailBuff; // available bytes in buffer
+ eReadOrWrite m_rw;
+ BOOL m_bStreamsRead;
+ DacEENamesStreamable m_EENames;
+};
+
+#endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+
+//----------------------------------------------------------------------------
+//
+// ClrDataAccess.
+//
+//----------------------------------------------------------------------------
+
+LONG ClrDataAccess::s_procInit;
+
+ClrDataAccess::ClrDataAccess(ICorDebugDataTarget * pTarget, ICLRDataTarget * pLegacyTarget/*=0*/)
+{
+ SUPPORTS_DAC_HOST_ONLY; // ctor does no marshalling - don't check with DacCop
+
+ /*
+ * Stash the various forms of the new ICorDebugDataTarget interface
+ */
+ m_pTarget = pTarget;
+ m_pTarget->AddRef();
+
+ HRESULT hr;
+
+ hr = m_pTarget->QueryInterface(__uuidof(ICorDebugMutableDataTarget),
+ (void**)&m_pMutableTarget);
+
+ if (hr != S_OK)
+ {
+ // Create a target which always fails the write requests with CORDBG_E_TARGET_READONLY
+ m_pMutableTarget = new ReadOnlyDataTargetFacade();
+ m_pMutableTarget->AddRef();
+ }
+
+ /*
+ * If we have a legacy target, it means we're providing compatibility for code that used
+ * the old ICLRDataTarget interfaces. There are still a few things (like metadata location,
+ * GetImageBase, and VirtualAlloc) that the implementation may use which we haven't superseded
+ * in ICorDebugDataTarget, so we still need access to the old target interfaces.
+ * Any functionality that does exist in ICorDebugDataTarget is accessed from that interface
+ * using the DataTargetAdapter on top of the legacy interface (to unify the calling code).
+ * Eventually we may expose all functionality we need using ICorDebug (possibly a private
+ * interface for things like VirtualAlloc), at which point we can stop using the legacy interfaces
+ * completely (except in the DataTargetAdapter).
+ */
+ m_pLegacyTarget = NULL;
+ m_pLegacyTarget2 = NULL;
+ m_pLegacyTarget3 = NULL;
+ m_legacyMetaDataLocator = NULL;
+ m_target3 = NULL;
+ if (pLegacyTarget != NULL)
+ {
+ m_pLegacyTarget = pLegacyTarget;
+
+ m_pLegacyTarget->AddRef();
+
+ m_pLegacyTarget->QueryInterface(__uuidof(ICLRDataTarget2), (void**)&m_pLegacyTarget2);
+
+ m_pLegacyTarget->QueryInterface(__uuidof(ICLRDataTarget3), (void**)&m_pLegacyTarget3);
+
+ if (pLegacyTarget->QueryInterface(__uuidof(ICLRMetadataLocator),
+ (void**)&m_legacyMetaDataLocator) != S_OK)
+ {
+ // The debugger doesn't implement IMetadataLocator. Use
+ // IXCLRDataTarget3 if that exists. Otherwise we don't need it.
+ pLegacyTarget->QueryInterface(__uuidof(IXCLRDataTarget3),
+ (void**)&m_target3);
+ }
+ }
+
+ m_globalBase = 0;
+ m_refs = 1;
+ m_instanceAge = 0;
+ m_debugMode = GetEnvironmentVariableA("MSCORDACWKS_DEBUG", NULL, 0) != 0;
+
+ m_enumMemCb = NULL;
+ m_updateMemCb = NULL;
+ m_enumMemFlags = (CLRDataEnumMemoryFlags)-1; // invalid
+ m_jitNotificationTable = NULL;
+ m_gcNotificationTable = NULL;
+
+#ifdef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+ m_streams = NULL;
+#endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+
+ // Target consistency checks are disabled by default.
+ // See code:ClrDataAccess::SetTargetConsistencyChecks for details.
+ m_fEnableTargetConsistencyAsserts = false;
+
+#ifdef _DEBUG
+ if (CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgDACEnableAssert))
+ {
+ m_fEnableTargetConsistencyAsserts = true;
+ }
+
+ // Verification asserts are disabled by default because some debuggers (cdb/windbg) probe likely locations
+ // for DAC and having this assert pop up all the time can be annoying. We let derived classes enable
+ // this if they want. It can also be overridden at run-time with COMPlus_DbgDACAssertOnMismatch,
+ // see ClrDataAccess::VerifyDlls for details.
+ m_fEnableDllVerificationAsserts = false;
+#endif
+
+}
+
+ClrDataAccess::~ClrDataAccess(void)
+{
+ SUPPORTS_DAC_HOST_ONLY;
+
+#ifdef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+ if (m_streams)
+ {
+ delete m_streams;
+ }
+#endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+
+ delete [] m_jitNotificationTable;
+ if (m_pLegacyTarget)
+ {
+ m_pLegacyTarget->Release();
+ }
+ if (m_pLegacyTarget2)
+ {
+ m_pLegacyTarget2->Release();
+ }
+ if (m_pLegacyTarget3)
+ {
+ m_pLegacyTarget3->Release();
+ }
+ if (m_legacyMetaDataLocator)
+ {
+ m_legacyMetaDataLocator->Release();
+ }
+ if (m_target3)
+ {
+ m_target3->Release();
+ }
+ m_pTarget->Release();
+ m_pMutableTarget->Release();
+}
+
+STDMETHODIMP
+ClrDataAccess::QueryInterface(THIS_
+ IN REFIID interfaceId,
+ OUT PVOID* iface)
+{
+ void* ifaceRet;
+
+ if (IsEqualIID(interfaceId, IID_IUnknown) ||
+ IsEqualIID(interfaceId, __uuidof(IXCLRDataProcess)) ||
+ IsEqualIID(interfaceId, __uuidof(IXCLRDataProcess2)))
+ {
+ ifaceRet = static_cast<IXCLRDataProcess2*>(this);
+ }
+ else if (IsEqualIID(interfaceId, __uuidof(ICLRDataEnumMemoryRegions)))
+ {
+ ifaceRet = static_cast<ICLRDataEnumMemoryRegions*>(this);
+ }
+ else if (IsEqualIID(interfaceId, __uuidof(ISOSDacInterface)))
+ {
+ ifaceRet = static_cast<ISOSDacInterface*>(this);
+ }
+ else if (IsEqualIID(interfaceId, __uuidof(ISOSDacInterface2)))
+ {
+ ifaceRet = static_cast<ISOSDacInterface2*>(this);
+ }
+ else if (IsEqualIID(interfaceId, __uuidof(ISOSDacInterface3)))
+ {
+ ifaceRet = static_cast<ISOSDacInterface3*>(this);
+ }
+ else if (IsEqualIID(interfaceId, __uuidof(ISOSDacInterface4)))
+ {
+ ifaceRet = static_cast<ISOSDacInterface4*>(this);
+ }
+ else
+ {
+ *iface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ *iface = ifaceRet;
+ return S_OK;
+}
+
+STDMETHODIMP_(ULONG)
+ClrDataAccess::AddRef(THIS)
+{
+ return InterlockedIncrement(&m_refs);
+}
+
+STDMETHODIMP_(ULONG)
+ClrDataAccess::Release(THIS)
+{
+ SUPPORTS_DAC_HOST_ONLY;
+ LONG newRefs = InterlockedDecrement(&m_refs);
+ if (newRefs == 0)
+ {
+ delete this;
+ }
+ return newRefs;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::Flush(void)
+{
+ SUPPORTS_DAC_HOST_ONLY;
+
+ //
+ // Free MD import objects.
+ //
+ m_mdImports.Flush();
+
+ // Free instance memory.
+ m_instances.Flush();
+
+ // When the host instance cache is flushed we
+ // update the instance age count so that
+ // all child objects automatically become
+ // invalid. This prevents them from using
+ // any pointers they've kept to host instances
+ // which are now gone.
+ m_instanceAge++;
+
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::StartEnumTasks(
+ /* [out] */ CLRDATA_ENUM* handle)
+{
+ HRESULT status;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ if (ThreadStore::s_pThreadStore)
+ {
+ Thread* thread = ThreadStore::GetAllThreadList(NULL, 0, 0);
+ *handle = TO_CDENUM(thread);
+ status = *handle ? S_OK : S_FALSE;
+ }
+ else
+ {
+ status = S_FALSE;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::EnumTask(
+ /* [in, out] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataTask **task)
+{
+ HRESULT status;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ if (*handle)
+ {
+ Thread* thread = FROM_CDENUM(Thread, *handle);
+ *task = new (nothrow) ClrDataTask(this, thread);
+ if (*task)
+ {
+ thread = ThreadStore::GetAllThreadList(thread, 0, 0);
+ *handle = TO_CDENUM(thread);
+ status = S_OK;
+ }
+ else
+ {
+ status = E_OUTOFMEMORY;
+ }
+ }
+ else
+ {
+ status = S_FALSE;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::EndEnumTasks(
+ /* [in] */ CLRDATA_ENUM handle)
+{
+ HRESULT status;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ // Enumerator holds no resources.
+ status = S_OK;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::GetTaskByOSThreadID(
+ /* [in] */ ULONG32 osThreadID,
+ /* [out] */ IXCLRDataTask **task)
+{
+ HRESULT status;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ status = E_INVALIDARG;
+ Thread* thread = DacGetThread(osThreadID);
+ if (thread != NULL)
+ {
+ *task = new (nothrow) ClrDataTask(this, thread);
+ status = *task ? S_OK : E_OUTOFMEMORY;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::GetTaskByUniqueID(
+ /* [in] */ ULONG64 uniqueID,
+ /* [out] */ IXCLRDataTask **task)
+{
+ HRESULT status;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ Thread* thread = FindClrThreadByTaskId(uniqueID);
+ if (thread)
+ {
+ *task = new (nothrow) ClrDataTask(this, thread);
+ status = *task ? S_OK : E_OUTOFMEMORY;
+ }
+ else
+ {
+ status = E_INVALIDARG;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::GetFlags(
+ /* [out] */ ULONG32 *flags)
+{
+ HRESULT status;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ // XXX Microsoft - GC check.
+ *flags = CLRDATA_PROCESS_DEFAULT;
+ status = S_OK;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::IsSameObject(
+ /* [in] */ IXCLRDataProcess* process)
+{
+ HRESULT status;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ status = m_pTarget == ((ClrDataAccess*)process)->m_pTarget ?
+ S_OK : S_FALSE;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::GetManagedObject(
+ /* [out] */ IXCLRDataValue **value)
+{
+ HRESULT status;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ // XXX Microsoft.
+ status = E_NOTIMPL;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::GetDesiredExecutionState(
+ /* [out] */ ULONG32 *state)
+{
+ HRESULT status;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ // XXX Microsoft.
+ status = E_NOTIMPL;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::SetDesiredExecutionState(
+ /* [in] */ ULONG32 state)
+{
+ HRESULT status;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ // XXX Microsoft.
+ status = E_NOTIMPL;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::GetAddressType(
+ /* [in] */ CLRDATA_ADDRESS address,
+ /* [out] */ CLRDataAddressType* type)
+{
+ HRESULT status;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ // The only thing that constitutes a failure is some
+ // dac failure while checking things.
+ status = S_OK;
+ TADDR taAddr = CLRDATA_ADDRESS_TO_TADDR(address);
+ if (IsPossibleCodeAddress(taAddr) == S_OK)
+ {
+ if (ExecutionManager::IsManagedCode(taAddr))
+ {
+ *type = CLRDATA_ADDRESS_MANAGED_METHOD;
+ goto Exit;
+ }
+
+ if (StubManager::IsStub(taAddr))
+ {
+ *type = CLRDATA_ADDRESS_RUNTIME_UNMANAGED_STUB;
+ goto Exit;
+ }
+ }
+
+ *type = CLRDATA_ADDRESS_UNRECOGNIZED;
+
+ Exit: ;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::GetRuntimeNameByAddress(
+ /* [in] */ CLRDATA_ADDRESS address,
+ /* [in] */ ULONG32 flags,
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *symbolLen,
+ /* [size_is][out] */ __out_ecount_opt(bufLen) WCHAR symbolBuf[ ],
+ /* [out] */ CLRDATA_ADDRESS* displacement)
+{
+ HRESULT status;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+#ifdef _TARGET_ARM_
+ address &= ~THUMB_CODE; //workaround for windbg passing in addresses with the THUMB mode bit set
+#endif
+ status = RawGetMethodName(address, flags, bufLen, symbolLen, symbolBuf,
+ displacement);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::StartEnumAppDomains(
+ /* [out] */ CLRDATA_ENUM* handle)
+{
+ HRESULT status;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ AppDomainIterator* iter = new (nothrow) AppDomainIterator(FALSE);
+ if (iter)
+ {
+ *handle = TO_CDENUM(iter);
+ status = S_OK;
+ }
+ else
+ {
+ status = E_OUTOFMEMORY;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::EnumAppDomain(
+ /* [in, out] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataAppDomain **appDomain)
+{
+ HRESULT status;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ AppDomainIterator* iter = FROM_CDENUM(AppDomainIterator, *handle);
+ if (iter->Next())
+ {
+ *appDomain = new (nothrow)
+ ClrDataAppDomain(this, iter->GetDomain());
+ status = *appDomain ? S_OK : E_OUTOFMEMORY;
+ }
+ else
+ {
+ status = S_FALSE;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::EndEnumAppDomains(
+ /* [in] */ CLRDATA_ENUM handle)
+{
+ HRESULT status;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ AppDomainIterator* iter = FROM_CDENUM(AppDomainIterator, handle);
+ delete iter;
+ status = S_OK;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::GetAppDomainByUniqueID(
+ /* [in] */ ULONG64 uniqueID,
+ /* [out] */ IXCLRDataAppDomain **appDomain)
+{
+ HRESULT status;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ AppDomainIterator iter(FALSE);
+
+ status = E_INVALIDARG;
+ while (iter.Next())
+ {
+ if (iter.GetDomain()->GetId().m_dwId == uniqueID)
+ {
+ *appDomain = new (nothrow)
+ ClrDataAppDomain(this, iter.GetDomain());
+ status = *appDomain ? S_OK : E_OUTOFMEMORY;
+ break;
+ }
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::StartEnumAssemblies(
+ /* [out] */ CLRDATA_ENUM* handle)
+{
+ HRESULT status;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ ProcessModIter* iter = new (nothrow) ProcessModIter;
+ if (iter)
+ {
+ *handle = TO_CDENUM(iter);
+ status = S_OK;
+ }
+ else
+ {
+ status = E_OUTOFMEMORY;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::EnumAssembly(
+ /* [in, out] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataAssembly **assembly)
+{
+ HRESULT status;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ ProcessModIter* iter = FROM_CDENUM(ProcessModIter, *handle);
+ Assembly* assem;
+
+ if ((assem = iter->NextAssem()))
+ {
+ *assembly = new (nothrow)
+ ClrDataAssembly(this, assem);
+ status = *assembly ? S_OK : E_OUTOFMEMORY;
+ }
+ else
+ {
+ status = S_FALSE;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::EndEnumAssemblies(
+ /* [in] */ CLRDATA_ENUM handle)
+{
+ HRESULT status;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ ProcessModIter* iter = FROM_CDENUM(ProcessModIter, handle);
+ delete iter;
+ status = S_OK;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::StartEnumModules(
+ /* [out] */ CLRDATA_ENUM* handle)
+{
+ HRESULT status;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ ProcessModIter* iter = new (nothrow) ProcessModIter;
+ if (iter)
+ {
+ *handle = TO_CDENUM(iter);
+ status = S_OK;
+ }
+ else
+ {
+ status = E_OUTOFMEMORY;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::EnumModule(
+ /* [in, out] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataModule **mod)
+{
+ HRESULT status;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ ProcessModIter* iter = FROM_CDENUM(ProcessModIter, *handle);
+ Module* curMod;
+
+ if ((curMod = iter->NextModule()))
+ {
+ *mod = new (nothrow)
+ ClrDataModule(this, curMod);
+ status = *mod ? S_OK : E_OUTOFMEMORY;
+ }
+ else
+ {
+ status = S_FALSE;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::EndEnumModules(
+ /* [in] */ CLRDATA_ENUM handle)
+{
+ HRESULT status;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ ProcessModIter* iter = FROM_CDENUM(ProcessModIter, handle);
+ delete iter;
+ status = S_OK;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::GetModuleByAddress(
+ /* [in] */ CLRDATA_ADDRESS address,
+ /* [out] */ IXCLRDataModule** mod)
+{
+ HRESULT status;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ ProcessModIter modIter;
+ Module* modDef;
+
+ while ((modDef = modIter.NextModule()))
+ {
+ TADDR base;
+ ULONG32 length;
+ PEFile* file = modDef->GetFile();
+
+ if ((base = PTR_TO_TADDR(file->GetLoadedImageContents(&length))))
+ {
+ if (TO_CDADDR(base) <= address &&
+ TO_CDADDR(base + length) > address)
+ {
+ break;
+ }
+ }
+ if (file->HasNativeImage())
+ {
+ base = PTR_TO_TADDR(file->GetLoadedNative()->GetBase());
+ length = file->GetLoadedNative()->GetVirtualSize();
+ if (TO_CDADDR(base) <= address &&
+ TO_CDADDR(base + length) > address)
+ {
+ break;
+ }
+ }
+ }
+
+ if (modDef)
+ {
+ *mod = new (nothrow)
+ ClrDataModule(this, modDef);
+ status = *mod ? S_OK : E_OUTOFMEMORY;
+ }
+ else
+ {
+ status = S_FALSE;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::StartEnumMethodDefinitionsByAddress(
+ /* [in] */ CLRDATA_ADDRESS address,
+ /* [out] */ CLRDATA_ENUM *handle)
+{
+ HRESULT status;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ ProcessModIter modIter;
+ Module* modDef;
+
+ while ((modDef = modIter.NextModule()))
+ {
+ TADDR base;
+ ULONG32 length;
+ PEFile* file = modDef->GetFile();
+
+ if ((base = PTR_TO_TADDR(file->GetLoadedImageContents(&length))))
+ {
+ if (TO_CDADDR(base) <= address &&
+ TO_CDADDR(base + length) > address)
+ {
+ break;
+ }
+ }
+ }
+
+ status = EnumMethodDefinitions::
+ CdStart(modDef, true, address, handle);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::EnumMethodDefinitionByAddress(
+ /* [out][in] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataMethodDefinition **method)
+{
+ HRESULT status;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ status = EnumMethodDefinitions::CdNext(this, handle, method);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::EndEnumMethodDefinitionsByAddress(
+ /* [in] */ CLRDATA_ENUM handle)
+{
+ HRESULT status;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ status = EnumMethodDefinitions::CdEnd(handle);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::StartEnumMethodInstancesByAddress(
+ /* [in] */ CLRDATA_ADDRESS address,
+ /* [in] */ IXCLRDataAppDomain* appDomain,
+ /* [out] */ CLRDATA_ENUM *handle)
+{
+ HRESULT status;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ MethodDesc* methodDesc;
+
+ *handle = 0;
+ status = S_FALSE;
+ TADDR taddr;
+ if( (status = TRY_CLRDATA_ADDRESS_TO_TADDR(address, &taddr)) != S_OK )
+ {
+ goto Exit;
+ }
+
+ if (IsPossibleCodeAddress(taddr) != S_OK)
+ {
+ goto Exit;
+ }
+
+ methodDesc = ExecutionManager::GetCodeMethodDesc(taddr);
+ if (!methodDesc)
+ {
+ goto Exit;
+ }
+
+ status = EnumMethodInstances::CdStart(methodDesc, appDomain,
+ handle);
+
+ Exit: ;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::EnumMethodInstanceByAddress(
+ /* [out][in] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataMethodInstance **method)
+{
+ HRESULT status;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ status = EnumMethodInstances::CdNext(this, handle, method);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::EndEnumMethodInstancesByAddress(
+ /* [in] */ CLRDATA_ENUM handle)
+{
+ HRESULT status;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ status = EnumMethodInstances::CdEnd(handle);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::GetDataByAddress(
+ /* [in] */ CLRDATA_ADDRESS address,
+ /* [in] */ ULONG32 flags,
+ /* [in] */ IXCLRDataAppDomain* appDomain,
+ /* [in] */ IXCLRDataTask* tlsTask,
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(bufLen, *nameLen) WCHAR nameBuf[ ],
+ /* [out] */ IXCLRDataValue **value,
+ /* [out] */ CLRDATA_ADDRESS *displacement)
+{
+ HRESULT status;
+
+ if (flags != 0)
+ {
+ return E_INVALIDARG;
+ }
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ // XXX Microsoft.
+ status = E_NOTIMPL;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::GetExceptionStateByExceptionRecord(
+ /* [in] */ EXCEPTION_RECORD64 *record,
+ /* [out] */ IXCLRDataExceptionState **exception)
+{
+ HRESULT status;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ // XXX Microsoft.
+ status = E_NOTIMPL;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::TranslateExceptionRecordToNotification(
+ /* [in] */ EXCEPTION_RECORD64 *record,
+ /* [in] */ IXCLRDataExceptionNotification *notify)
+{
+ HRESULT status = E_FAIL;
+ ClrDataModule* pubModule = NULL;
+ ClrDataMethodInstance* pubMethodInst = NULL;
+ ClrDataExceptionState* pubExState = NULL;
+ GcEvtArgs pubGcEvtArgs;
+ ULONG32 notifyType = 0;
+ DWORD catcherNativeOffset = 0;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ //
+ // We cannot hold the dac lock while calling
+ // out as the external code can do arbitrary things.
+ // Instead we make a pass over the exception
+ // information and create all necessary objects.
+ // We then leave the lock and make the callbac.
+ //
+
+ TADDR exInfo[EXCEPTION_MAXIMUM_PARAMETERS];
+ for (UINT i = 0; i < EXCEPTION_MAXIMUM_PARAMETERS; i++)
+ {
+ exInfo[i] = TO_TADDR(record->ExceptionInformation[i]);
+ }
+
+ notifyType = DACNotify::GetType(exInfo);
+ switch(notifyType)
+ {
+ case DACNotify::MODULE_LOAD_NOTIFICATION:
+ {
+ TADDR modulePtr;
+
+ if (DACNotify::ParseModuleLoadNotification(exInfo, modulePtr))
+ {
+ Module* clrModule = PTR_Module(modulePtr);
+ pubModule = new (nothrow) ClrDataModule(this, clrModule);
+ if (pubModule == NULL)
+ {
+ status = E_OUTOFMEMORY;
+ }
+ else
+ {
+ status = S_OK;
+ }
+ }
+ break;
+ }
+
+ case DACNotify::MODULE_UNLOAD_NOTIFICATION:
+ {
+ TADDR modulePtr;
+
+ if (DACNotify::ParseModuleUnloadNotification(exInfo, modulePtr))
+ {
+ Module* clrModule = PTR_Module(modulePtr);
+ pubModule = new (nothrow) ClrDataModule(this, clrModule);
+ if (pubModule == NULL)
+ {
+ status = E_OUTOFMEMORY;
+ }
+ else
+ {
+ status = S_OK;
+ }
+ }
+ break;
+ }
+
+ case DACNotify::JIT_NOTIFICATION:
+ {
+ TADDR methodDescPtr;
+
+ if (DACNotify::ParseJITNotification(exInfo, methodDescPtr))
+ {
+ // Try and find the right appdomain
+ MethodDesc* methodDesc = PTR_MethodDesc(methodDescPtr);
+ BaseDomain* baseDomain = methodDesc->GetDomain();
+ AppDomain* appDomain = NULL;
+
+ if (baseDomain->IsAppDomain())
+ {
+ appDomain = PTR_AppDomain(PTR_HOST_TO_TADDR(baseDomain));
+ }
+ else
+ {
+ // Find a likely domain, because it's the shared domain.
+ AppDomainIterator adi(FALSE);
+ appDomain = adi.GetDomain();
+ }
+
+ pubMethodInst =
+ new (nothrow) ClrDataMethodInstance(this,
+ appDomain,
+ methodDesc);
+ if (pubMethodInst == NULL)
+ {
+ status = E_OUTOFMEMORY;
+ }
+ else
+ {
+ status = S_OK;
+ }
+ }
+ break;
+ }
+
+ case DACNotify::EXCEPTION_NOTIFICATION:
+ {
+ TADDR threadPtr;
+
+ if (DACNotify::ParseExceptionNotification(exInfo, threadPtr))
+ {
+ // Translation can only occur at the time of
+ // receipt of the notify exception, so we assume
+ // that the Thread's current exception state
+ // is the state we want.
+ status = ClrDataExceptionState::
+ NewFromThread(this,
+ PTR_Thread(threadPtr),
+ &pubExState,
+ NULL);
+ }
+ break;
+ }
+
+ case DACNotify::GC_NOTIFICATION:
+ {
+ if (DACNotify::ParseGCNotification(exInfo, pubGcEvtArgs))
+ {
+ status = S_OK;
+ }
+ break;
+ }
+
+ case DACNotify::CATCH_ENTER_NOTIFICATION:
+ {
+ TADDR methodDescPtr;
+ if (DACNotify::ParseExceptionCatcherEnterNotification(exInfo, methodDescPtr, catcherNativeOffset))
+ {
+ // Try and find the right appdomain
+ MethodDesc* methodDesc = PTR_MethodDesc(methodDescPtr);
+ BaseDomain* baseDomain = methodDesc->GetDomain();
+ AppDomain* appDomain = NULL;
+
+ if (baseDomain->IsAppDomain())
+ {
+ appDomain = PTR_AppDomain(PTR_HOST_TO_TADDR(baseDomain));
+ }
+ else
+ {
+ // Find a likely domain, because it's the shared domain.
+ AppDomainIterator adi(FALSE);
+ appDomain = adi.GetDomain();
+ }
+
+ pubMethodInst =
+ new (nothrow) ClrDataMethodInstance(this,
+ appDomain,
+ methodDesc);
+ if (pubMethodInst == NULL)
+ {
+ status = E_OUTOFMEMORY;
+ }
+ else
+ {
+ status = S_OK;
+ }
+ }
+ break;
+ }
+
+ default:
+ status = E_INVALIDARG;
+ break;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+
+ if (status == S_OK)
+ {
+ IXCLRDataExceptionNotification2* notify2;
+
+ if (notify->QueryInterface(__uuidof(IXCLRDataExceptionNotification2),
+ (void**)&notify2) != S_OK)
+ {
+ notify2 = NULL;
+ }
+
+ IXCLRDataExceptionNotification3* notify3;
+ if (notify->QueryInterface(__uuidof(IXCLRDataExceptionNotification3),
+ (void**)&notify3) != S_OK)
+ {
+ notify3 = NULL;
+ }
+
+ IXCLRDataExceptionNotification4* notify4;
+ if (notify->QueryInterface(__uuidof(IXCLRDataExceptionNotification4),
+ (void**)&notify4) != S_OK)
+ {
+ notify4 = NULL;
+ }
+
+ switch(notifyType)
+ {
+ case DACNotify::MODULE_LOAD_NOTIFICATION:
+ notify->OnModuleLoaded(pubModule);
+ break;
+
+ case DACNotify::MODULE_UNLOAD_NOTIFICATION:
+ notify->OnModuleUnloaded(pubModule);
+ break;
+
+ case DACNotify::JIT_NOTIFICATION:
+ notify->OnCodeGenerated(pubMethodInst);
+ break;
+
+ case DACNotify::EXCEPTION_NOTIFICATION:
+ if (notify2)
+ {
+ notify2->OnException(pubExState);
+ }
+ else
+ {
+ status = E_INVALIDARG;
+ }
+ break;
+
+ case DACNotify::GC_NOTIFICATION:
+ if (notify3)
+ {
+ notify3->OnGcEvent(pubGcEvtArgs);
+ }
+ break;
+
+ case DACNotify::CATCH_ENTER_NOTIFICATION:
+ if (notify4)
+ {
+ notify4->ExceptionCatcherEnter(pubMethodInst, catcherNativeOffset);
+ }
+ break;
+
+ default:
+ // notifyType has already been validated.
+ _ASSERTE(FALSE);
+ break;
+ }
+
+ if (notify2)
+ {
+ notify2->Release();
+ }
+ if (notify3)
+ {
+ notify3->Release();
+ }
+ }
+
+ if (pubModule)
+ {
+ pubModule->Release();
+ }
+ if (pubMethodInst)
+ {
+ pubMethodInst->Release();
+ }
+ if (pubExState)
+ {
+ pubExState->Release();
+ }
+
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::CreateMemoryValue(
+ /* [in] */ IXCLRDataAppDomain* appDomain,
+ /* [in] */ IXCLRDataTask* tlsTask,
+ /* [in] */ IXCLRDataTypeInstance* type,
+ /* [in] */ CLRDATA_ADDRESS addr,
+ /* [out] */ IXCLRDataValue** value)
+{
+ HRESULT status;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ AppDomain* dacDomain;
+ Thread* dacThread;
+ TypeHandle dacType;
+ ULONG32 flags;
+ NativeVarLocation loc;
+
+ dacDomain = ((ClrDataAppDomain*)appDomain)->GetAppDomain();
+ if (tlsTask)
+ {
+ dacThread = ((ClrDataTask*)tlsTask)->GetThread();
+ }
+ else
+ {
+ dacThread = NULL;
+ }
+ dacType = ((ClrDataTypeInstance*)type)->GetTypeHandle();
+
+ flags = GetTypeFieldValueFlags(dacType, NULL, 0, false);
+
+ loc.addr = addr;
+ loc.size = dacType.GetSize();
+ loc.contextReg = false;
+
+ *value = new (nothrow)
+ ClrDataValue(this, dacDomain, dacThread, flags,
+ dacType, addr, 1, &loc);
+ status = *value ? S_OK : E_OUTOFMEMORY;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::SetAllTypeNotifications(
+ /* [in] */ IXCLRDataModule* mod,
+ /* [in] */ ULONG32 flags)
+{
+ HRESULT status;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ // XXX Microsoft.
+ status = E_NOTIMPL;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::SetAllCodeNotifications(
+ /* [in] */ IXCLRDataModule* mod,
+ /* [in] */ ULONG32 flags)
+{
+ HRESULT status;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ status = E_FAIL;
+
+ if (!IsValidMethodCodeNotification(flags))
+ {
+ status = E_INVALIDARG;
+ }
+ else
+ {
+ JITNotifications jn(GetHostJitNotificationTable());
+ if (!jn.IsActive())
+ {
+ status = E_OUTOFMEMORY;
+ }
+ else
+ {
+ BOOL changedTable;
+ TADDR modulePtr = mod ?
+ PTR_HOST_TO_TADDR(((ClrDataModule*)mod)->GetModule()) :
+ NULL;
+
+ if (jn.SetAllNotifications(modulePtr, flags, &changedTable))
+ {
+ if (!changedTable ||
+ (changedTable && jn.UpdateOutOfProcTable()))
+ {
+ status = S_OK;
+ }
+ }
+ }
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::GetTypeNotifications(
+ /* [in] */ ULONG32 numTokens,
+ /* [in, size_is(numTokens)] */ IXCLRDataModule* mods[],
+ /* [in] */ IXCLRDataModule* singleMod,
+ /* [in, size_is(numTokens)] */ mdTypeDef tokens[],
+ /* [out, size_is(numTokens)] */ ULONG32 flags[])
+{
+ HRESULT status;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ // XXX Microsoft.
+ status = E_NOTIMPL;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::SetTypeNotifications(
+ /* [in] */ ULONG32 numTokens,
+ /* [in, size_is(numTokens)] */ IXCLRDataModule* mods[],
+ /* [in] */ IXCLRDataModule* singleMod,
+ /* [in, size_is(numTokens)] */ mdTypeDef tokens[],
+ /* [in, size_is(numTokens)] */ ULONG32 flags[],
+ /* [in] */ ULONG32 singleFlags)
+{
+ HRESULT status;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ // XXX Microsoft.
+ status = E_NOTIMPL;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::GetCodeNotifications(
+ /* [in] */ ULONG32 numTokens,
+ /* [in, size_is(numTokens)] */ IXCLRDataModule* mods[],
+ /* [in] */ IXCLRDataModule* singleMod,
+ /* [in, size_is(numTokens)] */ mdMethodDef tokens[],
+ /* [out, size_is(numTokens)] */ ULONG32 flags[])
+{
+ HRESULT status;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ if ((flags == NULL || tokens == NULL) ||
+ (mods == NULL && singleMod == NULL) ||
+ (mods != NULL && singleMod != NULL))
+ {
+ status = E_INVALIDARG;
+ }
+ else
+ {
+ JITNotifications jn(GetHostJitNotificationTable());
+ if (!jn.IsActive())
+ {
+ status = E_OUTOFMEMORY;
+ }
+ else
+ {
+ TADDR modulePtr = NULL;
+ if (singleMod)
+ {
+ modulePtr = PTR_HOST_TO_TADDR(((ClrDataModule*)singleMod)->
+ GetModule());
+ }
+
+ for (ULONG32 i = 0; i < numTokens; i++)
+ {
+ if (singleMod == NULL)
+ {
+ modulePtr =
+ PTR_HOST_TO_TADDR(((ClrDataModule*)mods[i])->
+ GetModule());
+ }
+ USHORT jt = jn.Requested(modulePtr, tokens[i]);
+ flags[i] = jt;
+ }
+
+ status = S_OK;
+ }
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::SetCodeNotifications(
+ /* [in] */ ULONG32 numTokens,
+ /* [in, size_is(numTokens)] */ IXCLRDataModule* mods[],
+ /* [in] */ IXCLRDataModule* singleMod,
+ /* [in, size_is(numTokens)] */ mdMethodDef tokens[],
+ /* [in, size_is(numTokens)] */ ULONG32 flags[],
+ /* [in] */ ULONG32 singleFlags)
+{
+ HRESULT status = E_UNEXPECTED;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ if ((tokens == NULL) ||
+ (mods == NULL && singleMod == NULL) ||
+ (mods != NULL && singleMod != NULL))
+ {
+ status = E_INVALIDARG;
+ }
+ else
+ {
+ JITNotifications jn(GetHostJitNotificationTable());
+ if (!jn.IsActive() || numTokens > jn.GetTableSize())
+ {
+ status = E_OUTOFMEMORY;
+ }
+ else
+ {
+ BOOL changedTable = FALSE;
+
+ // Are flags valid?
+ if (flags)
+ {
+ for (ULONG32 check = 0; check < numTokens; check++)
+ {
+ if (!IsValidMethodCodeNotification(flags[check]))
+ {
+ status = E_INVALIDARG;
+ goto Exit;
+ }
+ }
+ }
+ else if (!IsValidMethodCodeNotification(singleFlags))
+ {
+ status = E_INVALIDARG;
+ goto Exit;
+ }
+
+ TADDR modulePtr = NULL;
+ if (singleMod)
+ {
+ modulePtr =
+ PTR_HOST_TO_TADDR(((ClrDataModule*)singleMod)->
+ GetModule());
+ }
+
+ for (ULONG32 i = 0; i < numTokens; i++)
+ {
+ if (singleMod == NULL)
+ {
+ modulePtr =
+ PTR_HOST_TO_TADDR(((ClrDataModule*)mods[i])->
+ GetModule());
+ }
+
+ USHORT curFlags = jn.Requested(modulePtr, tokens[i]);
+ USHORT setFlags = (USHORT)(flags ? flags[i] : singleFlags);
+
+ if (curFlags != setFlags)
+ {
+ if (!jn.SetNotification(modulePtr, tokens[i],
+ setFlags))
+ {
+ status = E_FAIL;
+ goto Exit;
+ }
+
+ changedTable = TRUE;
+ }
+ }
+
+ if (!changedTable ||
+ (changedTable && jn.UpdateOutOfProcTable()))
+ {
+ status = S_OK;
+ }
+ }
+ }
+
+Exit: ;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT
+ClrDataAccess::GetOtherNotificationFlags(
+ /* [out] */ ULONG32* flags)
+{
+ HRESULT status;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ *flags = g_dacNotificationFlags;
+ status = S_OK;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT
+ClrDataAccess::SetOtherNotificationFlags(
+ /* [in] */ ULONG32 flags)
+{
+ HRESULT status;
+
+ if ((flags & ~(CLRDATA_NOTIFY_ON_MODULE_LOAD |
+ CLRDATA_NOTIFY_ON_MODULE_UNLOAD |
+ CLRDATA_NOTIFY_ON_EXCEPTION |
+ CLRDATA_NOTIFY_ON_EXCEPTION_CATCH_ENTER)) != 0)
+ {
+ return E_INVALIDARG;
+ }
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ g_dacNotificationFlags = flags;
+ status = S_OK;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+enum
+{
+ STUB_BUF_FLAGS_START,
+
+ STUB_BUF_METHOD_JITTED,
+ STUB_BUF_FRAME_PUSHED,
+ STUB_BUF_STUB_MANAGER_PUSHED,
+
+ STUB_BUF_FLAGS_END,
+};
+
+union STUB_BUF
+{
+ CLRDATA_FOLLOW_STUB_BUFFER apiBuf;
+ struct
+ {
+ ULONG64 flags;
+ ULONG64 addr;
+ ULONG64 arg1;
+ } u;
+};
+
+HRESULT
+ClrDataAccess::FollowStubStep(
+ /* [in] */ Thread* thread,
+ /* [in] */ ULONG32 inFlags,
+ /* [in] */ TADDR inAddr,
+ /* [in] */ union STUB_BUF* inBuffer,
+ /* [out] */ TADDR* outAddr,
+ /* [out] */ union STUB_BUF* outBuffer,
+ /* [out] */ ULONG32* outFlags)
+{
+ TraceDestination trace;
+ bool traceDone = false;
+ BYTE* retAddr;
+ T_CONTEXT localContext;
+ REGDISPLAY regDisp;
+ MethodDesc* methodDesc;
+
+ ZeroMemory(outBuffer, sizeof(*outBuffer));
+
+ if (inBuffer)
+ {
+ switch(inBuffer->u.flags)
+ {
+ case STUB_BUF_METHOD_JITTED:
+ if (inAddr != GFN_TADDR(DACNotifyCompilationFinished))
+ {
+ return E_INVALIDARG;
+ }
+
+ // It's possible that this notification is
+ // for a different method, so double-check
+ // and recycle the notification if necessary.
+ methodDesc = PTR_MethodDesc(CORDB_ADDRESS_TO_TADDR(inBuffer->u.addr));
+ if (methodDesc->HasNativeCode())
+ {
+ *outAddr = methodDesc->GetNativeCode();
+ *outFlags = CLRDATA_FOLLOW_STUB_EXIT;
+ return S_OK;
+ }
+
+ // We didn't end up with native code so try again.
+ trace.InitForUnjittedMethod(methodDesc);
+ traceDone = true;
+ break;
+
+ case STUB_BUF_FRAME_PUSHED:
+ if (!thread ||
+ inAddr != inBuffer->u.addr)
+ {
+ return E_INVALIDARG;
+ }
+
+ trace.InitForFramePush(CORDB_ADDRESS_TO_TADDR(inBuffer->u.addr));
+ DacGetThreadContext(thread, &localContext);
+ thread->FillRegDisplay(&regDisp, &localContext);
+ if (!thread->GetFrame()->
+ TraceFrame(thread,
+ TRUE,
+ &trace,
+ &regDisp))
+ {
+ return E_FAIL;
+ }
+
+ traceDone = true;
+ break;
+
+ case STUB_BUF_STUB_MANAGER_PUSHED:
+ if (!thread ||
+ inAddr != inBuffer->u.addr ||
+ !inBuffer->u.arg1)
+ {
+ return E_INVALIDARG;
+ }
+
+ trace.InitForManagerPush(CORDB_ADDRESS_TO_TADDR(inBuffer->u.addr),
+ PTR_StubManager(CORDB_ADDRESS_TO_TADDR(inBuffer->u.arg1)));
+ DacGetThreadContext(thread, &localContext);
+ if (!trace.GetStubManager()->
+ TraceManager(thread,
+ &trace,
+ &localContext,
+ &retAddr))
+ {
+ return E_FAIL;
+ }
+
+ traceDone = true;
+ break;
+
+ default:
+ return E_INVALIDARG;
+ }
+ }
+
+ if ((!traceDone &&
+ !StubManager::TraceStub(inAddr, &trace)) ||
+ !StubManager::FollowTrace(&trace))
+ {
+ return E_NOINTERFACE;
+ }
+
+ switch(trace.GetTraceType())
+ {
+ case TRACE_UNMANAGED:
+ case TRACE_MANAGED:
+ // We've hit non-stub code so we're done.
+ *outAddr = trace.GetAddress();
+ *outFlags = CLRDATA_FOLLOW_STUB_EXIT;
+ break;
+
+ case TRACE_UNJITTED_METHOD:
+ // The stub causes jitting, so return
+ // the address of the jit-complete routine
+ // so that the real native address can
+ // be picked up once the JIT is done.
+
+ // One special case is ngen'ed code that
+ // needs the prestub run. This results in
+ // an unjitted trace but no jitting will actually
+ // occur since the code is ngen'ed. Detect
+ // this and redirect to the actual code.
+ methodDesc = trace.GetMethodDesc();
+ if (methodDesc->IsPreImplemented() &&
+ !methodDesc->IsPointingToNativeCode() &&
+ !methodDesc->IsGenericMethodDefinition() &&
+ methodDesc->HasNativeCode())
+ {
+ *outAddr = methodDesc->GetNativeCode();
+ *outFlags = CLRDATA_FOLLOW_STUB_EXIT;
+ break;
+ }
+
+ *outAddr = GFN_TADDR(DACNotifyCompilationFinished);
+ outBuffer->u.flags = STUB_BUF_METHOD_JITTED;
+ outBuffer->u.addr = PTR_HOST_TO_TADDR(methodDesc);
+ *outFlags = CLRDATA_FOLLOW_STUB_INTERMEDIATE;
+ break;
+
+ case TRACE_FRAME_PUSH:
+ if (!thread)
+ {
+ return E_INVALIDARG;
+ }
+
+ *outAddr = trace.GetAddress();
+ outBuffer->u.flags = STUB_BUF_FRAME_PUSHED;
+ outBuffer->u.addr = trace.GetAddress();
+ *outFlags = CLRDATA_FOLLOW_STUB_INTERMEDIATE;
+ break;
+
+ case TRACE_MGR_PUSH:
+ if (!thread)
+ {
+ return E_INVALIDARG;
+ }
+
+ *outAddr = trace.GetAddress();
+ outBuffer->u.flags = STUB_BUF_STUB_MANAGER_PUSHED;
+ outBuffer->u.addr = trace.GetAddress();
+ outBuffer->u.arg1 = PTR_HOST_TO_TADDR(trace.GetStubManager());
+ *outFlags = CLRDATA_FOLLOW_STUB_INTERMEDIATE;
+ break;
+
+ default:
+ return E_INVALIDARG;
+ }
+
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::FollowStub(
+ /* [in] */ ULONG32 inFlags,
+ /* [in] */ CLRDATA_ADDRESS inAddr,
+ /* [in] */ CLRDATA_FOLLOW_STUB_BUFFER* _inBuffer,
+ /* [out] */ CLRDATA_ADDRESS* outAddr,
+ /* [out] */ CLRDATA_FOLLOW_STUB_BUFFER* _outBuffer,
+ /* [out] */ ULONG32* outFlags)
+{
+ return FollowStub2(NULL, inFlags, inAddr, _inBuffer,
+ outAddr, _outBuffer, outFlags);
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAccess::FollowStub2(
+ /* [in] */ IXCLRDataTask* task,
+ /* [in] */ ULONG32 inFlags,
+ /* [in] */ CLRDATA_ADDRESS _inAddr,
+ /* [in] */ CLRDATA_FOLLOW_STUB_BUFFER* _inBuffer,
+ /* [out] */ CLRDATA_ADDRESS* _outAddr,
+ /* [out] */ CLRDATA_FOLLOW_STUB_BUFFER* _outBuffer,
+ /* [out] */ ULONG32* outFlags)
+{
+ HRESULT status;
+
+ if ((inFlags & ~(CLRDATA_FOLLOW_STUB_DEFAULT)) != 0)
+ {
+ return E_INVALIDARG;
+ }
+
+ STUB_BUF* inBuffer = (STUB_BUF*)_inBuffer;
+ STUB_BUF* outBuffer = (STUB_BUF*)_outBuffer;
+
+ if (inBuffer &&
+ (inBuffer->u.flags <= STUB_BUF_FLAGS_START ||
+ inBuffer->u.flags >= STUB_BUF_FLAGS_END))
+ {
+ return E_INVALIDARG;
+ }
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ STUB_BUF cycleBuf;
+ TADDR inAddr = TO_TADDR(_inAddr);
+ TADDR outAddr;
+ Thread* thread = task ? ((ClrDataTask*)task)->GetThread() : NULL;
+ ULONG32 loops = 4;
+
+ for (;;)
+ {
+ if ((status = FollowStubStep(thread,
+ inFlags,
+ inAddr,
+ inBuffer,
+ &outAddr,
+ outBuffer,
+ outFlags)) != S_OK)
+ {
+ break;
+ }
+
+ // Some stub tracing just requests further iterations
+ // of processing, so detect that case and loop.
+ if (outAddr != inAddr)
+ {
+ // We can make forward progress, we're done.
+ *_outAddr = TO_CDADDR(outAddr);
+ break;
+ }
+
+ // We need more processing. As a protection
+ // against infinite loops in corrupted or buggy
+ // situations, we only allow this to happen a
+ // small number of times.
+ if (--loops == 0)
+ {
+ ZeroMemory(outBuffer, sizeof(*outBuffer));
+ status = E_FAIL;
+ break;
+ }
+
+ cycleBuf = *outBuffer;
+ inBuffer = &cycleBuf;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable:4297)
+#endif // _MSC_VER
+STDMETHODIMP
+ClrDataAccess::GetGcNotification(GcEvtArgs* gcEvtArgs)
+{
+ HRESULT status;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ if (gcEvtArgs->typ >= GC_EVENT_TYPE_MAX)
+ {
+ status = E_INVALIDARG;
+ }
+ else
+ {
+ GcNotifications gn(GetHostGcNotificationTable());
+ if (!gn.IsActive())
+ {
+ status = E_OUTOFMEMORY;
+ }
+ else
+ {
+ GcEvtArgs *res = gn.GetNotification(*gcEvtArgs);
+ if (res != NULL)
+ {
+ *gcEvtArgs = *res;
+ status = S_OK;
+ }
+ else
+ {
+ status = E_FAIL;
+ }
+ }
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+STDMETHODIMP
+ClrDataAccess::SetGcNotification(IN GcEvtArgs gcEvtArgs)
+{
+ HRESULT status;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ if (gcEvtArgs.typ >= GC_EVENT_TYPE_MAX)
+ {
+ status = E_INVALIDARG;
+ }
+ else
+ {
+ GcNotifications gn(GetHostGcNotificationTable());
+ if (!gn.IsActive())
+ {
+ status = E_OUTOFMEMORY;
+ }
+ else
+ {
+ if (gn.SetNotification(gcEvtArgs) && gn.UpdateOutOfProcTable())
+ {
+ status = S_OK;
+ }
+ else
+ {
+ status = E_FAIL;
+ }
+ }
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif // _MSC_VER
+
+HRESULT
+ClrDataAccess::Initialize(void)
+{
+ HRESULT hr;
+ CLRDATA_ADDRESS base;
+
+ //
+ // We do not currently support cross-platform
+ // debugging. Verify that cross-platform is not
+ // being attempted.
+ //
+
+ // Determine our platform based on the pre-processor macros set when we were built
+
+#ifdef FEATURE_PAL
+ #if defined(DBG_TARGET_X86)
+ CorDebugPlatform hostPlatform = CORDB_PLATFORM_POSIX_X86;
+ #elif defined(DBG_TARGET_AMD64)
+ CorDebugPlatform hostPlatform = CORDB_PLATFORM_POSIX_AMD64;
+ #elif defined(DBG_TARGET_ARM)
+ CorDebugPlatform hostPlatform = CORDB_PLATFORM_POSIX_ARM;
+ #elif defined(DBG_TARGET_ARM64)
+ CorDebugPlatform hostPlatform = CORDB_PLATFORM_POSIX_ARM64;
+ #else
+ #error Unknown Processor.
+ #endif
+#else
+ #if defined(DBG_TARGET_X86)
+ CorDebugPlatform hostPlatform = CORDB_PLATFORM_WINDOWS_X86;
+ #elif defined(DBG_TARGET_AMD64)
+ CorDebugPlatform hostPlatform = CORDB_PLATFORM_WINDOWS_AMD64;
+ #elif defined(DBG_TARGET_ARM)
+ CorDebugPlatform hostPlatform = CORDB_PLATFORM_WINDOWS_ARM;
+ #elif defined(DBG_TARGET_ARM64)
+ CorDebugPlatform hostPlatform = CORDB_PLATFORM_WINDOWS_ARM64;
+ #else
+ #error Unknown Processor.
+ #endif
+#endif
+
+ CorDebugPlatform targetPlatform;
+ IfFailRet(m_pTarget->GetPlatform(&targetPlatform));
+
+ if (targetPlatform != hostPlatform)
+ {
+ // DAC fatal error: Platform mismatch - the platform reported by the data target
+ // is not what this version of mscordacwks.dll was built for.
+ return CORDBG_E_UNCOMPATIBLE_PLATFORMS;
+ }
+
+ //
+ // Get the current DLL base for mscorwks globals.
+ // In case of multiple-CLRs, there may be multiple dlls named "mscorwks".
+ // code:OpenVirtualProcess can take the base address (clrInstanceId) to select exactly
+ // which CLR to is being target. If so, m_globalBase will already be set.
+ //
+
+ if (m_globalBase == 0)
+ {
+ // Caller didn't specify which CLR to debug. This supports Whidbey SOS cases, so we should
+ // be using a legacy data target.
+ if (m_pLegacyTarget == NULL)
+ {
+ DacError(E_INVALIDARG);
+ UNREACHABLE();
+ }
+
+ // Since this is Whidbey, assume there's only 1 CLR named "mscorwks.dll" and pick that.
+ IfFailRet(m_pLegacyTarget->GetImageBase(MAIN_CLR_DLL_NAME_W, &base));
+
+ m_globalBase = TO_TADDR(base);
+ }
+
+ // We don't need to try too hard to prevent
+ // multiple initializations as each one will
+ // copy the same data into the globals and so
+ // cannot interfere with each other.
+ if (!s_procInit)
+ {
+ IfFailRet(GetDacGlobals());
+ IfFailRet(DacGetHostVtPtrs());
+ s_procInit = true;
+ }
+
+ //
+ // DAC is now setup and ready to use
+ //
+
+ // Do some validation
+ IfFailRet(VerifyDlls());
+
+ // To support EH SxS, utilcode requires the base address of the runtime
+ // as part of its initialization so that functions like "WasThrownByUs" work correctly since
+ // they use the CLR base address to check if an exception was raised by a given instance of the runtime
+ // or not.
+ //
+ // Thus, when DAC is initialized, initialize utilcode with the base address of the runtime loaded in the
+ // target process. This is similar to work done in CorDB::SetTargetCLR for mscordbi.
+
+ // Initialize UtilCode for SxS scenarios
+ CoreClrCallbacks cccallbacks;
+ cccallbacks.m_hmodCoreCLR = (HINSTANCE)m_globalBase; // Base address of the runtime in the target process
+ cccallbacks.m_pfnIEE = NULL;
+ cccallbacks.m_pfnGetCORSystemDirectory = NULL;
+ cccallbacks.m_pfnGetCLRFunction = NULL;
+ InitUtilcode(cccallbacks);
+
+ return S_OK;
+}
+
+Thread*
+ClrDataAccess::FindClrThreadByTaskId(ULONG64 taskId)
+{
+ Thread* thread = NULL;
+
+ if (!ThreadStore::s_pThreadStore)
+ {
+ return NULL;
+ }
+
+ while ((thread = ThreadStore::GetAllThreadList(thread, 0, 0)))
+ {
+ if (thread->GetThreadId() == (DWORD)taskId)
+ {
+ return thread;
+ }
+ }
+
+ return NULL;
+}
+
+HRESULT
+ClrDataAccess::IsPossibleCodeAddress(IN TADDR address)
+{
+ SUPPORTS_DAC;
+ BYTE testRead;
+ ULONG32 testDone;
+
+ // First do a trivial check on the readability of the
+ // address. This makes for quick rejection of bogus
+ // addresses that the debugger sends in when searching
+ // stacks for return addresses.
+ // XXX Microsoft - Will this cause problems in minidumps
+ // where it's possible the stub is identifiable but
+ // the stub code isn't present? Yes, but the lack
+ // of that code could confuse the walker on its own
+ // if it does code analysis.
+ if ((m_pTarget->ReadVirtual(address, &testRead, sizeof(testRead),
+ &testDone) != S_OK) ||
+ !testDone)
+ {
+ return E_INVALIDARG;
+ }
+
+ return S_OK;
+}
+
+HRESULT
+ClrDataAccess::GetFullMethodName(
+ IN MethodDesc* methodDesc,
+ IN ULONG32 symbolChars,
+ OUT ULONG32* symbolLen,
+ __out_ecount_part_opt(symbolChars, *symbolLen) LPWSTR symbol
+ )
+{
+ StackSString s;
+#ifdef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+ PAL_CPP_TRY
+ {
+#endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+
+ TypeString::AppendMethodInternal(s, methodDesc, TypeString::FormatSignature|TypeString::FormatNamespace|TypeString::FormatFullInst);
+
+#ifdef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+ }
+ PAL_CPP_CATCH_ALL
+ {
+ if (!MdCacheGetEEName(dac_cast<TADDR>(methodDesc), s))
+ {
+ PAL_CPP_RETHROW;
+ }
+ }
+ PAL_CPP_ENDTRY
+#endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+
+ if (symbol)
+ {
+ // Copy as much as we can and truncate the rest.
+ wcsncpy_s(symbol, symbolChars, s.GetUnicode(), _TRUNCATE);
+ }
+
+ if (symbolLen)
+ *symbolLen = s.GetCount() + 1;
+
+ if (symbol != NULL && symbolChars < (s.GetCount() + 1))
+ return S_FALSE;
+ else
+ return S_OK;
+}
+
+PCSTR
+ClrDataAccess::GetJitHelperName(
+ IN TADDR address,
+ IN bool dynamicHelpersOnly /*=false*/
+ )
+{
+ const static PCSTR s_rgHelperNames[] = {
+#define JITHELPER(code,fn,sig) #code,
+#include <jithelpers.h>
+ };
+ static_assert_no_msg(COUNTOF(s_rgHelperNames) == CORINFO_HELP_COUNT);
+
+#ifdef FEATURE_PAL
+ if (!dynamicHelpersOnly)
+#else
+ if (!dynamicHelpersOnly && g_runtimeLoadedBaseAddress <= address &&
+ address < g_runtimeLoadedBaseAddress + g_runtimeVirtualSize)
+#endif // FEATURE_PAL
+ {
+ // Read the whole table from the target in one shot for better performance
+ VMHELPDEF * pTable = static_cast<VMHELPDEF *>(
+ PTR_READ(dac_cast<TADDR>(&hlpFuncTable), CORINFO_HELP_COUNT * sizeof(VMHELPDEF)));
+
+ for (int i = 0; i < CORINFO_HELP_COUNT; i++)
+ {
+ if (address == (TADDR)(pTable[i].pfnHelper))
+ return s_rgHelperNames[i];
+ }
+ }
+
+ // Check if its a dynamically generated JIT helper
+ const static CorInfoHelpFunc s_rgDynamicHCallIds[] = {
+#define DYNAMICJITHELPER(code, fn, sig) code,
+#define JITHELPER(code, fn,sig)
+#include <jithelpers.h>
+ };
+
+ // Read the whole table from the target in one shot for better performance
+ VMHELPDEF * pDynamicTable = static_cast<VMHELPDEF *>(
+ PTR_READ(dac_cast<TADDR>(&hlpDynamicFuncTable), DYNAMIC_CORINFO_HELP_COUNT * sizeof(VMHELPDEF)));
+ for (unsigned d = 0; d < DYNAMIC_CORINFO_HELP_COUNT; d++)
+ {
+ if (address == (TADDR)(pDynamicTable[d].pfnHelper))
+ {
+ return s_rgHelperNames[s_rgDynamicHCallIds[d]];
+ }
+ }
+
+ return NULL;
+}
+
+HRESULT
+ClrDataAccess::RawGetMethodName(
+ /* [in] */ CLRDATA_ADDRESS address,
+ /* [in] */ ULONG32 flags,
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *symbolLen,
+ /* [size_is][out] */ __out_ecount_opt(bufLen) WCHAR symbolBuf[ ],
+ /* [out] */ CLRDATA_ADDRESS* displacement)
+{
+#ifdef _TARGET_ARM_
+ _ASSERTE((address & THUMB_CODE) == 0);
+ address &= ~THUMB_CODE;
+#endif
+
+ const UINT k_cch64BitHexFormat = COUNTOF("1234567812345678");
+ HRESULT status;
+
+ if (flags != 0)
+ {
+ return E_INVALIDARG;
+ }
+
+ TADDR taddr;
+ if( (status = TRY_CLRDATA_ADDRESS_TO_TADDR(address, &taddr)) != S_OK )
+ {
+ return status;
+ }
+
+ if ((status = IsPossibleCodeAddress(taddr)) != S_OK)
+ {
+ return status;
+ }
+
+ PTR_StubManager pStubManager;
+ MethodDesc* methodDesc = NULL;
+
+ {
+ EECodeInfo codeInfo(TO_TADDR(address));
+ if (codeInfo.IsValid())
+ {
+ if (displacement)
+ {
+ *displacement = codeInfo.GetRelOffset();
+ }
+
+ methodDesc = codeInfo.GetMethodDesc();
+ goto NameFromMethodDesc;
+ }
+ }
+
+ pStubManager = StubManager::FindStubManager(TO_TADDR(address));
+ if (pStubManager != NULL)
+ {
+ if (displacement)
+ {
+ *displacement = 0;
+ }
+
+ //
+ // Special-cased stub managers
+ //
+#ifdef FEATURE_PREJIT
+ if (pStubManager == RangeSectionStubManager::g_pManager)
+ {
+ switch (RangeSectionStubManager::GetStubKind(TO_TADDR(address)))
+ {
+ case STUB_CODE_BLOCK_PRECODE:
+ goto PrecodeStub;
+
+ case STUB_CODE_BLOCK_JUMPSTUB:
+ goto JumpStub;
+
+ default:
+ break;
+ }
+ }
+ else
+#endif
+ if (pStubManager == PrecodeStubManager::g_pManager)
+ {
+ PrecodeStub:
+ PCODE alignedAddress = AlignDown(TO_TADDR(address), PRECODE_ALIGNMENT);
+
+#ifdef _TARGET_ARM_
+ alignedAddress += THUMB_CODE;
+#endif
+
+ SIZE_T maxPrecodeSize = sizeof(StubPrecode);
+
+#ifdef HAS_THISPTR_RETBUF_PRECODE
+ maxPrecodeSize = max(maxPrecodeSize, sizeof(ThisPtrRetBufPrecode));
+#endif
+#ifdef HAS_REMOTING_PRECODE
+ maxPrecodeSize = max(maxPrecodeSize, sizeof(RemotingPrecode));
+#endif
+
+ for (SIZE_T i = 0; i < maxPrecodeSize / PRECODE_ALIGNMENT; i++)
+ {
+ EX_TRY
+ {
+ // Try to find matching precode entrypoint
+ Precode* pPrecode = Precode::GetPrecodeFromEntryPoint(alignedAddress, TRUE);
+ if (pPrecode != NULL)
+ {
+ methodDesc = pPrecode->GetMethodDesc();
+ if (methodDesc != NULL)
+ {
+ if (DacValidateMD(methodDesc))
+ {
+ if (displacement)
+ {
+ *displacement = TO_TADDR(address) - PCODEToPINSTR(alignedAddress);
+ }
+ goto NameFromMethodDesc;
+ }
+ }
+ }
+ alignedAddress -= PRECODE_ALIGNMENT;
+ }
+ EX_CATCH
+ {
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+ }
+ }
+ else
+ if (pStubManager == JumpStubStubManager::g_pManager)
+ {
+ JumpStub:
+ PCODE pTarget = decodeBackToBackJump(TO_TADDR(address));
+
+ HRESULT hr = GetRuntimeNameByAddress(pTarget, flags, bufLen, symbolLen, symbolBuf, NULL);
+ if (SUCCEEDED(hr))
+ {
+ return hr;
+ }
+
+ PCSTR pHelperName = GetJitHelperName(pTarget);
+ if (pHelperName != NULL)
+ {
+ hr = ConvertUtf8(pHelperName, bufLen, symbolLen, symbolBuf);
+ if (FAILED(hr))
+ return S_FALSE;
+
+ return hr;
+ }
+ }
+
+ static WCHAR s_wszFormatNameWithStubManager[] = W("CLRStub[%s]@%I64x");
+
+ LPCWSTR wszStubManagerName = pStubManager->GetStubManagerName(TO_TADDR(address));
+ _ASSERTE(wszStubManagerName != NULL);
+
+ HRESULT hr = StringCchPrintfW(
+ symbolBuf,
+ bufLen,
+ s_wszFormatNameWithStubManager,
+ wszStubManagerName, // Arg 1 = stub name
+ TO_TADDR(address)); // Arg 2 = stub hex address
+
+ if (hr == S_OK)
+ {
+ // Printf succeeded, so we have an exact char count to return
+ if (symbolLen)
+ {
+ size_t cchSymbol = wcslen(symbolBuf) + 1;
+ if (!FitsIn<ULONG32>(cchSymbol))
+ return COR_E_OVERFLOW;
+
+ *symbolLen = (ULONG32) cchSymbol;
+ }
+ return S_OK;
+ }
+
+ // Printf failed. Estimate a size that will be at least big enough to hold the name
+ if (symbolLen)
+ {
+ size_t cchSymbol = COUNTOF(s_wszFormatNameWithStubManager) +
+ wcslen(wszStubManagerName) +
+ k_cch64BitHexFormat +
+ 1;
+
+ if (!FitsIn<ULONG32>(cchSymbol))
+ return COR_E_OVERFLOW;
+
+ *symbolLen = (ULONG32) cchSymbol;
+ }
+ return S_FALSE;
+ }
+
+ // Do not waste time looking up name for static helper. Debugger can get the actual name from .pdb.
+ PCSTR pHelperName;
+ pHelperName = GetJitHelperName(TO_TADDR(address), true /* dynamicHelpersOnly */);
+ if (pHelperName != NULL)
+ {
+ if (displacement)
+ {
+ *displacement = 0;
+ }
+
+ HRESULT hr = ConvertUtf8(pHelperName, bufLen, symbolLen, symbolBuf);
+ if (FAILED(hr))
+ return S_FALSE;
+
+ return S_OK;
+ }
+
+ return E_NOINTERFACE;
+
+NameFromMethodDesc:
+ if (methodDesc->GetClassification() == mcDynamic &&
+ !methodDesc->GetSig())
+ {
+ // XXX Microsoft - Should this case have a more specific name?
+ static WCHAR s_wszFormatNameAddressOnly[] = W("CLRStub@%I64x");
+
+ HRESULT hr = StringCchPrintfW(
+ symbolBuf,
+ bufLen,
+ s_wszFormatNameAddressOnly,
+ TO_TADDR(address));
+
+ if (hr == S_OK)
+ {
+ // Printf succeeded, so we have an exact char count to return
+ if (symbolLen)
+ {
+ size_t cchSymbol = wcslen(symbolBuf) + 1;
+ if (!FitsIn<ULONG32>(cchSymbol))
+ return COR_E_OVERFLOW;
+
+ *symbolLen = (ULONG32) cchSymbol;
+ }
+ return S_OK;
+ }
+
+ // Printf failed. Estimate a size that will be at least big enough to hold the name
+ if (symbolLen)
+ {
+ size_t cchSymbol = COUNTOF(s_wszFormatNameAddressOnly) +
+ k_cch64BitHexFormat +
+ 1;
+
+ if (!FitsIn<ULONG32>(cchSymbol))
+ return COR_E_OVERFLOW;
+
+ *symbolLen = (ULONG32) cchSymbol;
+ }
+
+ return S_FALSE;
+ }
+
+ return GetFullMethodName(methodDesc, bufLen, symbolLen, symbolBuf);
+}
+
+HRESULT
+ClrDataAccess::GetMethodExtents(MethodDesc* methodDesc,
+ METH_EXTENTS** extents)
+{
+ CLRDATA_ADDRESS_RANGE* curExtent;
+
+ {
+ //
+ // Get the information from the methoddesc.
+ // We'll go through the CodeManager + JitManagers, so this should work
+ // for all types of managed code.
+ //
+
+ PCODE methodStart = methodDesc->GetNativeCode();
+ if (!methodStart)
+ {
+ return E_NOINTERFACE;
+ }
+
+ EECodeInfo codeInfo(methodStart);
+ _ASSERTE(codeInfo.IsValid());
+
+ TADDR codeSize = codeInfo.GetCodeManager()->GetFunctionSize(codeInfo.GetGCInfoToken());
+
+ *extents = new (nothrow) METH_EXTENTS;
+ if (!*extents)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ (*extents)->numExtents = 1;
+ curExtent = (*extents)->extents;
+ curExtent->startAddress = TO_CDADDR(methodStart);
+ curExtent->endAddress =
+ curExtent->startAddress + codeSize;
+ curExtent++;
+ }
+
+ (*extents)->curExtent = 0;
+
+ return S_OK;
+}
+
+// Allocator to pass to the debug-info-stores...
+BYTE* DebugInfoStoreNew(void * pData, size_t cBytes)
+{
+ return new (nothrow) BYTE[cBytes];
+}
+
+HRESULT
+ClrDataAccess::GetMethodVarInfo(MethodDesc* methodDesc,
+ TADDR address,
+ ULONG32* numVarInfo,
+ ICorDebugInfo::NativeVarInfo** varInfo,
+ ULONG32* codeOffset)
+{
+ SUPPORTS_DAC;
+ COUNT_T countNativeVarInfo;
+ NewHolder<ICorDebugInfo::NativeVarInfo> nativeVars(NULL);
+
+ DebugInfoRequest request;
+ TADDR nativeCodeStartAddr = PCODEToPINSTR(methodDesc->GetNativeCode());
+ request.InitFromStartingAddr(methodDesc, nativeCodeStartAddr);
+
+ BOOL success = DebugInfoManager::GetBoundariesAndVars(
+ request,
+ DebugInfoStoreNew, NULL, // allocator
+ NULL, NULL,
+ &countNativeVarInfo, &nativeVars);
+
+
+ if (!success)
+ {
+ return E_FAIL;
+ }
+
+ if (!nativeVars || !countNativeVarInfo)
+ {
+ return E_NOINTERFACE;
+ }
+
+ *numVarInfo = countNativeVarInfo;
+ *varInfo = nativeVars;
+ nativeVars.SuppressRelease(); // To prevent NewHolder from releasing the memory
+
+ if (codeOffset)
+ {
+ *codeOffset = (ULONG32)
+ (address - nativeCodeStartAddr);
+ }
+ return S_OK;
+}
+
+HRESULT
+ClrDataAccess::GetMethodNativeMap(MethodDesc* methodDesc,
+ TADDR address,
+ ULONG32* numMap,
+ DebuggerILToNativeMap** map,
+ bool* mapAllocated,
+ CLRDATA_ADDRESS* codeStart,
+ ULONG32* codeOffset)
+{
+ _ASSERTE((codeOffset == NULL) || (address != NULL));
+
+ // Use the DebugInfoStore to get IL->Native maps.
+ // It doesn't matter whether we're jitted, ngenned etc.
+
+ DebugInfoRequest request;
+ TADDR nativeCodeStartAddr = PCODEToPINSTR(methodDesc->GetNativeCode());
+ request.InitFromStartingAddr(methodDesc, nativeCodeStartAddr);
+
+
+ // Bounds info.
+ ULONG32 countMapCopy;
+ NewHolder<ICorDebugInfo::OffsetMapping> mapCopy(NULL);
+
+ BOOL success = DebugInfoManager::GetBoundariesAndVars(
+ request,
+ DebugInfoStoreNew, NULL, // allocator
+ &countMapCopy, &mapCopy,
+ NULL, NULL);
+
+ if (!success)
+ {
+ return E_FAIL;
+ }
+
+
+ // Need to convert map formats.
+ *numMap = countMapCopy;
+
+ *map = new (nothrow) DebuggerILToNativeMap[countMapCopy];
+ if (!*map)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ ULONG32 i;
+ for (i = 0; i < *numMap; i++)
+ {
+ (*map)[i].ilOffset = mapCopy[i].ilOffset;
+ (*map)[i].nativeStartOffset = mapCopy[i].nativeOffset;
+ if (i > 0)
+ {
+ (*map)[i - 1].nativeEndOffset = (*map)[i].nativeStartOffset;
+ }
+ (*map)[i].source = mapCopy[i].source;
+ }
+ if (*numMap >= 1)
+ {
+ (*map)[i - 1].nativeEndOffset = 0;
+ }
+
+
+ // Update varion out params.
+ if (codeStart)
+ {
+ *codeStart = TO_CDADDR(nativeCodeStartAddr);
+ }
+ if (codeOffset)
+ {
+ *codeOffset = (ULONG32)
+ (address - nativeCodeStartAddr);
+ }
+
+ *mapAllocated = true;
+ return S_OK;
+}
+
+// Get the MethodDesc for a function
+// Arguments:
+// Input:
+// pModule - pointer to the module for the function
+// memberRef - metadata token for the function
+// Return Value:
+// MethodDesc for the function
+MethodDesc * ClrDataAccess::FindLoadedMethodRefOrDef(Module* pModule,
+ mdToken memberRef)
+{
+ CONTRACT(MethodDesc *)
+ {
+ GC_NOTRIGGER;
+ PRECONDITION(CheckPointer(pModule));
+ POSTCONDITION(CheckPointer(RETVAL, NULL_OK));
+ }
+ CONTRACT_END;
+
+ // Must have a MemberRef or a MethodDef
+ mdToken tkType = TypeFromToken(memberRef);
+ _ASSERTE((tkType == mdtMemberRef) || (tkType == mdtMethodDef));
+
+ if (tkType == mdtMemberRef)
+ {
+ RETURN pModule->LookupMemberRefAsMethod(memberRef);
+ }
+
+ RETURN pModule->LookupMethodDef(memberRef);
+} // FindLoadedMethodRefOrDef
+
+//
+// ReportMem - report a region of memory for dump gathering
+//
+// If you specify that you expect success, any failure will cause ReportMem to
+// return false. If you do not expect success, true is always returned.
+// This function only throws when all dump collection should be cancelled.
+//
+// Arguments:
+// addr - the starting target address for the memory to report
+// size - the length (in bytes) to report
+// fExpectSuccess - if true (the default), then we expect that this region of memory
+// should be fully readable. Any read errors indicate a corrupt target.
+//
+bool ClrDataAccess::ReportMem(TADDR addr, TSIZE_T size, bool fExpectSuccess /*= true*/)
+{
+ SUPPORTS_DAC_HOST_ONLY;
+
+ // This block of code is to help debugging blocks that we report
+ // to minidump/heapdump. You can set break point here to view the static
+ // variable to figure out the size of blocks that we are reporting.
+ // Most useful is set conditional break point to catch large chuck of
+ // memory. We will leave it here for all builds.
+ //
+ static TADDR debugAddr;
+ static TSIZE_T debugSize;
+ debugAddr = addr;
+ debugSize = size;
+
+ HRESULT status;
+ if (!addr || addr == (TADDR)-1 || !size)
+ {
+ if (fExpectSuccess)
+ return false;
+ else
+ return true;
+ }
+
+ //
+ // Try and sanity-check the reported region of memory
+ //
+#ifdef _DEBUG
+ // in debug builds, sanity-check all reports
+ const TSIZE_T k_minSizeToCheck = 1;
+#else
+ // in retail builds, only sanity-check larger chunks which have the potential to waste a
+ // lot of time and/or space. This avoids the overhead of checking for the majority of
+ // memory regions (which are small).
+ const TSIZE_T k_minSizeToCheck = 1024;
+#endif
+ if (size >= k_minSizeToCheck)
+ {
+ if (!IsFullyReadable(addr, size))
+ {
+ if (!fExpectSuccess)
+ {
+ // We know the read might fail (eg. we're trying to find mapped pages in
+ // a module image), so just skip this block silently.
+ // Note that the EnumMemoryRegion callback won't necessarily do anything if any part of
+ // the region is unreadable, and so there is no point in calling it. For cases where we expect
+ // the read might fail, but we want to report any partial blocks, we have to break up the region
+ // into pages and try reporting each page anyway
+ return true;
+ }
+
+ // We're reporting bogus memory, so the target must be corrupt (or there is a issue). We should abort
+ // reporting and continue with the next data structure (where the exception is caught),
+ // just like we would for a DAC read error (otherwise we might do something stupid
+ // like get into an infinite loop, or otherwise waste time with corrupt data).
+
+ TARGET_CONSISTENCY_CHECK(false, "Found unreadable memory while reporting memory regions for dump gathering");
+ return false;
+ }
+ }
+
+ // Minidumps should never contain data structures that are anywhere near 4MB. If we see this, it's
+ // probably due to memory corruption. To keep the dump small, we'll truncate the block. Note that
+ // the size to which the block is truncated is pretty unique, so should be good evidence in a dump
+ // that this has happened.
+ // Note that it's hard to say what a good value would be here, or whether we should dump any of the
+ // data structure at all. Hopefully experience will help guide this going forward.
+ // @dbgtodo : Extend dump-gathering API to allow a dump-log to be included.
+ const TSIZE_T kMaxMiniDumpRegion = 4*1024*1024 - 3; // 4MB-3
+ if( size > kMaxMiniDumpRegion
+ && (m_enumMemFlags == CLRDATA_ENUM_MEM_MINI
+ || m_enumMemFlags == CLRDATA_ENUM_MEM_TRIAGE))
+ {
+ TARGET_CONSISTENCY_CHECK( false, "Dump target consistency failure - truncating minidump data structure");
+ size = kMaxMiniDumpRegion;
+ }
+
+ // track the total memory reported.
+ m_cbMemoryReported += size;
+
+ // ICLRData APIs take only 32-bit sizes. In practice this will almost always be sufficient, but
+ // in theory we might have some >4GB ranges on large 64-bit processes doing a heap dump
+ // (for example, the code:LoaderHeap). If necessary, break up the reporting into maximum 4GB
+ // chunks so we can use the existing API.
+ // @dbgtodo : ICorDebugDataTarget should probably use 64-bit sizes
+ while (size)
+ {
+ ULONG32 enumSize;
+ if (size > ULONG_MAX)
+ {
+ enumSize = ULONG_MAX;
+ }
+ else
+ {
+ enumSize = (ULONG32)size;
+ }
+
+ // Actually perform the memory reporting callback
+ status = m_enumMemCb->EnumMemoryRegion(TO_CDADDR(addr), enumSize);
+ if (status != S_OK)
+ {
+ m_memStatus = status;
+
+ // If dump generation was cancelled, allow us to throw upstack so we'll actually quit.
+ if ((fExpectSuccess) && (status != COR_E_OPERATIONCANCELED))
+ return false;
+ }
+
+ // If the return value of EnumMemoryRegion is COR_E_OPERATIONCANCELED,
+ // it means that user has requested that the minidump gathering be canceled.
+ // To do this we throw an exception which is caught in EnumMemoryRegionsWrapper.
+ if (status == COR_E_OPERATIONCANCELED)
+ {
+ ThrowHR(status);
+ }
+
+ // Move onto the next chunk (if any)
+ size -= enumSize;
+ addr += enumSize;
+ }
+
+ return true;
+}
+
+
+//
+// DacUpdateMemoryRegion - updates/poisons a region of memory of generated dump
+//
+// Parameters:
+// addr - target address of the beginning of the memory region
+// bufferSize - number of bytes to update/poison
+// buffer - data to be written at given target address
+//
+bool ClrDataAccess::DacUpdateMemoryRegion(TADDR addr, TSIZE_T bufferSize, BYTE* buffer)
+{
+ SUPPORTS_DAC_HOST_ONLY;
+
+ HRESULT status;
+ if (!addr || addr == (TADDR)-1 || !bufferSize)
+ {
+ return false;
+ }
+
+ // track the total memory reported.
+ m_cbMemoryReported += bufferSize;
+
+ if (m_updateMemCb == NULL)
+ {
+ return false;
+ }
+
+ // Actually perform the memory updating callback
+ status = m_updateMemCb->UpdateMemoryRegion(TO_CDADDR(addr), (ULONG32)bufferSize, buffer);
+ if (status != S_OK)
+ {
+ return false;
+ }
+
+ return true;
+}
+
+//
+// Check whether a region of target memory is fully readable.
+//
+// Arguments:
+// addr The base target address of the region
+// size The size of the region to analyze
+//
+// Return value:
+// True if the entire regions appears to be readable, false otherwise.
+//
+// Notes:
+// The motivation here is that reporting large regions of unmapped address space to dbgeng can result in
+// it taking a long time trying to identify a valid subrange. This can happen when the target
+// memory is corrupt, and we enumerate a data structure with a dynamic size. Ideally we would just spec
+// the ICLRDataEnumMemoryRegionsCallback API to require the client to fail if it detects an unmapped
+// memory address in the region. However, we can't change the existing dbgeng code, so for now we'll
+// rely on this heuristic here.
+// @dbgtodo : Try and get the dbg team to change their EnumMemoryRegion behavior. See DevDiv Bugs 6265
+//
+bool ClrDataAccess::IsFullyReadable(TADDR taBase, TSIZE_T dwSize)
+{
+ // The only way we have to verify that a memory region is readable is to try reading it in it's
+ // entirety. This is potentially expensive, so we'll rely on a heuristic that spot-checks various
+ // points in the region.
+
+ // Ensure we've got something to check
+ if( dwSize == 0 )
+ return true;
+
+ // Check for overflow
+ TADDR taEnd = DacTAddrOffset(taBase, dwSize, 1);
+
+ // Loop through using expontential growth, being sure to check both the first and last byte
+ TADDR taCurr = taBase;
+ TSIZE_T dwInc = 4096;
+ bool bDone = false;
+ while (!bDone)
+ {
+ // Try and read a byte from the target. Note that we don't use PTR_BYTE here because we don't want
+ // the overhead of inserting entries into the DAC instance cache.
+ BYTE b;
+ ULONG32 dwBytesRead;
+ HRESULT hr = m_pTarget->ReadVirtual(taCurr, &b, 1, &dwBytesRead);
+ if( hr != S_OK || dwBytesRead < 1 )
+ {
+ return false;
+ }
+
+ if (taEnd - taCurr <= 1)
+ {
+ // We just read the last byte so we're done
+ _ASSERTE( taCurr = taEnd - 1 );
+ bDone = true;
+ }
+ else if (dwInc == 0 || dwInc >= taEnd - taCurr)
+ {
+ // we've reached the end of the exponential series, check the last byte
+ taCurr = taEnd - 1;
+ }
+ else
+ {
+ // advance current pointer (subtraction above ensures this won't overflow)
+ taCurr += dwInc;
+
+ // double the increment for next time (or set to 0 if it's already the max)
+ dwInc <<= 1;
+ }
+ }
+ return true;
+}
+
+JITNotification*
+ClrDataAccess::GetHostJitNotificationTable()
+{
+ if (m_jitNotificationTable == NULL)
+ {
+ m_jitNotificationTable =
+ JITNotifications::InitializeNotificationTable(1000);
+ }
+
+ return m_jitNotificationTable;
+}
+
+GcNotification*
+ClrDataAccess::GetHostGcNotificationTable()
+{
+ if (m_gcNotificationTable == NULL)
+ {
+ m_gcNotificationTable =
+ GcNotifications::InitializeNotificationTable(128);
+ }
+
+ return m_gcNotificationTable;
+}
+
+/* static */ bool
+ClrDataAccess::GetMetaDataFileInfoFromPEFile(PEFile *pPEFile,
+ DWORD &dwTimeStamp,
+ DWORD &dwSize,
+ DWORD &dwDataSize,
+ DWORD &dwRvaHint,
+ bool &isNGEN,
+ __out_ecount(cchFilePath) LPWSTR wszFilePath,
+ const DWORD cchFilePath)
+{
+ SUPPORTS_DAC_HOST_ONLY;
+ PEImage *mdImage = NULL;
+ PEImageLayout *layout;
+ IMAGE_DATA_DIRECTORY *pDir = NULL;
+ COUNT_T uniPathChars = 0;
+
+ isNGEN = false;
+
+ if (pPEFile->HasNativeImage())
+ {
+ mdImage = pPEFile->GetNativeImage();
+ _ASSERTE(mdImage != NULL);
+ layout = mdImage->GetLoadedLayout();
+ pDir = &(layout->GetCorHeader()->MetaData);
+ // For ngen image, the IL metadata is stored for private use. So we need to pass
+ // the RVA hint to find it to debuggers.
+ //
+ if (pDir->Size != 0)
+ {
+ isNGEN = true;
+ dwRvaHint = pDir->VirtualAddress;
+ dwDataSize = pDir->Size;
+ }
+
+ }
+ if (pDir == NULL || pDir->Size == 0)
+ {
+ mdImage = pPEFile->GetILimage();
+ if (mdImage != NULL)
+ {
+ layout = mdImage->GetLoadedLayout();
+ pDir = &layout->GetCorHeader()->MetaData;
+
+ // In IL image case, we do not have any hint to IL metadata since it is stored
+ // in the corheader.
+ //
+ dwRvaHint = 0;
+ dwDataSize = pDir->Size;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ // Do not fail if path can not be read. Triage dumps don't have paths and we want to fallback
+ // on searching metadata from IL image.
+ mdImage->GetPath().DacGetUnicode(cchFilePath, wszFilePath, &uniPathChars);
+
+ if (!mdImage->HasNTHeaders() ||
+ !mdImage->HasCorHeader() ||
+ !mdImage->HasLoadedLayout() ||
+ (uniPathChars > cchFilePath))
+ {
+ return false;
+ }
+
+ // It is possible that the module is in-memory. That is the wszFilePath here is empty.
+ // We will try to use the module name instead in this case for hosting debugger
+ // to find match.
+ if (wcslen(wszFilePath) == 0)
+ {
+ mdImage->GetModuleFileNameHintForDAC().DacGetUnicode(cchFilePath, wszFilePath, &uniPathChars);
+ if (uniPathChars > cchFilePath)
+ {
+ return false;
+ }
+ }
+
+ dwTimeStamp = layout->GetTimeDateStamp();
+ dwSize = (ULONG32)layout->GetVirtualSize();
+
+ return true;
+}
+
+/* static */
+bool ClrDataAccess::GetILImageInfoFromNgenPEFile(PEFile *peFile,
+ DWORD &dwTimeStamp,
+ DWORD &dwSize,
+ __out_ecount(cchFilePath) LPWSTR wszFilePath,
+ const DWORD cchFilePath)
+{
+ SUPPORTS_DAC_HOST_ONLY;
+ DWORD dwWritten = 0;
+
+ // use the IL File name
+ if (!peFile->GetPath().DacGetUnicode(cchFilePath, wszFilePath, (COUNT_T *)(&dwWritten)))
+ {
+ // Use DAC hint to retrieve the IL name.
+ peFile->GetModuleFileNameHint().DacGetUnicode(cchFilePath, wszFilePath, (COUNT_T *)(&dwWritten));
+ }
+#ifdef FEATURE_PREJIT
+ // Need to get IL image information from cached info in the ngen image.
+ dwTimeStamp = peFile->GetLoaded()->GetNativeVersionInfo()->sourceAssembly.timeStamp;
+ dwSize = peFile->GetLoaded()->GetNativeVersionInfo()->sourceAssembly.ilImageSize;
+#else
+ dwTimeStamp = 0;
+ dwSize = 0;
+#endif // FEATURE_PREJIT
+
+ return true;
+}
+
+#if defined(FEATURE_CORESYSTEM)
+/* static */
+// We extract "ni.dll or .ni.winmd" from the NGEM image name to obtain the IL image name.
+// In the end we add given ilExtension.
+// This dependecy is based on Apollo installer behavior.
+bool ClrDataAccess::GetILImageNameFromNgenImage( LPCWSTR ilExtension,
+ __out_ecount(cchFilePath) LPWSTR wszFilePath,
+ const DWORD cchFilePath)
+{
+ if (wszFilePath == NULL || cchFilePath == 0)
+ {
+ return false;
+ }
+
+ _wcslwr_s(wszFilePath, cchFilePath);
+ // Find the "ni.dll" or "ni.winmd" extension (check for PEFile isWinRT something to know when is winmd or not.
+ // If none exists use NGEN image name.
+ //
+ const WCHAR* ngenExtension[] = {W("ni.dll"), W("ni.winmd")};
+
+ for (unsigned i = 0; i < COUNTOF(ngenExtension); ++i)
+ {
+ if (wcslen(ilExtension) > wcslen(ngenExtension[i]))
+ {
+ // We should not have IL image name bigger than NGEN image.
+ // It will not fit inside wszFilePath.
+ continue;
+ }
+ LPWSTR wszFileExtension = wcsstr(wszFilePath, ngenExtension[i]);
+ if (wszFileExtension != 0)
+ {
+ LPWSTR wszNextFileExtension = wszFileExtension;
+ // Find last occurence
+ do
+ {
+ wszFileExtension = wszNextFileExtension;
+ wszNextFileExtension = wcsstr(wszFileExtension + 1, ngenExtension[i]);
+ } while (wszNextFileExtension != 0);
+
+ // Overwrite ni.dll or ni.winmd with ilExtension(.dll, .winmd)
+ if (!memcpy_s(wszFileExtension,
+ wcslen(ngenExtension[i])*sizeof(WCHAR),
+ ilExtension,
+ wcslen(ilExtension)*sizeof(WCHAR)))
+ {
+ wszFileExtension[wcslen(ilExtension)] = '\0';
+ return true;
+ }
+ }
+ }
+
+ //Use ngen filename if there is no ".ni"
+ if (wcsstr(wszFilePath, W(".ni")) == 0)
+ {
+ return true;
+ }
+
+ return false;
+}
+#endif // FEATURE_CORESYSTEM
+
+void *
+ClrDataAccess::GetMetaDataFromHost(PEFile* peFile,
+ bool* isAlternate)
+{
+ DWORD imageTimestamp, imageSize, dataSize;
+ void* buffer = NULL;
+ WCHAR uniPath[MAX_LONGPATH] = {0};
+ bool isAlt = false;
+ bool isNGEN = false;
+ DAC_INSTANCE* inst = NULL;
+ HRESULT hr = S_OK;
+ DWORD ulRvaHint;
+ //
+ // We always ask for the IL image metadata,
+ // as we expect that to be more
+ // available than others. The drawback is that
+ // there may be differences between the IL image
+ // metadata and native image metadata, so we
+ // have to mark such alternate metadata so that
+ // we can fail unsupported usage of it.
+ //
+
+ // Microsoft - above comment seems to be an unimplemented thing.
+ // The DAC_MD_IMPORT.isAlternate field gets ultimately set, but
+ // on the searching I did, I cannot find any usage of it
+ // other than in the ctor. Should we be doing something, or should
+ // we remove this comment and the isAlternate field?
+ // It's possible that test will want us to track whether we have
+ // an IL image's metadata loaded against an NGEN'ed image
+ // so the field remains for now.
+
+ if (!ClrDataAccess::GetMetaDataFileInfoFromPEFile(
+ peFile,
+ imageTimestamp,
+ imageSize,
+ dataSize,
+ ulRvaHint,
+ isNGEN,
+ uniPath,
+ NumItems(uniPath)))
+ {
+ return NULL;
+ }
+
+ // try direct match for the image that is loaded into the managed process
+ peFile->GetLoadedMetadata((COUNT_T *)(&dataSize));
+
+ DWORD allocSize = 0;
+ if (!ClrSafeInt<DWORD>::addition(dataSize, sizeof(DAC_INSTANCE), allocSize))
+ {
+ DacError(HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW));
+ }
+
+ inst = m_instances.Alloc(0, allocSize, DAC_DPTR);
+ if (!inst)
+ {
+ DacError(E_OUTOFMEMORY);
+ return NULL;
+ }
+
+ buffer = (void*)(inst + 1);
+
+ // APIs implemented by hosting debugger. It can use the path/filename, timestamp, and
+ // file size to find an exact match for the image. If that fails for an ngen'ed image,
+ // we can request the IL image which it came from.
+ if (m_legacyMetaDataLocator)
+ {
+ // Legacy API implemented by hosting debugger.
+ hr = m_legacyMetaDataLocator->GetMetadata(
+ uniPath,
+ imageTimestamp,
+ imageSize,
+ NULL, // MVID - not used yet
+ ulRvaHint,
+ 0, // flags - reserved for future.
+ dataSize,
+ (BYTE*)buffer,
+ NULL);
+ }
+ else
+ {
+ hr = m_target3->GetMetaData(
+ uniPath,
+ imageTimestamp,
+ imageSize,
+ NULL, // MVID - not used yet
+ ulRvaHint,
+ 0, // flags - reserved for future.
+ dataSize,
+ (BYTE*)buffer,
+ NULL);
+ }
+ if (FAILED(hr) && isNGEN)
+ {
+ // We failed to locate the ngen'ed image. We should try to
+ // find the matching IL image
+ //
+ isAlt = true;
+ if (!ClrDataAccess::GetILImageInfoFromNgenPEFile(
+ peFile,
+ imageTimestamp,
+ imageSize,
+ uniPath,
+ NumItems(uniPath)))
+ {
+ goto ErrExit;
+ }
+
+#if defined(FEATURE_CORESYSTEM)
+ const WCHAR* ilExtension[] = {W("dll"), W("winmd")};
+ WCHAR ngenImageName[MAX_LONGPATH] = {0};
+ if (wcscpy_s(ngenImageName, NumItems(ngenImageName), uniPath) != 0)
+ {
+ goto ErrExit;
+ }
+ for (unsigned i = 0; i < COUNTOF(ilExtension); i++)
+ {
+ if (wcscpy_s(uniPath, NumItems(uniPath), ngenImageName) != 0)
+ {
+ goto ErrExit;
+ }
+ // Transform NGEN image name into IL Image name
+ if (!GetILImageNameFromNgenImage(ilExtension[i], uniPath, NumItems(uniPath)))
+ {
+ goto ErrExit;
+ }
+#endif//FEATURE_CORESYSTEM
+
+ // RVA size in ngen image and IL image is the same. Because the only
+ // different is in RVA. That is 4 bytes column fixed.
+ //
+
+ // try again
+ if (m_legacyMetaDataLocator)
+ {
+ hr = m_legacyMetaDataLocator->GetMetadata(
+ uniPath,
+ imageTimestamp,
+ imageSize,
+ NULL, // MVID - not used yet
+ 0, // pass zero hint here... important
+ 0, // flags - reserved for future.
+ dataSize,
+ (BYTE*)buffer,
+ NULL);
+ }
+ else
+ {
+ hr = m_target3->GetMetaData(
+ uniPath,
+ imageTimestamp,
+ imageSize,
+ NULL, // MVID - not used yet
+ 0, // pass zero hint here... important
+ 0, // flags - reserved for future.
+ dataSize,
+ (BYTE*)buffer,
+ NULL);
+ }
+#if defined(FEATURE_CORESYSTEM)
+ if (SUCCEEDED(hr))
+ {
+ break;
+ }
+ }
+#endif // FEATURE_CORESYSTEM
+ }
+
+ if (FAILED(hr))
+ {
+ goto ErrExit;
+ }
+
+ *isAlternate = isAlt;
+ m_instances.AddSuperseded(inst);
+ return buffer;
+
+ErrExit:
+ if (inst != NULL)
+ {
+ m_instances.ReturnAlloc(inst);
+ }
+ return NULL;
+}
+
+
+//++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+//
+// Given a PEFile or a ReflectionModule try to find the corresponding metadata
+// We will first ask debugger to locate it. If fail, we will try
+// to get it from the target process
+//
+//++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+IMDInternalImport*
+ClrDataAccess::GetMDImport(const PEFile* peFile, const ReflectionModule* reflectionModule, bool throwEx)
+{
+ HRESULT status;
+ PTR_CVOID mdBaseTarget = NULL;
+ COUNT_T mdSize;
+ IMDInternalImport* mdImport = NULL;
+ PVOID mdBaseHost = NULL;
+ bool isAlternate = false;
+
+ _ASSERTE(peFile == NULL && reflectionModule != NULL || peFile != NULL && reflectionModule == NULL);
+ TADDR peFileAddr = (peFile != NULL) ? dac_cast<TADDR>(peFile) : dac_cast<TADDR>(reflectionModule);
+
+ //
+ // Look for one we've already created.
+ //
+ mdImport = m_mdImports.Get(peFileAddr);
+ if (mdImport != NULL)
+ {
+ return mdImport;
+ }
+
+ if (peFile != NULL)
+ {
+ // Get the metadata size
+ mdBaseTarget = ((PEFile*)peFile)->GetLoadedMetadata(&mdSize);
+ }
+ else if (reflectionModule != NULL)
+ {
+ // Get the metadata
+ PTR_SBuffer metadataBuffer = reflectionModule->GetDynamicMetadataBuffer();
+ if (metadataBuffer != PTR_NULL)
+ {
+ mdBaseTarget = dac_cast<PTR_CVOID>((metadataBuffer->DacGetRawBuffer()).StartAddress());
+ mdSize = metadataBuffer->GetSize();
+ }
+ else
+ {
+ if (throwEx)
+ {
+ DacError(E_FAIL);
+ }
+ return NULL;
+ }
+ }
+ else
+ {
+ if (throwEx)
+ {
+ DacError(E_FAIL);
+ }
+ return NULL;
+ }
+
+ if (mdBaseTarget == PTR_NULL)
+ {
+ mdBaseHost = NULL;
+ }
+ else
+ {
+
+ //
+ // Maybe the target process has the metadata
+ // Find out where the metadata for the image is
+ // in the target's memory.
+ //
+ //
+ // Read the metadata into the host process. Make sure pass in false in the last
+ // parameter. This is only matters when producing skinny mini-dump. This will
+ // prevent metadata gets reported into mini-dump.
+ //
+ mdBaseHost = DacInstantiateTypeByAddressNoReport(dac_cast<TADDR>(mdBaseTarget), mdSize,
+ false);
+ }
+
+ // Try to see if debugger can locate it
+ if (peFile != NULL && mdBaseHost == NULL && (m_target3 || m_legacyMetaDataLocator))
+ {
+ // We couldn't read the metadata from memory. Ask
+ // the target for metadata as it may be able to
+ // provide it from some alternate means.
+ mdBaseHost = GetMetaDataFromHost(const_cast<PEFile *>(peFile), &isAlternate);
+ }
+
+ if (mdBaseHost == NULL)
+ {
+ // cannot locate metadata anywhere
+ if (throwEx)
+ {
+ DacError(E_INVALIDARG);
+ }
+ return NULL;
+ }
+
+ //
+ // Open the MD interface on the host copy of the metadata.
+ //
+
+ status = GetMDInternalInterface(mdBaseHost, mdSize, ofRead,
+ IID_IMDInternalImport,
+ (void**)&mdImport);
+ if (status != S_OK)
+ {
+ if (throwEx)
+ {
+ DacError(status);
+ }
+ return NULL;
+ }
+
+ //
+ // Remember the object for this module for
+ // possible later use.
+ // The m_mdImports list does get cleaned up by calls to ClrDataAccess::Flush,
+ // i.e. every time the process changes state.
+
+ if (m_mdImports.Add(peFileAddr, mdImport, isAlternate) == NULL)
+ {
+ mdImport->Release();
+ DacError(E_OUTOFMEMORY);
+ }
+
+ return mdImport;
+}
+
+
+//
+// Set whether inconsistencies in the target should raise asserts.
+// This overrides the default initial setting.
+//
+// Arguments:
+// fEnableAsserts - whether ASSERTs in dacized code should be enabled
+//
+
+void ClrDataAccess::SetTargetConsistencyChecks(bool fEnableAsserts)
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ m_fEnableTargetConsistencyAsserts = fEnableAsserts;
+}
+
+//
+// Get whether inconsistencies in the target should raise asserts.
+//
+// Return value:
+// whether ASSERTs in dacized code should be enabled
+//
+// Notes:
+// The implementation of ASSERT accesses this via code:DacTargetConsistencyAssertsEnabled
+//
+// By default, this is disabled, unless COMPlus_DbgDACEnableAssert is set (see code:ClrDataAccess::ClrDataAccess).
+// This is necessary for compatibility. For example, SOS expects to be able to scan for
+// valid MethodTables etc. (which may cause ASSERTs), and also doesn't want ASSERTs when working
+// with targets with corrupted memory.
+//
+// Calling code:ClrDataAccess::SetTargetConsistencyChecks overrides the default setting.
+//
+bool ClrDataAccess::TargetConsistencyAssertsEnabled()
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return m_fEnableTargetConsistencyAsserts;
+}
+
+#ifdef FEATURE_CORESYSTEM
+#define ctime_s _ctime32_s
+#define time_t __time32_t
+#endif
+
+//
+// VerifyDlls - Validate that the mscorwks in the target matches this version of mscordacwks
+// Only done on Windows and Mac builds at the moment.
+// See code:CordbProcess::CordbProcess#DBIVersionChecking for more information regarding version checking.
+//
+HRESULT ClrDataAccess::VerifyDlls()
+{
+#ifndef FEATURE_PAL
+ // Provide a knob for disabling this check if we really want to try and proceed anyway with a
+ // DAC mismatch. DAC behavior may be arbitrarily bad - globals probably won't be at the same
+ // address, data structures may be laid out differently, etc.
+ if (CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgDACSkipVerifyDlls))
+ {
+ return S_OK;
+ }
+
+ // Read the debug directory timestamp from the target mscorwks image using DAC
+ // Note that we don't use the PE timestamp because the PE file might be changed in ways
+ // that don't effect the PDB (and therefore don't effect DAC). Specifically, we rebase
+ // our DLLs at the end of a build, that changes the PE file, but not the PDB.
+ // Note that if we wanted to be extra careful, we could read the CV contents (which includes
+ // the GUID signature) and verify it matches. Using the timestamp is useful for helpful error
+ // messages, and should be sufficient in any real scenario.
+ DWORD timestamp = 0;
+ HRESULT hr = S_OK;
+ DAC_ENTER();
+ EX_TRY
+ {
+ // Note that we don't need to worry about ensuring the image memory read by this code
+ // is saved in a minidump. Managed minidump debugging already requires that you have
+ // the full mscorwks.dll available at debug time (eg. windbg won't even load DAC without it).
+ PEDecoder pedecoder(dac_cast<PTR_VOID>(m_globalBase));
+
+ // We use the first codeview debug directory entry since this should always refer to the single
+ // PDB for mscorwks.dll.
+ const UINT k_maxDebugEntries = 32; // a reasonable upper limit in case of corruption
+ for( UINT i = 0; i < k_maxDebugEntries; i++)
+ {
+ PTR_IMAGE_DEBUG_DIRECTORY pDebugEntry = pedecoder.GetDebugDirectoryEntry(i);
+
+ // If there are no more entries, then stop
+ if (pDebugEntry == NULL)
+ break;
+
+ // Ignore non-codeview entries. Some scenarios (eg. optimized builds), there may be extra
+ // debug directory entries at the end of some other type.
+ if (pDebugEntry->Type == IMAGE_DEBUG_TYPE_CODEVIEW)
+ {
+ // Found a codeview entry - use it's timestamp for comparison
+ timestamp = pDebugEntry->TimeDateStamp;
+ break;
+ }
+ }
+ char szMsgBuf[1024];
+ _snprintf_s(szMsgBuf, sizeof(szMsgBuf), _TRUNCATE,
+ "Failed to find any valid codeview debug directory entry in %s image",
+ MAIN_CLR_MODULE_NAME_A);
+ _ASSERTE_MSG(timestamp != 0, szMsgBuf);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &hr))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+ DAC_LEAVE();
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ // Validate that we got a timestamp and it matches what the DAC table told us to expect
+ if (timestamp == 0 || timestamp != g_dacTableInfo.dwID0)
+ {
+ // Timestamp mismatch. This means mscordacwks is being used with a version of
+ // mscorwks other than the one it was built for. This will not work reliably.
+
+#ifdef _DEBUG
+ // Check if verbose asserts are enabled. The default is up to the specific instantiation of
+ // ClrDataAccess, but can be overridden (in either direction) by a COMPlus_ knob.
+ // Note that we check this knob every time because it may be handy to turn it on in
+ // the environment mid-flight.
+ DWORD dwAssertDefault = m_fEnableDllVerificationAsserts ? 1 : 0;
+ if (REGUTIL::GetConfigDWORD_DontUse_(CLRConfig::INTERNAL_DbgDACAssertOnMismatch, dwAssertDefault))
+ {
+ // Output a nice error message that contains the timestamps in string format.
+ time_t actualTime = timestamp;
+ char szActualTime[30];
+ ctime_s(szActualTime, sizeof(szActualTime), &actualTime);
+
+ time_t expectedTime = g_dacTableInfo.dwID0;
+ char szExpectedTime[30];
+ ctime_s(szExpectedTime, sizeof(szExpectedTime), &expectedTime);
+
+ // Create a nice detailed message for the assert dialog.
+ // Note that the strings returned by ctime_s have terminating newline characters.
+ // This is technically a TARGET_CONSISTENCY_CHECK because a corrupt target could,
+ // in-theory, have a corrupt mscrowks PE header and cause this check to fail
+ // unnecessarily. However, this check occurs during startup, before we know
+ // whether target consistency checks should be enabled, so it's always enabled
+ // at the moment.
+
+ char szMsgBuf[1024];
+ _snprintf_s(szMsgBuf, sizeof(szMsgBuf), _TRUNCATE,
+ "DAC fatal error: %s/mscordacwks.dll version mismatch\n\n"\
+ "The debug directory timestamp of the loaded %s does not match the\n"\
+ "version mscordacwks.dll was built for.\n"\
+ "Expected %s timestamp: %s"\
+ "Actual %s timestamp: %s\n"\
+ "DAC will now fail to initialize with a CORDBG_E_MISMATCHED_CORWKS_AND_DACWKS_DLLS\n"\
+ "error. If you really want to try and use the mimatched DLLs, you can disable this\n"\
+ "check by setting COMPlus_DbgDACSkipVerifyDlls=1. However, using a mismatched DAC\n"\
+ "DLL will usually result in arbitrary debugger failures.\n",
+ MAIN_CLR_DLL_NAME_A,
+ MAIN_CLR_DLL_NAME_A,
+ MAIN_CLR_DLL_NAME_A,
+ szExpectedTime,
+ MAIN_CLR_DLL_NAME_A,
+ szActualTime);
+ _ASSERTE_MSG(false, szMsgBuf);
+ }
+#endif
+
+ // Return a specific hresult indicating this problem
+ return CORDBG_E_MISMATCHED_CORWKS_AND_DACWKS_DLLS;
+ }
+#endif // FEATURE_PAL
+
+ return S_OK;
+}
+
+#ifdef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+
+void ClrDataAccess::InitStreamsForWriting(IN CLRDataEnumMemoryFlags flags)
+{
+ // enforce this should only be called when generating triage and mini-dumps
+ if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE)
+ return;
+
+ EX_TRY
+ {
+ if (m_streams == NULL)
+ m_streams = new DacStreamManager(g_MiniMetaDataBuffAddress, g_MiniMetaDataBuffMaxSize);
+
+ if (!m_streams->PrepareStreamsForWriting())
+ {
+ delete m_streams;
+ m_streams = NULL;
+ }
+ }
+ EX_CATCH
+ {
+ if (m_streams != NULL)
+ {
+ delete m_streams;
+ m_streams = NULL;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+}
+
+bool ClrDataAccess::MdCacheAddEEName(TADDR taEEStruct, const SString& name)
+{
+ bool result = false;
+ EX_TRY
+ {
+ if (m_streams != NULL)
+ result = m_streams->MdCacheAddEEName(taEEStruct, name);
+ }
+ EX_CATCH
+ {
+ result = false;
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ return result;
+}
+
+void ClrDataAccess::EnumStreams(IN CLRDataEnumMemoryFlags flags)
+{
+ // enforce this should only be called when generating triage and mini-dumps
+ if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE)
+ return;
+
+ EX_TRY
+ {
+ if (m_streams != NULL)
+ m_streams->EnumStreams(flags);
+ }
+ EX_CATCH
+ {
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+}
+
+bool ClrDataAccess::MdCacheGetEEName(TADDR taEEStruct, SString & eeName)
+{
+ bool result = false;
+ EX_TRY
+ {
+ if (m_streams == NULL)
+ m_streams = new DacStreamManager(g_MiniMetaDataBuffAddress, g_MiniMetaDataBuffMaxSize);
+
+ result = m_streams->MdCacheGetEEName(taEEStruct, eeName);
+ }
+ EX_CATCH
+ {
+ result = false;
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ return result;
+}
+
+#endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+
+// Needed for RT_RCDATA.
+#define MAKEINTRESOURCE(v) MAKEINTRESOURCEW(v)
+
+// this funny looking double macro forces x to be macro expanded before L is prepended
+#define _WIDE(x) _WIDE2(x)
+#define _WIDE2(x) W(x)
+
+HRESULT
+ClrDataAccess::GetDacGlobals()
+{
+#ifdef FEATURE_PAL
+#ifdef DAC_TABLE_SIZE
+ if (DAC_TABLE_SIZE != sizeof(g_dacGlobals))
+ {
+ return E_INVALIDARG;
+ }
+#endif
+ ULONG64 dacTableAddress = m_globalBase + DAC_TABLE_RVA;
+ if (FAILED(ReadFromDataTarget(m_pTarget, dacTableAddress, (BYTE*)&g_dacGlobals, sizeof(g_dacGlobals))))
+ {
+ return CORDBG_E_MISSING_DEBUGGER_EXPORTS;
+ }
+ if (g_dacGlobals.ThreadStore__s_pThreadStore == NULL)
+ {
+ return CORDBG_E_UNSUPPORTED;
+ }
+ return S_OK;
+#else
+ HRESULT status = E_FAIL;
+ DWORD rsrcRVA = 0;
+ LPVOID rsrcData = NULL;
+ DWORD rsrcSize = 0;
+
+ HRSRC rsrcFound;
+ HGLOBAL rsrc;
+
+ DWORD resourceSectionRVA = 0;
+
+ if (FAILED(status = GetMachineAndResourceSectionRVA(m_pTarget, m_globalBase, NULL, &resourceSectionRVA)))
+ {
+ _ASSERTE_MSG(false, "DAC fatal error: can't locate resource section in " MAIN_CLR_DLL_NAME_A);
+ return CORDBG_E_MISSING_DEBUGGER_EXPORTS;
+ }
+
+ if (FAILED(status = GetResourceRvaFromResourceSectionRvaByName(m_pTarget, m_globalBase,
+ resourceSectionRVA, (DWORD)RT_RCDATA, _WIDE(DACCESS_TABLE_RESOURCE), 0,
+ &rsrcRVA, &rsrcSize)))
+ {
+ _ASSERTE_MSG(false, "DAC fatal error: can't locate DAC table resource in " MAIN_CLR_DLL_NAME_A);
+ return CORDBG_E_MISSING_DEBUGGER_EXPORTS;
+ }
+
+ rsrcData = new (nothrow) BYTE[rsrcSize];
+ if (rsrcData == NULL)
+ return E_OUTOFMEMORY;
+
+ if (FAILED(status = ReadFromDataTarget(m_pTarget, m_globalBase + rsrcRVA, (BYTE*)rsrcData, rsrcSize)))
+ {
+ _ASSERTE_MSG(false, "DAC fatal error: can't load DAC table resource from " MAIN_CLR_DLL_NAME_A);
+ return CORDBG_E_MISSING_DEBUGGER_EXPORTS;
+ }
+
+
+ PBYTE rawData = (PBYTE)rsrcData;
+ DWORD bytesLeft = rsrcSize;
+
+ // Read the header
+ struct DacTableHeader header;
+
+ // We currently expect the header to be 2 32-bit values and 1 16-byte value,
+ // make sure there is no packing going on or anything.
+ static_assert_no_msg(sizeof(DacTableHeader) == 2 * 4 + 16);
+
+ if (bytesLeft < sizeof(DacTableHeader))
+ {
+ _ASSERTE_MSG(false, "DAC fatal error: DAC table too small for header.");
+ goto Exit;
+ }
+ memcpy(&header, rawData, sizeof(DacTableHeader));
+ rawData += sizeof(DacTableHeader);
+ bytesLeft -= sizeof(DacTableHeader);
+
+ // Save the table info for later use
+ g_dacTableInfo = header.info;
+
+ // Sanity check that the DAC table is the size we expect.
+ // This could fail if a different version of dacvars.h or vptr_list.h was used when building
+ // mscordacwks.dll than when running DacTableGen.
+
+ if (offsetof(DacGlobals, Thread__vtAddr) != header.numGlobals * sizeof(ULONG))
+ {
+#ifdef _DEBUG
+ char szMsgBuf[1024];
+ _snprintf_s(szMsgBuf, sizeof(szMsgBuf), _TRUNCATE,
+ "DAC fatal error: mismatch in number of globals in DAC table. Read from file: %d, expected: %d.",
+ header.numGlobals,
+ offsetof(DacGlobals, Thread__vtAddr) / sizeof(ULONG));
+ _ASSERTE_MSG(false, szMsgBuf);
+#endif // _DEBUG
+
+ status = E_INVALIDARG;
+ goto Exit;
+ }
+
+ if (sizeof(DacGlobals) != (header.numGlobals + header.numVptrs) * sizeof(ULONG))
+ {
+#ifdef _DEBUG
+ char szMsgBuf[1024];
+ _snprintf_s(szMsgBuf, sizeof(szMsgBuf), _TRUNCATE,
+ "DAC fatal error: mismatch in number of vptrs in DAC table. Read from file: %d, expected: %d.",
+ header.numVptrs,
+ (sizeof(DacGlobals) - offsetof(DacGlobals, Thread__vtAddr)) / sizeof(ULONG));
+ _ASSERTE_MSG(false, szMsgBuf);
+#endif // _DEBUG
+
+ status = E_INVALIDARG;
+ goto Exit;
+ }
+
+ // Copy the DAC table into g_dacGlobals
+ if (bytesLeft < sizeof(DacGlobals))
+ {
+ _ASSERTE_MSG(false, "DAC fatal error: DAC table resource too small for DacGlobals.");
+ status = E_UNEXPECTED;
+ goto Exit;
+ }
+ memcpy(&g_dacGlobals, rawData, sizeof(DacGlobals));
+ rawData += sizeof(DacGlobals);
+ bytesLeft -= sizeof(DacGlobals);
+
+ status = S_OK;
+
+Exit:
+
+ return status;
+#endif
+}
+
+#undef MAKEINTRESOURCE
+
+//----------------------------------------------------------------------------
+//
+// IsExceptionFromManagedCode - report if pExceptionRecord points to a exception belonging to the current runtime
+//
+// Arguments:
+// pExceptionRecord - the exception record
+//
+// Return Value:
+// TRUE if it is
+// Otherwise, FALSE
+//
+//----------------------------------------------------------------------------
+BOOL ClrDataAccess::IsExceptionFromManagedCode(EXCEPTION_RECORD* pExceptionRecord)
+{
+ DAC_ENTER();
+
+ HRESULT status;
+ BOOL flag = FALSE;
+
+ if (::IsExceptionFromManagedCode(pExceptionRecord))
+ {
+ flag = TRUE;
+ }
+
+ DAC_LEAVE();
+
+ return flag;
+}
+
+#ifndef FEATURE_PAL
+
+//----------------------------------------------------------------------------
+//
+// GetWatsonBuckets - retrieve Watson buckets from the specified thread
+//
+// Arguments:
+// dwThreadId - the thread ID
+// pGM - pointer to the space to store retrieved Watson buckets
+//
+// Return Value:
+// S_OK if the operation is successful.
+// or S_FALSE if Watson buckets cannot be found
+// else detailed error code.
+//
+//----------------------------------------------------------------------------
+HRESULT ClrDataAccess::GetWatsonBuckets(DWORD dwThreadId, GenericModeBlock * pGM)
+{
+ _ASSERTE((dwThreadId != 0) && (pGM != NULL));
+ if ((dwThreadId == 0) || (pGM == NULL))
+ {
+ return E_INVALIDARG;
+ }
+
+ DAC_ENTER();
+
+ Thread * pThread = DacGetThread(dwThreadId);
+ _ASSERTE(pThread != NULL);
+
+ HRESULT hr = E_UNEXPECTED;
+
+ if (pThread != NULL)
+ {
+ hr = GetClrWatsonBucketsWorker(pThread, pGM);
+ }
+
+ DAC_LEAVE();
+ return hr;
+}
+
+#endif // FEATURE_PAL
+
+//----------------------------------------------------------------------------
+//
+// CLRDataAccessCreateInstance - create and initialize a ClrDataAccess object
+//
+// Arguments:
+// pLegacyTarget - data target object
+// pClrDataAccess - ClrDataAccess object
+//
+// Return Value:
+// S_OK on success, else detailed error code.
+//
+//----------------------------------------------------------------------------
+STDAPI CLRDataAccessCreateInstance(ICLRDataTarget * pLegacyTarget,
+ ClrDataAccess ** pClrDataAccess)
+{
+ if ((pLegacyTarget == NULL) || (pClrDataAccess == NULL))
+ {
+ return E_INVALIDARG;
+ }
+
+ *pClrDataAccess = NULL;
+
+ // Create an adapter which implements the new ICorDebugDataTarget interfaces using
+ // a legacy implementation of ICLRDataTarget
+ // ClrDataAccess will take a take a ref on this and delete it when it's released.
+ DataTargetAdapter * pDtAdapter = new (nothrow) DataTargetAdapter(pLegacyTarget);
+ if (!pDtAdapter)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ ClrDataAccess* dacClass = new (nothrow) ClrDataAccess(pDtAdapter, pLegacyTarget);
+ if (!dacClass)
+ {
+ delete pDtAdapter;
+ return E_OUTOFMEMORY;
+ }
+
+ HRESULT hr = dacClass->Initialize();
+ if (FAILED(hr))
+ {
+ dacClass->Release();
+ return hr;
+ }
+
+ *pClrDataAccess = dacClass;
+ return S_OK;
+}
+
+
+//----------------------------------------------------------------------------
+//
+// CLRDataCreateInstance.
+// Creates the IXClrData object
+// This is the legacy entrypoint to DAC, used by dbgeng/dbghelp (windbg, SOS, watson, etc).
+//
+//----------------------------------------------------------------------------
+#ifdef __llvm__
+__attribute__((used))
+#endif // __llvm__
+STDAPI
+CLRDataCreateInstance(REFIID iid,
+ ICLRDataTarget * pLegacyTarget,
+ void ** iface)
+{
+ if ((pLegacyTarget == NULL) || (iface == NULL))
+ {
+ return E_INVALIDARG;
+ }
+
+ *iface = NULL;
+ ClrDataAccess * pClrDataAccess;
+ HRESULT hr = CLRDataAccessCreateInstance(pLegacyTarget, &pClrDataAccess);
+ if (hr != S_OK)
+ {
+ return hr;
+ }
+
+ hr = pClrDataAccess->QueryInterface(iid, iface);
+
+ pClrDataAccess->Release();
+ return hr;
+}
+
+
+//----------------------------------------------------------------------------
+//
+// OutOfProcessExceptionEventGetProcessIdAndThreadId - get ProcessID and ThreadID
+//
+// Arguments:
+// hProcess - process handle
+// hThread - thread handle
+// pPId - pointer to DWORD to store ProcessID
+// pThreadId - pointer to DWORD to store ThreadID
+//
+// Return Value:
+// TRUE if the operation is successful.
+// FALSE if it fails
+//
+//----------------------------------------------------------------------------
+BOOL OutOfProcessExceptionEventGetProcessIdAndThreadId(HANDLE hProcess, HANDLE hThread, DWORD * pPId, DWORD * pThreadId)
+{
+ _ASSERTE((pPId != NULL) && (pThreadId != NULL));
+
+#ifdef FEATURE_PAL
+ // UNIXTODO: mikem 1/13/15 Need appropriate PAL functions for getting ids
+ *pPId = (DWORD)hProcess;
+ *pThreadId = (DWORD)hThread;
+#else
+#if !defined(FEATURE_CORESYSTEM)
+ HMODULE hKernel32 = WszGetModuleHandle(W("kernel32.dll"));
+#else
+ HMODULE hKernel32 = WszGetModuleHandle(W("api-ms-win-core-processthreads-l1-1-1.dll"));
+#endif
+ if (hKernel32 == NULL)
+ {
+ return FALSE;
+ }
+
+ typedef WINBASEAPI DWORD (WINAPI GET_PROCESSID_OF_THREAD)(HANDLE);
+ GET_PROCESSID_OF_THREAD * pGetProcessIdOfThread;
+
+ typedef WINBASEAPI DWORD (WINAPI GET_THREADID)(HANDLE);
+ GET_THREADID * pGetThreadId;
+
+ pGetProcessIdOfThread = (GET_PROCESSID_OF_THREAD *)GetProcAddress(hKernel32, "GetProcessIdOfThread");
+ pGetThreadId = (GET_THREADID *)GetProcAddress(hKernel32, "GetThreadId");
+
+ // OOP callbacks are used on Win7 or later. We should have having below two APIs available.
+ _ASSERTE((pGetProcessIdOfThread != NULL) && (pGetThreadId != NULL));
+ if ((pGetProcessIdOfThread == NULL) || (pGetThreadId == NULL))
+ {
+ return FALSE;
+ }
+
+ *pPId = (*pGetProcessIdOfThread)(hThread);
+ *pThreadId = (*pGetThreadId)(hThread);
+#endif // FEATURE_PAL
+ return TRUE;
+}
+
+// WER_RUNTIME_EXCEPTION_INFORMATION will be available from Win7 SDK once Win7 SDK is released.
+#if !defined(WER_RUNTIME_EXCEPTION_INFORMATION)
+typedef struct _WER_RUNTIME_EXCEPTION_INFORMATION
+{
+ DWORD dwSize;
+ HANDLE hProcess;
+ HANDLE hThread;
+ EXCEPTION_RECORD exceptionRecord;
+ CONTEXT context;
+} WER_RUNTIME_EXCEPTION_INFORMATION, * PWER_RUNTIME_EXCEPTION_INFORMATION;
+#endif // !defined(WER_RUNTIME_EXCEPTION_INFORMATION)
+
+
+#ifndef FEATURE_PAL
+
+//----------------------------------------------------------------------------
+//
+// OutOfProcessExceptionEventGetWatsonBucket - retrieve Watson buckets if it is a managed exception
+//
+// Arguments:
+// pContext - the context passed at helper module registration
+// pExceptionInformation - structure that contains information about the crash
+// pGM - pointer to the space to store retrieved Watson buckets
+//
+// Return Value:
+// S_OK if the operation is successful.
+// or S_FALSE if it is not a managed exception or Watson buckets cannot be found
+// else detailed error code.
+//
+//----------------------------------------------------------------------------
+STDAPI OutOfProcessExceptionEventGetWatsonBucket(__in PDWORD pContext,
+ __in const PWER_RUNTIME_EXCEPTION_INFORMATION pExceptionInformation,
+ __out GenericModeBlock * pGMB)
+{
+ HANDLE hProcess = pExceptionInformation->hProcess;
+ HANDLE hThread = pExceptionInformation->hThread;
+ DWORD PId, ThreadId;
+
+ if (!OutOfProcessExceptionEventGetProcessIdAndThreadId(hProcess, hThread, &PId, &ThreadId))
+ {
+ return E_FAIL;
+ }
+
+ CLRDATA_ADDRESS baseAddressOfRuntime = (CLRDATA_ADDRESS)pContext;
+ NewHolder<LiveProcDataTarget> dataTarget(NULL);
+
+ dataTarget = new (nothrow) LiveProcDataTarget(hProcess, PId, baseAddressOfRuntime);
+ if (dataTarget == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ NewHolder<ClrDataAccess> pClrDataAccess(NULL);
+
+ HRESULT hr = CLRDataAccessCreateInstance(dataTarget, &pClrDataAccess);
+ if (hr != S_OK)
+ {
+ if (hr == S_FALSE)
+ {
+ return E_FAIL;
+ }
+ else
+ {
+ return hr;
+ }
+ }
+
+ if (!pClrDataAccess->IsExceptionFromManagedCode(&pExceptionInformation->exceptionRecord))
+ {
+ return S_FALSE;
+ }
+
+ return pClrDataAccess->GetWatsonBuckets(ThreadId, pGMB);
+}
+
+//----------------------------------------------------------------------------
+//
+// OutOfProcessExceptionEventCallback - claim the ownership of this event if current
+// runtime threw the unhandled exception
+//
+// Arguments:
+// pContext - the context passed at helper module registration
+// pExceptionInformation - structure that contains information about the crash
+// pbOwnershipClaimed - output parameter for claiming the ownership of this event
+// pwszEventName - name of the event. If this is NULL, pchSize cannot be NULL.
+// This parameter is valid only if * pbOwnershipClaimed is TRUE.
+// pchSize - the size of the buffer pointed by pwszEventName
+// pdwSignatureCount - the count of signature parameters. Valid values range from
+// 0 to 10. If the value returned is greater than 10, only the
+// 1st 10 parameters are used for bucketing parameters. This
+// parameter is valid only if * pbOwnershipClaimed is TRUE.
+//
+// Return Value:
+// S_OK on success, else detailed error code.
+//
+// Note:
+// This is the 1st function that is called into by WER. This API through its out
+// parameters, tells WER as to whether or not it is claiming the crash. If it does
+// claim the crash, WER uses the event name specified in the string pointed to by
+// pwszEventName for error reporting. WER then proceed to call the
+// OutOfProcessExceptionEventSignatureCallback to get the bucketing parameters from
+// the helper dll.
+//
+// This function follows the multiple call paradigms. WER may call into this function
+// with *pwszEventName pointer set to NULL. This is to indicate to the function, that
+// WER wants to know the buffer size needed by the function to populate the string
+// into the buffer. The function should return E_INSUFFICIENTBUFFER with the needed
+// buffer size in *pchSize. WER shall then allocate a buffer of size *pchSize for
+// pwszEventName and then call this function again at which point the function should
+// populate the string and return S_OK.
+//
+// Note that *pdOwnershipClaimed should be set to TRUE everytime this function is called
+// for the helper dll to claim ownership of bucketing.
+//
+// The Win7 WER spec is at
+// http://windows/windows7/docs/COSD%20Documents/Fundamentals/Feedback%20Services%20and%20Platforms/WER-CLR%20Integration%20Dev%20Spec.docx
+//
+// !!!READ THIS!!!
+// Since this is called by external modules it's important that we don't let any exceptions leak out (see Win8 95224).
+//
+//----------------------------------------------------------------------------
+STDAPI OutOfProcessExceptionEventCallback(__in PDWORD pContext,
+ __in const PWER_RUNTIME_EXCEPTION_INFORMATION pExceptionInformation,
+ __out BOOL * pbOwnershipClaimed,
+ __out_ecount(*pchSize) PWSTR pwszEventName,
+ __inout PDWORD pchSize,
+ __out PDWORD pdwSignatureCount)
+{
+ SUPPORTS_DAC_HOST_ONLY;
+
+ if ((pContext == NULL) ||
+ (pExceptionInformation == NULL) ||
+ (pExceptionInformation->dwSize < sizeof(WER_RUNTIME_EXCEPTION_INFORMATION)) ||
+ (pbOwnershipClaimed == NULL) ||
+ (pchSize == NULL) ||
+ (pdwSignatureCount == NULL))
+ {
+ return E_INVALIDARG;
+ }
+
+ *pbOwnershipClaimed = FALSE;
+
+ GenericModeBlock gmb;
+ HRESULT hr = E_FAIL;
+
+ EX_TRY
+ {
+ // get Watson buckets if it is a managed exception
+ hr = OutOfProcessExceptionEventGetWatsonBucket(pContext, pExceptionInformation, &gmb);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ if (hr != S_OK)
+ {
+ // S_FALSE means either it is not a managed exception or we do not have Watson buckets.
+ // Since we have set pbOwnershipClaimed to FALSE, we return S_OK to WER.
+ if (hr == S_FALSE)
+ {
+ hr = S_OK;
+ }
+
+ return hr;
+ }
+
+ if ((pwszEventName == NULL) || (*pchSize <= wcslen(gmb.wzEventTypeName)))
+ {
+ *pchSize = static_cast<DWORD>(wcslen(gmb.wzEventTypeName)) + 1;
+ return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
+ }
+
+ // copy custom event name
+ wcscpy_s(pwszEventName, *pchSize, gmb.wzEventTypeName);
+ *pdwSignatureCount = GetCountBucketParamsForEvent(gmb.wzEventTypeName);
+ *pbOwnershipClaimed = TRUE;
+
+ return S_OK;
+}
+
+
+//----------------------------------------------------------------------------
+//
+// OutOfProcessExceptionEventCallback - provide custom Watson buckets
+//
+// Arguments:
+// pContext - the context passed at helper module registration
+// pExceptionInformation - structure that contains information about the crash
+// dwIndex - the index of the bucketing parameter being requested. Valid values are
+// from 0 to 9
+// pwszName - pointer to the name of the bucketing parameter
+// pchName - pointer to character count of the pwszName buffer. If pwszName points to
+// null, *pchName represents the buffer size (represented in number of characters)
+// needed to populate the name in pwszName.
+// pwszValue - pointer to the value of the pwszName bucketing parameter
+// pchValue - pointer to the character count of the pwszValue buffer. If pwszValue points
+// to null, *pchValue represents the buffer size (represented in number of
+// characters) needed to populate the value in pwszValue.
+//
+// Return Value:
+// S_OK on success, else detailed error code.
+//
+// Note:
+// This function is called by WER only if the call to OutOfProcessExceptionEventCallback()
+// was successful and the value of *pbOwnershipClaimed was TRUE. This function is called
+// pdwSignatureCount times to collect the bucketing parameters from the helper dll.
+//
+// This function also follows the multiple call paradigm as described for the
+// OutOfProcessExceptionEventCallback() function. The buffer sizes needed for
+// this function are of the pwszName and pwszValue buffers.
+//
+// !!!READ THIS!!!
+// Since this is called by external modules it's important that we don't let any exceptions leak out (see Win8 95224).
+//
+//----------------------------------------------------------------------------
+STDAPI OutOfProcessExceptionEventSignatureCallback(__in PDWORD pContext,
+ __in const PWER_RUNTIME_EXCEPTION_INFORMATION pExceptionInformation,
+ __in DWORD dwIndex,
+ __out_ecount(*pchName) PWSTR pwszName,
+ __inout PDWORD pchName,
+ __out_ecount(*pchValue) PWSTR pwszValue,
+ __inout PDWORD pchValue)
+{
+ SUPPORTS_DAC_HOST_ONLY;
+
+ if ((pContext == NULL) ||
+ (pExceptionInformation == NULL) ||
+ (pExceptionInformation->dwSize < sizeof(WER_RUNTIME_EXCEPTION_INFORMATION)) ||
+ (pchName == NULL) ||
+ (pchValue == NULL))
+ {
+ return E_INVALIDARG;
+ }
+
+ if ((pwszName == NULL) || (*pchName == 0))
+ {
+ *pchName = 1;
+ return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
+ }
+
+ GenericModeBlock gmb;
+ const PWSTR pwszBucketValues[] = {gmb.wzP1,
+ gmb.wzP2,
+ gmb.wzP3,
+ gmb.wzP4,
+ gmb.wzP5,
+ gmb.wzP6,
+ gmb.wzP7,
+ gmb.wzP8,
+ gmb.wzP9,
+ gmb.wzP10};
+
+ HRESULT hr = E_FAIL;
+
+ EX_TRY
+ {
+ // get Watson buckets if it is a managed exception
+ hr = OutOfProcessExceptionEventGetWatsonBucket(pContext, pExceptionInformation, &gmb);
+ }
+ EX_CATCH_HRESULT(hr);
+
+#ifndef FEATURE_WINDOWSPHONE
+ // we can't assert this on phone as it's possible for the OS to kill
+ // the faulting process before WER crash reporting has completed.
+ _ASSERTE(hr == S_OK);
+#else
+ _ASSERTE(hr == S_OK || hr == CORDBG_E_READVIRTUAL_FAILURE);
+#endif
+ if (hr != S_OK)
+ {
+ // S_FALSE means either it is not a managed exception or we do not have Watson buckets.
+ // Either case is a logic error becuase this function is called by WER only if the call
+ // to OutOfProcessExceptionEventCallback() was successful and the value of
+ // *pbOwnershipClaimed was TRUE.
+ if (hr == S_FALSE)
+ {
+ hr = E_FAIL;
+ }
+
+ return hr;
+ }
+
+ DWORD paramCount = GetCountBucketParamsForEvent(gmb.wzEventTypeName);
+
+ if (dwIndex >= paramCount)
+ {
+ _ASSERTE(!"dwIndex is out of range");
+ return E_INVALIDARG;
+ }
+
+ // Return pwszName as an emptry string to let WER use localized version of "Parameter n"
+ *pwszName = W('\0');
+
+ if ((pwszValue == NULL) || (*pchValue <= wcslen(pwszBucketValues[dwIndex])))
+ {
+ *pchValue = static_cast<DWORD>(wcslen(pwszBucketValues[dwIndex]))+ 1;
+ return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
+ }
+
+ // copy custom Watson bucket value
+ wcscpy_s(pwszValue, *pchValue, pwszBucketValues[dwIndex]);
+
+ return S_OK;
+}
+
+#endif // FEATURE_PAL
+
+//----------------------------------------------------------------------------
+//
+// OutOfProcessExceptionEventCallback - provide custom debugger launch string
+//
+// Arguments:
+// pContext - the context passed at helper module registration
+// pExceptionInformation - structure that contains information about the crash
+// pbCustomDebuggerNeeded - pointer to a BOOL. If this BOOL is set to TRUE, then
+// a custom debugger launch option is needed by the
+// process. In that case, the subsequent parameters will
+// be meaningfully used. If this is FALSE, the subsequent
+// parameters will be ignored.
+// pwszDebuggerLaunch - pointer to a string that will be used to launch the debugger,
+// if the debugger is launched. The value of this string overrides
+// the default debugger launch string used by WER.
+// pchSize - pointer to the character count of the pwszDebuggerLaunch buffer. If
+// pwszDebuggerLaunch points to null, *pchSize represents the buffer size
+// (represented in number of characters) needed to populate the debugger
+// launch string in pwszDebuggerLaunch.
+// pbAutoLaunchDebugger - pointer to a BOOL. If this BOOL is set to TRUE, WER will
+// directly launch the debugger. If set to FALSE, WER will show
+// the debug option to the user in the WER UI.
+//
+// Return Value:
+// S_OK on success, else detailed error code.
+//
+// Note:
+// This function is called into by WER only if the call to OutOfProcessExceptionEventCallback()
+// was successful and the value of *pbOwnershipClaimed was TRUE. This function allows the helper
+// dll to customize the debugger launch options including the launch string.
+//
+// This function also follows the multiple call paradigm as described for the
+// OutOfProcessExceptionEventCallback() function. The buffer sizes needed for
+// this function are of the pwszName and pwszValue buffers.
+//
+//----------------------------------------------------------------------------
+STDAPI OutOfProcessExceptionEventDebuggerLaunchCallback(__in PDWORD pContext,
+ __in const PWER_RUNTIME_EXCEPTION_INFORMATION pExceptionInformation,
+ __out BOOL * pbCustomDebuggerNeeded,
+ __out_ecount_opt(*pchSize) PWSTR pwszDebuggerLaunch,
+ __inout PDWORD pchSize,
+ __out BOOL * pbAutoLaunchDebugger)
+{
+ SUPPORTS_DAC_HOST_ONLY;
+
+ if ((pContext == NULL) ||
+ (pExceptionInformation == NULL) ||
+ (pExceptionInformation->dwSize < sizeof(WER_RUNTIME_EXCEPTION_INFORMATION)) ||
+ (pbCustomDebuggerNeeded == NULL) ||
+ (pwszDebuggerLaunch == NULL) ||
+ (pchSize == NULL) ||
+ (pbAutoLaunchDebugger == NULL))
+ {
+ return E_INVALIDARG;
+ }
+
+ // Starting from CLRv4 managed debugger string and setting are unified with native debuggers.
+ // There is no need to provide custom debugger string for WER.
+ *pbCustomDebuggerNeeded = FALSE;
+
+ return S_OK;
+}
+
+// DacHandleEnum
+
+#include "handletablepriv.h"
+#include "comcallablewrapper.h"
+
+DacHandleWalker::DacHandleWalker()
+ : mDac(0), m_instanceAge(0), mMap(0), mIndex(0),
+ mTypeMask(0), mGenerationFilter(-1), mChunkIndex(0), mCurr(0),
+ mIteratorIndex(0)
+{
+ SUPPORTS_DAC;
+}
+
+DacHandleWalker::~DacHandleWalker()
+{
+ SUPPORTS_DAC;
+
+ HandleChunkHead *curr = mHead.Next;
+
+ while (curr)
+ {
+ HandleChunkHead *tmp = curr;
+ curr = curr->Next;
+ delete tmp;
+ }
+}
+
+HRESULT DacHandleWalker::Init(ClrDataAccess *dac, UINT types[], UINT typeCount)
+{
+ SUPPORTS_DAC;
+
+ if (dac == NULL || types == NULL)
+ return E_POINTER;
+
+ mDac = dac;
+ m_instanceAge = dac->m_instanceAge;
+
+ return Init(BuildTypemask(types, typeCount));
+}
+
+HRESULT DacHandleWalker::Init(ClrDataAccess *dac, UINT types[], UINT typeCount, int gen)
+{
+ SUPPORTS_DAC;
+
+ if (gen < 0 || gen > (int)GCHeap::GetMaxGeneration())
+ return E_INVALIDARG;
+
+ mGenerationFilter = gen;
+
+ return Init(dac, types, typeCount);
+}
+
+HRESULT DacHandleWalker::Init(UINT32 typemask)
+{
+ SUPPORTS_DAC;
+
+ mMap = &g_HandleTableMap;
+ mTypeMask = typemask;
+
+ return S_OK;
+}
+
+UINT32 DacHandleWalker::BuildTypemask(UINT types[], UINT typeCount)
+{
+ SUPPORTS_DAC;
+
+ UINT32 mask = 0;
+
+ for (UINT i = 0; i < typeCount; ++i)
+ {
+ _ASSERTE(types[i] < 32);
+ mask |= (1 << types[i]);
+ }
+
+ return mask;
+}
+
+HRESULT DacHandleWalker::Next(unsigned int celt,
+ SOSHandleData handles[],
+ unsigned int *pceltFetched)
+{
+ SUPPORTS_DAC;
+
+ if (handles == NULL || pceltFetched == NULL)
+ return E_POINTER;
+
+ SOSHelperEnter();
+
+ hr = DoHandleWalk<SOSHandleData, unsigned int, DacHandleWalker::EnumCallbackSOS>(celt, handles, pceltFetched);
+
+ SOSHelperLeave();
+
+ return hr;
+}
+
+bool DacHandleWalker::FetchMoreHandles(HANDLESCANPROC callback)
+{
+ SUPPORTS_DAC;
+
+ // The table slots are based on the number of GC heaps in the process.
+ int max_slots = 1;
+
+#ifdef FEATURE_SVR_GC
+ if (GCHeap::IsServerHeap())
+ max_slots = GCHeapCount();
+#endif // FEATURE_SVR_GC
+
+ // Reset the Count on all cached chunks. We reuse chunks after allocating
+ // them, and the count is the only thing which needs resetting.
+ for (HandleChunkHead *curr = &mHead; curr; curr = curr->Next)
+ curr->Count = 0;
+
+ DacHandleWalkerParam param(&mHead);
+
+ do
+ {
+ // Have we advanced past the end of the current bucket?
+ if (mMap && mIndex >= INITIAL_HANDLE_TABLE_ARRAY_SIZE)
+ {
+ mIndex = 0;
+ mMap = mMap->pNext;
+ }
+
+ // Have we walked the entire handle table map?
+ if (mMap == NULL)
+ {
+ mCurr = NULL;
+ return false;
+ }
+
+ if (mMap->pBuckets[mIndex] != NULL)
+ {
+ for (int i = 0; i < max_slots; ++i)
+ {
+ HHANDLETABLE hTable = mMap->pBuckets[mIndex]->pTable[i];
+ if (hTable)
+ {
+ // Yikes! The handle table callbacks don't produce the handle type or
+ // the AppDomain that we need, and it's too difficult to propogate out
+ // these things (especially the type) without worrying about performance
+ // implications for the GC. Instead we'll have the callback walk each
+ // type individually. There are only a few handle types, and the handle
+ // table has a fast-path for only walking a single type anyway.
+ UINT32 handleType = 0;
+ for (UINT32 mask = mTypeMask; mask; mask >>= 1, handleType++)
+ {
+ if (mask & 1)
+ {
+ HandleTable *pTable = (HandleTable *)hTable;
+ PTR_AppDomain pDomain = SystemDomain::GetAppDomainAtIndex(pTable->uADIndex);
+ param.AppDomain = TO_CDADDR(pDomain.GetAddr());
+ param.Type = handleType;
+
+ // Either enumerate the handles regularly, or walk the handle
+ // table as the GC does if a generation filter was requested.
+ if (mGenerationFilter != -1)
+ HndScanHandlesForGC(hTable, callback,
+ (LPARAM)&param, 0,
+ &handleType, 1,
+ mGenerationFilter, GCHeap::GetMaxGeneration(), 0);
+ else
+ HndEnumHandles(hTable, &handleType, 1, callback, (LPARAM)&param, 0, FALSE);
+ }
+ }
+ }
+ }
+ }
+
+ // Stop looping as soon as we have found data. We also stop if we have a failed HRESULT during
+ // the callback (this should indicate OOM).
+ mIndex++;
+ } while (mHead.Count == 0 && SUCCEEDED(param.Result));
+
+ mCurr = mHead.Next;
+ return true;
+}
+
+
+HRESULT DacHandleWalker::Skip(unsigned int celt)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT DacHandleWalker::Reset()
+{
+ return E_NOTIMPL;
+}
+
+HRESULT DacHandleWalker::GetCount(unsigned int *pcelt)
+{
+ return E_NOTIMPL;
+}
+
+
+void DacHandleWalker::GetRefCountedHandleInfo(
+ OBJECTREF oref, unsigned int uType,
+ unsigned int *pRefCount, unsigned int *pJupiterRefCount, BOOL *pIsPegged, BOOL *pIsStrong)
+{
+ SUPPORTS_DAC;
+
+#ifdef FEATURE_COMINTEROP
+ if (uType == HNDTYPE_REFCOUNTED)
+ {
+ // get refcount from the CCW
+ PTR_ComCallWrapper pWrap = ComCallWrapper::GetWrapperForObject(oref);
+ if (pWrap != NULL)
+ {
+ if (pRefCount)
+ *pRefCount = (unsigned int)pWrap->GetRefCount();
+
+ if (pJupiterRefCount)
+ *pJupiterRefCount = (unsigned int)pWrap->GetJupiterRefCount();
+
+ if (pIsPegged)
+ *pIsPegged = pWrap->IsConsideredPegged();
+
+ if (pIsStrong)
+ *pIsStrong = pWrap->IsWrapperActive();
+
+ return;
+ }
+ }
+#endif // FEATURE_COMINTEROP
+
+ if (pRefCount)
+ *pRefCount = 0;
+
+ if (pJupiterRefCount)
+ *pJupiterRefCount = 0;
+
+ if (pIsPegged)
+ *pIsPegged = FALSE;
+
+ if (pIsStrong)
+ *pIsStrong = FALSE;
+}
+
+void CALLBACK DacHandleWalker::EnumCallbackSOS(PTR_UNCHECKED_OBJECTREF handle, uintptr_t *pExtraInfo, uintptr_t param1, uintptr_t param2)
+{
+ SUPPORTS_DAC;
+
+ DacHandleWalkerParam *param = (DacHandleWalkerParam *)param1;
+ HandleChunkHead *curr = param->Curr;
+
+ // If we failed on a previous call (OOM) don't keep trying to allocate, it's not going to work.
+ if (FAILED(param->Result))
+ return;
+
+ // We've moved past the size of the current chunk. We'll allocate a new chunk
+ // and stuff the handles there. These are cleaned up by the destructor
+ if (curr->Count >= (curr->Size/sizeof(SOSHandleData)))
+ {
+ if (curr->Next == NULL)
+ {
+ HandleChunk *next = new (nothrow) HandleChunk;
+ if (next != NULL)
+ {
+ curr->Next = next;
+ }
+ else
+ {
+ param->Result = E_OUTOFMEMORY;
+ return;
+ }
+ }
+
+ curr = param->Curr = param->Curr->Next;
+ }
+
+ // Fill the current handle.
+ SOSHandleData *dataArray = (SOSHandleData*)curr->pData;
+ SOSHandleData &data = dataArray[curr->Count++];
+
+ data.Handle = TO_CDADDR(handle.GetAddr());
+ data.Type = param->Type;
+ if (param->Type == HNDTYPE_DEPENDENT)
+ data.Secondary = GetDependentHandleSecondary(handle.GetAddr()).GetAddr();
+#ifdef FEATURE_COMINTEROP
+ else if (param->Type == HNDTYPE_WEAK_WINRT)
+ data.Secondary = HndGetHandleExtraInfo(handle.GetAddr());
+#endif // FEATURE_COMINTEROP
+ else
+ data.Secondary = 0;
+ data.AppDomain = param->AppDomain;
+ GetRefCountedHandleInfo((OBJECTREF)*handle, param->Type, &data.RefCount, &data.JupiterRefCount, &data.IsPegged, &data.StrongReference);
+ data.StrongReference |= (BOOL)IsAlwaysStrongReference(param->Type);
+}
+
+DacStackReferenceWalker::DacStackReferenceWalker(ClrDataAccess *dac, DWORD osThreadID)
+ : mDac(dac), m_instanceAge(dac ? dac->m_instanceAge : 0), mThread(0), mErrors(0), mEnumerated(false),
+ mChunkIndex(0), mCurr(0), mIteratorIndex(0)
+{
+ Thread *curr = NULL;
+
+ for (curr = ThreadStore::GetThreadList(curr);
+ curr;
+ curr = ThreadStore::GetThreadList(curr))
+ {
+ if (curr->GetOSThreadId() == osThreadID)
+ {
+ mThread = curr;
+ break;
+ }
+ }
+}
+
+DacStackReferenceWalker::~DacStackReferenceWalker()
+{
+ StackRefChunkHead *curr = mHead.next;
+
+ while (curr)
+ {
+ StackRefChunkHead *tmp = curr;
+ curr = curr->next;
+ delete tmp;
+ }
+}
+
+HRESULT DacStackReferenceWalker::Init()
+{
+ if (!mThread)
+ return E_INVALIDARG;
+ return mHeap.Init();
+}
+
+HRESULT STDMETHODCALLTYPE DacStackReferenceWalker::Skip(unsigned int count)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT STDMETHODCALLTYPE DacStackReferenceWalker::Reset()
+{
+ return E_NOTIMPL;
+}
+
+HRESULT DacStackReferenceWalker::GetCount(unsigned int *pCount)
+{
+ if (!pCount)
+ return E_POINTER;
+
+ SOSHelperEnter();
+
+ if (!mEnumerated)
+ {
+ // Fill out our data structures.
+ WalkStack<unsigned int, SOSStackRefData>(0, NULL, DacStackReferenceWalker::GCReportCallbackSOS, DacStackReferenceWalker::GCEnumCallbackSOS);
+ }
+
+ unsigned int count = 0;
+ for(StackRefChunkHead *curr = &mHead; curr; curr = curr->next)
+ count += curr->count;
+
+ *pCount = count;
+
+ SOSHelperLeave();
+ return hr;
+}
+
+HRESULT DacStackReferenceWalker::Next(unsigned int count,
+ SOSStackRefData stackRefs[],
+ unsigned int *pFetched)
+{
+ if (stackRefs == NULL || pFetched == NULL)
+ return E_POINTER;
+
+ SOSHelperEnter();
+
+ hr = DoStackWalk<unsigned int, SOSStackRefData,
+ DacStackReferenceWalker::GCReportCallbackSOS,
+ DacStackReferenceWalker::GCEnumCallbackSOS>
+ (count, stackRefs, pFetched);
+
+ SOSHelperLeave();
+
+ return hr;
+}
+
+HRESULT DacStackReferenceWalker::EnumerateErrors(ISOSStackRefErrorEnum **ppEnum)
+{
+ if (!ppEnum)
+ return E_POINTER;
+
+ SOSHelperEnter();
+
+ if (mThread)
+ {
+ // Fill out our data structures.
+ WalkStack<unsigned int, SOSStackRefData>(0, NULL, DacStackReferenceWalker::GCReportCallbackSOS, DacStackReferenceWalker::GCEnumCallbackSOS);
+ }
+
+ DacStackReferenceErrorEnum *pEnum = new DacStackReferenceErrorEnum(this, mErrors);
+ hr = pEnum->QueryInterface(__uuidof(ISOSStackRefErrorEnum), (void**)ppEnum);
+
+ SOSHelperLeave();
+ return hr;
+}
+
+CLRDATA_ADDRESS DacStackReferenceWalker::ReadPointer(TADDR addr)
+{
+ ULONG32 bytesRead = 0;
+ TADDR result = 0;
+ HRESULT hr = mDac->m_pTarget->ReadVirtual(addr, (BYTE*)&result, sizeof(TADDR), &bytesRead);
+
+ if (FAILED(hr) || (bytesRead != sizeof(TADDR)))
+ return (CLRDATA_ADDRESS)~0;
+
+ return TO_CDADDR(result);
+}
+
+
+void DacStackReferenceWalker::GCEnumCallbackSOS(LPVOID hCallback, OBJECTREF *pObject, uint32_t flags, DacSlotLocation loc)
+{
+ GCCONTEXT *gcctx = (GCCONTEXT *)hCallback;
+ DacScanContext *dsc = (DacScanContext*)gcctx->sc;
+
+ // Yuck. The GcInfoDecoder reports a local pointer for registers (as it's reading out of the REGDISPLAY
+ // in the stack walk), and it reports a TADDR for stack locations. This is architecturally difficulty
+ // to fix, so we are leaving it for now.
+ TADDR addr = 0;
+ TADDR obj = 0;
+
+ if (loc.targetPtr)
+ {
+ addr = (TADDR)pObject;
+ obj = TO_TADDR(dsc->pWalker->ReadPointer((CORDB_ADDRESS)addr));
+ }
+ else
+ {
+ obj = pObject->GetAddr();
+ }
+
+ if (flags & GC_CALL_INTERIOR)
+ {
+ CORDB_ADDRESS fixed_obj = 0;
+ HRESULT hr = dsc->pWalker->mHeap.ListNearObjects((CORDB_ADDRESS)obj, NULL, &fixed_obj, NULL);
+
+ // If we failed...oh well, SOS won't mind. We'll just report the interior pointer as is.
+ if (SUCCEEDED(hr))
+ obj = TO_TADDR(fixed_obj);
+ }
+
+ SOSStackRefData *data = dsc->pWalker->GetNextObject<SOSStackRefData>(dsc);
+ if (data != NULL)
+ {
+ // Report where the object and where it was found.
+ data->HasRegisterInformation = true;
+ data->Register = loc.reg;
+ data->Offset = loc.regOffset;
+ data->Address = TO_CDADDR(addr);
+ data->Object = TO_CDADDR(obj);
+ data->Flags = flags;
+
+ // Report the frame that the data came from.
+ data->StackPointer = TO_CDADDR(dsc->sp);
+
+ if (dsc->pFrame)
+ {
+ data->SourceType = SOS_StackSourceFrame;
+ data->Source = dac_cast<PTR_Frame>(dsc->pFrame).GetAddr();
+ }
+ else
+ {
+ data->SourceType = SOS_StackSourceIP;
+ data->Source = TO_CDADDR(dsc->pc);
+ }
+ }
+}
+
+
+void DacStackReferenceWalker::GCReportCallbackSOS(PTR_PTR_Object ppObj, ScanContext *sc, uint32_t flags)
+{
+ DacScanContext *dsc = (DacScanContext*)sc;
+ CLRDATA_ADDRESS obj = dsc->pWalker->ReadPointer(ppObj.GetAddr());
+
+ if (flags & GC_CALL_INTERIOR)
+ {
+ CORDB_ADDRESS fixed_addr = 0;
+ HRESULT hr = dsc->pWalker->mHeap.ListNearObjects((CORDB_ADDRESS)obj, NULL, &fixed_addr, NULL);
+
+ // If we failed...oh well, SOS won't mind. We'll just report the interior pointer as is.
+ if (SUCCEEDED(hr))
+ obj = TO_CDADDR(fixed_addr);
+ }
+
+ SOSStackRefData *data = dsc->pWalker->GetNextObject<SOSStackRefData>(dsc);
+ if (data != NULL)
+ {
+ data->HasRegisterInformation = false;
+ data->Register = 0;
+ data->Offset = 0;
+ data->Address = ppObj.GetAddr();
+ data->Object = obj;
+ data->Flags = flags;
+ data->StackPointer = TO_CDADDR(dsc->sp);
+
+ if (dsc->pFrame)
+ {
+ data->SourceType = SOS_StackSourceFrame;
+ data->Source = dac_cast<PTR_Frame>(dsc->pFrame).GetAddr();
+ }
+ else
+ {
+ data->SourceType = SOS_StackSourceIP;
+ data->Source = TO_CDADDR(dsc->pc);
+ }
+ }
+}
+
+StackWalkAction DacStackReferenceWalker::Callback(CrawlFrame *pCF, VOID *pData)
+{
+ //
+ // KEEP IN SYNC WITH GcStackCrawlCallBack in vm\gcscan.cpp
+ //
+
+ GCCONTEXT *gcctx = (GCCONTEXT*)pData;
+ DacScanContext *dsc = (DacScanContext*)gcctx->sc;
+
+ MethodDesc *pMD = pCF->GetFunction();
+ gcctx->sc->pMD = pMD;
+ gcctx->sc->pCurrentDomain = pCF->GetAppDomain();
+
+ PREGDISPLAY pRD = pCF->GetRegisterSet();
+ dsc->sp = (TADDR)GetRegdisplaySP(pRD);;
+ dsc->pc = PCODEToPINSTR(GetControlPC(pRD));
+
+ ResetPointerHolder<CrawlFrame*> rph(&gcctx->cf);
+ gcctx->cf = pCF;
+
+ bool fReportGCReferences = true;
+#if defined(WIN64EXCEPTIONS)
+ // On Win64 and ARM, we may have unwound this crawlFrame and thus, shouldn't report the invalid
+ // references it may contain.
+ // todo.
+ fReportGCReferences = pCF->ShouldCrawlframeReportGCReferences();
+#endif // defined(WIN64EXCEPTIONS)
+
+ Frame *pFrame = ((DacScanContext*)gcctx->sc)->pFrame = pCF->GetFrame();
+
+ EX_TRY
+ {
+ if (fReportGCReferences)
+ {
+ if (pCF->IsFrameless())
+ {
+ ICodeManager * pCM = pCF->GetCodeManager();
+ _ASSERTE(pCM != NULL);
+
+ unsigned flags = pCF->GetCodeManagerFlags();
+
+ pCM->EnumGcRefs(pCF->GetRegisterSet(),
+ pCF->GetCodeInfo(),
+ flags,
+ dsc->pEnumFunc,
+ pData);
+ }
+ else
+ {
+ pFrame->GcScanRoots(gcctx->f, gcctx->sc);
+ }
+ }
+ }
+ EX_CATCH
+ {
+ SOSStackErrorList *err = new SOSStackErrorList;
+ err->pNext = NULL;
+
+ if (pFrame)
+ {
+ err->error.SourceType = SOS_StackSourceFrame;
+ err->error.Source = dac_cast<PTR_Frame>(pFrame).GetAddr();
+ }
+ else
+ {
+ err->error.SourceType = SOS_StackSourceIP;
+ err->error.Source = TO_CDADDR(dsc->pc);
+ }
+
+ if (dsc->pWalker->mErrors == NULL)
+ {
+ dsc->pWalker->mErrors = err;
+ }
+ else
+ {
+ // This exception case should be non-existent. It only happens when there is either
+ // a clr!Frame on the callstack which is not properly dac-ized, or when a call down
+ // EnumGcRefs causes a data read exception. Since this is so rare, we don't worry
+ // about making this code very efficient.
+ SOSStackErrorList *curr = dsc->pWalker->mErrors;
+ while (curr->pNext)
+ curr = curr->pNext;
+
+ curr->pNext = err;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+#if 0
+ // todo
+
+ // If we're executing a LCG dynamic method then we must promote the associated resolver to ensure it
+ // doesn't get collected and yank the method code out from under us).
+
+ // Be careful to only promote the reference -- we can also be called to relocate the reference and
+ // that can lead to all sorts of problems since we could be racing for the relocation with the long
+ // weak handle we recover the reference from. Promoting the reference is enough, the handle in the
+ // reference will be relocated properly as long as we keep it alive till the end of the collection
+ // as long as the reference is actually maintained by the long weak handle.
+ if (pMD)
+ {
+ BOOL fMaybeCollectibleMethod = TRUE;
+
+ // If this is a frameless method then the jitmanager can answer the question of whether
+ // or not this is LCG simply by looking at the heap where the code lives, however there
+ // is also the prestub case where we need to explicitly look at the MD for stuff that isn't
+ // ngen'd
+ if (pCF->IsFrameless() && pMD->IsLCGMethod())
+ {
+ fMaybeCollectibleMethod = ExecutionManager::IsCollectibleMethod(pCF->GetMethodToken());
+ }
+
+ if (fMaybeCollectibleMethod && pMD->IsLCGMethod())
+ {
+ PTR_Object obj = OBJECTREFToObject(pMD->AsDynamicMethodDesc()->GetLCGMethodResolver()->GetManagedResolver());
+ dsc->pWalker->ReportObject(obj);
+ }
+ else
+ {
+ if (fMaybeCollectibleMethod)
+ {
+ PTR_Object obj = pMD->GetLoaderAllocator()->GetExposedObject();
+ dsc->pWalker->ReportObject(obj);
+ }
+
+ if (fReportGCReferences)
+ {
+ GenericParamContextType paramContextType = GENERIC_PARAM_CONTEXT_NONE;
+
+ if (pCF->IsFrameless())
+ {
+ // We need to grab the Context Type here because there are cases where the MethodDesc
+ // is shared, and thus indicates there should be an instantion argument, but the JIT
+ // was still allowed to optimize it away and we won't grab it below because we're not
+ // reporting any references from this frame.
+ paramContextType = pCF->GetCodeManager()->GetParamContextType(pCF->GetRegisterSet(), pCF->GetCodeInfo());
+ }
+ else
+ {
+ if (pMD->RequiresInstMethodDescArg())
+ paramContextType = GENERIC_PARAM_CONTEXT_METHODDESC;
+ else if (pMD->RequiresInstMethodTableArg())
+ paramContextType = GENERIC_PARAM_CONTEXT_METHODTABLE;
+ }
+
+ // Handle the case where the method is a static shared generic method and we need to keep the type of the generic parameters alive
+ if (paramContextType == GENERIC_PARAM_CONTEXT_METHODDESC)
+ {
+ MethodDesc *pMDReal = dac_cast<PTR_MethodDesc>(pCF->GetParamTypeArg());
+ _ASSERTE((pMDReal != NULL) || !pCF->IsFrameless());
+ if (pMDReal != NULL)
+ {
+ PTR_Object obj = pMDReal->GetLoaderAllocator()->GetExposedObject();
+ dsc->pWalker->ReportObject(obj);
+ }
+ }
+ else if (paramContextType == GENERIC_PARAM_CONTEXT_METHODTABLE)
+ {
+ MethodTable *pMTReal = dac_cast<PTR_MethodTable>(pCF->GetParamTypeArg());
+ _ASSERTE((pMTReal != NULL) || !pCF->IsFrameless());
+ if (pMTReal != NULL)
+ {
+ PTR_Object obj = pMTReal->GetLoaderAllocator()->GetExposedObject();
+ dsc->pWalker->ReportObject(obj);
+ }
+ }
+ }
+ }
+ }
+#endif
+
+ return SWA_CONTINUE;
+}
+
+
+DacStackReferenceErrorEnum::DacStackReferenceErrorEnum(DacStackReferenceWalker *pEnum, SOSStackErrorList *pErrors)
+ : mEnum(pEnum), mHead(pErrors), mCurr(pErrors)
+{
+ _ASSERTE(mEnum);
+
+ if (mHead != NULL)
+ mEnum->AddRef();
+}
+
+DacStackReferenceErrorEnum::~DacStackReferenceErrorEnum()
+{
+ if (mHead)
+ mEnum->Release();
+}
+
+HRESULT DacStackReferenceErrorEnum::Skip(unsigned int count)
+{
+ unsigned int i = 0;
+ for (i = 0; i < count && mCurr; ++i)
+ mCurr = mCurr->pNext;
+
+ return i < count ? S_FALSE : S_OK;
+}
+
+HRESULT DacStackReferenceErrorEnum::Reset()
+{
+ mCurr = mHead;
+
+ return S_OK;
+}
+
+HRESULT DacStackReferenceErrorEnum::GetCount(unsigned int *pCount)
+{
+ SOSStackErrorList *curr = mHead;
+ unsigned int count = 0;
+
+ while (curr)
+ {
+ curr = curr->pNext;
+ count++;
+ }
+
+ *pCount = count;
+ return S_OK;
+}
+
+HRESULT DacStackReferenceErrorEnum::Next(unsigned int count, SOSStackRefError ref[], unsigned int *pFetched)
+{
+ if (pFetched == NULL || ref == NULL)
+ return E_POINTER;
+
+ unsigned int i;
+ for (i = 0; i < count && mCurr; ++i, mCurr = mCurr->pNext)
+ ref[i] = mCurr->error;
+
+ *pFetched = i;
+ return i < count ? S_FALSE : S_OK;
+}
diff --git a/src/debug/daccess/daccess.targets b/src/debug/daccess/daccess.targets
new file mode 100644
index 0000000000..a7d9e41554
--- /dev/null
+++ b/src/debug/daccess/daccess.targets
@@ -0,0 +1,67 @@
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="dogfood">
+ <!--*****************************************************-->
+ <!--This MSBuild project file was automatically generated-->
+ <!--from the original SOURCES/DIRS file by the KBC tool.-->
+ <!--*****************************************************-->
+ <!--Import the settings-->
+ <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\clr.props" />
+ <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\dac.props" />
+ <Import Project="..\SetDebugTargetLocal.props" />
+ <!--Leaf project Properties-->
+ <PropertyGroup>
+ <UseStl Condition="'$(BuildForCoreSystem)' != 'true'">true</UseStl>
+ <DaccessSrcDirectory>$(ClrSrcDirectory)\debug\daccess</DaccessSrcDirectory>
+ <UserIncludes>$(UserIncludes);
+ $(DaccessSrcDirectory);
+ $(ClrSrcDirectory)\vm;
+ $(ClrSrcDirectory)\vm\$(TargetCpu);
+ $(ClrSrcDirectory)\debug\inc;
+ $(ClrSrcDirectory)\debug\inc\$(TargetCpu);
+ $(ClrSrcDirectory)\debug\inc\dump;
+ $(ClrSrcDirectory)\debug\ee;
+ $(ClrSrcDirectory)\inc;
+ $(ClrSrcDirectory)\inc\$(IntermediateOutputDirectory);
+ $(VCToolsIncPath);
+ $(ClrSrcDirectory)\gcdump;
+ $(ClrSrcDirectory)\md\inc;
+ $(ClrSrcDirectory)\gc;
+ $(ClrSrcDirectory)\strongname\inc</UserIncludes>
+ <CDefines>$(CDefines);UNICODE;_UNICODE;$(USER_SPECIFIC_C_DEFINES);FEATURE_NO_HOST</CDefines>
+ <OutputName Condition="'$(OutputName)' == ''">dac_wks</OutputName>
+ <OutputPath>$(ClrLibDest)</OutputPath>
+ <TargetType>LIBRARY</TargetType>
+ <PCHHeader>stdafx.h</PCHHeader>
+ <EnableCxxPCHHeaders>true</EnableCxxPCHHeaders>
+ <PCHCompile>$(DaccessSrcDirectory)\stdafx.cpp</PCHCompile>
+ </PropertyGroup>
+ <!--Leaf Project Items-->
+ <ItemGroup>
+ <ProjectReference Include="$(ClrSrcDirectory)inc\corguids.nativeproj" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <CppCompile Include="$(DaccessSrcDirectory)\dacdbiimpl.cpp" />
+ <CppCompile Include="$(DaccessSrcDirectory)\dacdbiimpllocks.cpp" />
+ <CppCompile Include="$(DaccessSrcDirectory)\dacdbiimplstackwalk.cpp" />
+ <CppCompile Include="$(DaccessSrcDirectory)\daccess.cpp" />
+ <CppCompile Include="$(DaccessSrcDirectory)\dacfn.cpp" />
+ <CppCompile Include="$(DaccessSrcDirectory)\enummem.cpp" />
+ <CppCompile Include="$(DaccessSrcDirectory)\fntableaccess.cpp" />
+ <CppCompile Include="$(DaccessSrcDirectory)\inspect.cpp" />
+ <CppCompile Include="$(DaccessSrcDirectory)\reimpl.cpp" />
+ <CppCompile Include="$(DaccessSrcDirectory)\request.cpp" />
+ <CppCompile Include="$(DaccessSrcDirectory)\request_svr.cpp" />
+ <CppCompile Include="$(DaccessSrcDirectory)\stack.cpp" />
+ <CppCompile Include="$(DaccessSrcDirectory)\task.cpp" />
+ <CppCompile Include="$(DaccessSrcDirectory)\nidump.cpp" />
+ <CppCompile Include="$(DaccessSrcDirectory)\datatargetadapter.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <CppCompile Condition="'$(TargetArch)' == 'i386'" Include="$(DaccessSrcDirectory)\i386\primitives.cpp" />
+ <CppCompile Condition="'$(TargetArch)' == 'amd64'" Include="$(DaccessSrcDirectory)\amd64\primitives.cpp" />
+ <CppCompile Condition="'$(TargetArch)' == 'arm'" Include="$(DaccessSrcDirectory)\arm\primitives.cpp" />
+ <CppCompile Condition="'$(TargetArch)' == 'arm64'" Include="$(DaccessSrcDirectory)\arm64\primitives.cpp" />
+ </ItemGroup>
+ <!--Import the targets-->
+ <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\clr.targets" />
+</Project>
diff --git a/src/debug/daccess/dacdbiimpl.cpp b/src/debug/daccess/dacdbiimpl.cpp
new file mode 100644
index 0000000000..9b17f4cd46
--- /dev/null
+++ b/src/debug/daccess/dacdbiimpl.cpp
@@ -0,0 +1,7639 @@
+// 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.
+//*****************************************************************************
+// File: DacDbiImpl.cpp
+//
+
+//
+// Implement DAC/DBI interface
+//
+//*****************************************************************************
+
+
+#include "stdafx.h"
+
+#include "dacdbiinterface.h"
+
+#include "typestring.h"
+#include "holder.h"
+#include "debuginfostore.h"
+#include "peimagelayout.inl"
+#include "encee.h"
+#include "switches.h"
+#include "generics.h"
+#include "stackwalk.h"
+
+#include "dacdbiimpl.h"
+#ifndef FEATURE_CORECLR
+#include "assemblyusagelogmanager.h"
+#endif
+
+#ifdef FEATURE_COMINTEROP
+#include "runtimecallablewrapper.h"
+#include "comcallablewrapper.h"
+#endif // FEATURE_COMINTEROP
+
+//-----------------------------------------------------------------------------
+// Have standard enter and leave macros at the DacDbi boundary to enforce
+// standard behavior.
+// 1. catch exceptions and convert them at the boundary.
+// 2. provide a space to hook logging and transitions.
+// 3. provide a hook to verify return values.
+//
+// Usage notes:
+// - use this at the DacDbi boundary; but not at internal functions
+// - it's ok to Return from the middle.
+//
+// Expected usage is:
+// Foo()
+// {
+// DD_ENTER_MAY_THROW
+// ...
+// if (...) { ThrowHr(E_SOME_FAILURE); }
+// ...
+// if (...) { return; } // early success case
+// ...
+// }
+//-----------------------------------------------------------------------------
+
+
+
+
+// Global allocator for DD. Access is protected under the g_dacCritSec lock.
+IDacDbiInterface::IAllocator * g_pAllocator = NULL;
+
+//---------------------------------------------------------------------------------------
+//
+// Extra sugar for wrapping IAllocator under friendly New/Delete operators.
+//
+// Sample usage:
+// void Foo(TestClass ** ppOut)
+// {
+// *ppOut = NULL;
+// TestClass * p = new (forDbi) TestClass();
+// ...
+// if (ok)
+// {
+// *ppOut = p;
+// return; // DBI will then free this memory.
+// }
+// ...
+// DeleteDbiMemory(p);
+// }
+//
+// Be very careful when using this on classes since Dbi and DAC may be in
+// separate dlls. This is best used when operating on blittable data-structures.
+// (no ctor/dtor, plain data fields) to guarantee the proper DLL isolation.
+// You don't want to call the ctor in DAC's context and the dtor in DBI's context
+// unless you really know what you're doing and that it's safe.
+//
+
+// Need a class to serve as a tag that we can use to overload New/Delete.
+#define forDbi (*(forDbiWorker *)NULL)
+
+void * operator new(size_t lenBytes, const forDbiWorker &)
+{
+ _ASSERTE(g_pAllocator != NULL);
+ void *result = g_pAllocator->Alloc(lenBytes);
+ if (result == NULL)
+ {
+ ThrowOutOfMemory();
+ }
+ return result;
+}
+
+void * operator new[](size_t lenBytes, const forDbiWorker &)
+{
+ _ASSERTE(g_pAllocator != NULL);
+ void *result = g_pAllocator->Alloc(lenBytes);
+ if (result == NULL)
+ {
+ ThrowOutOfMemory();
+ }
+ return result;
+}
+
+// Note: there is no C++ syntax for manually invoking this, but if a constructor throws an exception I understand that
+// this delete operator will be invoked automatically to destroy the object.
+void operator delete(void *p, const forDbiWorker &)
+{
+ if (p == NULL)
+ {
+ return;
+ }
+
+ _ASSERTE(g_pAllocator != NULL);
+ g_pAllocator->Free((BYTE*) p);
+
+}
+
+// Note: there is no C++ syntax for manually invoking this, but if a constructor throws an exception I understand that
+// this delete operator will be invoked automatically to destroy the object.
+void operator delete[](void *p, const forDbiWorker &)
+{
+ if (p == NULL)
+ {
+ return;
+ }
+
+ _ASSERTE(g_pAllocator != NULL);
+ g_pAllocator->Free((BYTE*) p);
+}
+
+// @dbgtodo dac support: determine how to handle an array of class instances to ensure the dtors get
+// called correctly or document that they won't
+// Delete memory and invoke dtor for memory allocated with 'operator (forDbi) new'
+template<class T> void DeleteDbiMemory(T *p)
+{
+ if (p == NULL)
+ {
+ return;
+ }
+ p->~T();
+
+ _ASSERTE(g_pAllocator != NULL);
+ g_pAllocator->Free((BYTE*) p);
+}
+
+
+//---------------------------------------------------------------------------------------
+// Creates the DacDbiInterface object, used by Dbi.
+//
+// Arguments:
+// pTarget - pointer to a Data-Target
+// baseAddress - non-zero base address of mscorwks in target to debug.
+// pAllocator - pointer to client allocator object. This lets DD allocate objects and
+// pass them out back to the client, which can then delete them.
+// DD takes a weak ref to this, so client must keep it alive until it
+// calls Destroy.
+// pMetadataLookup - callback interface to do internal metadata lookup. This is because
+// metadata is not dac-ized.
+// ppInterface - mandatory out-parameter
+//
+// Return Value:
+// S_OK on success.
+//
+//
+// Notes:
+// On Windows, this is public function that can be retrieved by GetProcAddress.
+
+// On Mac, this is used internally by DacDbiMarshalStubInstance below
+// This will yield an IDacDbiInterface to provide structured access to the
+// data-target.
+//
+// Must call Destroy to on interface to free its resources.
+//
+//---------------------------------------------------------------------------------------
+STDAPI
+DacDbiInterfaceInstance(
+ ICorDebugDataTarget * pTarget,
+ CORDB_ADDRESS baseAddress,
+ IDacDbiInterface::IAllocator * pAllocator,
+ IDacDbiInterface::IMetaDataLookup * pMetaDataLookup,
+ IDacDbiInterface ** ppInterface)
+{
+ // No marshalling is done by the instantiationf function - we just need to setup the infrastructure.
+ // We don't want to warn if this involves creating and accessing undacized data structures,
+ // because it's for the infrastructure, not DACized code itself.
+ SUPPORTS_DAC_HOST_ONLY;
+
+ // Since this is public, verify it.
+ if ((ppInterface == NULL) || (pTarget == NULL) || (baseAddress == 0))
+ {
+ return E_INVALIDARG;
+ }
+
+ *ppInterface = NULL;
+
+ //
+ // Actually allocate the real object and initialize it.
+ //
+ DacDbiInterfaceImpl * pDac = new (nothrow) DacDbiInterfaceImpl(pTarget, baseAddress, pAllocator, pMetaDataLookup);
+ if (!pDac)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ HRESULT hrStatus = pDac->Initialize();
+
+ if (SUCCEEDED(hrStatus))
+ {
+ *ppInterface = pDac;
+ }
+ else
+ {
+ delete pDac;
+ }
+ return hrStatus;
+}
+
+
+//---------------------------------------------------------------------------------------
+// Constructor. Instantiates a DAC/DBI interface around a DataTarget.
+//
+// Arguments:
+// pTarget - pointer to a Data-Target
+// baseAddress - non-zero base address of mscorwks in target to debug.
+// pAllocator - pointer to client allocator object. This lets DD allocate objects and
+// pass them out back to the client, which can then delete them.
+// DD takes a weak ref to this, so client must keep it alive until it
+// calls Destroy.
+// pMetadataLookup - callback interface to do internal metadata lookup. This is because
+// metadata is not dac-ized.
+//
+// Notes:
+// pAllocator is a weak reference.
+//---------------------------------------------------------------------------------------
+DacDbiInterfaceImpl::DacDbiInterfaceImpl(
+ ICorDebugDataTarget* pTarget,
+ CORDB_ADDRESS baseAddress,
+ IAllocator * pAllocator,
+ IMetaDataLookup * pMetaDataLookup
+) : ClrDataAccess(pTarget),
+ m_pAllocator(pAllocator),
+ m_pMetaDataLookup(pMetaDataLookup),
+ m_pCachedPEFile(VMPTR_PEFile::NullPtr()),
+ m_pCachedImporter(NULL),
+ m_isCachedHijackFunctionValid(FALSE)
+{
+ _ASSERTE(baseAddress != NULL);
+ m_globalBase = CORDB_ADDRESS_TO_TADDR(baseAddress);
+
+ _ASSERTE(pMetaDataLookup != NULL);
+ _ASSERTE(pAllocator != NULL);
+ _ASSERTE(pTarget != NULL);
+
+#ifdef _DEBUG
+ // Enable verification asserts in ICorDebug scenarios. ICorDebug never guesses at the DAC path, so any
+ // mismatch should be fatal, and so always of interest to the user.
+ // This overrides the assignment in the base class ctor (which runs first).
+ m_fEnableDllVerificationAsserts = true;
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Destructor.
+//
+// Notes:
+// This gets invoked after Destroy().
+//-----------------------------------------------------------------------------
+DacDbiInterfaceImpl::~DacDbiInterfaceImpl()
+{
+ SUPPORTS_DAC_HOST_ONLY;
+ // This will automatically chain to the base class dtor
+}
+
+//-----------------------------------------------------------------------------
+// Called from DAC-ized code to get a IMDInternalImport
+//
+// Arguments:
+// pPEFile - PE file for which to get importer for
+// fThrowEx - if true, throw instead of returning NULL.
+//
+// Returns:
+// an Internal importer object for this file.
+// May return NULL or throw (depending on fThrowEx).
+// May throw in exceptional circumstances (eg, corrupt debuggee).
+//
+// Assumptions:
+// This is called from DAC-ized code within the VM, which
+// was in turn called from some DD primitive. The returned importer will
+// be used by the DAC-ized code in the callstack, but it won't be cached.
+//
+// Notes:
+// This is an Internal importer, not a public Metadata importer.
+//
+interface IMDInternalImport* DacDbiInterfaceImpl::GetMDImport(
+ const PEFile* pPEFile,
+ const ReflectionModule * pReflectionModule,
+ bool fThrowEx)
+{
+ // Since this is called from an existing DAC-primitive, we already hold the g_dacCritSec lock.
+ // The lock conveniently protects our cache.
+ SUPPORTS_DAC;
+
+ IDacDbiInterface::IMetaDataLookup * pLookup = m_pMetaDataLookup;
+ _ASSERTE(pLookup != NULL);
+
+ VMPTR_PEFile vmPEFile = VMPTR_PEFile::NullPtr();
+
+ if (pPEFile != NULL)
+ {
+ vmPEFile.SetHostPtr(pPEFile);
+ }
+ else if (pReflectionModule != NULL)
+ {
+ // SOS and ClrDataAccess rely on special logic to find the metadata for methods in dynamic modules.
+ // We don't need to. The RS has already taken care of the special logic for us.
+ // So here we just grab the PEFile off of the ReflectionModule and continue down the normal
+ // code path. See code:ClrDataAccess::GetMDImport for comparison.
+ vmPEFile.SetHostPtr(pReflectionModule->GetFile());
+ }
+
+ // Optimize for the case where the VM queries the same Importer many times in a row.
+ if (m_pCachedPEFile == vmPEFile)
+ {
+ return m_pCachedImporter;
+ }
+
+ // Go to DBI to find the metadata.
+ IMDInternalImport * pInternal = NULL;
+ bool isILMetaDataForNI = false;
+ EX_TRY
+ {
+ // If test needs it in the future, prop isILMetaDataForNI back up to
+ // ClrDataAccess.m_mdImports.Add() call.
+ // example in code:ClrDataAccess::GetMDImport
+ // CordbModule::GetMetaDataInterface also looks up MetaData and would need attention.
+
+ // This is the new codepath that uses ICorDebugMetaDataLookup.
+ // To get the old codepath that uses the v2 metadata lookup methods,
+ // you'd have to load DAC only and then you'll get ClrDataAccess's implementation
+ // of this function.
+ pInternal = pLookup->LookupMetaData(vmPEFile, isILMetaDataForNI);
+ }
+ EX_CATCH
+ {
+ // Any expected error we should ignore.
+ if ((GET_EXCEPTION()->GetHR() != HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY)) &&
+ (GET_EXCEPTION()->GetHR() != CORDBG_E_READVIRTUAL_FAILURE) &&
+ (GET_EXCEPTION()->GetHR() != CORDBG_E_SYMBOLS_NOT_AVAILABLE) &&
+ (GET_EXCEPTION()->GetHR() != CORDBG_E_MODULE_LOADED_FROM_DISK))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ if (pInternal == NULL)
+ {
+ SIMPLIFYING_ASSUMPTION(!"MD lookup failed");
+ if (fThrowEx)
+ {
+ ThrowHR(E_FAIL);
+ }
+ return NULL;
+ }
+ else
+ {
+ // Cache it such that it we look for the exact same Importer again, we'll return it.
+ m_pCachedPEFile = vmPEFile;
+ m_pCachedImporter = pInternal;
+ }
+
+ return pInternal;
+}
+
+//-----------------------------------------------------------------------------
+// Implementation of IDacDbiInterface
+// See DacDbiInterface.h for full descriptions of all of these functions
+//-----------------------------------------------------------------------------
+
+// Destroy the connection, freeing up any resources.
+void DacDbiInterfaceImpl::Destroy()
+{
+ m_pAllocator = NULL;
+
+ this->Release();
+ // Memory is deleted, don't access this object any more
+}
+
+// Check whether the version of the DBI matches the version of the runtime.
+// See code:CordbProcess::CordbProcess#DBIVersionChecking for more information regarding version checking.
+HRESULT DacDbiInterfaceImpl::CheckDbiVersion(const DbiVersion * pVersion)
+{
+ DD_ENTER_MAY_THROW;
+
+ if (pVersion->m_dwFormat != kCurrentDbiVersionFormat)
+ {
+ return CORDBG_E_INCOMPATIBLE_PROTOCOL;
+ }
+
+ if ((pVersion->m_dwProtocolBreakingChangeCounter != kCurrentDacDbiProtocolBreakingChangeCounter) ||
+ (pVersion->m_dwReservedMustBeZero1 != 0))
+ {
+ return CORDBG_E_INCOMPATIBLE_PROTOCOL;
+ }
+
+ return S_OK;
+}
+
+// Flush the DAC cache. This should be called when target memory changes.
+HRESULT DacDbiInterfaceImpl::FlushCache()
+{
+ // Non-reentrant. We don't want to flush cached instances from a callback.
+ // That would remove host DAC instances while they're being used.
+ DD_NON_REENTRANT_MAY_THROW;
+
+ m_pCachedPEFile = VMPTR_PEFile::NullPtr();
+ m_pCachedImporter = NULL;
+ m_isCachedHijackFunctionValid = FALSE;
+
+ HRESULT hr = ClrDataAccess::Flush();
+
+ // Current impl of Flush() should always succeed. If it ever fails, we want to know.
+ _ASSERTE(SUCCEEDED(hr));
+ return hr;
+}
+
+// enable or disable DAC target consistency checks
+void DacDbiInterfaceImpl::DacSetTargetConsistencyChecks(bool fEnableAsserts)
+{
+ // forward on to our ClrDataAccess base class
+ ClrDataAccess::SetTargetConsistencyChecks(fEnableAsserts);
+}
+
+// Query if Left-side is started up?
+BOOL DacDbiInterfaceImpl::IsLeftSideInitialized()
+{
+ DD_ENTER_MAY_THROW;
+
+ if (g_pDebugger != NULL)
+ {
+ // This check is "safe".
+ // The initialize order in the left-side is:
+ // 1) g_pDebugger is an RVA based global initialized to NULL when the module is loaded.
+ // 2) Allocate a "Debugger" object.
+ // 3) run the ctor, which will set m_fLeftSideInitialized = FALSE.
+ // 4) assign the object to g_pDebugger.
+ // 5) later, LS initialization code will assign g_pDebugger->m_fLeftSideInitialized = TRUE.
+ //
+ // The memory write in #5 is atomic. There is no window where we're reading unitialized data.
+
+ return (g_pDebugger->m_fLeftSideInitialized != 0);
+ }
+
+ return FALSE;
+}
+
+
+// Determines if a given adddress is a CLR stub.
+BOOL DacDbiInterfaceImpl::IsTransitionStub(CORDB_ADDRESS address)
+{
+ DD_ENTER_MAY_THROW;
+
+ BOOL fIsStub = FALSE;
+
+#if defined(FEATURE_PAL)
+ // Currently IsIPInModule() is not implemented in the PAL. Rather than skipping the check, we should
+ // either E_NOTIMPL this API or implement IsIPInModule() in the PAL. Since ICDProcess::IsTransitionStub()
+ // is only called by VS in mixed-mode debugging scenarios, and mixed-mode debugging is not supported on
+ // POSIX systems, there is really no incentive to implement this API at this point.
+ ThrowHR(E_NOTIMPL);
+
+#else // !FEATURE_PAL
+
+ TADDR ip = (TADDR)address;
+
+ if (ip == NULL)
+ {
+ fIsStub = FALSE;
+ }
+ else
+ {
+ fIsStub = StubManager::IsStub(ip);
+ }
+
+ // If it's in Mscorwks, count that as a stub too.
+ if (fIsStub == FALSE)
+ {
+ fIsStub = IsIPInModule(m_globalBase, ip);
+ }
+
+#endif // FEATURE_PAL
+
+ return fIsStub;
+}
+
+// Gets the type of 'address'.
+IDacDbiInterface::AddressType DacDbiInterfaceImpl::GetAddressType(CORDB_ADDRESS address)
+{
+ DD_ENTER_MAY_THROW;
+ TADDR taAddr = CORDB_ADDRESS_TO_TADDR(address);
+
+ if (IsPossibleCodeAddress(taAddr) == S_OK)
+ {
+ if (ExecutionManager::IsManagedCode(taAddr))
+ {
+ return kAddressManagedMethod;
+ }
+
+ if (StubManager::IsStub(taAddr))
+ {
+ return kAddressRuntimeUnmanagedStub;
+ }
+ }
+
+ return kAddressUnrecognized;
+}
+
+
+// Get a VM appdomain pointer that matches the appdomain ID
+VMPTR_AppDomain DacDbiInterfaceImpl::GetAppDomainFromId(ULONG appdomainId)
+{
+ DD_ENTER_MAY_THROW;
+
+ VMPTR_AppDomain vmAppDomain;
+
+ // @dbgtodo dac support - We would like to wean ourselves off the IXClrData interfaces.
+ IXCLRDataProcess * pDAC = this;
+ ReleaseHolder<IXCLRDataAppDomain> pDacAppDomain;
+
+ HRESULT hrStatus = pDAC->GetAppDomainByUniqueID(appdomainId, &pDacAppDomain);
+ IfFailThrow(hrStatus);
+
+ IXCLRDataAppDomain * pIAppDomain = pDacAppDomain;
+ AppDomain * pAppDomain = (static_cast<ClrDataAppDomain *> (pIAppDomain))->GetAppDomain();
+ SIMPLIFYING_ASSUMPTION(pAppDomain != NULL);
+ if (pAppDomain == NULL)
+ {
+ ThrowHR(E_FAIL); // corrupted left-side?
+ }
+
+ TADDR addrAppDomain = PTR_HOST_TO_TADDR(pAppDomain);
+ vmAppDomain.SetDacTargetPtr(addrAppDomain);
+
+ return vmAppDomain;
+}
+
+
+// Get the AppDomain ID for an AppDomain.
+ULONG DacDbiInterfaceImpl::GetAppDomainId(VMPTR_AppDomain vmAppDomain)
+{
+ DD_ENTER_MAY_THROW;
+
+ if (vmAppDomain.IsNull())
+ {
+ return 0;
+ }
+ else
+ {
+ AppDomain * pAppDomain = vmAppDomain.GetDacPtr();
+ return pAppDomain->GetId().m_dwId;
+ }
+}
+
+// Get the managed AppDomain object for an AppDomain.
+VMPTR_OBJECTHANDLE DacDbiInterfaceImpl::GetAppDomainObject(VMPTR_AppDomain vmAppDomain)
+{
+ DD_ENTER_MAY_THROW;
+
+ AppDomain* pAppDomain = vmAppDomain.GetDacPtr();
+ OBJECTHANDLE hAppDomainManagedObject = pAppDomain->GetRawExposedObjectHandleForDebugger();
+ VMPTR_OBJECTHANDLE vmObj = VMPTR_OBJECTHANDLE::NullPtr();
+ vmObj.SetDacTargetPtr(hAppDomainManagedObject);
+ return vmObj;
+
+}
+
+// Determine if the specified AppDomain is the default domain
+BOOL DacDbiInterfaceImpl::IsDefaultDomain(VMPTR_AppDomain vmAppDomain)
+{
+ DD_ENTER_MAY_THROW;
+
+ AppDomain * pAppDomain = vmAppDomain.GetDacPtr();
+ BOOL fDefaultDomain = pAppDomain->IsDefaultDomain();
+
+ return fDefaultDomain;
+}
+
+
+// Get the full AD friendly name for the given EE AppDomain.
+void DacDbiInterfaceImpl::GetAppDomainFullName(
+ VMPTR_AppDomain vmAppDomain,
+ IStringHolder * pStrName )
+{
+ DD_ENTER_MAY_THROW;
+ AppDomain * pAppDomain = vmAppDomain.GetDacPtr();
+
+ // Get the AppDomain name from the VM without changing anything
+ // We might be able to simplify this, eg. by returning an SString.
+ bool fIsUtf8;
+ PVOID pRawName = pAppDomain->GetFriendlyNameNoSet(&fIsUtf8);
+
+ if (!pRawName)
+ {
+ ThrowHR(E_NOINTERFACE);
+ }
+
+ HRESULT hrStatus = S_OK;
+ if (fIsUtf8)
+ {
+ // we have to allocate a temporary string
+ // we could avoid this by adding a version of IStringHolder::AssignCopy that takes a UTF8 string
+ // We should also probably check to see when fIsUtf8 is ever true (it looks like it should normally be false).
+ ULONG32 dwNameLen = 0;
+ hrStatus = ConvertUtf8((LPCUTF8)pRawName, 0, &dwNameLen, NULL);
+ if (SUCCEEDED( hrStatus ))
+ {
+ NewArrayHolder<WCHAR> pwszName(new WCHAR[dwNameLen]);
+ hrStatus = ConvertUtf8((LPCUTF8)pRawName, dwNameLen, &dwNameLen, pwszName );
+ IfFailThrow(hrStatus);
+
+ hrStatus = pStrName->AssignCopy(pwszName);
+ }
+ }
+ else
+ {
+ hrStatus = pStrName->AssignCopy(static_cast<PCWSTR>(pRawName));
+ }
+
+ // Very important that this either sets pStrName or Throws.
+ // Don't set it and then then throw.
+ IfFailThrow(hrStatus);
+}
+
+//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// JIT Compiler Flags
+//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+// Get the values of the JIT Optimization and EnC flags.
+void DacDbiInterfaceImpl::GetCompilerFlags (
+ VMPTR_DomainFile vmDomainFile,
+ BOOL *pfAllowJITOpts,
+ BOOL *pfEnableEnC)
+{
+ DD_ENTER_MAY_THROW;
+
+ DomainFile * pDomainFile = vmDomainFile.GetDacPtr();
+
+ if (pDomainFile == NULL)
+ {
+ ThrowHR(E_FAIL);
+ }
+
+ // Get the underlying module - none of this is AppDomain specific
+ Module * pModule = pDomainFile->GetModule();
+ DWORD dwBits = pModule->GetDebuggerInfoBits();
+ *pfAllowJITOpts = !CORDisableJITOptimizations(dwBits);
+ *pfEnableEnC = pModule->IsEditAndContinueEnabled();
+
+
+} //GetCompilerFlags
+
+//-----------------------------------------------------------------------------
+// Helper function for SetCompilerFlags to set EnC status.
+// Arguments:
+// Input:
+// pModule - The runtime module for which flags are being set.
+//
+// Return value:
+// true if the Enc bits can be set on this module
+//-----------------------------------------------------------------------------
+
+bool DacDbiInterfaceImpl::CanSetEnCBits(Module * pModule)
+{
+ _ASSERTE(pModule != NULL);
+#ifdef EnC_SUPPORTED
+ // If we're using explicit sequence points (from the PDB), then we can't do EnC
+ // because EnC won't get updated pdbs and so the sequence points will be wrong.
+ bool fIgnorePdbs = ((pModule->GetDebuggerInfoBits() & DACF_IGNORE_PDBS) != 0);
+
+ bool fAllowEnc = pModule->IsEditAndContinueCapable() &&
+
+#ifdef PROFILING_SUPPORTED_DATA
+ !CORProfilerPresent() && // this queries target
+#endif
+ fIgnorePdbs;
+#else // ! EnC_SUPPORTED
+ // Enc not supported on any other platforms.
+ bool fAllowEnc = false;
+#endif
+
+ return fAllowEnc;
+} // DacDbiInterfaceImpl::SetEnCBits
+
+// Set the values of the JIT optimization and EnC flags.
+HRESULT DacDbiInterfaceImpl::SetCompilerFlags(VMPTR_DomainFile vmDomainFile,
+ BOOL fAllowJitOpts,
+ BOOL fEnableEnC)
+{
+ DD_ENTER_MAY_THROW;
+
+ DWORD dwBits = 0;
+ DomainFile * pDomainFile = vmDomainFile.GetDacPtr();
+ Module * pModule = pDomainFile->GetCurrentModule();
+ HRESULT hr = S_OK;
+
+
+#ifdef FEATURE_PREJIT
+ if (pModule->HasNativeImage())
+ {
+ ThrowHR(CORDBG_E_CANT_CHANGE_JIT_SETTING_FOR_ZAP_MODULE);
+ }
+#endif
+ _ASSERTE(pModule != NULL);
+
+ // Initialize dwBits.
+ dwBits = (pModule->GetDebuggerInfoBits() & ~(DACF_ALLOW_JIT_OPTS | DACF_ENC_ENABLED));
+ dwBits &= DACF_CONTROL_FLAGS_MASK;
+
+ if (fAllowJitOpts)
+ {
+ dwBits |= DACF_ALLOW_JIT_OPTS;
+ }
+ if (fEnableEnC)
+ {
+ if (CanSetEnCBits(pModule))
+ {
+ dwBits |= DACF_ENC_ENABLED;
+ }
+ else
+ {
+ hr = CORDBG_S_NOT_ALL_BITS_SET;
+ }
+ }
+ // Settings from the debugger take precedence over all other settings.
+ dwBits |= DACF_USER_OVERRIDE;
+
+ // set flags. This will write back to the target
+ pModule->SetDebuggerInfoBits((DebuggerAssemblyControlFlags)dwBits);
+
+
+ LOG((LF_CORDB, LL_INFO100, "D::HIPCE, Changed Jit-Debug-Info: fOpt=%d, fEnableEnC=%d, new bits=0x%08x\n",
+ (dwBits & DACF_ALLOW_JIT_OPTS) != 0,
+ (dwBits & DACF_ENC_ENABLED) != 0,
+ dwBits));
+
+ _ASSERTE(SUCCEEDED(hr));
+ return hr;
+
+} // DacDbiInterfaceImpl::SetCompilerFlags
+
+
+//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// sequence points and var info
+//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+// Initialize the native/IL sequence points and native var info for a function.
+void DacDbiInterfaceImpl::GetNativeCodeSequencePointsAndVarInfo(VMPTR_MethodDesc vmMethodDesc,
+ CORDB_ADDRESS startAddr,
+ BOOL fCodeAvailable,
+ NativeVarData * pNativeVarData,
+ SequencePoints * pSequencePoints)
+{
+ DD_ENTER_MAY_THROW;
+
+ _ASSERTE(!vmMethodDesc.IsNull());
+
+ MethodDesc * pMD = vmMethodDesc.GetDacPtr();
+
+ _ASSERTE(fCodeAvailable != 0);
+
+ // get information about the locations of arguments and local variables
+ GetNativeVarData(pMD, startAddr, GetArgCount(pMD), pNativeVarData);
+
+ // get the sequence points
+ GetSequencePoints(pMD, startAddr, pSequencePoints);
+
+} // GetNativeCodeSequencePointsAndVarInfo
+
+//-----------------------------------------------------------------------------
+// Get the number of fixed arguments to a function, i.e., the explicit args and the "this" pointer.
+// This does not include other implicit arguments or varargs. This is used to compute a variable ID
+// (see comment in CordbJITILFrame::ILVariableToNative for more detail)
+// Arguments:
+// input: pMD pointer to the method desc for the function
+// output: none
+// Return value:
+// the number of fixed arguments to the function
+//-----------------------------------------------------------------------------
+SIZE_T DacDbiInterfaceImpl::GetArgCount(MethodDesc * pMD)
+{
+
+ // Create a MetaSig for the given method's sig. (Easier than
+ // picking the sig apart ourselves.)
+ PCCOR_SIGNATURE pCallSig;
+ DWORD cbCallSigSize;
+
+ pMD->GetSig(&pCallSig, &cbCallSigSize);
+
+ if (pCallSig == NULL)
+ {
+ // Sig should only be null if the image is corrupted. (Even for lightweight-codegen)
+ // We expect the jit+verifier to catch this, so that we never land here.
+ // But just in case ...
+ CONSISTENCY_CHECK_MSGF(false, ("Corrupted image, null sig.(%s::%s)",
+ pMD->m_pszDebugClassName, pMD->m_pszDebugMethodName));
+ return 0;
+ }
+
+ MetaSig msig(pCallSig, cbCallSigSize, pMD->GetModule(), NULL, MetaSig::sigMember);
+
+ // Get the arg count.
+ UINT32 NumArguments = msig.NumFixedArgs();
+
+ // Account for the 'this' argument.
+ if (!pMD->IsStatic())
+ {
+ NumArguments++;
+ }
+/*
+ SigParser sigParser(pCallSig, cbCallSigSize);
+ sigParser.SkipMethodHeaderSignature(&m_allArgsCount);
+*/
+ return NumArguments;
+} //GetArgCount
+
+// Allocator to pass to DebugInfoStores, allocating forDBI
+BYTE* InfoStoreForDbiNew(void * pData, size_t cBytes)
+{
+ return new(forDbi) BYTE[cBytes];
+}
+
+// Allocator to pass to the debug-info-stores...
+BYTE* InfoStoreNew(void * pData, size_t cBytes)
+{
+ return new BYTE[cBytes];
+}
+
+//-----------------------------------------------------------------------------
+// Get locations and code offsets for local variables and arguments in a function
+// This information is used to find the location of a value at a given IP.
+// Arguments:
+// input:
+// pMethodDesc pointer to the method desc for the function
+// startAddr starting address of the function--used to differentiate
+// EnC versions
+// fixedArgCount number of fixed arguments to the function
+// output:
+// pVarInfo data structure containing a list of variable and
+// argument locations by range of IP offsets
+// Note: this function may throw
+//-----------------------------------------------------------------------------
+void DacDbiInterfaceImpl::GetNativeVarData(MethodDesc * pMethodDesc,
+ CORDB_ADDRESS startAddr,
+ SIZE_T fixedArgCount,
+ NativeVarData * pVarInfo)
+{
+ // make sure we haven't done this already
+ if (pVarInfo->IsInitialized())
+ {
+ return;
+ }
+
+ NewHolder<ICorDebugInfo::NativeVarInfo> nativeVars(NULL);
+
+ DebugInfoRequest request;
+ request.InitFromStartingAddr(pMethodDesc, CORDB_ADDRESS_TO_TADDR(startAddr));
+
+ ULONG32 entryCount;
+
+ BOOL success = DebugInfoManager::GetBoundariesAndVars(request,
+ InfoStoreNew, NULL, // allocator
+ NULL, NULL,
+ &entryCount, &nativeVars);
+
+ if (!success)
+ ThrowHR(E_FAIL);
+
+ // set key fields of pVarInfo
+ pVarInfo->InitVarDataList(nativeVars, (int)fixedArgCount, (int)entryCount);
+} // GetNativeVarData
+
+
+//-----------------------------------------------------------------------------
+// Given a instrumented IL map from the profiler that maps:
+// Original offset IL_A -> Instrumentend offset IL_B
+// And a native mapping from the JIT that maps:
+// Instrumented offset IL_B -> native offset Native_C
+// This function merges the two maps and stores the result back into the nativeMap.
+// The nativeMap now maps:
+// Original offset IL_A -> native offset Native_C
+// pEntryCount is the number of valid entries in nativeMap, and it may be adjusted downwards
+// as part of the composition.
+//-----------------------------------------------------------------------------
+void DacDbiInterfaceImpl::ComposeMapping(InstrumentedILOffsetMapping profilerILMap, ICorDebugInfo::OffsetMapping nativeMap[], ULONG32* pEntryCount)
+{
+ // Translate the IL offset if the profiler has provided us with a mapping.
+ // The ICD public API should always expose the original IL offsets, but GetBoundaries()
+ // directly accesses the debug info, which stores the instrumented IL offsets.
+
+ ULONG32 entryCount = *pEntryCount;
+ if (!profilerILMap.IsNull())
+ {
+ // If we did instrument, then we can't have any sequence points that
+ // are "in-between" the old-->new map that the profiler gave us.
+ // Ex, if map is:
+ // (6 old -> 36 new)
+ // (8 old -> 50 new)
+ // And the jit gives us an entry for 44 new, that will map back to 6 old.
+ // Since the map can only have one entry for 6 old, we remove 44 new.
+
+ // First Pass: invalidate all the duplicate entries by setting their IL offset to MAX_ILNUM
+ ULONG32 cDuplicate = 0;
+ ULONG32 prevILOffset = (ULONG32)(ICorDebugInfo::MAX_ILNUM);
+ for (ULONG32 i = 0; i < entryCount; i++)
+ {
+ ULONG32 origILOffset = TranslateInstrumentedILOffsetToOriginal(nativeMap[i].ilOffset, &profilerILMap);
+
+ if (origILOffset == prevILOffset)
+ {
+ // mark this sequence point as invalid; refer to the comment above
+ nativeMap[i].ilOffset = (ULONG32)(ICorDebugInfo::MAX_ILNUM);
+ cDuplicate += 1;
+ }
+ else
+ {
+ // overwrite the instrumented IL offset with the original IL offset
+ nativeMap[i].ilOffset = origILOffset;
+ prevILOffset = origILOffset;
+ }
+ }
+
+ // Second Pass: move all the valid entries up front
+ ULONG32 realIndex = 0;
+ for (ULONG32 curIndex = 0; curIndex < entryCount; curIndex++)
+ {
+ if (nativeMap[curIndex].ilOffset != (ULONG32)(ICorDebugInfo::MAX_ILNUM))
+ {
+ // This is a valid entry. Move it up front.
+ nativeMap[realIndex] = nativeMap[curIndex];
+ realIndex += 1;
+ }
+ }
+
+ // make sure we have done the bookkeeping correctly
+ _ASSERTE((realIndex + cDuplicate) == entryCount);
+
+ // Final Pass: derecement entryCount
+ entryCount -= cDuplicate;
+ *pEntryCount = entryCount;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Get the native/IL sequence points for a function
+// Arguments:
+// input:
+// pMethodDesc pointer to the method desc for the function
+// startAddr starting address of the function--used to differentiate
+// output:
+// pNativeMap data structure containing a list of sequence points
+// Note: this function may throw
+//-----------------------------------------------------------------------------
+void DacDbiInterfaceImpl::GetSequencePoints(MethodDesc * pMethodDesc,
+ CORDB_ADDRESS startAddr,
+ SequencePoints * pSeqPoints)
+{
+
+ // make sure we haven't done this already
+ if (pSeqPoints->IsInitialized())
+ {
+ return;
+ }
+
+ // Use the DebugInfoStore to get IL->Native maps.
+ // It doesn't matter whether we're jitted, ngenned etc.
+ DebugInfoRequest request;
+ request.InitFromStartingAddr(pMethodDesc, CORDB_ADDRESS_TO_TADDR(startAddr));
+
+
+ // Bounds info.
+ NewArrayHolder<ICorDebugInfo::OffsetMapping> mapCopy(NULL);
+
+ ULONG32 entryCount;
+ BOOL success = DebugInfoManager::GetBoundariesAndVars(request,
+ InfoStoreNew, NULL, // allocator
+ &entryCount, &mapCopy,
+ NULL, NULL);
+ if (!success)
+ ThrowHR(E_FAIL);
+
+ // if there is a rejit IL map for this function, apply that in preference to load-time mapping
+#ifdef FEATURE_REJIT
+ ReJitManager * pReJitMgr = pMethodDesc->GetReJitManager();
+ ReJitInfo* pReJitInfo = pReJitMgr->FindReJitInfo(dac_cast<PTR_MethodDesc>(pMethodDesc), (PCODE)startAddr, 0);
+ if (pReJitInfo != NULL)
+ {
+ InstrumentedILOffsetMapping rejitMapping = pReJitInfo->m_pShared->m_instrumentedILMap;
+ ComposeMapping(rejitMapping, mapCopy, &entryCount);
+ }
+ else
+ {
+#endif
+ // if there is a profiler load-time mapping and not a rejit mapping, apply that instead
+ InstrumentedILOffsetMapping loadTimeMapping =
+ pMethodDesc->GetModule()->GetInstrumentedILOffsetMapping(pMethodDesc->GetMemberDef());
+ ComposeMapping(loadTimeMapping, mapCopy, &entryCount);
+#ifdef FEATURE_REJIT
+ }
+#endif
+
+ pSeqPoints->InitSequencePoints(entryCount);
+
+ // mapCopy and pSeqPoints have elements of different types. Thus, we
+ // need to copy the individual members from the elements of mapCopy to the
+ // elements of pSeqPoints. Once we're done, we can release mapCopy
+ pSeqPoints->CopyAndSortSequencePoints(mapCopy);
+
+} // GetSequencePoints
+
+// ----------------------------------------------------------------------------
+// DacDbiInterfaceImpl::TranslateInstrumentedILOffsetToOriginal
+//
+// Description:
+// Helper function to convert an instrumented IL offset to the corresponding original IL offset.
+//
+// Arguments:
+// * ilOffset - offset to be translated
+// * pMapping - the profiler-provided mapping between original IL offsets and instrumented IL offsets
+//
+// Return Value:
+// Return the translated offset.
+//
+
+ULONG DacDbiInterfaceImpl::TranslateInstrumentedILOffsetToOriginal(ULONG ilOffset,
+ const InstrumentedILOffsetMapping * pMapping)
+{
+ SIZE_T cMap = pMapping->GetCount();
+ ARRAY_PTR_COR_IL_MAP rgMap = pMapping->GetOffsets();
+
+ _ASSERTE((cMap == 0) == (rgMap == NULL));
+
+ // Early out if there is no mapping, or if we are dealing with a special IL offset such as
+ // prolog, epilog, etc.
+ if ((cMap == 0) || ((int)ilOffset < 0))
+ {
+ return ilOffset;
+ }
+
+ SIZE_T i = 0;
+ for (i = 1; i < cMap; i++)
+ {
+ if (ilOffset < rgMap[i].newOffset)
+ {
+ return rgMap[i - 1].oldOffset;
+ }
+ }
+ return rgMap[i - 1].oldOffset;
+}
+
+//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+// Function Data
+//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+
+// GetILCodeAndSig returns the function's ILCode and SigToken given
+// a module and a token. The info will come from a MethodDesc, if
+// one exists or from metadata.
+//
+void DacDbiInterfaceImpl::GetILCodeAndSig(VMPTR_DomainFile vmDomainFile,
+ mdToken functionToken,
+ TargetBuffer * pCodeInfo,
+ mdToken * pLocalSigToken)
+{
+ DD_ENTER_MAY_THROW;
+
+ DomainFile * pDomainFile = vmDomainFile.GetDacPtr();
+ Module * pModule = pDomainFile->GetCurrentModule();
+ RVA methodRVA = 0;
+ DWORD implFlags;
+
+ // preinitialize out params
+ pCodeInfo->Clear();
+ *pLocalSigToken = mdSignatureNil;
+
+ // Get the RVA and impl flags for this method.
+ IfFailThrow(pModule->GetMDImport()->GetMethodImplProps(functionToken,
+ &methodRVA,
+ &implFlags));
+
+ MethodDesc* pMethodDesc =
+ FindLoadedMethodRefOrDef(pModule, functionToken);
+
+ // If the RVA is 0 or it's native, then the method is not IL
+ if (methodRVA == 0)
+ {
+ LOG((LF_CORDB,LL_INFO100000, "DDI::GICAS: Function is not IL - methodRVA == NULL!\n"));
+ // return (CORDBG_E_FUNCTION_NOT_IL);
+ // Sanity check this....
+
+ if(!pMethodDesc || !pMethodDesc->IsIL())
+ {
+ LOG((LF_CORDB,LL_INFO100000, "DDI::GICAS: And the MD agrees..\n"));
+ ThrowHR(CORDBG_E_FUNCTION_NOT_IL);
+ }
+ else
+ {
+ LOG((LF_CORDB,LL_INFO100000, "DDI::GICAS: But the MD says it's IL..\n"));
+ }
+
+ if (pMethodDesc != NULL && pMethodDesc->GetRVA() == 0)
+ {
+ LOG((LF_CORDB,LL_INFO100000, "DDI::GICAS: Actually, MD says RVA is 0 too - keep going...!\n"));
+ }
+ }
+ if (IsMiNative(implFlags))
+ {
+ LOG((LF_CORDB,LL_INFO100000, "DDI::GICAS: Function is not IL - IsMiNative!\n"));
+ ThrowHR(CORDBG_E_FUNCTION_NOT_IL);
+ }
+
+ *pLocalSigToken = GetILCodeAndSigHelper(pModule, pMethodDesc, functionToken, methodRVA, pCodeInfo);
+
+#ifdef LOGGING
+ else
+ {
+ LOG((LF_CORDB,LL_INFO100000, "DDI::GICAS: GetMethodImplProps failed!\n"));
+ }
+#endif
+} // GetILCodeAndSig
+
+//---------------------------------------------------------------------------------------
+//
+// This is just a worker function for GetILCodeAndSig. It returns the function's ILCode and SigToken
+// given a module, a token, and the RVA. If a MethodDesc is provided, it has to be consistent with
+// the token and the RVA.
+//
+// Arguments:
+// pModule - the Module containing the specified method
+// pMD - the specified method; can be NULL
+// mdMethodToken - the MethodDef token of the specified method
+// methodRVA - the RVA of the IL for the specified method
+// pIL - out parameter; return the target address and size of the IL of the specified method
+//
+// Return Value:
+// Return the local variable signature token of the specified method. Can be mdSignatureNil.
+//
+
+mdSignature DacDbiInterfaceImpl::GetILCodeAndSigHelper(Module * pModule,
+ MethodDesc * pMD,
+ mdMethodDef mdMethodToken,
+ RVA methodRVA,
+ TargetBuffer * pIL)
+{
+ _ASSERTE(pModule != NULL);
+
+ // If a MethodDesc is provided, it has to be consistent with the MethodDef token and the RVA.
+ _ASSERTE((pMD == NULL) || ((pMD->GetMemberDef() == mdMethodToken) && (pMD->GetRVA() == methodRVA)));
+
+ TADDR pTargetIL; // target address of start of IL blob
+
+ // This works for methods in dynamic modules, and methods overriden by a profiler.
+ pTargetIL = pModule->GetDynamicIL(mdMethodToken, TRUE);
+
+ // Method not overriden - get the original copy of the IL by going to the PE file/RVA
+ // If this is in a dynamic module then don't even attempt this since ReflectionModule::GetIL isn't
+ // implemend for DAC.
+ if (pTargetIL == 0 && !pModule->IsReflection())
+ {
+ pTargetIL = (TADDR)pModule->GetIL(methodRVA);
+ }
+
+ mdSignature mdSig = mdSignatureNil;
+ if (pTargetIL == 0)
+ {
+ // Currently this should only happen for LCG methods (including IL stubs).
+ // LCG methods have a 0 RVA, and so we don't currently have any way to get the IL here.
+ _ASSERTE(pMD->IsDynamicMethod());
+ _ASSERTE(pMD->AsDynamicMethodDesc()->IsLCGMethod()||
+ pMD->AsDynamicMethodDesc()->IsILStub());
+
+ // Clear the buffer.
+ pIL->Clear();
+ }
+ else
+ {
+ // Now we have the target address of the IL blob, we need to bring it over to the host.
+ // DacGetILMethod will copy the COR_ILMETHOD information that we need
+ COR_ILMETHOD * pHostIL = DacGetIlMethod(pTargetIL); // host address of start of IL blob
+ COR_ILMETHOD_DECODER header(pHostIL); // host address of header
+
+
+ // Get the IL code info. We need the address of the IL itself, which will be beyond the header
+ // at the beginning of the blob. We ultimately need the target address. To get this, we take
+ // target address of the target IL blob and add the offset from the beginning of the host IL blob
+ // (the header) to the beginning of the IL itself (we get this information from the header).
+ pIL->pAddress = pTargetIL + ((SIZE_T)(header.Code) - (SIZE_T)pHostIL);
+ pIL->cbSize = header.GetCodeSize();
+
+ // Now we get the signature token
+ if (header.LocalVarSigTok != NULL)
+ {
+ mdSig = header.GetLocalVarSigTok();
+ }
+ else
+ {
+ mdSig = mdSignatureNil;
+ }
+ }
+
+ return mdSig;
+}
+
+
+bool DacDbiInterfaceImpl::GetMetaDataFileInfoFromPEFile(VMPTR_PEFile vmPEFile,
+ DWORD &dwTimeStamp,
+ DWORD &dwSize,
+ bool &isNGEN,
+ IStringHolder* pStrFilename)
+{
+#if !defined(FEATURE_PREJIT)
+
+ return false;
+
+#else // defined(FEATURE_PREJIT)
+
+ DD_ENTER_MAY_THROW;
+
+ DWORD dwDataSize;
+ DWORD dwRvaHint;
+ PEFile * pPEFile = vmPEFile.GetDacPtr();
+ _ASSERTE(pPEFile != NULL);
+ if (pPEFile == NULL)
+ return false;
+
+ WCHAR wszFilePath[MAX_LONGPATH] = {0};
+ DWORD cchFilePath = MAX_LONGPATH;
+ bool ret = ClrDataAccess::GetMetaDataFileInfoFromPEFile(pPEFile,
+ dwTimeStamp,
+ dwSize,
+ dwDataSize,
+ dwRvaHint,
+ isNGEN,
+ wszFilePath,
+ cchFilePath);
+
+ pStrFilename->AssignCopy(wszFilePath);
+ return ret;
+#endif // !defined(FEATURE_PREJIT)
+}
+
+
+bool DacDbiInterfaceImpl::GetILImageInfoFromNgenPEFile(VMPTR_PEFile vmPEFile,
+ DWORD &dwTimeStamp,
+ DWORD &dwSize,
+ IStringHolder* pStrFilename)
+{
+#if !defined(FEATURE_PREJIT)
+
+ return false;
+
+#else // defined(FEATURE_PREJIT)
+
+ DD_ENTER_MAY_THROW;
+
+ PEFile * pPEFile = vmPEFile.GetDacPtr();
+ _ASSERTE(pPEFile != NULL);
+ if (pPEFile == NULL)
+ {
+ return false;
+ }
+
+ WCHAR wszFilePath[MAX_LONGPATH] = {0};
+ DWORD cchFilePath = MAX_LONGPATH;
+ bool ret = ClrDataAccess::GetILImageInfoFromNgenPEFile(pPEFile,
+ dwTimeStamp,
+ dwSize,
+ wszFilePath,
+ cchFilePath);
+
+ pStrFilename->AssignCopy(wszFilePath);
+ return ret;
+#endif // !defined(FEATURE_PREJIT)
+}
+
+// Get start addresses and sizes for hot and cold regions for a native code blob.
+// Arguments:
+// Input:
+// pMethodDesc - method desc for the function we are inspecting
+// Output (required):
+// pCodeInfo - initializes the m_rgCodeRegions field of this structure
+// if the native code is available. Otherwise,
+// pCodeInfo->IsValid() is false.
+
+void DacDbiInterfaceImpl::GetMethodRegionInfo(MethodDesc * pMethodDesc,
+ NativeCodeFunctionData * pCodeInfo)
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ GC_NOTRIGGER;
+ PRECONDITION(CheckPointer(pCodeInfo));
+ }
+ CONTRACTL_END;
+
+ IJitManager::MethodRegionInfo methodRegionInfo = {NULL, 0, NULL, 0};
+ PCODE functionAddress = pMethodDesc->GetNativeCode();
+
+ // get the start address of the hot region and initialize the jit manager
+ pCodeInfo->m_rgCodeRegions[kHot].pAddress = CORDB_ADDRESS(PCODEToPINSTR(functionAddress));
+
+ // if the start address is NULL, the code isn't available yet, so just return
+ if (functionAddress != NULL)
+ {
+ EECodeInfo codeInfo(functionAddress);
+ _ASSERTE(codeInfo.IsValid());
+
+ codeInfo.GetMethodRegionInfo(&methodRegionInfo);
+
+ // now get the rest of the region information
+ pCodeInfo->m_rgCodeRegions[kHot].cbSize = (ULONG)methodRegionInfo.hotSize;
+ pCodeInfo->m_rgCodeRegions[kCold].Init(PCODEToPINSTR(methodRegionInfo.coldStartAddress),
+ (ULONG)methodRegionInfo.coldSize);
+ _ASSERTE(pCodeInfo->IsValid());
+ }
+ else
+ {
+ _ASSERTE(!pCodeInfo->IsValid());
+ }
+} // GetMethodRegionInfo
+
+
+// Gets the following information about a native code blob:
+// - its method desc
+// - whether it's an instantiated generic
+// - its EnC version number
+// - hot and cold region information.
+// If the hot region start address is NULL at the end, it means the native code
+// isn't currently available. In this case, all values in pCodeInfo will be
+// cleared.
+
+void DacDbiInterfaceImpl::GetNativeCodeInfo(VMPTR_DomainFile vmDomainFile,
+ mdToken functionToken,
+ NativeCodeFunctionData * pCodeInfo)
+{
+ DD_ENTER_MAY_THROW;
+
+ _ASSERTE(pCodeInfo != NULL);
+
+ // pre-initialize:
+ pCodeInfo->Clear();
+
+ DomainFile * pDomainFile = vmDomainFile.GetDacPtr();
+ Module * pModule = pDomainFile->GetCurrentModule();
+
+ MethodDesc* pMethodDesc = FindLoadedMethodRefOrDef(pModule, functionToken);
+ pCodeInfo->vmNativeCodeMethodDescToken.SetHostPtr(pMethodDesc);
+
+ // if we are loading a module and trying to bind a previously set breakpoint, we may not have
+ // a method desc yet, so check for that situation
+ if(pMethodDesc != NULL)
+ {
+ GetMethodRegionInfo(pMethodDesc, pCodeInfo);
+ if (pCodeInfo->m_rgCodeRegions[kHot].pAddress != NULL)
+ {
+ pCodeInfo->isInstantiatedGeneric = pMethodDesc->HasClassOrMethodInstantiation();
+ LookupEnCVersions(pModule,
+ pCodeInfo->vmNativeCodeMethodDescToken,
+ functionToken,
+ pCodeInfo->m_rgCodeRegions[kHot].pAddress,
+ &(pCodeInfo->encVersion));
+ }
+ }
+} // GetNativeCodeInfo
+
+// Gets the following information about a native code blob:
+// - its method desc
+// - whether it's an instantiated generic
+// - its EnC version number
+// - hot and cold region information.
+void DacDbiInterfaceImpl::GetNativeCodeInfoForAddr(VMPTR_MethodDesc vmMethodDesc,
+ CORDB_ADDRESS hotCodeStartAddr,
+ NativeCodeFunctionData * pCodeInfo)
+{
+ DD_ENTER_MAY_THROW;
+
+ _ASSERTE(pCodeInfo != NULL);
+
+ if (hotCodeStartAddr == NULL)
+ {
+ // if the start address is NULL, the code isn't available yet, so just return
+ _ASSERTE(!pCodeInfo->IsValid());
+ return;
+ }
+
+ IJitManager::MethodRegionInfo methodRegionInfo = {NULL, 0, NULL, 0};
+ TADDR codeAddr = CORDB_ADDRESS_TO_TADDR(hotCodeStartAddr);
+
+#ifdef _TARGET_ARM_
+ // TADDR should not have the thumb code bit set.
+ _ASSERTE((codeAddr & THUMB_CODE) == 0);
+ codeAddr &= ~THUMB_CODE;
+#endif
+
+ EECodeInfo codeInfo(codeAddr);
+ _ASSERTE(codeInfo.IsValid());
+
+ // We may not have the memory for the cold code region in a minidump.
+ // Do not fail stackwalking because of this.
+ EX_TRY_ALLOW_DATATARGET_MISSING_MEMORY
+ {
+ codeInfo.GetMethodRegionInfo(&methodRegionInfo);
+ }
+ EX_END_CATCH_ALLOW_DATATARGET_MISSING_MEMORY;
+
+ // Even if GetMethodRegionInfo() fails to retrieve the cold code region info,
+ // we should still be able to get the hot code region info. We are counting on this for
+ // stackwalking to work in dump debugging scenarios.
+ _ASSERTE(methodRegionInfo.hotStartAddress == codeAddr);
+
+ // now get the rest of the region information
+ pCodeInfo->m_rgCodeRegions[kHot].Init(PCODEToPINSTR(methodRegionInfo.hotStartAddress),
+ (ULONG)methodRegionInfo.hotSize);
+ pCodeInfo->m_rgCodeRegions[kCold].Init(PCODEToPINSTR(methodRegionInfo.coldStartAddress),
+ (ULONG)methodRegionInfo.coldSize);
+ _ASSERTE(pCodeInfo->IsValid());
+
+ MethodDesc* pMethodDesc = vmMethodDesc.GetDacPtr();
+ pCodeInfo->isInstantiatedGeneric = pMethodDesc->HasClassOrMethodInstantiation();
+ pCodeInfo->vmNativeCodeMethodDescToken = vmMethodDesc;
+
+ SIZE_T unusedLatestEncVersion;
+ Module * pModule = pMethodDesc->GetModule();
+ _ASSERTE(pModule != NULL);
+ LookupEnCVersions(pModule,
+ vmMethodDesc,
+ pMethodDesc->GetMemberDef(),
+ codeAddr,
+ &unusedLatestEncVersion, //unused by caller
+ &(pCodeInfo->encVersion));
+
+} // GetNativeCodeInfo
+
+
+//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+//
+// Functions to get Type and Class information
+//
+//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+//-----------------------------------------------------------------------------
+//DacDbiInterfaceImpl::GetTypeHandles
+// Get the approximate and exact type handles for a type
+// Arguments:
+// input:
+// vmThExact - VMPTR of the exact type handle. If this method is called
+// to get information for a new generic instantiation, this will already
+// be initialized. If it's called to get type information for an arbitrary
+// type (i.e., called to initialize an instance of CordbClass), it will be NULL
+// vmThApprox - VMPTR of the approximate type handle. If this method is called
+// to get information for a new generic instantiation, this will already
+// be initialized. If it's called to get type information for an arbitrary
+// type (i.e., called to initialize an instance of CordbClass), it will be NULL
+// output:
+// pThExact - handle for exact type information for a generic instantiation
+// pThApprox - handle for type information
+// Notes:
+// pThExact and pTHApprox must be pointers to existing memory.
+//-----------------------------------------------------------------------------
+void DacDbiInterfaceImpl::GetTypeHandles(VMPTR_TypeHandle vmThExact,
+ VMPTR_TypeHandle vmThApprox,
+ TypeHandle * pThExact,
+ TypeHandle * pThApprox)
+ {
+ _ASSERTE((pThExact != NULL) && (pThApprox != NULL));
+
+ *pThExact = TypeHandle::FromPtr(vmThExact.GetDacPtr());
+ *pThApprox = TypeHandle::FromPtr(vmThApprox.GetDacPtr());
+
+ // If we can't find the class, return the proper HR to the right side. Note: if the class is not a value class and
+ // the class is also not restored, then we must pretend that the class is still not loaded. We are gonna let
+ // unrestored value classes slide, though, and special case access to the class's parent below.
+ if ((pThApprox->IsNull()) || ((!pThApprox->IsValueType()) && (!pThApprox->IsRestored())))
+ {
+ LOG((LF_CORDB, LL_INFO10000, "D::GASCI: class isn't loaded.\n"));
+ ThrowHR(CORDBG_E_CLASS_NOT_LOADED);
+ }
+ // If the exact type handle is not restored ignore it.
+ if (!pThExact->IsNull() && !pThExact->IsRestored())
+ {
+ *pThExact = TypeHandle();
+ }
+ } // DacDbiInterfaceImpl::GetTypeHandles
+
+//-----------------------------------------------------------------------------
+// DacDbiInterfaceImpl::GetTotalFieldCount
+// Gets the total number of fields for a type.
+// Input Argument: thApprox - type handle used to determine the number of fields
+// Return Value: count of the total fields of the type.
+//-----------------------------------------------------------------------------
+unsigned int DacDbiInterfaceImpl::GetTotalFieldCount(TypeHandle thApprox)
+{
+ MethodTable *pMT = thApprox.GetMethodTable();
+
+ // Count the instance and static fields for this class (not including parent).
+ // This will not include any newly added EnC fields.
+ unsigned int IFCount = pMT->GetNumIntroducedInstanceFields();
+ unsigned int SFCount = pMT->GetNumStaticFields();
+
+#ifdef EnC_SUPPORTED
+ PTR_Module pModule = pMT->GetModule();
+
+ // Stats above don't include EnC fields. So add them now.
+ if (pModule->IsEditAndContinueEnabled())
+ {
+ PTR_EnCEEClassData pEncData =
+ (dac_cast<PTR_EditAndContinueModule>(pModule))->GetEnCEEClassData(pMT, TRUE);
+
+ if (pEncData != NULL)
+ {
+ _ASSERTE(pEncData->GetMethodTable() == pMT);
+
+ // EnC only adds fields, never removes them.
+ IFCount += pEncData->GetAddedInstanceFields();
+ SFCount += pEncData->GetAddedStaticFields();
+ }
+ }
+#endif
+ return IFCount + SFCount;
+} // DacDbiInterfaceImpl::GetTotalFieldCount
+
+//-----------------------------------------------------------------------------
+// DacDbiInterfaceImpl::InitClassData
+// initializes various values of the ClassInfo data structure, including the
+// field count, generic args count, size and value class flag
+// Arguments:
+// input: thApprox - used to get access to all the necessary values
+// fIsInstantiatedType - used to determine how to compute the size
+// output: pData - contains fields to be initialized
+//-----------------------------------------------------------------------------
+void DacDbiInterfaceImpl::InitClassData(TypeHandle thApprox,
+ BOOL fIsInstantiatedType,
+ ClassInfo * pData)
+{
+ pData->m_fieldList.Alloc(GetTotalFieldCount(thApprox));
+
+ // For Generic classes you must get the object size via the type handle, which
+ // will get you to the right information for the particular instantiation
+ // you're working with...
+ pData->m_objectSize = 0;
+ if ((!thApprox.GetNumGenericArgs()) || fIsInstantiatedType)
+ {
+ pData->m_objectSize = thApprox.GetMethodTable()->GetNumInstanceFieldBytes();
+ }
+
+} // DacDbiInterfaceImpl::InitClassData
+
+//-----------------------------------------------------------------------------
+// DacDbiInterfaceImpl::GetStaticsBases
+// Gets the base table addresses for both GC and non-GC statics
+// Arguments:
+// input: thExact - exact type handle for the class
+// pAppDomain - AppDomain in which the class is loaded
+// output: ppGCStaticsBase - base pointer for GC statics
+// ppNonGCStaticsBase - base pointer for non GC statics
+// Notes:
+// If this is a non-generic type, or an instantiated type, then we'll be able to get the static var bases
+// If the typeHandle represents a generic type constructor (i.e. an uninstantiated generic class), then
+// the static bases will be null (since statics are per-instantiation).
+//-----------------------------------------------------------------------------
+void DacDbiInterfaceImpl::GetStaticsBases(TypeHandle thExact,
+ AppDomain * pAppDomain,
+ PTR_BYTE * ppGCStaticsBase,
+ PTR_BYTE * ppNonGCStaticsBase)
+ {
+ MethodTable * pMT = thExact.GetMethodTable();
+ Module * pModuleForStatics = pMT->GetModuleForStatics();
+ if (pModuleForStatics != NULL)
+ {
+ PTR_DomainLocalModule pLocalModule = pModuleForStatics->GetDomainLocalModule(pAppDomain);
+ if (pLocalModule != NULL)
+ {
+ *ppGCStaticsBase = pLocalModule->GetGCStaticsBasePointer(pMT);
+ *ppNonGCStaticsBase = pLocalModule->GetNonGCStaticsBasePointer(pMT);
+ }
+ }
+} // DacDbiInterfaceImpl::GetStaticsBases
+
+//-----------------------------------------------------------------------------
+// DacDbiInterfaceImpl::ComputeFieldData
+// Computes the field info for pFD and stores it in pcurrentFieldData
+// Arguments:
+// input: pFD - FieldDesc used to get necessary information
+// pGCStaticsBase - base table address for GC statics
+// pNonGCStaticsBase - base table address for non-GC statics
+// output: pCurrentFieldData - contains fields to be initialized
+//-----------------------------------------------------------------------------
+void DacDbiInterfaceImpl::ComputeFieldData(PTR_FieldDesc pFD,
+ PTR_BYTE pGCStaticsBase,
+ PTR_BYTE pNonGCStaticsBase,
+ FieldData * pCurrentFieldData)
+{
+ pCurrentFieldData->Initialize(pFD->IsStatic(), pFD->IsPrimitive(), pFD->GetMemberDef());
+
+#ifdef EnC_SUPPORTED
+ // If the field was newly introduced via EnC, and hasn't yet
+ // been fixed up, then we'll send back a marker indicating
+ // that it isn't yet available.
+ if (pFD->IsEnCNew())
+ {
+ // @dbgtodo Microsoft inspection: eliminate the debugger token when ICDClass and ICDType are
+ // completely DACized
+ pCurrentFieldData->m_vmFieldDesc.SetHostPtr(pFD);
+ pCurrentFieldData->m_fFldStorageAvailable = FALSE;
+ pCurrentFieldData->m_fFldIsTLS = FALSE;
+ pCurrentFieldData->m_fFldIsContextStatic = FALSE;
+ pCurrentFieldData->m_fFldIsRVA = FALSE;
+ pCurrentFieldData->m_fFldIsCollectibleStatic = FALSE;
+ }
+ else
+#endif // EnC_SUPPORTED
+ {
+ // Otherwise, we'll compute the info & send it back.
+ pCurrentFieldData->m_fFldStorageAvailable = TRUE;
+ // @dbgtodo Microsoft inspection: eliminate the debugger token when ICDClass and ICDType are
+ // completely DACized
+ pCurrentFieldData->m_vmFieldDesc.SetHostPtr(pFD);
+ pCurrentFieldData->m_fFldIsTLS = (pFD->IsThreadStatic() == TRUE);
+ pCurrentFieldData->m_fFldIsContextStatic = (pFD->IsContextStatic() == TRUE);
+ pCurrentFieldData->m_fFldIsRVA = (pFD->IsRVA() == TRUE);
+ pCurrentFieldData->m_fFldIsCollectibleStatic = (pFD->IsStatic() == TRUE &&
+ pFD->GetEnclosingMethodTable()->Collectible());
+
+ // Compute the address of the field
+ if (pFD->IsStatic())
+ {
+ // statics are addressed using an absolute address.
+ if (pFD->IsRVA())
+ {
+ // RVA statics are relative to a base module address
+ DWORD offset = pFD->GetOffset();
+ PTR_VOID addr = pFD->GetModule()->GetRvaField(offset, pFD->IsZapped());
+ if (pCurrentFieldData->OkToGetOrSetStaticAddress())
+ {
+ pCurrentFieldData->SetStaticAddress(PTR_TO_TADDR(addr));
+ }
+ }
+ else if (pFD->IsThreadStatic() || pFD->IsContextStatic() ||
+ pCurrentFieldData->m_fFldIsCollectibleStatic)
+ {
+ // this is a special type of static that must be queried using DB_IPCE_GET_SPECIAL_STATIC
+ }
+ else
+ {
+ // This is a normal static variable in the GC or Non-GC static base table
+ PTR_BYTE base = pFD->IsPrimitive() ? pNonGCStaticsBase : pGCStaticsBase;
+ if (base == NULL)
+ {
+ // static var not available. This may be an open generic class (not an instantiated type),
+ // or we might only have approximate type information because the type hasn't been
+ // initialized yet.
+
+ if (pCurrentFieldData->OkToGetOrSetStaticAddress())
+ {
+ pCurrentFieldData->SetStaticAddress(NULL);
+ }
+ }
+ else
+ {
+ if (pCurrentFieldData->OkToGetOrSetStaticAddress())
+ {
+ // calculate the absolute address using the base and the offset from the base
+ pCurrentFieldData->SetStaticAddress(PTR_TO_TADDR(base) + pFD->GetOffset());
+ }
+ }
+ }
+ }
+ else
+ {
+ // instance variables are addressed using an offset within the instance
+ if (pCurrentFieldData->OkToGetOrSetInstanceOffset())
+ {
+ pCurrentFieldData->SetInstanceOffset(pFD->GetOffset());
+ }
+ }
+ }
+
+} // DacDbiInterfaceImpl::ComputeFieldData
+
+//-----------------------------------------------------------------------------
+// DacDbiInterfaceImpl::CollectFields
+// Gets information for all the fields for a given type
+// Arguments:
+// input: thExact - used to determine whether we need to get statics base tables
+// thApprox - used to get the field desc iterator
+// pAppDomain - used to get statics base tables
+// output:
+// pFieldList - contains fields to be initialized
+// Note: the caller must ensure that *ppFields is NULL (i.e., any previously allocated memory
+// must have been deallocated.
+//-----------------------------------------------------------------------------
+void DacDbiInterfaceImpl::CollectFields(TypeHandle thExact,
+ TypeHandle thApprox,
+ AppDomain * pAppDomain,
+ DacDbiArrayList<FieldData> * pFieldList)
+{
+ PTR_BYTE pGCStaticsBase = NULL;
+ PTR_BYTE pNonGCStaticsBase = NULL;
+ if (!thExact.IsNull() && !thExact.GetMethodTable()->Collectible())
+ {
+ // get base tables for static fields
+ GetStaticsBases(thExact, pAppDomain, &pGCStaticsBase, &pNonGCStaticsBase);
+ }
+
+ unsigned int fieldCount = 0;
+
+ // <TODO> we are losing exact type information for static fields in generic types. We have
+ // field desc iterators only for approximate types, but statics are per instantiation, so we
+ // need an exact type to be able to handle these correctly. We need to use
+ // FieldDesc::GetExactDeclaringType to get at the correct field. This requires the exact
+ // TypeHandle. </TODO>
+ EncApproxFieldDescIterator fdIterator(thApprox.GetMethodTable(),
+ ApproxFieldDescIterator::ALL_FIELDS,
+ FALSE); // don't fixup EnC (we can't, we're stopped)
+
+ PTR_FieldDesc pCurrentFD;
+ int index = 0;
+ while (((pCurrentFD = fdIterator.Next()) != NULL) && (index < pFieldList->Count()))
+ {
+ // fill in the pCurrentEntry structure
+ ComputeFieldData(pCurrentFD, pGCStaticsBase, pNonGCStaticsBase, &((*pFieldList)[index]));
+
+ // Bump our counts and pointers.
+ fieldCount++;
+ index++;
+ }
+ _ASSERTE(fieldCount == (unsigned int)pFieldList->Count());
+
+} // DacDbiInterfaceImpl::CollectFields
+
+
+// Determine if a type is a ValueType
+BOOL DacDbiInterfaceImpl::IsValueType (VMPTR_TypeHandle vmTypeHandle)
+{
+ DD_ENTER_MAY_THROW;
+
+ TypeHandle th = TypeHandle::FromPtr(vmTypeHandle.GetDacPtr());
+ return th.IsValueType();
+}
+
+// Determine if a type has generic parameters
+BOOL DacDbiInterfaceImpl::HasTypeParams (VMPTR_TypeHandle vmTypeHandle)
+{
+ DD_ENTER_MAY_THROW;
+
+ TypeHandle th = TypeHandle::FromPtr(vmTypeHandle.GetDacPtr());
+ return th.ContainsGenericVariables();
+}
+
+// DacDbi API: Get type information for a class
+void DacDbiInterfaceImpl::GetClassInfo(VMPTR_AppDomain vmAppDomain,
+ VMPTR_TypeHandle vmThExact,
+ ClassInfo * pData)
+{
+ DD_ENTER_MAY_THROW;
+
+ AppDomain * pAppDomain = vmAppDomain.GetDacPtr();
+
+ TypeHandle thExact;
+ TypeHandle thApprox;
+
+ GetTypeHandles(vmThExact, vmThExact, &thExact, &thApprox);
+
+ // initialize field count, generic args count, size and value class flag
+ InitClassData(thApprox, false, pData);
+
+ if (pAppDomain != NULL)
+ CollectFields(thExact, thApprox, pAppDomain, &(pData->m_fieldList));
+} // DacDbiInterfaceImpl::GetClassInfo
+
+// DacDbi API: Get field information and object size for an instantiated generic type
+void DacDbiInterfaceImpl::GetInstantiationFieldInfo (VMPTR_DomainFile vmDomainFile,
+ VMPTR_TypeHandle vmThExact,
+ VMPTR_TypeHandle vmThApprox,
+ DacDbiArrayList<FieldData> * pFieldList,
+ SIZE_T * pObjectSize)
+{
+ DD_ENTER_MAY_THROW;
+
+ DomainFile * pDomainFile = vmDomainFile.GetDacPtr();
+ _ASSERTE(pDomainFile != NULL);
+ AppDomain * pAppDomain = pDomainFile->GetAppDomain();
+ TypeHandle thExact;
+ TypeHandle thApprox;
+
+ GetTypeHandles(vmThExact, vmThApprox, &thExact, &thApprox);
+
+ *pObjectSize = thApprox.GetMethodTable()->GetNumInstanceFieldBytes();
+
+ pFieldList->Alloc(GetTotalFieldCount(thApprox));
+
+ CollectFields(thExact, thApprox, pAppDomain, pFieldList);
+
+} // DacDbiInterfaceImpl::GetInstantiationFieldInfo
+
+//-----------------------------------------------------------------------------------
+// DacDbiInterfaceImpl::TypeDataWalk member functions
+//-----------------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+// TypeDataWalk constructor--initialize the buffer and number of remaining items from input data
+// Arguments: pData - pointer to a list of records containing information about type parameters for an
+// instantiated type
+// nData - number of entries in pData
+//-----------------------------------------------------------------------------
+DacDbiInterfaceImpl::TypeDataWalk::TypeDataWalk(DebuggerIPCE_TypeArgData * pData, unsigned int nData)
+{
+ m_pCurrentData = pData;
+ m_nRemaining = nData;
+} // DacDbiInterfaceImpl::TypeDataWalk::TypeDataWalk
+
+//-----------------------------------------------------------------------------
+// DacDbiInterfaceImpl::TypeDataWalk::ReadOne
+// read and return a single node from the list of type parameters
+// Arguments: none (uses internal state)
+// Return value: information about the next type parameter in m_pCurrentData
+//-----------------------------------------------------------------------------
+DebuggerIPCE_TypeArgData * DacDbiInterfaceImpl::TypeDataWalk::ReadOne()
+{
+ LIMITED_METHOD_CONTRACT;
+ if (m_nRemaining)
+ {
+ m_nRemaining--;
+ return m_pCurrentData++;
+ }
+ else
+ {
+ return NULL;
+ }
+} // DacDbiInterfaceImpl::TypeDataWalk::ReadOne
+
+//-----------------------------------------------------------------------------
+// DacDbiInterfaceImpl::TypeDataWalk::Skip
+// Skip a single node from the list of type handles along with any children it might have
+// Arguments: none (uses internal state)
+// Return value: none (updates internal state)
+//-----------------------------------------------------------------------------
+void DacDbiInterfaceImpl::TypeDataWalk::Skip()
+{
+ LIMITED_METHOD_CONTRACT;
+
+ DebuggerIPCE_TypeArgData * pData = ReadOne();
+ if (pData)
+ {
+ for (unsigned int i = 0; i < pData->numTypeArgs; i++)
+ {
+ Skip();
+ }
+ }
+} // DacDbiInterfaceImpl::TypeDataWalk::Skip
+
+//-----------------------------------------------------------------------------
+// DacDbiInterfaceImpl::TypeDataWalk::ReadLoadedTypeArg
+// Read a type handle when it is used in the position of a generic argument or
+// argument of an array or address type. Take into account generic code sharing if we
+// have been requested to find the canonical representation amongst a set of shared-
+// code generic types. That is, if generics code sharing is enabled then return "Object"
+// for all reference types, and canonicalize underneath value types, e.g. V<string> --> V<object>.
+// Return TypeHandle() if any of the type handles are not loaded.
+//
+// Arguments: retrieveWhich - indicates whether to retrieve a canonical representation or
+// an exact representation
+// Return value: the type handle for the type parameter
+//-----------------------------------------------------------------------------
+TypeHandle DacDbiInterfaceImpl::TypeDataWalk::ReadLoadedTypeArg(TypeHandleReadType retrieveWhich)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+#if !defined(FEATURE_SHARE_GENERIC_CODE)
+ return ReadLoadedTypeHandle(kGetExact);
+#else
+
+ if (retrieveWhich == kGetExact)
+ return ReadLoadedTypeHandle(kGetExact);
+
+ // This nasty bit of code works out what the "canonicalization" of a
+ // parameter to a generic is once we take into account generics code sharing.
+ //
+ // This logic is somewhat a duplication of logic in vm\typehandle.cpp, though
+ // that logic operates on a TypeHandle format, i.e. assumes we're finding the
+ // canonical form of a type that has already been loaded. Here we are finding
+ // the canonical form of a type that may not have been loaded (but where we expect
+ // its canonical form to have been loaded).
+ //
+ // Ideally this logic would not be duplicated in this way, but it is difficult
+ // to arrange for that.
+ DebuggerIPCE_TypeArgData * pData = ReadOne();
+ if (!pData)
+ return TypeHandle();
+
+ // If we have code sharing then the process of canonicalizing is trickier.
+ // unfortunately we have to include the exact specification of compatibility at
+ // this point.
+ CorElementType elementType = pData->data.elementType;
+
+ switch (elementType)
+ {
+ case ELEMENT_TYPE_PTR:
+ _ASSERTE(pData->numTypeArgs == 1);
+ return PtrOrByRefTypeArg(pData, retrieveWhich);
+ break;
+
+ case ELEMENT_TYPE_CLASS:
+ case ELEMENT_TYPE_VALUETYPE:
+ return ClassTypeArg(pData, retrieveWhich);
+ break;
+
+ case ELEMENT_TYPE_FNPTR:
+ return FnPtrTypeArg(pData, retrieveWhich);
+ break;
+
+ default:
+ return ObjRefOrPrimitiveTypeArg(pData, elementType);
+ break;
+ }
+
+#endif // FEATURE_SHARE_GENERIC_CODE
+} // DacDbiInterfaceImpl::TypeDataWalk::ReadLoadedTypeArg
+
+//-----------------------------------------------------------------------------
+// DacDbiInterfaceImpl::TypeDataWalk::ReadLoadedTypeHandles
+// Iterate through the type argument data, creating type handles as we go.
+//
+// Arguments:
+// input: retrieveWhich - indicates whether we can return a canonical type handle
+// or we must return an exact type handle
+// nTypeArgs - number of type arguments to be read
+// output: ppResults - pointer to a list of TypeHandles that will hold the type handles
+// for each type parameter
+//
+// Return Value: FALSE iff any of the type handles are not loaded.
+//-----------------------------------------------------------------------------
+BOOL DacDbiInterfaceImpl::TypeDataWalk::ReadLoadedTypeHandles(TypeHandleReadType retrieveWhich,
+ unsigned int nTypeArgs,
+ TypeHandle * ppResults)
+{
+ WRAPPER_NO_CONTRACT;
+
+ BOOL allOK = true;
+ for (unsigned int i = 0; i < nTypeArgs; i++)
+ {
+ ppResults[i] = ReadLoadedTypeArg(retrieveWhich);
+ allOK &= !ppResults[i].IsNull();
+ }
+ return allOK;
+} // DacDbiInterfaceImpl::TypeDataWalk::ReadLoadedTypeHandles
+
+//-----------------------------------------------------------------------------
+// DacDbiInterfaceImpl::TypeDataWalk::ReadLoadedInstantiation
+// Read an instantiation of a generic type if it has already been created.
+//
+// Arguments:
+// input: retrieveWhich - indicates whether we can return a canonical type handle
+// or we must return an exact type handle
+// pModule - module in which the instantiated type is loaded
+// mdToken - metadata token for the type
+// nTypeArgs - number of type arguments to be read
+// Return value: the type handle for the instantiated type
+//-----------------------------------------------------------------------------
+TypeHandle DacDbiInterfaceImpl::TypeDataWalk::ReadLoadedInstantiation(TypeHandleReadType retrieveWhich,
+ Module * pModule,
+ mdTypeDef mdToken,
+ unsigned int nTypeArgs)
+{
+ WRAPPER_NO_CONTRACT;
+
+ NewHolder<TypeHandle> pInst(new TypeHandle[nTypeArgs]);
+
+ // get the type handle for each of the type parameters
+ if (!ReadLoadedTypeHandles(retrieveWhich, nTypeArgs, pInst))
+ {
+ return TypeHandle();
+ }
+
+ // get the type handle for the particular instantiation that corresponds to
+ // the given type parameters
+ return FindLoadedInstantiation(pModule, mdToken, nTypeArgs, pInst);
+
+} // DacDbiInterfaceImpl::TypeDataWalk::ReadLoadedInstantiation
+
+
+//-----------------------------------------------------------------------------
+// DacDbiInterfaceImpl::TypeDataWalk::ReadLoadedTypeHandle
+//
+// Compute the type handle for a given type.
+// This is the top-level function that will return the type handle for an
+// arbitrary type. It uses mutual recursion with ReadLoadedTypeArg to get
+// the type handle for a (possibly parameterized) type. Note that the referent of
+// address types or the element type of an array type are viewed as type parameters.
+//
+// For example, assume that we are retrieving only exact types, and we have as our
+// top level type an array defined as int [][].
+// We start by noting that the type is an array type, so we call ReadLoadedTypeArg to
+// get the element type. We find that the element type is also an array:int [].
+// ReadLoadedTypeArg will call ReadLoadedTypeHandle with this type information.
+// Again, we determine that the top-level type is an array, so we call ReadLoadedTypeArg
+// to get the element type, int. ReadLoadedTypeArg will again call ReadLoadedTypeHandle
+// which will find that this time, the top-level type is a primitive type. It will request
+// the loaded type handle from the loader and return it. On return, we get the type handle
+// for an array of int from the loader. We return again and request the type handle for an
+// array of arrays of int. This is the type handle we will return.
+//
+// Arguments:
+// input: retrieveWhich - determines whether we can return the type handle for
+// a canonical type or only for an exact type
+// we use the list of type data stored in the TypeDataWalk data members
+// for other input information
+// Return value: type handle for the current type.
+//-----------------------------------------------------------------------------
+TypeHandle DacDbiInterfaceImpl::TypeDataWalk::ReadLoadedTypeHandle(TypeHandleReadType retrieveWhich)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ // get the type information at the head of the list m_pCurrentData
+ DebuggerIPCE_TypeArgData * pData = ReadOne();
+ if (!pData)
+ return TypeHandle();
+
+ // get the type handle that corresponds to its elementType
+ TypeHandle typeHandle;
+ switch (pData->data.elementType)
+ {
+ case ELEMENT_TYPE_ARRAY:
+ case ELEMENT_TYPE_SZARRAY:
+ typeHandle = ArrayTypeArg(pData, retrieveWhich);
+ break;
+
+ case ELEMENT_TYPE_PTR:
+ case ELEMENT_TYPE_BYREF:
+ typeHandle = PtrOrByRefTypeArg(pData, retrieveWhich);
+ break;
+ case ELEMENT_TYPE_CLASS:
+ case ELEMENT_TYPE_VALUETYPE:
+ {
+ Module * pModule = pData->data.ClassTypeData.vmModule.GetDacPtr();
+ typeHandle = ReadLoadedInstantiation(retrieveWhich,
+ pModule,
+ pData->data.ClassTypeData.metadataToken,
+ pData->numTypeArgs);
+ }
+ break;
+
+ case ELEMENT_TYPE_FNPTR:
+ {
+ typeHandle = FnPtrTypeArg(pData, retrieveWhich);
+ }
+ break;
+
+ default:
+ typeHandle = FindLoadedElementType(pData->data.elementType);
+ break;
+ }
+ return typeHandle;
+} // DacDbiInterfaceImpl::TypeDataWalk::ReadLoadedTypeHandle
+
+//-----------------------------------------------------------------------------
+// DacDbiInterfaceImpl::TypeDataWalk::ArrayTypeArg
+// get a loaded type handle for an array type (E_T_ARRAY or E_T_SZARRAY)
+//
+// Arguments:
+// input: pArrayTypeInfo - type information for an array type
+// Although this is in fact a pointer (in)to a list, we treat it here
+// simply as a pointer to a single instance of DebuggerIPCE_TypeArgData
+// which holds type information for an array.
+// This is the most recent type node (for an array type) retrieved
+// by TypeDataWalk::ReadOne(). The call to ReadLoadedTypeArg will
+// result in call(s) to ReadOne to retrieve one or more type nodes
+// that are needed to compute the type handle for the
+// element type of the array. When we return from that call, we pass
+// pArrayTypeInfo along with arrayElementTypeArg to FindLoadedArrayType
+// to get the type handle for this particular array type.
+// Note:
+// On entry, we know that pArrayTypeInfo is the same as m_pCurrentData - 1,
+// but by the time we need to use it, this is no longer true. Because
+// we can't predict how many nodes will be consumed by the call to
+// ReadLoadedTypeArg, we can't compute this value from the member fields
+// of TypeDataWalk and therefore pass it as a parameter.
+// retrieveWhich - determines whether we can return the type handle for
+// a canonical type or only for an exact type
+// Return value: the type handle corresponding to the array type
+//-----------------------------------------------------------------------------
+
+TypeHandle DacDbiInterfaceImpl::TypeDataWalk::ArrayTypeArg(DebuggerIPCE_TypeArgData * pArrayTypeInfo,
+ TypeHandleReadType retrieveWhich)
+{
+ TypeHandle arrayElementTypeArg = ReadLoadedTypeArg(retrieveWhich);
+ if (!arrayElementTypeArg.IsNull())
+ {
+ return FindLoadedArrayType(pArrayTypeInfo->data.elementType,
+ arrayElementTypeArg,
+ pArrayTypeInfo->data.ArrayTypeData.arrayRank);
+ }
+ return TypeHandle();
+} // DacDbiInterfaceImpl::TypeDataWalk::ArrayTypeArg
+
+
+//-----------------------------------------------------------------------------
+// DacDbiInterfaceImpl::TypeDataWalk::PtrOrByRefTypeArg
+// get a loaded type handle for an address type (E_T_PTR or E_T_BYREF)
+//
+// Arguments:
+// input: pPtrOrByRefTypeInfo - type information for a pointer or byref type
+// Although this is in fact a pointer (in)to a list, we treat it here
+// simply as a pointer to a single instance of DebuggerIPCE_TypeArgData
+// which holds type information for a pointer or byref type.
+// This is the most recent type node (for a pointer or byref type) retrieved
+// by TypeDataWalk::ReadOne(). The call to ReadLoadedTypeArg will
+// result in call(s) to ReadOne to retrieve one or more type nodes
+// that are needed to compute the type handle for the
+// referent type of the pointer. When we return from that call, we pass
+// pPtrOrByRefTypeInfo along with referentTypeArg to FindLoadedPointerOrByrefType
+// to get the type handle for this particular pointer or byref type.
+// Note:
+// On entry, we know that pPtrOrByRefTypeInfo is the same as m_pCurrentData - 1,
+// but by the time we need to use it, this is no longer true. Because
+// we can't predict how many nodes will be consumed by the call to
+// ReadLoadedTypeArg, we can't compute this value from the member fields
+// of TypeDataWalk and therefore pass it as a parameter.
+// retrieveWhich - determines whether we can return the type handle for
+// a canonical type or only for an exact type
+// Return value: the type handle corresponding to the address type
+//-----------------------------------------------------------------------------
+TypeHandle DacDbiInterfaceImpl::TypeDataWalk::PtrOrByRefTypeArg(DebuggerIPCE_TypeArgData * pPtrOrByRefTypeInfo,
+ TypeHandleReadType retrieveWhich)
+{
+ TypeHandle referentTypeArg = ReadLoadedTypeArg(retrieveWhich);
+ if (!referentTypeArg.IsNull())
+ {
+ return FindLoadedPointerOrByrefType(pPtrOrByRefTypeInfo->data.elementType, referentTypeArg);
+ }
+
+ return TypeHandle();
+
+} // DacDbiInterfaceImpl::TypeDataWalk::PtrOrByRefTypeArg
+
+//-----------------------------------------------------------------------------
+// DacDbiInterfaceImpl::TypeDataWalk::ClassTypeArg
+// get a loaded type handle for a class type (E_T_CLASS or E_T_VALUETYPE)
+//
+// Arguments:
+// input: pClassTypeInfo - type information for a class type
+// Although this is in fact a pointer (in)to a list, we treat it here
+// simply as a pointer to a single instance of DebuggerIPCE_TypeArgData
+// which holds type information for a pointer or byref type.
+// This is the most recent type node (for a pointer or byref type) retrieved
+// by TypeDataWalk::ReadOne(). The call to ReadLoadedInstantiation will
+// result in call(s) to ReadOne to retrieve one or more type nodes
+// that are needed to compute the type handle for the type parameters
+// for the class. If we can't find an exact loaded type for the class, we will
+// instead return a canonical method table. In this case, we need to skip
+// the type parameter information for each actual parameter to the class.
+// This is necessary because we may be getting a type handle for a class which is
+// in turn an argument to a parent type. If the parent type has more arguments, we
+// need to be at the right place in the list when we return. We use
+// pClassTypeInfo to get the number of type arguments that we need to skip.
+// retrieveWhich - determines whether we can return the type handle for
+// a canonical type or only for an exact type
+// Return value: the type handle corresponding to the class type
+//-----------------------------------------------------------------------------
+TypeHandle DacDbiInterfaceImpl::TypeDataWalk::ClassTypeArg(DebuggerIPCE_TypeArgData * pClassTypeInfo,
+ TypeHandleReadType retrieveWhich)
+{
+ Module * pModule = pClassTypeInfo->data.ClassTypeData.vmModule.GetDacPtr();
+ TypeHandle typeDef = ClassLoader::LookupTypeDefOrRefInModule(pModule,
+ pClassTypeInfo->data.ClassTypeData.metadataToken);
+
+ if ((!typeDef.IsNull() && typeDef.IsValueType()) || (pClassTypeInfo->data.elementType == ELEMENT_TYPE_VALUETYPE))
+ {
+ return ReadLoadedInstantiation(retrieveWhich,
+ pModule,
+ pClassTypeInfo->data.ClassTypeData.metadataToken,
+ pClassTypeInfo->numTypeArgs);
+ }
+ else
+ {
+ _ASSERTE(retrieveWhich == kGetCanonical);
+ // skip the instantiation - no need to look at it since the type canonicalizes to "Object"
+ for (unsigned int i = 0; i < pClassTypeInfo->numTypeArgs; i++)
+ {
+ Skip();
+ }
+ return TypeHandle(g_pCanonMethodTableClass);
+ }
+}// DacDbiInterfaceImpl::TypeDataWalk::ClassTypeArg
+
+//-----------------------------------------------------------------------------
+// DacDbiInterfaceImpl::TypeDataWalk::FnPtrTypeArg
+// get a loaded type handle for a function pointer type (E_T_FNPTR)
+//
+// Arguments:
+// input: pFnPtrTypeInfo - type information for a pointer or byref type
+// Although this is in fact a pointer (in)to a list, we treat it here
+// simply as a pointer to a single instance of DebuggerIPCE_TypeArgData
+// which holds type information for a function pointer type.
+// This is the most recent type node (for a function pointer type) retrieved
+// by TypeDataWalk::ReadOne(). The call to ReadLoadedTypeHandles will
+// result in call(s) to ReadOne to retrieve one or more type nodes
+// that are needed to compute the type handle for the return type and
+// parameter types of the function. When we return from that call, we pass
+// pFnPtrTypeInfo along with pInst to FindLoadedFnptrType
+// to get the type handle for this particular function pointer type.
+// retrieveWhich - determines whether we can return the type handle for
+// a canonical type or only for an exact type
+// Return value: the type handle corresponding to the function pointer type
+//-----------------------------------------------------------------------------
+TypeHandle DacDbiInterfaceImpl::TypeDataWalk::FnPtrTypeArg(DebuggerIPCE_TypeArgData * pFnPtrTypeInfo,
+ TypeHandleReadType retrieveWhich)
+{
+ // allocate space to store a list of type handles, one for the return type and one for each
+ // of the parameter types of the function to which the FnPtr type refers.
+ NewHolder<TypeHandle> pInst(new TypeHandle[sizeof(TypeHandle) * pFnPtrTypeInfo->numTypeArgs]);
+
+ if (ReadLoadedTypeHandles(retrieveWhich, pFnPtrTypeInfo->numTypeArgs, pInst))
+ {
+ return FindLoadedFnptrType(pFnPtrTypeInfo->numTypeArgs, pInst);
+ }
+
+ return TypeHandle();
+
+} // DacDbiInterfaceImpl::TypeDataWalk::FnPtrTypeArg
+
+//-----------------------------------------------------------------------------
+// DacDbiInterfaceImpl::TypeDataWalk::ObjRefOrPrimitiveTypeArg
+// get a loaded type handle for a primitive type or ObjRef
+//
+// Arguments:
+// input: pArgInfo - type information for an objref or primitive type.
+// This is called only when the objref or primitive type
+// is a type argument for a parent type. In this case,
+// we treat all objrefs the same, that is, we don't care
+// about type parameters for the referent. Instead, we will
+// simply return the canonical object type handle as the type
+// of the referent. <@dbgtodo Microsoft: why is this?>
+// If this is a primitive type, we'll simply get the
+// type handle for that type.
+// elementType - type of the argument
+// Return value: the type handle corresponding to the elementType
+//-----------------------------------------------------------------------------
+TypeHandle DacDbiInterfaceImpl::TypeDataWalk::ObjRefOrPrimitiveTypeArg(DebuggerIPCE_TypeArgData * pArgInfo,
+ CorElementType elementType)
+{
+ // If there are any type args (e.g. for arrays) they can be skipped. The thing
+ // is a reference type anyway.
+ for (unsigned int i = 0; i < pArgInfo->numTypeArgs; i++)
+ {
+ Skip();
+ }
+
+ // for an ObjRef, just return the CLASS____CANON type handle
+ if (CorTypeInfo::IsObjRef_NoThrow(elementType))
+ {
+ return TypeHandle(g_pCanonMethodTableClass);
+ }
+ else
+ {
+ return FindLoadedElementType(elementType);
+ }
+} // DacDbiInterfaceImpl::TypeDataWalk::ObjRefOrPrimitiveTypeArg
+
+
+//-------------------------------------------------------------------------
+// end of TypeDataWalk implementations
+//-------------------------------------------------------------------------
+//-------------------------------------------------------------------------
+// functions to use loader to get type handles
+// ------------------------------------------------------------------------
+
+// Note, in these functions, the use of ClassLoader::DontLoadTypes was chosen
+// instead of FailIfNotLoaded because, although we may want to debug unrestored
+// VCs, we can't do it because the debug API is not set up to handle them.
+//
+//-----------------------------------------------------------------------------
+// DacDbiInterfaceImpl::FindLoadedArrayType
+// Use ClassLoader to find a loaded type handle for an array type (E_T_ARRAY or E_T_SZARRAY)
+// Arguments:
+// input: arrayType - type of the array
+// TypeArg - type handle for the base type
+// rank - array rank
+// Return Value: type handle for the array type
+//-----------------------------------------------------------------------------
+// static
+TypeHandle DacDbiInterfaceImpl::FindLoadedArrayType(CorElementType arrayType,
+ TypeHandle typeArg,
+ unsigned rank)
+{
+ // Lookup operations run the class loader in non-load mode.
+ ENABLE_FORBID_GC_LOADER_USE_IN_THIS_SCOPE();
+
+ if (typeArg.IsNull())
+ {
+ return TypeHandle();
+ }
+ else
+ {
+ return ClassLoader::LoadArrayTypeThrowing(typeArg,
+ arrayType,
+ rank,
+ ClassLoader::DontLoadTypes );
+ }
+} // DacDbiInterfaceImpl::FindLoadedArrayType;
+
+//-----------------------------------------------------------------------------
+// DacDbiInterfaceImpl::FindLoadedPointerOrByrefType
+// Use ClassLoader to find a loaded type handle for an address type (E_T_PTR or E_T_BYREF)
+// Arguments:
+// input: addressType - type of the address type
+// TypeArg - type handle for the base type
+// Return Value: type handle for the address type
+//-----------------------------------------------------------------------------
+// static
+TypeHandle DacDbiInterfaceImpl::FindLoadedPointerOrByrefType(CorElementType addressType, TypeHandle typeArg)
+{
+ // Lookup operations run the class loader in non-load mode.
+ ENABLE_FORBID_GC_LOADER_USE_IN_THIS_SCOPE();
+
+ return ClassLoader::LoadPointerOrByrefTypeThrowing(addressType,
+ typeArg,
+ ClassLoader::DontLoadTypes);
+} // DacDbiInterfaceImpl::FindLoadedPointerOrByrefType
+
+//-----------------------------------------------------------------------------
+// DacDbiInterfaceImpl::FindLoadedFnptrType
+// Use ClassLoader to find a loaded type handle for a function pointer type (E_T_FNPTR)
+// Arguments:
+// input: pInst - type handles of the function's return value and arguments
+// numTypeArgs - number of type handles in pInst
+// Return Value: type handle for the function pointer type
+//-----------------------------------------------------------------------------
+// static
+TypeHandle DacDbiInterfaceImpl::FindLoadedFnptrType(DWORD numTypeArgs, TypeHandle * pInst)
+{
+ // Lookup operations run the class loader in non-load mode.
+ ENABLE_FORBID_GC_LOADER_USE_IN_THIS_SCOPE();
+
+ // @dbgtodo : Do we need to worry about calling convention here?
+ return ClassLoader::LoadFnptrTypeThrowing(0,
+ numTypeArgs,
+ pInst,
+ ClassLoader::DontLoadTypes);
+} // DacDbiInterfaceImpl::FindLoadedFnptrType
+
+//-----------------------------------------------------------------------------
+// DacDbiInterfaceImpl::FindLoadedInstantiation
+// Use ClassLoader to find a loaded type handle for a particular instantiation of a
+// class type (E_T_CLASS or E_T_VALUECLASS)
+//
+// Arguments:
+// input: pModule - module in which the type is loaded
+// mdToken - metadata token for the type
+// nTypeArgs - number of type arguments in pInst
+// pInst - list of type handles for the type parameters
+// Return value: type handle for the instantiated class type
+//-----------------------------------------------------------------------------
+// static
+TypeHandle DacDbiInterfaceImpl::FindLoadedInstantiation(Module * pModule,
+ mdTypeDef mdToken,
+ DWORD nTypeArgs,
+ TypeHandle * pInst)
+{
+ // Lookup operations run the class loader in non-load mode.
+ ENABLE_FORBID_GC_LOADER_USE_IN_THIS_SCOPE();
+
+ return ClassLoader::LoadGenericInstantiationThrowing(pModule,
+ mdToken,
+ Instantiation(pInst,nTypeArgs),
+ ClassLoader::DontLoadTypes);
+
+} // DacDbiInterfaceImpl::FindLoadedInstantiation
+
+//-----------------------------------------------------------------------------
+// DacDbiInterfaceImpl::FindLoadedElementType
+// Get the type handle for a primitive type
+// Arguments:
+// input: elementType - type of the primitive type
+// Return Value: Type handle for the primitive type
+//-----------------------------------------------------------------------------
+// static
+TypeHandle DacDbiInterfaceImpl::FindLoadedElementType(CorElementType elementType)
+{
+ // Lookup operations run the class loader in non-load mode.
+ ENABLE_FORBID_GC_LOADER_USE_IN_THIS_SCOPE();
+
+ MethodTable * pMethodTable = (&g_Mscorlib)->GetElementType(elementType);
+
+ return TypeHandle(pMethodTable);
+} // DacDbiInterfaceImpl::FindLoadedElementType
+
+
+//-----------------------------------------------------------------------------
+// DacDbiInterfaceImpl::GetArrayTypeInfo
+// Gets additional information to convert a type handle to an instance of CordbType if the type is E_T_ARRAY.
+// Specifically, we get the rank and the type of the array elements
+//
+// Arguments:
+// input: typeHandle - type handle for the array type
+// pAppDomain - AppDomain into which the type is loaded
+// output: pTypeInfo - information for the array rank and element type
+//
+//-----------------------------------------------------------------------------
+void DacDbiInterfaceImpl::GetArrayTypeInfo(TypeHandle typeHandle,
+ DebuggerIPCE_ExpandedTypeData * pTypeInfo,
+ AppDomain * pAppDomain)
+{
+ _ASSERTE(typeHandle.IsArray());
+ pTypeInfo->ArrayTypeData.arrayRank = typeHandle.AsArray()->GetRank();
+ TypeHandleToBasicTypeInfo(typeHandle.AsArray()->GetArrayElementTypeHandle(),
+ &(pTypeInfo->ArrayTypeData.arrayTypeArg),
+ pAppDomain);
+} // DacDbiInterfaceImpl::GetArrayTypeInfo
+
+//-----------------------------------------------------------------------------
+// DacDbiInterfaceImpl::GetPtrTypeInfo
+// Gets additional information to convert a type handle to an instance of CordbType if the type is
+// E_T_PTR or E_T_BYREF. Specifically, we get the type for the referent of the address type
+//
+// Arguments:
+// input: boxed - indicates what, if anything, is boxed (see code:AreValueTypesBoxed for
+// more specific information)
+// typeHandle - type handle for the address type
+// pAppDomain - AppDomain into which the type is loaded
+// output: pTypeInfo - information for the referent type
+//
+//-----------------------------------------------------------------------------
+void DacDbiInterfaceImpl::GetPtrTypeInfo(AreValueTypesBoxed boxed,
+ TypeHandle typeHandle,
+ DebuggerIPCE_ExpandedTypeData * pTypeInfo,
+ AppDomain * pAppDomain)
+{
+ if (boxed == AllBoxed)
+ {
+ GetClassTypeInfo(typeHandle, pTypeInfo, pAppDomain);
+ }
+ else
+ {
+ _ASSERTE(typeHandle.IsTypeDesc());
+ TypeHandleToBasicTypeInfo(typeHandle.AsTypeDesc()->GetTypeParam(),
+ &(pTypeInfo->UnaryTypeData.unaryTypeArg),
+ pAppDomain);
+ }
+} // DacDbiInterfaceImpl::GetPtrTypeInfo
+
+//-----------------------------------------------------------------------------
+// DacDbiInterfaceImpl::GetFnPtrTypeInfo
+// Gets additional information to convert a type handle to an instance of CordbType if the type is
+// E_T_FNPTR, specifically the typehandle for the referent.
+//
+// Arguments
+// input: boxed - indicates what, if anything, is boxed (see code:AreValueTypesBoxed for
+// more specific information)
+// typeHandle - type handle for the address type
+// pAppDomain - AppDomain into which the type is loaded
+// output: pTypeInfo - information for the referent type
+//
+//-----------------------------------------------------------------------------
+void DacDbiInterfaceImpl::GetFnPtrTypeInfo(AreValueTypesBoxed boxed,
+ TypeHandle typeHandle,
+ DebuggerIPCE_ExpandedTypeData * pTypeInfo,
+ AppDomain * pAppDomain)
+{
+ if (boxed == AllBoxed)
+ {
+ GetClassTypeInfo(typeHandle, pTypeInfo, pAppDomain);
+ }
+ else
+ {
+ pTypeInfo->NaryTypeData.typeHandle.SetDacTargetPtr(typeHandle.AsTAddr());
+ }
+} // DacDbiInterfaceImpl::GetFnPtrTypeInfo
+
+
+//-----------------------------------------------------------------------------
+// DacDbiInterfaceImpl::GetClassTypeInfo
+// Gets additional information to convert a type handle to an instance of CordbType if the type is
+// E_T_CLASS or E_T_VALUETYPE
+//
+// Arguments
+// input: typeHandle - type handle for the address type
+// pAppDomain - AppDomain into which the type is loaded
+// output: pTypeInfo - information for the referent type
+//
+//-----------------------------------------------------------------------------
+void DacDbiInterfaceImpl::GetClassTypeInfo(TypeHandle typeHandle,
+ DebuggerIPCE_ExpandedTypeData * pTypeInfo,
+ AppDomain * pAppDomain)
+{
+ Module * pModule = typeHandle.GetModule();
+
+ if (typeHandle.HasInstantiation()) // the type handle represents a generic instantiation
+ {
+ pTypeInfo->ClassTypeData.typeHandle.SetDacTargetPtr(typeHandle.AsTAddr());
+ }
+ else // non-generic
+ {
+ pTypeInfo->ClassTypeData.typeHandle = VMPTR_TypeHandle::NullPtr();
+ }
+
+ pTypeInfo->ClassTypeData.metadataToken = typeHandle.GetCl();
+
+ _ASSERTE(pModule);
+ pTypeInfo->ClassTypeData.vmModule.SetDacTargetPtr(PTR_HOST_TO_TADDR(pModule));
+ if (pAppDomain)
+ {
+ pTypeInfo->ClassTypeData.vmDomainFile.SetDacTargetPtr(PTR_HOST_TO_TADDR(pModule->GetDomainFile(pAppDomain)));
+ }
+ else
+ {
+ pTypeInfo->ClassTypeData.vmDomainFile = VMPTR_DomainFile::NullPtr();
+ }
+} // DacDbiInterfaceImpl::GetClassTypeInfo
+
+//-----------------------------------------------------------------------------
+// DacDbiInterfaceImpl::GetElementType
+// Gets the correct CorElementType value from a type handle
+//
+// Arguments
+// input: typeHandle - type handle for the address type
+// Return Value: the CorElementType enum value for the type handle
+//-----------------------------------------------------------------------------
+CorElementType DacDbiInterfaceImpl::GetElementType (TypeHandle typeHandle)
+{
+ if (typeHandle.IsNull())
+ {
+ return ELEMENT_TYPE_VOID;
+ }
+ else if (typeHandle.GetMethodTable() == g_pObjectClass)
+ {
+ return ELEMENT_TYPE_OBJECT;
+ }
+ else if (typeHandle.GetMethodTable() == g_pStringClass)
+ {
+ return ELEMENT_TYPE_STRING;
+ }
+ else
+ {
+ // GetSignatureCorElementType returns E_T_CLASS for E_T_STRING... :-(
+ return typeHandle.GetSignatureCorElementType();
+ }
+
+} // DacDbiInterfaceImpl::GetElementType
+
+//-----------------------------------------------------------------------------
+// DacDbiInterfaceImpl::TypeHandleToBasicTypeInfo
+// Gets additional information to convert a type handle to an instance of CordbType for the referent of an
+// E_T_BYREF or E_T_PTR or for the element type of an E_T_ARRAY or E_T_SZARRAY
+//
+// Arguments:
+// input: typeHandle - type handle for the address type
+// pAppDomain - AppDomain into which the type is loaded
+// output: pTypeInfo - information for the referent type
+//
+//-----------------------------------------------------------------------------
+void DacDbiInterfaceImpl::TypeHandleToBasicTypeInfo(TypeHandle typeHandle,
+ DebuggerIPCE_BasicTypeData * pTypeInfo,
+ AppDomain * pAppDomain)
+{
+ pTypeInfo->elementType = GetElementType(typeHandle);
+
+ switch (pTypeInfo->elementType)
+ {
+ case ELEMENT_TYPE_ARRAY:
+ case ELEMENT_TYPE_SZARRAY:
+ case ELEMENT_TYPE_FNPTR:
+ case ELEMENT_TYPE_PTR:
+ case ELEMENT_TYPE_BYREF:
+ pTypeInfo->vmTypeHandle.SetDacTargetPtr(typeHandle.AsTAddr());
+ pTypeInfo->metadataToken = mdTokenNil;
+ pTypeInfo->vmDomainFile = VMPTR_DomainFile::NullPtr();
+ break;
+
+ case ELEMENT_TYPE_CLASS:
+ case ELEMENT_TYPE_VALUETYPE:
+ {
+ Module * pModule = typeHandle.GetModule();
+
+ if (typeHandle.HasInstantiation()) // only set if instantiated
+ {
+ pTypeInfo->vmTypeHandle.SetDacTargetPtr(typeHandle.AsTAddr());
+ }
+ else
+ {
+ pTypeInfo->vmTypeHandle = VMPTR_TypeHandle::NullPtr();
+ }
+
+ pTypeInfo->metadataToken = typeHandle.GetCl();
+ _ASSERTE(pModule);
+
+ pTypeInfo->vmModule.SetDacTargetPtr(PTR_HOST_TO_TADDR(pModule));
+ if (pAppDomain)
+ {
+ pTypeInfo->vmDomainFile.SetDacTargetPtr(PTR_HOST_TO_TADDR(pModule->GetDomainFile(pAppDomain)));
+ }
+ else
+ {
+ pTypeInfo->vmDomainFile = VMPTR_DomainFile::NullPtr();
+ }
+ break;
+ }
+
+ default:
+ pTypeInfo->vmTypeHandle = VMPTR_TypeHandle::NullPtr();
+ pTypeInfo->metadataToken = mdTokenNil;
+ pTypeInfo->vmDomainFile = VMPTR_DomainFile::NullPtr();
+ break;
+ }
+ return;
+} // DacDbiInterfaceImpl::TypeHandleToBasicTypeInfo
+
+
+void DacDbiInterfaceImpl::GetObjectExpandedTypeInfoFromID(AreValueTypesBoxed boxed,
+ VMPTR_AppDomain vmAppDomain,
+ COR_TYPEID id,
+ DebuggerIPCE_ExpandedTypeData *pTypeInfo)
+{
+ DD_ENTER_MAY_THROW;
+
+ PTR_MethodTable pMT(TO_TADDR(id.token1));
+
+ if (pMT->IsArray())
+ {
+ // ArrayBase::GetTypeHandle() may return a NULL handle in corner case scenarios. This check prevents
+ // us from an AV but doesn't actually fix the problem. See DevDiv 653441 for more info.
+ TypeHandle arrayHandle = ArrayBase::GetTypeHandle(pMT);
+ if (arrayHandle.IsNull())
+ {
+ ThrowHR(CORDBG_E_CLASS_NOT_LOADED);
+ }
+
+ TypeHandleToExpandedTypeInfoImpl(boxed, vmAppDomain, arrayHandle, pTypeInfo);
+ }
+ else
+ {
+ TypeHandleToExpandedTypeInfoImpl(boxed, vmAppDomain, TypeHandle::FromPtr(TO_TADDR(id.token1)), pTypeInfo);
+ }
+}
+
+void DacDbiInterfaceImpl::GetObjectExpandedTypeInfo(AreValueTypesBoxed boxed,
+ VMPTR_AppDomain vmAppDomain,
+ CORDB_ADDRESS addr,
+ DebuggerIPCE_ExpandedTypeData *pTypeInfo)
+{
+ DD_ENTER_MAY_THROW;
+
+ PTR_Object obj(TO_TADDR(addr));
+ TypeHandleToExpandedTypeInfoImpl(boxed, vmAppDomain, obj->GetGCSafeTypeHandle(), pTypeInfo);
+}
+
+// DacDbi API: use a type handle to get the information needed to create the corresponding RS CordbType instance
+void DacDbiInterfaceImpl::TypeHandleToExpandedTypeInfo(AreValueTypesBoxed boxed,
+ VMPTR_AppDomain vmAppDomain,
+ VMPTR_TypeHandle vmTypeHandle,
+ DebuggerIPCE_ExpandedTypeData * pTypeInfo)
+{
+ DD_ENTER_MAY_THROW;
+
+
+ TypeHandle typeHandle = TypeHandle::FromPtr(vmTypeHandle.GetDacPtr());
+ TypeHandleToExpandedTypeInfoImpl(boxed, vmAppDomain, typeHandle, pTypeInfo);
+}
+
+
+void DacDbiInterfaceImpl::TypeHandleToExpandedTypeInfoImpl(AreValueTypesBoxed boxed,
+ VMPTR_AppDomain vmAppDomain,
+ TypeHandle typeHandle,
+ DebuggerIPCE_ExpandedTypeData * pTypeInfo)
+{
+ AppDomain * pAppDomain = vmAppDomain.GetDacPtr();
+ pTypeInfo->elementType = GetElementType(typeHandle);
+
+ switch (pTypeInfo->elementType)
+ {
+ case ELEMENT_TYPE_ARRAY:
+ case ELEMENT_TYPE_SZARRAY:
+ GetArrayTypeInfo(typeHandle, pTypeInfo, pAppDomain);
+ break;
+
+ case ELEMENT_TYPE_PTR:
+ case ELEMENT_TYPE_BYREF:
+ GetPtrTypeInfo(boxed, typeHandle, pTypeInfo, pAppDomain);
+ break;
+
+ case ELEMENT_TYPE_VALUETYPE:
+ if (boxed == OnlyPrimitivesUnboxed || boxed == AllBoxed)
+ {
+ pTypeInfo->elementType = ELEMENT_TYPE_CLASS;
+ }
+ GetClassTypeInfo(typeHandle, pTypeInfo, pAppDomain);
+ break;
+
+ case ELEMENT_TYPE_CLASS:
+ GetClassTypeInfo(typeHandle, pTypeInfo, pAppDomain);
+ break;
+
+ case ELEMENT_TYPE_FNPTR:
+ GetFnPtrTypeInfo(boxed, typeHandle, pTypeInfo, pAppDomain);
+ break;
+ default:
+ if (boxed == AllBoxed)
+ {
+ pTypeInfo->elementType = ELEMENT_TYPE_CLASS;
+ GetClassTypeInfo(typeHandle, pTypeInfo, pAppDomain);
+ }
+ // else the element type is sufficient
+ break;
+ }
+ LOG((LF_CORDB, LL_INFO10000, "D::THTETI: converted left-side type handle to expanded right-side type info, pTypeInfo->ClassTypeData.typeHandle = 0x%08x.\n", pTypeInfo->ClassTypeData.typeHandle.GetRawPtr()));
+ return;
+} // DacDbiInterfaceImpl::TypeHandleToExpandedTypeInfo
+
+// Get type handle for a TypeDef token, if one exists. For generics this returns the open type.
+VMPTR_TypeHandle DacDbiInterfaceImpl::GetTypeHandle(VMPTR_Module vmModule,
+ mdTypeDef metadataToken)
+{
+ DD_ENTER_MAY_THROW;
+ Module* pModule = vmModule.GetDacPtr();
+ VMPTR_TypeHandle vmTypeHandle = VMPTR_TypeHandle::NullPtr();
+
+ TypeHandle th = ClassLoader::LookupTypeDefOrRefInModule(pModule, metadataToken);
+ if (th.IsNull())
+ {
+ LOG((LF_CORDB, LL_INFO10000, "D::GTH: class isn't loaded.\n"));
+ ThrowHR(CORDBG_E_CLASS_NOT_LOADED);
+ }
+
+ vmTypeHandle.SetDacTargetPtr(th.AsTAddr());
+ return vmTypeHandle;
+}
+
+// DacDbi API: GetAndSendApproxTypeHandle finds the type handle for the layout of the instance fields of an
+// instantiated type if it is available.
+VMPTR_TypeHandle DacDbiInterfaceImpl::GetApproxTypeHandle(TypeInfoList * pTypeData)
+{
+ DD_ENTER_MAY_THROW;
+
+ LOG((LF_CORDB, LL_INFO10000, "D::GATH: getting info.\n"));
+
+
+ TypeDataWalk walk(&((*pTypeData)[0]), pTypeData->Count());
+ TypeHandle typeHandle = walk.ReadLoadedTypeHandle(TypeDataWalk::kGetCanonical);
+ VMPTR_TypeHandle vmTypeHandle = VMPTR_TypeHandle::NullPtr();
+
+ vmTypeHandle.SetDacTargetPtr(typeHandle.AsTAddr());
+ if (!typeHandle.IsNull())
+ {
+ vmTypeHandle.SetDacTargetPtr(typeHandle.AsTAddr());
+ }
+ else
+ {
+ ThrowHR(CORDBG_E_CLASS_NOT_LOADED);
+ }
+
+ LOG((LF_CORDB, LL_INFO10000,
+ "D::GATH: sending result, result = 0x%0x8\n",
+ typeHandle));
+ return vmTypeHandle;
+} // DacDbiInterfaceImpl::GetApproxTypeHandle
+
+// DacDbiInterface API: Get the exact type handle from type data
+HRESULT DacDbiInterfaceImpl::GetExactTypeHandle(DebuggerIPCE_ExpandedTypeData * pTypeData,
+ ArgInfoList * pArgInfo,
+ VMPTR_TypeHandle& vmTypeHandle)
+{
+ DD_ENTER_MAY_THROW;
+
+ LOG((LF_CORDB, LL_INFO10000, "D::GETH: getting info.\n"));
+
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+ vmTypeHandle = vmTypeHandle.NullPtr();
+
+ // convert the type information to a type handle
+ TypeHandle typeHandle = ExpandedTypeInfoToTypeHandle(pTypeData, pArgInfo);
+ _ASSERTE(!typeHandle.IsNull());
+ vmTypeHandle.SetDacTargetPtr(typeHandle.AsTAddr());
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+} // DacDbiInterfaceImpl::GetExactTypeHandle
+
+// Retrieve the generic type params for a given MethodDesc. This function is specifically
+// for stackwalking because it requires the generic type token on the stack.
+void DacDbiInterfaceImpl::GetMethodDescParams(
+ VMPTR_AppDomain vmAppDomain,
+ VMPTR_MethodDesc vmMethodDesc,
+ GENERICS_TYPE_TOKEN genericsToken,
+ UINT32 * pcGenericClassTypeParams,
+ TypeParamsList * pGenericTypeParams)
+{
+ DD_ENTER_MAY_THROW;
+
+ if (vmAppDomain.IsNull() || vmMethodDesc.IsNull())
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ _ASSERTE((pcGenericClassTypeParams != NULL) && (pGenericTypeParams != NULL));
+
+ MethodDesc * pMD = vmMethodDesc.GetDacPtr();
+
+ // Retrieve the number of type parameters for the class and
+ // the number of type parameters for the method itself.
+ // For example, the method Foo<T, U>::Bar<V>() has 2 class type parameters and 1 method type parameters.
+ UINT32 cGenericClassTypeParams = pMD->GetNumGenericClassArgs();
+ UINT32 cGenericMethodTypeParams = pMD->GetNumGenericMethodArgs();
+ UINT32 cTotalGenericTypeParams = cGenericClassTypeParams + cGenericMethodTypeParams;
+
+ // Set the out parameter.
+ *pcGenericClassTypeParams = cGenericClassTypeParams;
+
+ TypeHandle thSpecificClass;
+ MethodDesc * pSpecificMethod;
+
+ // Try to retrieve a more specific MethodDesc and TypeHandle via the generics type token.
+ // The generics token is not always guaranteed to be available.
+ // For example, it may be unavailable in prologs and epilogs.
+ // In dumps, not available can also mean a thrown exception for missing memory.
+ BOOL fExact = FALSE;
+ ALLOW_DATATARGET_MISSING_MEMORY(
+ fExact = Generics::GetExactInstantiationsOfMethodAndItsClassFromCallInformation(
+ pMD,
+ PTR_VOID((TADDR)genericsToken),
+ &thSpecificClass,
+ &pSpecificMethod);
+ );
+ if (!fExact ||
+ !thSpecificClass.GetMethodTable()->SanityCheck() ||
+ !pSpecificMethod->GetMethodTable()->SanityCheck())
+ {
+ // Use the canonical MethodTable and MethodDesc if the exact generics token is not available.
+ thSpecificClass = TypeHandle(pMD->GetMethodTable());
+ pSpecificMethod = pMD;
+ }
+
+ // Retrieve the array of class type parameters and the array of method type parameters.
+ Instantiation classInst = pSpecificMethod->GetExactClassInstantiation(thSpecificClass);
+ Instantiation methodInst = pSpecificMethod->GetMethodInstantiation();
+
+ _ASSERTE((classInst.IsEmpty()) == (cGenericClassTypeParams == 0));
+ _ASSERTE((methodInst.IsEmpty()) == (cGenericMethodTypeParams == 0));
+
+ // allocate memory for the return array
+ pGenericTypeParams->Alloc(cTotalGenericTypeParams);
+
+ for (UINT32 i = 0; i < cTotalGenericTypeParams; i++)
+ {
+ // Retrieve the current type parameter depending on the index.
+ TypeHandle thCurrent;
+ if (i < cGenericClassTypeParams)
+ {
+ thCurrent = classInst[i];
+ }
+ else
+ {
+ thCurrent = methodInst[i - cGenericClassTypeParams];
+ }
+
+ // There is the possiblity that we'll get this far with a dump and not fail, but still
+ // not be able to get full info for a particular param.
+ EX_TRY_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER
+ {
+ // Fill in the struct using the TypeHandle of the current type parameter if we can.
+ VMPTR_TypeHandle vmTypeHandle = VMPTR_TypeHandle::NullPtr();
+ vmTypeHandle.SetDacTargetPtr(thCurrent.AsTAddr());
+ TypeHandleToExpandedTypeInfo(NoValueTypeBoxing,
+ vmAppDomain,
+ vmTypeHandle,
+ &((*pGenericTypeParams)[i]));
+ }
+ EX_CATCH_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER
+ {
+ // On failure for a particular type, default it back to System.__Canon.
+ VMPTR_TypeHandle vmTHCanon = VMPTR_TypeHandle::NullPtr();
+ TypeHandle thCanon = TypeHandle(g_pCanonMethodTableClass);
+ vmTHCanon.SetDacTargetPtr(thCanon.AsTAddr());
+ TypeHandleToExpandedTypeInfo(NoValueTypeBoxing,
+ vmAppDomain,
+ vmTHCanon,
+ &((*pGenericTypeParams)[i]));
+ }
+ EX_END_CATCH_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER
+ }
+}
+
+//-----------------------------------------------------------------------------
+// DacDbiInterfaceImpl::GetClassOrValueTypeHandle
+// get a typehandle for a class or valuetype from basic type data (metadata token
+// and domain file).
+// Arguments:
+// input: pData - contains the metadata token and domain file
+// Return value: the type handle for the corresponding type
+//-----------------------------------------------------------------------------
+TypeHandle DacDbiInterfaceImpl::GetClassOrValueTypeHandle(DebuggerIPCE_BasicTypeData * pData)
+{
+ TypeHandle typeHandle;
+
+ // if we already have a type handle, just return it
+ if (!pData->vmTypeHandle.IsNull())
+ {
+ typeHandle = TypeHandle::FromPtr(pData->vmTypeHandle.GetDacPtr());
+ }
+ // otherwise, have the loader look it up using the metadata token and domain file
+ else
+ {
+ DomainFile * pDomainFile = pData->vmDomainFile.GetDacPtr();
+ Module * pModule = pDomainFile->GetModule();
+
+ typeHandle = ClassLoader::LookupTypeDefOrRefInModule(pModule, pData->metadataToken);
+ if (typeHandle.IsNull())
+ {
+ LOG((LF_CORDB, LL_INFO10000, "D::BTITTH: class isn't loaded.\n"));
+ ThrowHR(CORDBG_E_CLASS_NOT_LOADED);
+ }
+
+ _ASSERTE(typeHandle.GetNumGenericArgs() == 0);
+ }
+
+ return typeHandle;
+
+} // DacDbiInterfaceImpl::GetClassOrValueTypeHandle
+
+//-----------------------------------------------------------------------------
+// DacDbiInterfaceImpl::GetExactArrayTypeHandle
+// get an exact type handle for an array type
+// Arguments:
+// input: pTopLevelTypeData - type information for a top-level array type
+// pArgInfo - contains the following information:
+// m_genericArgsCount - number of generic parameters for the element type--this should be 1
+// m_pGenericArgs - pointer to the generic parameter for the element type--this is
+// effectively a one-element list. These are the actual parameters
+// Return Value: the exact type handle for the type
+//-----------------------------------------------------------------------------
+TypeHandle DacDbiInterfaceImpl::GetExactArrayTypeHandle(DebuggerIPCE_ExpandedTypeData * pTopLevelTypeData,
+ ArgInfoList * pArgInfo)
+{
+ TypeHandle typeArg;
+
+ _ASSERTE(pArgInfo->Count() == 1);
+
+ // get the type handle for the element type
+ typeArg = BasicTypeInfoToTypeHandle(&((*pArgInfo)[0]));
+
+ // get the exact type handle for the array type
+ return FindLoadedArrayType(pTopLevelTypeData->elementType,
+ typeArg,
+ pTopLevelTypeData->ArrayTypeData.arrayRank);
+
+} // DacDbiInterfaceImpl::GetExactArrayTypeHandle
+
+//-----------------------------------------------------------------------------
+// DacDbiInterfaceImpl::GetExactPtrOrByRefTypeHandle
+// get an exact type handle for a PTR or BYREF type
+// Arguments:
+// input: pTopLevelTypeData - type information for the PTR or BYREF type
+// pArgInfo - contains the following information:
+// m_genericArgsCount - number of generic parameters for the element type--this should be 1
+// m_pGenericArgs - pointer to the generic parameter for the element type--this is
+// effectively a one-element list. These are the actual parameters
+// Return Value: the exact type handle for the type
+//-----------------------------------------------------------------------------
+TypeHandle DacDbiInterfaceImpl::GetExactPtrOrByRefTypeHandle(DebuggerIPCE_ExpandedTypeData * pTopLevelTypeData,
+ ArgInfoList * pArgInfo)
+{
+ TypeHandle typeArg;
+ _ASSERTE(pArgInfo->Count() == 1);
+
+ // get the type handle for the referent
+ typeArg = BasicTypeInfoToTypeHandle(&((*pArgInfo)[0]));
+
+ // get the exact type handle for the PTR or BYREF type
+ return FindLoadedPointerOrByrefType(pTopLevelTypeData->elementType, typeArg);
+
+} // DacDbiInterfaceImpl::GetExactPtrOrByRefTypeHandle
+
+//-----------------------------------------------------------------------------
+// DacDbiInterfaceImpl::GetExactClassTypeHandle
+// get an exact type handle for a CLASS or VALUETYPE type
+// Arguments:
+// input: pTopLevelTypeData - type information for the CLASS or VALUETYPE type
+// pArgInfo - contains the following information:
+// m_genericArgsCount - number of generic parameters for the class
+// m_pGenericArgs - list of generic parameters for the class--these
+// are the actual parameters
+// Return Value: the exact type handle for the type
+//-----------------------------------------------------------------------------
+TypeHandle DacDbiInterfaceImpl::GetExactClassTypeHandle(DebuggerIPCE_ExpandedTypeData * pTopLevelTypeData,
+ ArgInfoList * pArgInfo)
+{
+ Module * pModule = pTopLevelTypeData->ClassTypeData.vmModule.GetDacPtr();
+ int argCount = pArgInfo->Count();
+
+ TypeHandle typeConstructor =
+ ClassLoader::LookupTypeDefOrRefInModule(pModule, pTopLevelTypeData->ClassTypeData.metadataToken);
+
+ // If we can't find the class, throw the appropriate HR. Note: if the class is not a value class and
+ // the class is also not restored, then we must pretend that the class is still not loaded. We are gonna let
+ // unrestored value classes slide, though, and special case access to the class's parent below.
+ if (typeConstructor.IsNull())
+ {
+ LOG((LF_CORDB, LL_INFO10000, "D::ETITTH: class isn't loaded.\n"));
+ ThrowHR(CORDBG_E_CLASS_NOT_LOADED);
+ }
+
+ // if there are no generic parameters, we already have the correct type handle
+ if (argCount == 0)
+ {
+ return typeConstructor;
+ }
+
+ // we have generic parameters--first validate we have a number consistent with the list
+ // of parameters we received
+ if ((unsigned int)argCount != typeConstructor.GetNumGenericArgs())
+ {
+ LOG((LF_CORDB, LL_INFO10000,
+ "D::ETITTH: wrong number of type parameters, %d given, %d expected\n",
+ argCount, typeConstructor.GetNumGenericArgs()));
+ _ASSERTE((unsigned int)argCount == typeConstructor.GetNumGenericArgs());
+ ThrowHR(E_FAIL);
+ }
+
+ // now we allocate a list to store the type handles for each parameter
+ S_UINT32 allocSize = S_UINT32(argCount) * S_UINT32(sizeof(TypeHandle));
+ if (allocSize.IsOverflow())
+ {
+ ThrowHR(E_OUTOFMEMORY);
+ }
+
+ NewHolder<TypeHandle> pInst(new TypeHandle[allocSize.Value()]);
+
+ // convert the type information for each parameter to its corresponding type handle
+ // and store it in the list
+ for (unsigned int i = 0; i < (unsigned int)argCount; i++)
+ {
+ pInst[i] = BasicTypeInfoToTypeHandle(&((*pArgInfo)[i]));
+ }
+
+ // Finally, we find the type handle corresponding to this particular instantiation
+ return FindLoadedInstantiation(typeConstructor.GetModule(),
+ typeConstructor.GetCl(),
+ argCount,
+ pInst);
+
+} // DacDbiInterfaceImpl::GetExactClassTypeHandle
+
+//-----------------------------------------------------------------------------
+// DacDbiInterfaceImpl::GetExactFnPtrTypeHandle
+// get an exact type handle for a FNPTR type
+// Arguments:
+// input: pArgInfo - Contains the following information:
+// m_genericArgsCount - number of generic parameters for the referent
+// m_pGenericArgs - list of generic parameters for the referent--these
+// are the actual parameters for the function signature
+// Return Value: the exact type handle for the type
+//-----------------------------------------------------------------------------
+TypeHandle DacDbiInterfaceImpl::GetExactFnPtrTypeHandle(ArgInfoList * pArgInfo)
+{
+ // allocate a list to store the type handles for each parameter
+ S_UINT32 allocSize = S_UINT32(pArgInfo->Count()) * S_UINT32(sizeof(TypeHandle));
+ if( allocSize.IsOverflow() )
+ {
+ ThrowHR(E_OUTOFMEMORY);
+ }
+ NewHolder<TypeHandle> pInst(new TypeHandle[allocSize.Value()]);
+
+ // convert the type information for each parameter to its corresponding type handle
+ // and store it in the list
+ for (int i = 0; i < pArgInfo->Count(); i++)
+ {
+ pInst[i] = BasicTypeInfoToTypeHandle(&((*pArgInfo)[i]));
+ }
+
+ // find the type handle corresponding to this particular FNPTR
+ return FindLoadedFnptrType(pArgInfo->Count(), pInst);
+} // DacDbiInterfaceImpl::GetExactFnPtrTypeHandle
+
+//-----------------------------------------------------------------------------
+// DacDbiInterfaceImpl::BasicTypeInfoToTypeHandle
+// Convert basic type info for a type parameter that came from a top-level type to
+// the corresponding type handle. If the type parameter is an array or pointer
+// type, we simply extract the LS type handle from the VMPTR_TypeHandle that is
+// part of the type information. If the type parameter is a class or value type,
+// we use the metadata token and domain file in the type info to look up the
+// appropriate type handle. If the type parameter is any other types, we get the
+// type handle by having the loader look up the type handle for the element type.
+// Arguments:
+// input: pArgTypeData - basic type information for the type.
+// Return Value: the type handle for the type.
+//-----------------------------------------------------------------------------
+TypeHandle DacDbiInterfaceImpl::BasicTypeInfoToTypeHandle(DebuggerIPCE_BasicTypeData * pArgTypeData)
+{
+ LOG((LF_CORDB, LL_INFO10000,
+ "D::BTITTH: expanding basic right-side type to left-side type, ELEMENT_TYPE: %d.\n",
+ pArgTypeData->elementType));
+ TypeHandle typeHandle = TypeHandle();
+
+ switch (pArgTypeData->elementType)
+ {
+ case ELEMENT_TYPE_ARRAY:
+ case ELEMENT_TYPE_SZARRAY:
+ case ELEMENT_TYPE_PTR:
+ case ELEMENT_TYPE_BYREF:
+ case ELEMENT_TYPE_FNPTR:
+ _ASSERTE(!pArgTypeData->vmTypeHandle.IsNull());
+ typeHandle = TypeHandle::FromPtr(pArgTypeData->vmTypeHandle.GetDacPtr());
+ break;
+
+ case ELEMENT_TYPE_CLASS:
+ case ELEMENT_TYPE_VALUETYPE:
+ typeHandle = GetClassOrValueTypeHandle(pArgTypeData);
+ break;
+
+ default:
+ typeHandle = FindLoadedElementType(pArgTypeData->elementType);
+ break;
+ }
+ if (typeHandle.IsNull())
+ {
+ ThrowHR(CORDBG_E_CLASS_NOT_LOADED);
+ }
+ return typeHandle;
+} // DacDbiInterfaceImpl::BasicTypeInfoToTypeHandle
+
+
+//-----------------------------------------------------------------------------
+// DacDbiInterfaceImpl::ExpandedTypeInfoToTypeHandle
+// Convert type information for a top-level type to an exact type handle. This
+// information includes information about the element type if the top-level type is
+// an array type, the referent if the top-level type is a pointer type, or actual
+// parameters if the top-level type is a generic class or value type.
+// Arguments:
+// input: pTopLevelTypeData - type information for the top-level type
+// pArgInfo - contains the following information:
+// m_genericArtsCount - number of parameters
+// m_pGenericArgs - list of actual parameters
+// Return Value: the exact type handle corresponding to the type represented by
+// pTopLevelTypeData
+//-----------------------------------------------------------------------------
+TypeHandle DacDbiInterfaceImpl::ExpandedTypeInfoToTypeHandle(DebuggerIPCE_ExpandedTypeData * pTopLevelTypeData,
+ ArgInfoList * pArgInfo)
+{
+ WRAPPER_NO_CONTRACT;
+
+ LOG((LF_CORDB, LL_INFO10000,
+ "D::ETITTH: expanding right-side type to left-side type, ELEMENT_TYPE: %d.\n",
+ pData->elementType));
+
+ TypeHandle typeHandle = TypeHandle();
+ // depending on the top-level type, get the type handle incorporating information about any type arguments
+ switch (pTopLevelTypeData->elementType)
+ {
+ case ELEMENT_TYPE_ARRAY:
+ case ELEMENT_TYPE_SZARRAY:
+ typeHandle = GetExactArrayTypeHandle(pTopLevelTypeData, pArgInfo);
+ break;
+
+ case ELEMENT_TYPE_PTR:
+ case ELEMENT_TYPE_BYREF:
+ typeHandle = GetExactPtrOrByRefTypeHandle(pTopLevelTypeData, pArgInfo);
+ break;
+
+ case ELEMENT_TYPE_CLASS:
+ case ELEMENT_TYPE_VALUETYPE:
+ typeHandle = GetExactClassTypeHandle(pTopLevelTypeData, pArgInfo);
+ break;
+ case ELEMENT_TYPE_FNPTR:
+ typeHandle = GetExactFnPtrTypeHandle(pArgInfo);
+ break;
+ default:
+ typeHandle = FindLoadedElementType(pTopLevelTypeData->elementType);
+ break;
+ } // end switch (pData->elementType)
+
+ if (typeHandle.IsNull())
+ {
+ // This may fail because there are cases when a type can be used (and so visible to the
+ // debugger), but not yet loaded to the point of being available in the EETypeHashTable.
+ // For example, generic value types (without explicit constructors) may not need their
+ // exact instantiation type to be loaded in order to be used as a field of an object
+ // created on the heap
+ LOG((LF_CORDB, LL_INFO10000, "D::ETITTH: type isn't loaded.\n"));
+ ThrowHR(CORDBG_E_CLASS_NOT_LOADED);
+ }
+ return typeHandle;
+} // DacDbiInterfaceImpl::ExpandedTypeInfoToTypeHandle
+
+// ----------------------------------------------------------------------------
+// DacDbi API: GetThreadOrContextStaticAddress
+// Get the target field address of a context or thread local static.
+//
+// Notes:
+// The address is constant and could be cached.
+//
+// If this is a context-static, the function uses the thread's current context.
+// [This is important because it means that you can't lookup a context static
+// unless you have a thread in that context. If anybody actually cared about contexts,
+// we might have to revise this in the future]
+//
+// This can commonly fail, in which case, it will return NULL.
+// ----------------------------------------------------------------------------
+CORDB_ADDRESS DacDbiInterfaceImpl::GetThreadOrContextStaticAddress(VMPTR_FieldDesc vmField,
+ VMPTR_Thread vmRuntimeThread)
+{
+ DD_ENTER_MAY_THROW;
+
+ Thread * pRuntimeThread = vmRuntimeThread.GetDacPtr();
+ PTR_FieldDesc pFieldDesc = vmField.GetDacPtr();
+ TADDR fieldAddress = NULL;
+
+ _ASSERTE(pRuntimeThread != NULL);
+
+ // Find out whether the field is thread local or context local and get its
+ // address.
+ if (pFieldDesc->IsThreadStatic())
+ {
+ fieldAddress = pRuntimeThread->GetStaticFieldAddrNoCreate(pFieldDesc, NULL);
+ }
+#ifdef FEATURE_REMOTING
+ else if (pFieldDesc->IsContextStatic())
+ {
+ fieldAddress = PTR_TO_TADDR(pRuntimeThread->GetContext()->GetStaticFieldAddrNoCreate(pFieldDesc));
+ }
+#endif
+ else
+ {
+ // In case we have more special cases added later, this will allow us to notice the need to
+ // update this function.
+ ThrowHR(E_NOTIMPL);
+ }
+ return fieldAddress;
+
+} // DacDbiInterfaceImpl::GetThreadOrContextStaticAddress
+
+ // Get the target field address of a collectible types static.
+CORDB_ADDRESS DacDbiInterfaceImpl::GetCollectibleTypeStaticAddress(VMPTR_FieldDesc vmField,
+ VMPTR_AppDomain vmAppDomain)
+{
+ DD_ENTER_MAY_THROW;
+
+ AppDomain * pAppDomain = vmAppDomain.GetDacPtr();
+ PTR_FieldDesc pFieldDesc = vmField.GetDacPtr();
+ _ASSERTE(pAppDomain != NULL);
+
+ //
+ // Verify this field is of the right type
+ //
+ if(!pFieldDesc->IsStatic() ||
+ pFieldDesc->IsSpecialStatic())
+ {
+ _ASSERTE(!"BUG: Unsupported static field type for collectible types");
+ }
+
+ //
+ // Check that the data is available
+ //
+ /* TODO: Ideally we should be checking if the class is allocated first, however
+ we don't appear to be doing this even for non-collectible statics and
+ we have never seen an issue.
+ */
+
+ //
+ // Get the address
+ //
+ PTR_VOID base = pFieldDesc->GetBaseInDomain(pAppDomain);
+ if (base == PTR_NULL)
+ {
+ return PTR_HOST_TO_TADDR(NULL);
+ }
+
+ //
+ // Store the result and return
+ //
+ PTR_VOID addr = pFieldDesc->GetStaticAddressHandle(base);
+ return PTR_TO_TADDR(addr);
+
+} // DacDbiInterfaceImpl::GetCollectibleTypeStaticAddress
+
+// DacDbi API: GetTypeHandleParams
+// - gets the necessary data for a type handle, i.e. its type parameters, e.g. "String" and "List<int>" from the type handle
+// for "Dict<String,List<int>>", and sends it back to the right side.
+// - pParams is allocated and initialized by this function
+// - This should not fail except for OOM
+void DacDbiInterfaceImpl::GetTypeHandleParams(VMPTR_AppDomain vmAppDomain,
+ VMPTR_TypeHandle vmTypeHandle,
+ TypeParamsList * pParams)
+{
+ DD_ENTER_MAY_THROW
+
+ TypeHandle typeHandle = TypeHandle::FromPtr(vmTypeHandle.GetDacPtr());
+ LOG((LF_CORDB, LL_INFO10000, "D::GTHP: getting type parameters for 0x%08x 0x%0x8.\n",
+ vmAppDomain.GetDacPtr(), typeHandle.AsPtr()));
+
+
+ // Find the class given its type handle.
+ _ASSERTE(pParams->IsEmpty());
+ pParams->Alloc(typeHandle.GetNumGenericArgs());
+
+ // collect type information for each type parameter
+ for (int i = 0; i < pParams->Count(); ++i)
+ {
+ VMPTR_TypeHandle thInst = VMPTR_TypeHandle::NullPtr();
+ thInst.SetDacTargetPtr(typeHandle.GetInstantiation()[i].AsTAddr());
+
+ TypeHandleToExpandedTypeInfo(NoValueTypeBoxing,
+ vmAppDomain,
+ thInst,
+ &((*pParams)[i]));
+ }
+
+ LOG((LF_CORDB, LL_INFO10000, "D::GTHP: sending result"));
+} // DacDbiInterfaceImpl::GetTypeHandleParams
+
+//-----------------------------------------------------------------------------
+// DacDbi API: GetSimpleType
+// gets the metadata token and domain file corresponding to a simple type
+//-----------------------------------------------------------------------------
+void DacDbiInterfaceImpl::GetSimpleType(VMPTR_AppDomain vmAppDomain,
+ CorElementType simpleType,
+ mdTypeDef *pMetadataToken,
+ VMPTR_Module *pVmModule,
+ VMPTR_DomainFile *pVmDomainFile)
+{
+ DD_ENTER_MAY_THROW;
+
+ AppDomain *pAppDomain = vmAppDomain.GetDacPtr();
+
+ // if we fail to get either a valid type handle or module, we will want to send back
+ // a NULL domain file too, so we'll to preinitialize this here.
+ _ASSERTE(pVmDomainFile != NULL);
+ *pVmDomainFile = VMPTR_DomainFile::NullPtr();
+ // FindLoadedElementType will return NULL if the type hasn't been loaded yet.
+ TypeHandle typeHandle = FindLoadedElementType(simpleType);
+
+ if (typeHandle.IsNull())
+ {
+ ThrowHR(CORDBG_E_CLASS_NOT_LOADED);
+ }
+ else
+ {
+ _ASSERTE(pMetadataToken != NULL);
+ *pMetadataToken = typeHandle.GetCl();
+
+ Module * pModule = typeHandle.GetModule();
+ if (pModule == NULL)
+ ThrowHR(CORDBG_E_TARGET_INCONSISTENT);
+
+ pVmModule->SetHostPtr(pModule);
+
+ if (pAppDomain)
+ {
+ pVmDomainFile->SetHostPtr(pModule->GetDomainFile(pAppDomain));
+ if (pVmDomainFile->IsNull())
+ ThrowHR(CORDBG_E_TARGET_INCONSISTENT);
+ }
+ }
+
+ LOG((LF_CORDB, LL_INFO10000, "D::STI: sending result.\n"));
+} // DacDbiInterfaceImpl::GetSimpleType
+
+BOOL DacDbiInterfaceImpl::IsExceptionObject(VMPTR_Object vmObject)
+{
+ DD_ENTER_MAY_THROW;
+
+ Object* objPtr = vmObject.GetDacPtr();
+ MethodTable* pMT = objPtr->GetMethodTable();
+
+ return IsExceptionObject(pMT);
+}
+
+BOOL DacDbiInterfaceImpl::IsExceptionObject(MethodTable* pMT)
+{
+ PTR_MethodTable pExMT = g_pExceptionClass;
+
+ TADDR targetMT = dac_cast<TADDR>(pMT);
+ TADDR exceptionMT = dac_cast<TADDR>(pExMT);
+
+ do
+ {
+ if (targetMT == exceptionMT)
+ return TRUE;
+
+ pMT = pMT->GetParentMethodTable();
+ targetMT = dac_cast<TADDR>(pMT);
+ } while (pMT);
+
+ return FALSE;
+}
+
+void DacDbiInterfaceImpl::GetStackFramesFromException(VMPTR_Object vmObject, DacDbiArrayList<DacExceptionCallStackData>& dacStackFrames)
+{
+ DD_ENTER_MAY_THROW;
+
+ PTR_Object objPtr = vmObject.GetDacPtr();
+
+#ifdef _DEBUG
+ // ensure we have an Exception object
+ MethodTable* pMT = objPtr->GetMethodTable();
+ _ASSERTE(IsExceptionObject(pMT));
+#endif
+
+ OBJECTREF objRef = ObjectToOBJECTREF(objPtr);
+
+ DebugStackTrace::GetStackFramesData stackFramesData;
+
+ stackFramesData.pDomain = NULL;
+ stackFramesData.skip = 0;
+ stackFramesData.NumFramesRequested = 0;
+
+ DebugStackTrace::GetStackFramesFromException(&objRef, &stackFramesData);
+
+ INT32 dacStackFramesLength = stackFramesData.cElements;
+
+ if (dacStackFramesLength > 0)
+ {
+ dacStackFrames.Alloc(dacStackFramesLength);
+
+ for (INT32 index = 0; index < dacStackFramesLength; ++index)
+ {
+ DebugStackTrace::DebugStackTraceElement const& currentElement = stackFramesData.pElements[index];
+ DacExceptionCallStackData& currentFrame = dacStackFrames[index];
+
+ Module* pModule = currentElement.pFunc->GetModule();
+ BaseDomain* pBaseDomain = currentElement.pFunc->GetAssembly()->GetDomain();
+
+ AppDomain* pDomain = NULL;
+ DomainFile* pDomainFile = NULL;
+
+ if (pBaseDomain->IsSharedDomain())
+ pDomain = SystemDomain::System()->DefaultDomain();
+ else
+ pDomain = pBaseDomain->AsAppDomain();
+
+ _ASSERTE(pDomain != NULL);
+
+ pDomainFile = pModule->FindDomainFile(pDomain);
+ _ASSERTE(pDomainFile != NULL);
+
+ currentFrame.vmAppDomain.SetHostPtr(pDomain);
+ currentFrame.vmDomainFile.SetHostPtr(pDomainFile);
+ currentFrame.ip = currentElement.ip;
+ currentFrame.methodDef = currentElement.pFunc->GetMemberDef();
+#if defined(FEATURE_EXCEPTIONDISPATCHINFO)
+ currentFrame.isLastForeignExceptionFrame = currentElement.fIsLastFrameFromForeignStackTrace;
+#else
+ // for CLRs lacking exception dispatch info just set it to 0
+ currentFrame.isLastForeignExceptionFrame = 0;
+#endif
+ }
+ }
+}
+
+#ifdef FEATURE_COMINTEROP
+
+PTR_RCW GetRcwFromVmptrObject(VMPTR_Object vmObject)
+{
+ PTR_RCW pRCW = NULL;
+
+ Object* objPtr = vmObject.GetDacPtr();
+
+ PTR_SyncBlock pSyncBlock = NULL;
+ pSyncBlock = objPtr->PassiveGetSyncBlock();
+ if (pSyncBlock == NULL)
+ return pRCW;
+
+ PTR_InteropSyncBlockInfo pInfo = NULL;
+ pInfo = pSyncBlock->GetInteropInfoNoCreate();
+ if (pInfo == NULL)
+ return pRCW;
+
+ pRCW = dac_cast<PTR_RCW>(pInfo->DacGetRawRCW());
+
+ return pRCW;
+}
+
+#endif
+
+BOOL DacDbiInterfaceImpl::IsRcw(VMPTR_Object vmObject)
+{
+#ifdef FEATURE_COMINTEROP
+ DD_ENTER_MAY_THROW;
+ return GetRcwFromVmptrObject(vmObject) != NULL;
+#else
+ return FALSE;
+#endif // FEATURE_COMINTEROP
+
+}
+
+void DacDbiInterfaceImpl::GetRcwCachedInterfaceTypes(
+ VMPTR_Object vmObject,
+ VMPTR_AppDomain vmAppDomain,
+ BOOL bIInspectableOnly,
+ DacDbiArrayList<DebuggerIPCE_ExpandedTypeData> * pDacInterfaces)
+{
+#ifdef FEATURE_COMINTEROP
+
+ DD_ENTER_MAY_THROW;
+
+ Object* objPtr = vmObject.GetDacPtr();
+
+ InlineSArray<PTR_MethodTable, INTERFACE_ENTRY_CACHE_SIZE> rgMT;
+
+ PTR_RCW pRCW = GetRcwFromVmptrObject(vmObject);
+ if (pRCW != NULL)
+ {
+ pRCW->GetCachedInterfaceTypes(bIInspectableOnly, &rgMT);
+
+ pDacInterfaces->Alloc(rgMT.GetCount());
+
+ for (COUNT_T i = 0; i < rgMT.GetCount(); ++i)
+ {
+ // There is the possiblity that we'll get this far with a dump and not fail, but still
+ // not be able to get full info for a particular param.
+ EX_TRY_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER
+ {
+ // Fill in the struct using the current TypeHandle
+ VMPTR_TypeHandle vmTypeHandle = VMPTR_TypeHandle::NullPtr();
+ TypeHandle th = TypeHandle::FromTAddr(dac_cast<TADDR>(rgMT[i]));
+ vmTypeHandle.SetDacTargetPtr(th.AsTAddr());
+ TypeHandleToExpandedTypeInfo(NoValueTypeBoxing,
+ vmAppDomain,
+ vmTypeHandle,
+ &((*pDacInterfaces)[i]));
+ }
+ EX_CATCH_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER
+ {
+ // On failure for a particular type, default it to NULL.
+ (*pDacInterfaces)[i].elementType = ELEMENT_TYPE_END;
+ }
+ EX_END_CATCH_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER
+
+ }
+
+ }
+ else
+#endif // FEATURE_COMINTEROP
+ {
+ pDacInterfaces->Alloc(0);
+ }
+}
+
+void DacDbiInterfaceImpl::GetRcwCachedInterfacePointers(
+ VMPTR_Object vmObject,
+ BOOL bIInspectableOnly,
+ DacDbiArrayList<CORDB_ADDRESS> * pDacItfPtrs)
+{
+#ifdef FEATURE_COMINTEROP
+
+ DD_ENTER_MAY_THROW;
+
+ Object* objPtr = vmObject.GetDacPtr();
+
+ InlineSArray<TADDR, INTERFACE_ENTRY_CACHE_SIZE> rgUnks;
+
+ PTR_RCW pRCW = GetRcwFromVmptrObject(vmObject);
+ if (pRCW != NULL)
+ {
+ pRCW->GetCachedInterfacePointers(bIInspectableOnly, &rgUnks);
+
+ pDacItfPtrs->Alloc(rgUnks.GetCount());
+
+ for (COUNT_T i = 0; i < rgUnks.GetCount(); ++i)
+ {
+ (*pDacItfPtrs)[i] = (CORDB_ADDRESS)(rgUnks[i]);
+ }
+
+ }
+ else
+#endif // FEATURE_COMINTEROP
+ {
+ pDacItfPtrs->Alloc(0);
+ }
+}
+
+void DacDbiInterfaceImpl::GetCachedWinRTTypesForIIDs(
+ VMPTR_AppDomain vmAppDomain,
+ DacDbiArrayList<GUID> & iids,
+ OUT DacDbiArrayList<DebuggerIPCE_ExpandedTypeData> * pTypes)
+{
+#ifdef FEATURE_COMINTEROP
+
+ DD_ENTER_MAY_THROW;
+
+ AppDomain * pAppDomain = vmAppDomain.GetDacPtr();
+ if (pAppDomain->IsUnloading())
+ {
+ return;
+ }
+
+ {
+ pTypes->Alloc(iids.Count());
+
+ for (int i = 0; i < iids.Count(); ++i)
+ {
+ // There is the possiblity that we'll get this far with a dump and not fail, but still
+ // not be able to get full info for a particular param.
+ EX_TRY_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER
+ {
+ PTR_MethodTable pMT = pAppDomain->LookupTypeByGuid(iids[i]);
+
+ // Fill in the struct using the current TypeHandle
+ VMPTR_TypeHandle vmTypeHandle = VMPTR_TypeHandle::NullPtr();
+ TypeHandle th = TypeHandle::FromTAddr(dac_cast<TADDR>(pMT));
+ vmTypeHandle.SetDacTargetPtr(th.AsTAddr());
+ TypeHandleToExpandedTypeInfo(NoValueTypeBoxing,
+ vmAppDomain,
+ vmTypeHandle,
+ &((*pTypes)[i]));
+ }
+ EX_CATCH_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER
+ {
+ // On failure for a particular type, default it to NULL.
+ (*pTypes)[i].elementType = ELEMENT_TYPE_END;
+ }
+ EX_END_CATCH_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER
+ }
+ }
+#else // FEATURE_COMINTEROP
+ {
+ pTypes->Alloc(0);
+ }
+#endif // FEATURE_COMINTEROP
+}
+
+void DacDbiInterfaceImpl::GetCachedWinRTTypes(
+ VMPTR_AppDomain vmAppDomain,
+ OUT DacDbiArrayList<GUID> * pGuids,
+ OUT DacDbiArrayList<DebuggerIPCE_ExpandedTypeData> * pTypes)
+{
+#ifdef FEATURE_COMINTEROP
+
+ DD_ENTER_MAY_THROW;
+
+ AppDomain * pAppDomain = vmAppDomain.GetDacPtr();
+ if (pAppDomain->IsUnloading())
+ {
+ return;
+ }
+
+ InlineSArray<PTR_MethodTable, 32> rgMT;
+ InlineSArray<GUID, 32> rgGuid;
+
+ {
+ pAppDomain->GetCachedWinRTTypes(&rgMT, &rgGuid, 0, NULL);
+
+ pTypes->Alloc(rgMT.GetCount());
+ pGuids->Alloc(rgGuid.GetCount());
+
+ for (COUNT_T i = 0; i < rgMT.GetCount(); ++i)
+ {
+ // There is the possiblity that we'll get this far with a dump and not fail, but still
+ // not be able to get full info for a particular param.
+ EX_TRY_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER
+ {
+ // Fill in the struct using the current TypeHandle
+ VMPTR_TypeHandle vmTypeHandle = VMPTR_TypeHandle::NullPtr();
+ TypeHandle th = TypeHandle::FromTAddr(dac_cast<TADDR>(rgMT[i]));
+ vmTypeHandle.SetDacTargetPtr(th.AsTAddr());
+ TypeHandleToExpandedTypeInfo(NoValueTypeBoxing,
+ vmAppDomain,
+ vmTypeHandle,
+ &((*pTypes)[i]));
+ }
+ EX_CATCH_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER
+ {
+ // On failure for a particular type, default it to NULL.
+ (*pTypes)[i].elementType = ELEMENT_TYPE_END;
+ }
+ EX_END_CATCH_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER
+ (*pGuids)[i] = rgGuid[i];
+
+ }
+
+ }
+#else // FEATURE_COMINTEROP
+ {
+ pTypes->Alloc(0);
+ }
+#endif // FEATURE_COMINTEROP
+}
+
+//-----------------------------------------------------------------------------
+// DacDbiInterfaceImpl::FindField
+// Finds information for a particular class field
+// Arguments:
+// input: thApprox - type handle for the type to which the field belongs
+// fldToken - metadata token for the field
+// Return Value: FieldDesc containing information for the field if found or NULL otherwise
+//-----------------------------------------------------------------------------
+PTR_FieldDesc DacDbiInterfaceImpl::FindField(TypeHandle thApprox, mdFieldDef fldToken)
+{
+ EncApproxFieldDescIterator fdIterator(thApprox.GetMethodTable(),
+ ApproxFieldDescIterator::ALL_FIELDS,
+ FALSE); // don't fixup EnC (we can't, we're stopped)
+
+ PTR_FieldDesc pCurrentFD;
+
+ while ((pCurrentFD = fdIterator.Next()) != NULL)
+ {
+ // We're looking for a specific fieldDesc, see if we got it.
+ if (pCurrentFD->GetMemberDef() == fldToken)
+ {
+ return pCurrentFD;
+ }
+ }
+
+ // we never found it...
+ return NULL;
+} // DacDbiInterfaceImpl::FindField
+
+//-----------------------------------------------------------------------------
+// DacDbiInterfaceImpl::GetEnCFieldDesc
+// Get the FieldDesc corresponding to a particular EnC field token
+// Arguments:
+// input: pEnCFieldInfo
+// Return Value: pointer to the FieldDesc that corresponds to the EnC field
+// Note: this function may throw
+//-----------------------------------------------------------------------------
+FieldDesc * DacDbiInterfaceImpl::GetEnCFieldDesc(const EnCHangingFieldInfo * pEnCFieldInfo)
+{
+ FieldDesc * pFD = NULL;
+
+ DomainFile * pDomainFile = pEnCFieldInfo->GetObjectTypeData().vmDomainFile.GetDacPtr();
+ Module * pModule = pDomainFile->GetModule();
+
+ // get the type handle for the object
+ TypeHandle typeHandle = ClassLoader::LookupTypeDefOrRefInModule(pModule,
+ pEnCFieldInfo->GetObjectTypeData().metadataToken);
+ if (typeHandle == NULL)
+ {
+ ThrowHR(CORDBG_E_CLASS_NOT_LOADED);
+ }
+ // and find the field desc
+ pFD = FindField(typeHandle, pEnCFieldInfo->GetFieldToken());
+ if (pFD == NULL)
+ {
+ // FieldDesc is not yet available, so can't get EnC field info
+ ThrowHR(CORDBG_E_ENC_HANGING_FIELD);
+ }
+ return pFD;
+
+} // DacDbiInterfaceImpl::GetEnCFieldDesc
+
+//-----------------------------------------------------------------------------
+// DacDbiInterfaceImpl::GetPtrToEnCField
+// Get the address of a field added with EnC.
+// Arguments:
+// input: pFD - field desc for the added field
+// pEnCFieldInfo - information about the new field
+// Return Value: The field address if the field is available (i.e., it has been accessed)
+// or NULL otherwise
+// Note: this function may throw
+//-----------------------------------------------------------------------------
+PTR_CBYTE DacDbiInterfaceImpl::GetPtrToEnCField(FieldDesc * pFD, const EnCHangingFieldInfo * pEnCFieldInfo)
+{
+#ifndef EnC_SUPPORTED
+ _ASSERTE(!"Trying to get the address of an EnC field where EnC is not supported! ");
+ return NULL;
+#else
+
+ PTR_EditAndContinueModule pEnCModule;
+ DomainFile * pDomainFile = pEnCFieldInfo->GetObjectTypeData().vmDomainFile.GetDacPtr();
+ Module * pModule = pDomainFile->GetModule();
+
+ // make sure we actually have an EditAndContinueModule
+ _ASSERTE(pModule->IsEditAndContinueCapable());
+ pEnCModule = dac_cast<PTR_EditAndContinueModule>(pModule);
+
+ // we should also have an EnCFieldDesc
+ _ASSERTE(pFD->IsEnCNew());
+ EnCFieldDesc * pEnCFieldDesc;
+ pEnCFieldDesc = dac_cast<PTR_EnCFieldDesc>(pFD);
+
+ // If it hasn't been fixed up yet, then we can't return the pointer.
+ if (pEnCFieldDesc->NeedsFixup())
+ {
+ ThrowHR(CORDBG_E_ENC_HANGING_FIELD);
+ }
+ // Get a pointer to the field
+ PTR_CBYTE pORField = NULL;
+
+ PTR_Object pObject = pEnCFieldInfo->GetVmObject().GetDacPtr();
+ pORField = pEnCModule->ResolveField(ObjectToOBJECTREF(pObject),
+ pEnCFieldDesc);
+
+ // The field could be absent because the code hasn't accessed it yet. If so, we're not going to add it
+ // since we can't allocate anyway.
+ if (pORField == NULL)
+ {
+ ThrowHR(CORDBG_E_ENC_HANGING_FIELD);
+ }
+ return pORField;
+#endif // EnC_SUPPORTED
+} // DacDbiInterfaceImpl::GetPtrToEnCField
+
+//-----------------------------------------------------------------------------
+// DacDbiInterfaceImpl::InitFieldData
+// Initialize information about a field added with EnC
+// Arguments :
+// input:
+// pFD - provides information about whether the field is static,
+// the metadata token, etc.
+// pORField - provides the field address or offset
+// pEnCFieldData - provides the offset to the fields of the object
+// output: pFieldData - initialized in accordance with the input information
+//-----------------------------------------------------------------------------
+void DacDbiInterfaceImpl::InitFieldData(const FieldDesc * pFD,
+ const PTR_CBYTE pORField,
+ const EnCHangingFieldInfo * pEnCFieldData,
+ FieldData * pFieldData)
+{
+
+ pFieldData->ClearFields();
+
+ pFieldData->m_fFldIsStatic = (pFD->IsStatic() != 0);
+ pFieldData->m_vmFieldDesc.SetHostPtr(pFD);
+ pFieldData->m_fFldIsTLS = (pFD->IsThreadStatic() == TRUE);
+ pFieldData->m_fldMetadataToken = pFD->GetMemberDef();
+ pFieldData->m_fFldIsRVA = (pFD->IsRVA() == TRUE);
+ pFieldData->m_fFldIsContextStatic = (pFD->IsContextStatic() == TRUE);
+ pFieldData->m_fFldIsCollectibleStatic = FALSE;
+ pFieldData->m_fFldStorageAvailable = true;
+
+ if (pFieldData->m_fFldIsStatic)
+ {
+ //EnC is only supported on regular static fields
+ _ASSERTE(!pFieldData->m_fFldIsContextStatic);
+ _ASSERTE(!pFieldData->m_fFldIsTLS);
+ _ASSERTE(!pFieldData->m_fFldIsRVA);
+
+ // pORField contains the absolute address
+ pFieldData->SetStaticAddress(PTR_TO_TADDR(pORField));
+ }
+ else
+ {
+ // fldInstanceOffset is computed to work correctly with GetFieldValue
+ // which computes:
+ // addr of pORField = object + pEnCFieldInfo->m_offsetToVars + offsetToFld
+ pFieldData->SetInstanceOffset(PTR_TO_TADDR(pORField) -
+ (PTR_TO_TADDR(pEnCFieldData->GetVmObject().GetDacPtr()) +
+ pEnCFieldData->GetOffsetToVars()));
+ }
+} // DacDbiInterfaceImpl::InitFieldData
+
+
+// ----------------------------------------------------------------------------
+// DacDbi API: GetEnCHangingFieldInfo
+// After a class has been loaded, if a field has been added via EnC we'll have to jump through
+// some hoops to get at it (it hangs off the sync block or FieldDesc).
+//
+// GENERICS: TODO: this method will need to be modified if we ever support EnC on
+// generic classes.
+//-----------------------------------------------------------------------------
+void DacDbiInterfaceImpl::GetEnCHangingFieldInfo(const EnCHangingFieldInfo * pEnCFieldInfo,
+ FieldData * pFieldData,
+ BOOL * pfStatic)
+{
+ DD_ENTER_MAY_THROW;
+
+ LOG((LF_CORDB, LL_INFO100000, "DDI::IEnCHFI: Obj:0x%x, objType"
+ ":0x%x, offset:0x%x\n", pEnCFieldInfo->m_pObject, pEnCFieldInfo->m_objectTypeData.elementType,
+ pEnCFieldInfo->m_offsetToVars));
+
+ FieldDesc * pFD = NULL;
+ PTR_CBYTE pORField = NULL;
+
+ pFD = GetEnCFieldDesc(pEnCFieldInfo);
+ _ASSERTE(pFD->IsEnCNew()); // We shouldn't be here if it wasn't added to an
+ // already loaded class.
+
+#ifdef EnC_SUPPORTED
+ pORField = GetPtrToEnCField(pFD, pEnCFieldInfo);
+#else
+ _ASSERTE(!"We shouldn't be here: EnC not supported");
+#endif // EnC_SUPPORTED
+
+ InitFieldData(pFD, pORField, pEnCFieldInfo, pFieldData);
+ *pfStatic = (pFD->IsStatic() != 0);
+
+} // DacDbiInterfaceImpl::GetEnCHangingFieldInfo
+
+//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+
+void DacDbiInterfaceImpl::GetAssemblyFromDomainAssembly(VMPTR_DomainAssembly vmDomainAssembly, VMPTR_Assembly *vmAssembly)
+{
+ DD_ENTER_MAY_THROW;
+
+ _ASSERTE(vmAssembly != NULL);
+
+ DomainAssembly * pDomainAssembly = vmDomainAssembly.GetDacPtr();
+ vmAssembly->SetHostPtr(pDomainAssembly->GetAssembly());
+}
+
+// Determines whether the runtime security system has assigned full-trust to this assembly.
+BOOL DacDbiInterfaceImpl::IsAssemblyFullyTrusted(VMPTR_DomainAssembly vmDomainAssembly)
+{
+ DD_ENTER_MAY_THROW;
+
+ DomainAssembly * pAssembly = vmDomainAssembly.GetDacPtr();
+ IAssemblySecurityDescriptor * pSecDisc = pAssembly->GetSecurityDescriptor();
+ return pSecDisc->IsFullyTrusted();
+}
+
+// Get the full path and file name to the assembly's manifest module.
+BOOL DacDbiInterfaceImpl::GetAssemblyPath(
+ VMPTR_Assembly vmAssembly,
+ IStringHolder * pStrFilename)
+{
+ DD_ENTER_MAY_THROW;
+
+ // Get the manifest module for this assembly
+ Assembly * pAssembly = vmAssembly.GetDacPtr();
+ Module * pManifestModule = pAssembly->GetManifestModule();
+
+ // Get the path for the manifest module.
+ // since we no longer support Win9x, we assume all paths will be in unicode format already
+ const WCHAR * szPath = pManifestModule->GetPath().DacGetRawUnicode();
+ HRESULT hrStatus = pStrFilename->AssignCopy(szPath);
+ IfFailThrow(hrStatus);
+
+ if(szPath == NULL || *szPath=='\0')
+ {
+ // The asembly has no (and will never have a) file name, but we didn't really fail
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+// DAC/DBI API
+// Get a resolved type def from a type ref. The type ref may come from a module other than the
+// referencing module.
+void DacDbiInterfaceImpl::ResolveTypeReference(const TypeRefData * pTypeRefInfo,
+ TypeRefData * pTargetRefInfo)
+{
+ DD_ENTER_MAY_THROW;
+ DomainFile * pDomainFile = pTypeRefInfo->vmDomainFile.GetDacPtr();
+ Module * pReferencingModule = pDomainFile->GetCurrentModule();
+ BOOL fSuccess = FALSE;
+
+ // Resolve the type ref
+ // g_pEEInterface->FindLoadedClass is almost what we want, but it isn't guaranteed to work if
+ // the typeRef was originally loaded from a different assembly. Also, we need to ensure that
+ // we can resolve even unloaded types in fully loaded assemblies, so APIs such as
+ // LoadTypeDefOrRefThrowing aren't acceptable.
+
+ Module * pTargetModule = NULL;
+ mdTypeDef targetTypeDef = mdTokenNil;
+
+ // The loader won't need to trigger a GC or throw because we've told it not to load anything
+ ENABLE_FORBID_GC_LOADER_USE_IN_THIS_SCOPE();
+
+ fSuccess = ClassLoader::ResolveTokenToTypeDefThrowing(pReferencingModule,
+ pTypeRefInfo->typeToken,
+ &pTargetModule,
+ &targetTypeDef,
+ Loader::SafeLookup //don't load, no locks/allocations
+ );
+ if (fSuccess)
+ {
+ _ASSERTE(pTargetModule != NULL);
+ _ASSERTE( TypeFromToken(targetTypeDef) == mdtTypeDef );
+
+ AppDomain * pAppDomain = pDomainFile->GetAppDomain();
+
+ pTargetRefInfo->vmDomainFile.SetDacTargetPtr(PTR_HOST_TO_TADDR(pTargetModule->GetDomainFile(pAppDomain)));
+ pTargetRefInfo->typeToken = targetTypeDef;
+ }
+ else
+ {
+ // failed - presumably because the target assembly isn't loaded
+ ThrowHR(CORDBG_E_CLASS_NOT_LOADED);
+ }
+} // DacDbiInterfaceImpl::ResolveTypeReference
+
+
+// Get the full path and file name to the module (if any).
+BOOL DacDbiInterfaceImpl::GetModulePath(VMPTR_Module vmModule,
+ IStringHolder * pStrFilename)
+{
+ DD_ENTER_MAY_THROW;
+
+ Module * pModule = vmModule.GetDacPtr();
+ PEFile * pFile = pModule->GetFile();
+ if (pFile != NULL)
+ {
+ if( !pFile->GetPath().IsEmpty() )
+ {
+ // Module has an on-disk path
+ const WCHAR * szPath = pFile->GetPath().DacGetRawUnicode();
+ if (szPath == NULL)
+ {
+ szPath = pFile->GetModuleFileNameHint().DacGetRawUnicode();
+ if (szPath == NULL)
+ {
+ goto NoFileName;
+ }
+ }
+ IfFailThrow(pStrFilename->AssignCopy(szPath));
+ return TRUE;
+ }
+ }
+
+NoFileName:
+ // no filename
+ IfFailThrow(pStrFilename->AssignCopy(W("")));
+ return FALSE;
+}
+
+// Get the full path and file name to the ngen image for the module (if any).
+BOOL DacDbiInterfaceImpl::GetModuleNGenPath(VMPTR_Module vmModule,
+ IStringHolder * pStrFilename)
+{
+ DD_ENTER_MAY_THROW;
+#ifdef FEATURE_PREJIT
+ Module * pModule = vmModule.GetDacPtr();
+ PEFile * pFile = pModule->GetFile();
+ if (pFile != NULL && pFile->HasNativeImage())
+ {
+ PEImage * pImage = pFile->GetPersistentNativeImage();
+ if (pImage != NULL && pImage->IsFile())
+ {
+ // We have an on-disk ngen image. Return the path.
+ // since we no longer support Win9x, we assume all paths will be in unicode format already
+ const WCHAR * szPath = pImage->GetPath().DacGetRawUnicode();
+ if (szPath == NULL)
+ {
+ szPath = pFile->GetModuleFileNameHint().DacGetRawUnicode();
+ if (szPath == NULL)
+ {
+ goto NoFileName;
+ }
+ }
+ IfFailThrow(pStrFilename->AssignCopy(szPath));
+ return TRUE;
+ }
+ }
+#endif // FEATURE_PREJIT
+
+NoFileName:
+ // no ngen filename
+ IfFailThrow(pStrFilename->AssignCopy(W("")));
+ return FALSE;
+}
+
+// Implementation of IDacDbiInterface::GetModuleSimpleName
+void DacDbiInterfaceImpl::GetModuleSimpleName(VMPTR_Module vmModule, IStringHolder * pStrFilename)
+{
+ DD_ENTER_MAY_THROW;
+
+ _ASSERTE(pStrFilename != NULL);
+
+ Module * pModule = vmModule.GetDacPtr();
+ LPCUTF8 szNameUtf8 = pModule->GetSimpleName();
+
+ SString convert(SString::Utf8, szNameUtf8);
+ IfFailThrow(pStrFilename->AssignCopy(convert.GetUnicode()));
+}
+
+// Helper to intialize a TargetBuffer from a MemoryRange
+//
+// Arguments:
+// memoryRange - memory range.
+// pTargetBuffer - required out parameter to be initialized to value of memory range.
+//
+// Notes:
+// MemoryRange and TargetBuffer both conceptually describe a single contiguous buffer of memory in the
+// target. MemoryRange is a VM structure, which can't bleed across the DacDbi boundary. TargetBuffer is
+// a DacDbi structure, which can cross the DacDbi boundary.
+void InitTargetBufferFromMemoryRange(const MemoryRange memoryRange, TargetBuffer * pTargetBuffer)
+{
+ SUPPORTS_DAC;
+
+ _ASSERTE(pTargetBuffer != NULL);
+ PTR_CVOID p = memoryRange.StartAddress();
+ CORDB_ADDRESS addr = PTR_TO_CORDB_ADDRESS(PTR_TO_TADDR(p));
+
+ _ASSERTE(memoryRange.Size() <= 0xffffffff);
+ pTargetBuffer->Init(addr, (ULONG)memoryRange.Size());
+}
+
+// Helper to intialize a TargetBuffer (host representation of target) from an SBuffer (target)
+//
+// Arguments:
+// pBuffer - target pointer to a SBuffer structure. If pBuffer is NULL, then target buffer will be empty.
+// pTargetBuffer - required out pointer to hold buffer description.
+//
+// Notes:
+// PTR_SBuffer and TargetBuffer are both semantically equivalent structures. They both are a pointer and length
+// describing a buffer in the target address space. (SBufer also has ownership semantics, but for DAC's
+// read-only nature, that doesn't matter).
+// Neither of these will actually copy the target buffer into the host without explicit action.
+// The important difference is that TargetBuffer is a host datastructure and so easier to manipulate.
+//
+void InitTargetBufferFromTargetSBuffer(PTR_SBuffer pBuffer, TargetBuffer * pTargetBuffer)
+{
+ SUPPORTS_DAC;
+
+ _ASSERTE(pTargetBuffer != NULL);
+
+ SBuffer * pBufferHost = pBuffer;
+ if (pBufferHost == NULL)
+ {
+ pTargetBuffer->Clear();
+ return;
+ }
+
+ MemoryRange m = pBufferHost->DacGetRawBuffer();
+ InitTargetBufferFromMemoryRange(m, pTargetBuffer);
+}
+
+
+// Implementation of IDacDbiInterface::GetMetadata
+void DacDbiInterfaceImpl::GetMetadata(VMPTR_Module vmModule, TargetBuffer * pTargetBuffer)
+{
+ DD_ENTER_MAY_THROW;
+
+ pTargetBuffer->Clear();
+
+ Module * pModule = vmModule.GetDacPtr();
+
+ // Target should only be asking about modules that are visible to debugger.
+ _ASSERTE(pModule->IsVisibleToDebugger());
+
+ // For dynamic modules, metadata is stored as an eagerly-serialized buffer hanging off the Reflection Module.
+ if (pModule->IsReflection())
+ {
+ // Here is the fetch.
+ ReflectionModule * pReflectionModule = pModule->GetReflectionModule();
+ InitTargetBufferFromTargetSBuffer(pReflectionModule->GetDynamicMetadataBuffer(), pTargetBuffer);
+ }
+ else
+ {
+ PEFile * pFile = pModule->GetFile();
+
+ // For non-dynamic modules, metadata is in the pe-image.
+ COUNT_T size;
+ CORDB_ADDRESS address = PTR_TO_CORDB_ADDRESS(dac_cast<TADDR>(pFile->GetLoadedMetadata(&size)));
+
+ pTargetBuffer->Init(address, (ULONG) size);
+ }
+
+ if (pTargetBuffer->IsEmpty())
+ {
+ // We never expect this to happen in a well-behaved scenario. But just in case.
+ ThrowHR(CORDBG_E_MISSING_METADATA);
+ }
+
+}
+
+// Implementation of IDacDbiInterface::GetSymbolsBuffer
+void DacDbiInterfaceImpl::GetSymbolsBuffer(VMPTR_Module vmModule, TargetBuffer * pTargetBuffer, SymbolFormat * pSymbolFormat)
+{
+ DD_ENTER_MAY_THROW;
+
+ pTargetBuffer->Clear();
+ *pSymbolFormat = kSymbolFormatNone;
+
+ Module * pModule = vmModule.GetDacPtr();
+
+ // Target should only be asking about modules that are visible to debugger.
+ _ASSERTE(pModule->IsVisibleToDebugger());
+
+ PTR_CGrowableStream pStream = pModule->GetInMemorySymbolStream();
+ if (pStream == NULL)
+ {
+ // Common case is to not have PDBs in-memory.
+ return;
+ }
+
+ const MemoryRange m = pStream->GetRawBuffer();
+ if (m.Size() == 0)
+ {
+ // We may be prepared to store symbols (in some particular format) but none are there yet.
+ // We treat this the same as not having any symbols above.
+ return;
+ }
+ InitTargetBufferFromMemoryRange(m, pTargetBuffer);
+
+ // Set the symbol format appropriately
+ ESymbolFormat symFormat = pModule->GetInMemorySymbolStreamFormat();
+ switch (symFormat)
+ {
+ case eSymbolFormatPDB:
+ *pSymbolFormat = kSymbolFormatPDB;
+ break;
+
+ case eSymbolFormatILDB:
+ *pSymbolFormat = kSymbolFormatILDB;
+ break;
+
+ default:
+ CONSISTENCY_CHECK_MSGF(false, "Unexpected symbol format");
+ pTargetBuffer->Clear();
+ ThrowHR(E_UNEXPECTED);
+ }
+}
+
+
+
+void DacDbiInterfaceImpl::GetModuleForDomainFile(VMPTR_DomainFile vmDomainFile, OUT VMPTR_Module * pModule)
+{
+ DD_ENTER_MAY_THROW;
+
+ _ASSERTE(pModule != NULL);
+
+ DomainFile * pDomainFile = vmDomainFile.GetDacPtr();
+ pModule->SetHostPtr(pDomainFile->GetModule());
+}
+
+
+// Implement IDacDbiInterface::GetDomainFileData
+void DacDbiInterfaceImpl::GetDomainFileData(VMPTR_DomainFile vmDomainFile, DomainFileInfo * pData)
+{
+ DD_ENTER_MAY_THROW;
+
+ _ASSERTE(pData != NULL);
+
+ ZeroMemory(pData, sizeof(*pData));
+
+ DomainFile * pDomainFile = vmDomainFile.GetDacPtr();
+ AppDomain * pAppDomain = pDomainFile->GetAppDomain();
+
+ // @dbgtodo - is this efficient DAC usage (perhaps a dac-cop rule)? Are we round-tripping the pointer?
+ // Should we have a GetDomainAssembly() that returns a PTR_DomainAssembly?
+ pData->vmDomainAssembly.SetHostPtr(pDomainFile->GetDomainAssembly());
+ pData->vmAppDomain.SetHostPtr(pAppDomain);
+}
+
+// Implement IDacDbiInterface::GetModuleData
+void DacDbiInterfaceImpl::GetModuleData(VMPTR_Module vmModule, ModuleInfo * pData)
+{
+ DD_ENTER_MAY_THROW;
+
+ _ASSERTE(pData != NULL);
+
+ ZeroMemory(pData, sizeof(*pData));
+
+ Module * pModule = vmModule.GetDacPtr();
+ PEFile * pFile = pModule->GetFile();
+
+ pData->vmPEFile.SetHostPtr(pFile);
+ pData->vmAssembly.SetHostPtr(pModule->GetAssembly());
+
+ // Is it dynamic?
+ BOOL fIsDynamic = pModule->IsReflection();
+ pData->fIsDynamic = fIsDynamic;
+
+ // Get PE BaseAddress and Size
+ // For dynamic modules, these are 0. Else,
+ pData->pPEBaseAddress = NULL;
+ pData->nPESize = 0;
+
+ if (!fIsDynamic)
+ {
+ COUNT_T size = 0;
+ pData->pPEBaseAddress = PTR_TO_TADDR(pFile->GetDebuggerContents(&size));
+ pData->nPESize = (ULONG) size;
+ }
+
+ // In-memory is determined by whether the module has a filename.
+ pData->fInMemory = FALSE;
+ if (pFile != NULL)
+ {
+ pData->fInMemory = pFile->GetPath().IsEmpty();
+ }
+}
+
+
+// Enumerate all AppDomains in the process.
+void DacDbiInterfaceImpl::EnumerateAppDomains(
+ FP_APPDOMAIN_ENUMERATION_CALLBACK fpCallback,
+ void * pUserData)
+{
+ DD_ENTER_MAY_THROW;
+
+ _ASSERTE(fpCallback != NULL);
+
+ // Only include active appdomains in the enumeration.
+ // This includes appdomains sent before the AD load event,
+ // and does not include appdomains that are in shutdown after the AD exit event.
+ const BOOL bOnlyActive = TRUE;
+ AppDomainIterator iterator(bOnlyActive);
+
+ while(iterator.Next())
+ {
+ // It's critical that we don't yield appdomains after the unload event has been sent.
+ // See code:IDacDbiInterface#Enumeration for details.
+ AppDomain * pAppDomain = iterator.GetDomain();
+ if (pAppDomain->IsUnloading())
+ {
+ continue;
+ }
+
+ VMPTR_AppDomain vmAppDomain = VMPTR_AppDomain::NullPtr();
+ vmAppDomain.SetHostPtr(pAppDomain);
+
+ fpCallback(vmAppDomain, pUserData);
+ }
+}
+
+// Enumerate all Assemblies in an appdomain.
+void DacDbiInterfaceImpl::EnumerateAssembliesInAppDomain(
+ VMPTR_AppDomain vmAppDomain,
+ FP_ASSEMBLY_ENUMERATION_CALLBACK fpCallback,
+ void * pUserData
+)
+{
+ DD_ENTER_MAY_THROW;
+
+ _ASSERTE(fpCallback != NULL);
+
+ // Iterate through all Assemblies (including shared) in the appdomain.
+ AppDomain::AssemblyIterator iterator;
+
+ // If the containing appdomain is unloading, then don't enumerate any assemblies
+ // in the domain. This is to enforce rules at code:IDacDbiInterface#Enumeration.
+ // See comment in code:DacDbiInterfaceImpl::EnumerateModulesInAssembly code for details.
+ AppDomain * pAppDomain = vmAppDomain.GetDacPtr();
+ if (pAppDomain->IsUnloading())
+ {
+ return;
+ }
+
+ // Pass the magical flags to the loader enumerator to get all Execution-only assemblies.
+ iterator = pAppDomain->IterateAssembliesEx((AssemblyIterationFlags)(kIncludeLoading | kIncludeLoaded | kIncludeExecution));
+ CollectibleAssemblyHolder<DomainAssembly *> pDomainAssembly;
+
+ while (iterator.Next(pDomainAssembly.This()))
+ {
+ if (!pDomainAssembly->IsVisibleToDebugger())
+ {
+ continue;
+ }
+
+ VMPTR_DomainAssembly vmDomainAssembly = VMPTR_DomainAssembly::NullPtr();
+ vmDomainAssembly.SetHostPtr(pDomainAssembly);
+
+ fpCallback(vmDomainAssembly, pUserData);
+ }
+}
+
+// Implementation of IDacDbiInterface::EnumerateModulesInAssembly,
+// Enumerate all the modules (non-resource) in an assembly.
+void DacDbiInterfaceImpl::EnumerateModulesInAssembly(
+ VMPTR_DomainAssembly vmAssembly,
+ FP_MODULE_ENUMERATION_CALLBACK fpCallback,
+ void * pUserData)
+{
+ DD_ENTER_MAY_THROW;
+
+ _ASSERTE(fpCallback != NULL);
+
+ DomainAssembly * pDomainAssembly = vmAssembly.GetDacPtr();
+
+ // If the appdomain or assembly containing this module is unloading, then don't enumerate any modules.
+ // in the domain. This is to enforce rules at code:IDacDbiInterface#Enumeration, specifically
+ // that new objects are not available after the unload event is sent.
+ // This is a very large hammer, but since modules only unload with appdomains or assemblies, we're
+ // erring on the side of safety. If the debugger happens to have VMPTR_DomainFiles (CordbModules) already
+ // cached, it can still use those until the unload event.
+ if (pDomainAssembly->IsUnloading())
+ {
+ return;
+ }
+
+
+ // If the domain is not yet fully-loaded, don't advertise it yet.
+ // It's not ready to be inspected.
+ DomainModuleIterator iterator = pDomainAssembly->IterateModules(kModIterIncludeLoaded);
+
+ while (iterator.Next())
+ {
+ DomainFile * pDomainFile = iterator.GetDomainFile();
+
+ // Debugger isn't notified of Resource / Inspection-only modules.
+ if (!pDomainFile->GetModule()->IsVisibleToDebugger())
+ {
+ continue;
+ }
+
+ _ASSERTE(pDomainFile->IsLoaded());
+
+ VMPTR_DomainFile vmDomainFile = VMPTR_DomainFile::NullPtr();
+ vmDomainFile.SetHostPtr(pDomainFile);
+
+ fpCallback(vmDomainFile, pUserData);
+ }
+}
+
+// Implementation of IDacDbiInterface::ResolveAssembly
+// Returns NULL if not found.
+VMPTR_DomainAssembly DacDbiInterfaceImpl::ResolveAssembly(
+ VMPTR_DomainFile vmScope,
+ mdToken tkAssemblyRef)
+{
+ DD_ENTER_MAY_THROW;
+
+
+ DomainFile * pDomainFile = vmScope.GetDacPtr();
+ AppDomain * pAppDomain = pDomainFile->GetAppDomain();
+ Module * pModule = pDomainFile->GetCurrentModule();
+
+ VMPTR_DomainAssembly vmDomainAssembly = VMPTR_DomainAssembly::NullPtr();
+
+ Assembly * pAssembly = pModule->LookupAssemblyRef(tkAssemblyRef);
+ if (pAssembly != NULL)
+ {
+ DomainAssembly * pDomainAssembly = pAssembly->FindDomainAssembly(pAppDomain);
+ vmDomainAssembly.SetHostPtr(pDomainAssembly);
+ }
+ return vmDomainAssembly;
+}
+
+// When stopped at an event, request a synchronization.
+// See DacDbiInterface.h for full comments
+void DacDbiInterfaceImpl::RequestSyncAtEvent()
+{
+ DD_ENTER_MAY_THROW;
+
+ // To request a sync, we just need to set g_pDebugger->m_RSRequestedSync high.
+ if (g_pDebugger != NULL)
+ {
+ TADDR addr = PTR_HOST_MEMBER_TADDR(Debugger, g_pDebugger, m_RSRequestedSync);
+
+ BOOL fTrue = TRUE;
+ SafeWriteStructOrThrow<BOOL>(addr, &fTrue);
+
+ }
+}
+
+HRESULT DacDbiInterfaceImpl::SetSendExceptionsOutsideOfJMC(BOOL sendExceptionsOutsideOfJMC)
+{
+ DD_ENTER_MAY_THROW
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ if (g_pDebugger != NULL)
+ {
+ TADDR addr = PTR_HOST_MEMBER_TADDR(Debugger, g_pDebugger, m_sendExceptionsOutsideOfJMC);
+ SafeWriteStructOrThrow<BOOL>(addr, &sendExceptionsOutsideOfJMC);
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+// Notify the debuggee that a debugger attach is pending.
+// See DacDbiInterface.h for full comments
+void DacDbiInterfaceImpl::MarkDebuggerAttachPending()
+{
+ DD_ENTER_MAY_THROW;
+
+ if (g_pDebugger != NULL)
+ {
+ DWORD flags = g_CORDebuggerControlFlags;
+ flags |= DBCF_PENDING_ATTACH;
+
+ // Uses special DAC writing. PTR_TO_TADDR doesn't fetch for globals.
+ // @dbgtodo dac support - the exact mechanism of writing to the target needs to be flushed out,
+ // especially as it relates to DAC cop and enforcing undac-ized writes.
+ g_CORDebuggerControlFlags = flags;
+ }
+ else
+ {
+ // Caller should have gauranteed that the LS is loaded.
+ // If we're detaching, then don't throw because we don't care.
+ ThrowHR(CORDBG_E_NOTREADY);
+ }
+}
+
+
+// Notify the debuggee that a debugger is attached.
+// See DacDbiInterface.h for full comments
+void DacDbiInterfaceImpl::MarkDebuggerAttached(BOOL fAttached)
+{
+ DD_ENTER_MAY_THROW;
+
+ if (g_pDebugger != NULL)
+ {
+ // To be attached, we need to set the following
+ // g_CORDebuggerControlFlags |= DBCF_ATTACHED;
+ // To detach (if !fAttached), we need to do the opposite.
+
+ DWORD flags = g_CORDebuggerControlFlags;
+ if (fAttached)
+ {
+ flags |= DBCF_ATTACHED;
+ }
+ else
+ {
+ flags &= ~ (DBCF_ATTACHED | DBCF_PENDING_ATTACH);
+ }
+
+ // Uses special DAC writing. PTR_TO_TADDR doesn't fetch for globals.
+ // @dbgtodo dac support - the exact mechanism of writing to the target needs to be flushed out,
+ // especially as it relates to DAC cop and enforcing undac-ized writes.
+ g_CORDebuggerControlFlags = flags;
+ }
+ else if (fAttached)
+ {
+ // Caller should have gauranteed that the LS is loaded.
+ // If we're detaching, then don't throw because we don't care.
+ ThrowHR(CORDBG_E_NOTREADY);
+ }
+
+}
+
+#ifdef FEATURE_INCLUDE_ALL_INTERFACES
+// Enumerate all the Connections in the process.
+void DacDbiInterfaceImpl::EnumerateConnections(FP_CONNECTION_CALLBACK fpCallback, void * pUserData)
+{
+ DD_ENTER_MAY_THROW;
+
+ ConnectionNameHashEntry * pConnection;
+
+ HASHFIND hashfind;
+
+ pConnection = CCLRDebugManager::FindFirst(&hashfind);
+ while (pConnection)
+ {
+ DWORD id = pConnection->m_dwConnectionId;
+ LPCWSTR pName = pConnection->m_pwzName;
+
+ fpCallback(id, pName, pUserData);
+
+ // now get the next connection record
+ pConnection = CCLRDebugManager::FindNext(&hashfind);
+ }
+}
+#endif
+
+
+// Enumerate all threads in the process.
+void DacDbiInterfaceImpl::EnumerateThreads(FP_THREAD_ENUMERATION_CALLBACK fpCallback, void * pUserData)
+{
+ DD_ENTER_MAY_THROW;
+
+ if (ThreadStore::s_pThreadStore == NULL)
+ {
+ return;
+ }
+
+ Thread *pThread = ThreadStore::GetThreadList(NULL);
+
+ while (pThread != NULL)
+ {
+
+ // Don't want to publish threads via enumeration before they're ready to be inspected.
+ // Use the same window that we used in whidbey.
+ Thread::ThreadState threadState = pThread->GetSnapshotState();
+ if (!((IsThreadMarkedDeadWorker(pThread)) || (threadState & Thread::TS_Unstarted)))
+ {
+ VMPTR_Thread vmThread = VMPTR_Thread::NullPtr();
+ vmThread.SetHostPtr(pThread);
+ fpCallback(vmThread, pUserData);
+ }
+
+ pThread = ThreadStore::GetThreadList(pThread);
+ }
+}
+
+// public implementation of IsThreadMarkedDead
+bool DacDbiInterfaceImpl::IsThreadMarkedDead(VMPTR_Thread vmThread)
+{
+ DD_ENTER_MAY_THROW;
+ Thread * pThread = vmThread.GetDacPtr();
+ return IsThreadMarkedDeadWorker(pThread);
+}
+
+// Private worker for IsThreadMarkedDead
+//
+// Arguments:
+// pThread - valid thread to check if dead
+//
+// Returns:
+// true iff thread is marked as dead.
+//
+// Notes:
+// This is an internal method that skips public validation.
+// See code:IDacDbiInterface::#IsThreadMarkedDead for purpose.
+bool DacDbiInterfaceImpl::IsThreadMarkedDeadWorker(Thread * pThread)
+{
+ _ASSERTE(pThread != NULL);
+
+ Thread::ThreadState threadState = pThread->GetSnapshotState();
+
+ bool fIsDead = (threadState & Thread::TS_Dead) != 0;
+
+ return fIsDead;
+}
+
+
+// Return the handle of the specified thread.
+HANDLE DacDbiInterfaceImpl::GetThreadHandle(VMPTR_Thread vmThread)
+{
+ DD_ENTER_MAY_THROW;
+
+ Thread * pThread = vmThread.GetDacPtr();
+ return pThread->GetThreadHandle();
+}
+
+// Return the object handle for the managed Thread object corresponding to the specified thread.
+VMPTR_OBJECTHANDLE DacDbiInterfaceImpl::GetThreadObject(VMPTR_Thread vmThread)
+{
+ DD_ENTER_MAY_THROW;
+
+ Thread * pThread = vmThread.GetDacPtr();
+ Thread::ThreadState threadState = pThread->GetSnapshotState();
+
+ if ( (threadState & Thread::TS_Dead) ||
+ (threadState & Thread::TS_Unstarted) ||
+ (threadState & Thread::TS_Detached) ||
+ g_fProcessDetach )
+ {
+ ThrowHR(CORDBG_E_BAD_THREAD_STATE);
+ }
+ else
+ {
+ VMPTR_OBJECTHANDLE vmObjHandle = VMPTR_OBJECTHANDLE::NullPtr();
+ vmObjHandle.SetDacTargetPtr(pThread->GetExposedObjectHandleForDebugger());
+ return vmObjHandle;
+ }
+}
+
+// Set and reset the TSNC_DebuggerUserSuspend bit on the state of the specified thread
+// according to the CorDebugThreadState.
+void DacDbiInterfaceImpl::SetDebugState(VMPTR_Thread vmThread,
+ CorDebugThreadState debugState)
+{
+ DD_ENTER_MAY_THROW;
+
+ Thread * pThread = vmThread.GetDacPtr();
+
+ // update the field on the host copy
+ if (debugState == THREAD_SUSPEND)
+ {
+ pThread->SetThreadStateNC(Thread::TSNC_DebuggerUserSuspend);
+ }
+ else if (debugState == THREAD_RUN)
+ {
+ pThread->ResetThreadStateNC(Thread::TSNC_DebuggerUserSuspend);
+ }
+ else
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ // update the field on the target copy
+ TADDR taThreadState = PTR_HOST_MEMBER_TADDR(Thread, pThread, m_StateNC);
+ SafeWriteStructOrThrow<Thread::ThreadStateNoConcurrency>(taThreadState, &(pThread->m_StateNC));
+}
+
+// Gets the debugger unhandled exception threadstate flag
+BOOL DacDbiInterfaceImpl::HasUnhandledException(VMPTR_Thread vmThread)
+{
+ DD_ENTER_MAY_THROW;
+
+ Thread * pThread = vmThread.GetDacPtr();
+
+ // some managed exceptions don't have any underlying
+ // native exception processing going on. They just consist
+ // of a managed throwable that we have stashed away followed
+ // by a debugger notification and some form of failfast.
+ // Everything that comes through EEFatalError is in this category
+ if(pThread->IsLastThrownObjectUnhandled())
+ {
+ return TRUE;
+ }
+
+ // most managed exceptions are just a throwable bound to a
+ // native exception. In that case this handle will be non-null
+ OBJECTHANDLE ohException = pThread->GetThrowableAsHandle();
+ if (ohException != NULL)
+ {
+ // during the UEF we set the unhandled bit, if it is set the exception
+ // was unhandled
+ // however if the exception has intercept info then we consider it handled
+ // again
+ return pThread->GetExceptionState()->GetFlags()->IsUnhandled() &&
+ !(pThread->GetExceptionState()->GetFlags()->DebuggerInterceptInfo());
+ }
+
+ return FALSE;
+}
+
+// Return the user state of the specified thread.
+CorDebugUserState DacDbiInterfaceImpl::GetUserState(VMPTR_Thread vmThread)
+{
+ DD_ENTER_MAY_THROW;
+
+ UINT result = 0;
+ result = GetPartialUserState(vmThread);
+
+ if (!IsThreadAtGCSafePlace(vmThread))
+ {
+ result |= USER_UNSAFE_POINT;
+ }
+
+ return (CorDebugUserState)result;
+}
+
+
+// Return the connection ID of the specified thread.
+CONNID DacDbiInterfaceImpl::GetConnectionID(VMPTR_Thread vmThread)
+{
+ DD_ENTER_MAY_THROW;
+
+ Thread * pThread = vmThread.GetDacPtr();
+ return pThread->GetConnectionId();
+}
+
+// Return the task ID of the specified thread.
+TASKID DacDbiInterfaceImpl::GetTaskID(VMPTR_Thread vmThread)
+{
+ DD_ENTER_MAY_THROW;
+
+ Thread * pThread = vmThread.GetDacPtr();
+ return pThread->GetTaskId();
+}
+
+// Return the OS thread ID of the specified thread
+DWORD DacDbiInterfaceImpl::TryGetVolatileOSThreadID(VMPTR_Thread vmThread)
+{
+ DD_ENTER_MAY_THROW;
+
+ Thread * pThread = vmThread.GetDacPtr();
+ _ASSERTE(pThread != NULL);
+
+ DWORD dwThreadId = pThread->GetOSThreadIdForDebugger();
+
+ // If the thread ID is a the magical cookie value, then this is really
+ // a switched out thread and doesn't have an OS tid. In that case, the
+ // DD contract is to return 0 (a much more sane value)
+ const DWORD dwSwitchedOutThreadId = SWITCHED_OUT_FIBER_OSID;
+ if (dwThreadId == dwSwitchedOutThreadId)
+ {
+ return 0;
+ }
+ return dwThreadId;
+}
+
+// Return the unique thread ID of the specified thread.
+DWORD DacDbiInterfaceImpl::GetUniqueThreadID(VMPTR_Thread vmThread)
+{
+ DD_ENTER_MAY_THROW;
+
+ Thread * pThread = vmThread.GetDacPtr();
+ _ASSERTE(pThread != NULL);
+
+ if (CLRTaskHosted())
+ {
+ return pThread->GetThreadId();
+ }
+ else
+ {
+ return pThread->GetOSThreadId();
+ }
+}
+
+// Return the object handle to the managed Exception object of the current exception
+// on the specified thread. The return value could be NULL if there is no current exception.
+VMPTR_OBJECTHANDLE DacDbiInterfaceImpl::GetCurrentException(VMPTR_Thread vmThread)
+{
+ DD_ENTER_MAY_THROW;
+
+ Thread * pThread = vmThread.GetDacPtr();
+
+ // OBJECTHANDLEs are really just TADDRs.
+ OBJECTHANDLE ohException = pThread->GetThrowableAsHandle(); // ohException can be NULL
+
+ if (ohException == NULL)
+ {
+ if (pThread->IsLastThrownObjectUnhandled())
+ {
+ ohException = pThread->LastThrownObjectHandle();
+ }
+ }
+
+ VMPTR_OBJECTHANDLE vmObjHandle;
+ vmObjHandle.SetDacTargetPtr(ohException);
+ return vmObjHandle;
+}
+
+// Return the object handle to the managed object for a given CCW pointer.
+VMPTR_OBJECTHANDLE DacDbiInterfaceImpl::GetObjectForCCW(CORDB_ADDRESS ccwPtr)
+{
+ DD_ENTER_MAY_THROW;
+
+ OBJECTHANDLE ohCCW = NULL;
+
+#ifdef FEATURE_COMINTEROP
+ ComCallWrapper *pCCW = DACGetCCWFromAddress(ccwPtr);
+ if (pCCW)
+ {
+ ohCCW = pCCW->GetObjectHandle();
+ }
+#endif
+
+ VMPTR_OBJECTHANDLE vmObjHandle;
+ vmObjHandle.SetDacTargetPtr(ohCCW);
+ return vmObjHandle;
+}
+
+// Return the object handle to the managed CustomNotification object of the current notification
+// on the specified thread. The return value could be NULL if there is no current notification.
+// Arguments:
+// input: vmThread - the thread on which the notification occurred
+// Return value: object handle for the current notification (if any) on the thread. This will return non-null
+// if and only if we are currently inside a CustomNotification Callback (or a dump was generated while in this
+// callback)
+//
+VMPTR_OBJECTHANDLE DacDbiInterfaceImpl::GetCurrentCustomDebuggerNotification(VMPTR_Thread vmThread)
+{
+ DD_ENTER_MAY_THROW;
+
+ Thread * pThread = vmThread.GetDacPtr();
+
+ // OBJECTHANDLEs are really just TADDRs.
+ OBJECTHANDLE ohNotification = pThread->GetThreadCurrNotification(); // ohNotification can be NULL
+
+ VMPTR_OBJECTHANDLE vmObjHandle;
+ vmObjHandle.SetDacTargetPtr(ohNotification);
+ return vmObjHandle;
+}
+
+// Return the current appdomain the specified thread is in.
+VMPTR_AppDomain DacDbiInterfaceImpl::GetCurrentAppDomain(VMPTR_Thread vmThread)
+{
+ DD_ENTER_MAY_THROW;
+
+ Thread * pThread = vmThread.GetDacPtr();
+ AppDomain * pAppDomain = pThread->GetDomain();
+
+ if (pAppDomain == NULL)
+ {
+ ThrowHR(E_FAIL);
+ }
+
+ VMPTR_AppDomain vmAppDomain = VMPTR_AppDomain::NullPtr();
+ vmAppDomain.SetDacTargetPtr(PTR_HOST_TO_TADDR(pAppDomain));
+ return vmAppDomain;
+}
+
+
+// Returns a bitfield reflecting the managed debugging state at the time of
+// the jit attach.
+CLR_DEBUGGING_PROCESS_FLAGS DacDbiInterfaceImpl::GetAttachStateFlags()
+{
+ DD_ENTER_MAY_THROW;
+
+ CLR_DEBUGGING_PROCESS_FLAGS res = (CLR_DEBUGGING_PROCESS_FLAGS)0;
+ if (g_pDebugger != NULL)
+ {
+ res = g_pDebugger->GetAttachStateFlags();
+ }
+ else
+ {
+ // When launching the process under a managed debugger we
+ // request these flags when CLR is loaded (before g_pDebugger
+ // had a chance to be initialized). In these cases simply
+ // return 0
+ }
+ return res;
+}
+
+//---------------------------------------------------------------------------------------
+// Helper to get the address of the 2nd-chance hijack function Or throw
+//
+// Returns:
+// Non-null Target Address of hijack function.
+TADDR DacDbiInterfaceImpl::GetHijackAddress()
+{
+ TADDR addr = NULL;
+ if (g_pDebugger != NULL)
+ {
+ // Get the start address of the redirect function for unhandled exceptions.
+ addr = dac_cast<TADDR>(g_pDebugger->m_rgHijackFunction[Debugger::kUnhandledException].StartAddress());
+ }
+ if (addr == NULL)
+ {
+ ThrowHR(CORDBG_E_NOTREADY);
+ }
+ return addr;
+}
+
+//---------------------------------------------------------------------------------------
+// Helper to determine whether a control PC is in any native stub which the runtime knows how to unwind.
+//
+// Arguments:
+// taControlPC - control PC to be checked
+//
+// Returns:
+// Returns true if the control PC is in a runtime unwindable stub.
+//
+// Notes:
+// Currently this function only recognizes the ExceptionHijack() stub,
+// which is used for unhandled exceptions.
+//
+
+bool DacDbiInterfaceImpl::IsRuntimeUnwindableStub(PCODE targetControlPC)
+{
+
+ TADDR controlPC = PCODEToPINSTR(targetControlPC);
+ // we call this function a lot while walking the stack and the values here will never change
+ // Getting the g_pDebugger and each entry in the m_rgHijackFunction is potentially ~7 DAC
+ // accesses per frame. Caching the data into a single local array is much faster. This optimization
+ // recovered a few % of DAC stackwalking time
+ if(!m_isCachedHijackFunctionValid)
+ {
+ Debugger* pDebugger = g_pDebugger;
+ if ((pDebugger == NULL) || (pDebugger->m_rgHijackFunction == NULL))
+ {
+ // The in-process debugging infrastructure hasn't been fully initialized, which means that we could
+ // NOT have hijacked anything yet.
+ return false;
+ }
+
+ // PERF NOTE: if needed this array copy could probably be made more efficient
+ // hitting the DAC only once for a single memory block, or even better
+ // put the array inline in the Debugger object so that we only do 1 DAC
+ // access for this entire thing
+ for (int i = 0; i < Debugger::kMaxHijackFunctions; i++)
+ {
+ InitTargetBufferFromMemoryRange(pDebugger->m_rgHijackFunction[i], &m_pCachedHijackFunction[i] );
+ }
+ m_isCachedHijackFunctionValid = TRUE;
+ }
+
+ // Check whether the control PC is in any of the thread redirection functions.
+ for (int i = 0; i < Debugger::kMaxHijackFunctions; i++)
+ {
+ CORDB_ADDRESS start = m_pCachedHijackFunction[i].pAddress;
+ CORDB_ADDRESS end = start + m_pCachedHijackFunction[i].cbSize;
+ if ((start <= controlPC) && (controlPC < end))
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+//---------------------------------------------------------------------------------------
+// Align a stack pointer for the given architecture
+//
+// Arguments:
+// pEsp - in/out: pointer to stack pointer.
+//
+void DacDbiInterfaceImpl::AlignStackPointer(CORDB_ADDRESS * pEsp)
+{
+ SUPPORTS_DAC;
+
+ // Nop on x86.
+#if defined(_WIN64)
+ // on 64-bit, stack pointer must be 16-byte aligned.
+ // Stacks grown down, so round down to nearest 0xF bits.
+ *pEsp &= ~((CORDB_ADDRESS) 0xF);
+#endif
+}
+
+//---------------------------------------------------------------------------------------
+// Emulate pushing something on a thread's stack.
+//
+// Arguments:
+// pEsp - in/out: pointer to stack pointer to push object at. On output,
+// updated stack pointer.
+// pData - object to push on the stack.
+// fAlignStack - whether to align the stack pointer before and after the push.
+// Callers which specify FALSE must be very careful and know exactly
+// what they are doing.
+//
+// Return:
+// address of pushed object. Throws on error.
+template <class T>
+CORDB_ADDRESS DacDbiInterfaceImpl::PushHelper(CORDB_ADDRESS * pEsp,
+ const T * pData,
+ BOOL fAlignStack)
+{
+ SUPPORTS_DAC;
+
+ if (fAlignStack == TRUE)
+ {
+ AlignStackPointer(pEsp);
+ }
+ *pEsp -= sizeof(T);
+ if (fAlignStack == TRUE)
+ {
+ AlignStackPointer(pEsp);
+ }
+ SafeWriteStructOrThrow(*pEsp, pData);
+ return *pEsp;
+}
+
+//---------------------------------------------------------------------------------------
+// Write an EXCEPTION_RECORD structure to the remote target at the specified address while taking
+// into account the number of exception parameters. On 64-bit OS and on the WOW64, the OS always
+// pushes the entire EXCEPTION_RECORD onto the stack. However, on native x86 OS, the OS only pushes
+// enough of the EXCEPTION_RECORD to cover the specified number of exception parameters. Thus we
+// need to be extra careful when we overwrite an EXCEPTION_RECORD on the stack.
+//
+// Arguments:
+// pRemotePtr - address of the EXCEPTION_RECORD in the remote target
+// pExcepRecord - EXCEPTION_RECORD to be written
+//
+// Notes:
+// This function is only used by the code which hijacks a therad when there's an unhandled exception.
+// It only works when we are actually debugging a live process, not a dump.
+//
+
+void DacDbiInterfaceImpl::WriteExceptionRecordHelper(CORDB_ADDRESS pRemotePtr,
+ const EXCEPTION_RECORD * pExcepRecord)
+{
+ // Calculate the correct size to push onto the stack.
+ ULONG32 cbSize = offsetof(EXCEPTION_RECORD, ExceptionInformation);
+ cbSize += pExcepRecord->NumberParameters * sizeof(pExcepRecord->ExceptionInformation[0]);
+
+ // Use the data target to write to the remote target. Here we are assuming that we are debugging a
+ // live process, since this function is only called by the hijacking code for unhandled exceptions.
+ HRESULT hr = m_pMutableTarget->WriteVirtual(pRemotePtr,
+ reinterpret_cast<const BYTE *>(pExcepRecord),
+ cbSize);
+
+ if (FAILED(hr))
+ {
+ ThrowHR(hr);
+ }
+}
+
+// Implement IDacDbiInterface::Hijack
+void DacDbiInterfaceImpl::Hijack(
+ VMPTR_Thread vmThread,
+ ULONG32 dwThreadId,
+ const EXCEPTION_RECORD * pRecord,
+ T_CONTEXT * pOriginalContext,
+ ULONG32 cbSizeContext,
+ EHijackReason::EHijackReason reason,
+ void * pUserData,
+ CORDB_ADDRESS * pRemoteContextAddr)
+{
+ DD_ENTER_MAY_THROW;
+
+ //
+ // Validate parameters
+ //
+
+ // pRecord may be NULL if we're not hijacking at an exception
+ // pOriginalContext may be NULL if caller doesn't want a copy of the context.
+ // (The hijack function already has the context)
+ _ASSERTE((pOriginalContext == NULL) == (cbSizeContext == 0));
+ _ASSERTE(EHijackReason::IsValid(reason));
+#ifdef PLATFORM_UNIX
+ _ASSERTE(!"Not supported on this platform");
+#endif
+
+ //
+ // If we hijack a thread which might not be managed we can set vmThread = NULL
+ // The only side-effect in this case is that we can't reuse CONTEXT and
+ // EXCEPTION_RECORD space on the stack by an already underway in-process exception
+ // filter. If you depend on those being used and updated you must provide the vmThread
+ //
+ Thread* pThread = NULL;
+ if(!vmThread.IsNull())
+ {
+ pThread = vmThread.GetDacPtr();
+ _ASSERTE(pThread->GetOSThreadIdForDebugger() == dwThreadId);
+ }
+
+ TADDR pfnHijackFunction = GetHijackAddress();
+
+ //
+ // Setup context for hijack
+ //
+ T_CONTEXT ctx;
+ HRESULT hr = m_pTarget->GetThreadContext(
+ dwThreadId,
+ CONTEXT_FULL,
+ sizeof(ctx),
+ (BYTE*) &ctx);
+ IfFailThrow(hr);
+
+ // If caller requested, copy back the original context that we're hijacking from.
+ if (pOriginalContext != NULL)
+ {
+ // Since Dac + DBI are tightly coupled, context sizes should be the same.
+ if (cbSizeContext != sizeof(T_CONTEXT))
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ memcpy(pOriginalContext, &ctx, cbSizeContext);
+ }
+
+ // Make sure the trace flag isn't on. This can happen if we were single stepping the thread when we faulted. This
+ // will ensure that we don't try to single step through the OS's exception logic, which greatly confuses our second
+ // chance hijack logic. This also mimics what the OS does for us automaically when single stepping in process, i.e.,
+ // when you turn the trace flag on in-process and go, if there is a fault, the fault is reported and the trace flag
+ // is automatically turned off.
+ //
+ // The debugger could always re-enable the single-step flag if it wants to.
+#ifndef _TARGET_ARM_
+ UnsetSSFlag(reinterpret_cast<DT_CONTEXT *>(&ctx));
+#endif
+
+ // Push pointers
+ void* espContext = NULL;
+ void* espRecord = NULL;
+ const void* pData = pUserData;
+
+ // @dbgtodo cross-plat - this is not cross plat
+ CORDB_ADDRESS esp = GetSP(&ctx);
+
+ //
+ // Find out where the OS exception dispatcher has pushed the EXCEPTION_RECORD and CONTEXT. The ExInfo and
+ // ExceptionTracker have pointers to these data structures, but when we get the unhandled exception
+ // notification, the OS exception dispatcher is no longer on the stack, so these pointers are no longer
+ // valid. We need to either update these pointers in the ExInfo/ExcepionTracker, or reuse the stack
+ // space used by the OS exception dispatcher. We are using the latter approach here.
+ //
+
+ CORDB_ADDRESS espOSContext = NULL;
+ CORDB_ADDRESS espOSRecord = NULL;
+ if (pThread != NULL && pThread->IsExceptionInProgress())
+ {
+ espOSContext = (CORDB_ADDRESS)PTR_TO_TADDR(pThread->GetExceptionState()->GetContextRecord());
+ espOSRecord = (CORDB_ADDRESS)PTR_TO_TADDR(pThread->GetExceptionState()->GetExceptionRecord());
+
+ // The managed exception may not be related to the unhandled exception for which we are trying to
+ // hijack. An example would be when a thread hits a managed exception, VS tries to do func eval on
+ // the thread, but the func eval causes an unhandled exception (e.g. AV in mscorwks.dll). In this
+ // case, the pointers stored on the ExInfo/ExceptionTracker are closer to the root than the current
+ // SP of the thread. The check below makes sure we don't reuse the pointers in this case.
+ if (espOSContext < esp)
+ {
+ SafeWriteStructOrThrow(espOSContext, &ctx);
+ espContext = CORDB_ADDRESS_TO_PTR(espOSContext);
+
+ // We should have an EXCEPTION_RECORD if we are hijacked at an exception.
+ // We need to be careful when we overwrite the exception record. On x86, the OS doesn't
+ // always push the full record onto the stack, and so we can't blindly use sizeof(EXCEPTION_RECORD).
+ // Instead, we have to look at the number of exception parameters and calculate the size.
+ _ASSERTE(pRecord != NULL);
+ WriteExceptionRecordHelper(espOSRecord, pRecord);
+ espRecord = CORDB_ADDRESS_TO_PTR(espOSRecord);
+
+ esp = min(espOSContext, espOSRecord);
+ }
+ }
+
+ // If we haven't reused the pointers, then push everything at the leaf of the stack.
+ if (espContext == NULL)
+ {
+ _ASSERTE(espRecord == NULL);
+
+ // Push on full Context and ExceptionRecord structures. We'll then push pointers to these,
+ // and those pointers will serve as the actual args to the function.
+ espContext = CORDB_ADDRESS_TO_PTR(PushHelper(&esp, &ctx, TRUE));
+
+ // If caller didn't pass an exception-record, then we're not being hijacked at an exception.
+ // We'll just pass NULL for the exception-record to the Hijack function.
+ if (pRecord != NULL)
+ {
+ espRecord = CORDB_ADDRESS_TO_PTR(PushHelper(&esp, pRecord, TRUE));
+ }
+ }
+
+ if(pRemoteContextAddr != NULL)
+ {
+ *pRemoteContextAddr = PTR_TO_CORDB_ADDRESS(espContext);
+ }
+
+ //
+ // Push args onto the stack to be able to call the hijack function
+ //
+
+ // Prototype of hijack is:
+ // void __stdcall ExceptionHijackWorker(CONTEXT * pContext, EXCEPTION_RECORD * pRecord, EHijackReason, void * pData)
+ // Set up everything so that the hijack stub can just do a "call" instruction.
+ //
+ // Regarding stack overflow: We could do an explicit check against the thread's stack base limit.
+ // However, we don't need an explicit overflow check because if the stack does overflow,
+ // the hijack will just hit a regular stack-overflow exception.
+#if defined(_TARGET_X86_) // TARGET
+ // X86 calling convention is to push args on the stack in reverse order.
+ // If we fail here, the stack is written, but esp hasn't been committed yet so it shouldn't matter.
+ PushHelper(&esp, &pData, TRUE);
+ PushHelper(&esp, &reason, TRUE);
+ PushHelper(&esp, &espRecord, TRUE);
+ PushHelper(&esp, &espContext, TRUE);
+#elif defined (_TARGET_AMD64_) // TARGET
+ // AMD64 calling convention is to place first 4 parameters in: rcx, rdx, r8 and r9
+ ctx.Rcx = (DWORD64) espContext;
+ ctx.Rdx = (DWORD64) espRecord;
+ ctx.R8 = (DWORD64) reason;
+ ctx.R9 = (DWORD64) pData;
+
+ // Caller must allocate stack space to spill for args.
+ // Push the arguments onto the outgoing argument homes.
+ // Make sure we push pointer-sized values to keep the stack aligned.
+ PushHelper(&esp, reinterpret_cast<SIZE_T *>(&(ctx.R9)), FALSE);
+ PushHelper(&esp, reinterpret_cast<SIZE_T *>(&(ctx.R8)), FALSE);
+ PushHelper(&esp, reinterpret_cast<SIZE_T *>(&(ctx.Rdx)), FALSE);
+ PushHelper(&esp, reinterpret_cast<SIZE_T *>(&(ctx.Rcx)), FALSE);
+#elif defined(_TARGET_ARM_)
+ ctx.R0 = (DWORD)espContext;
+ ctx.R1 = (DWORD)espRecord;
+ ctx.R2 = (DWORD)reason;
+ ctx.R3 = (DWORD)pData;
+#elif defined(_TARGET_ARM64_)
+ ctx.X0 = (DWORD64)espContext;
+ ctx.X1 = (DWORD64)espRecord;
+ ctx.X2 = (DWORD64)reason;
+ ctx.X3 = (DWORD64)pData;
+#else
+ PORTABILITY_ASSERT("CordbThread::HijackForUnhandledException is not implemented on this platform.");
+#endif
+ SetSP(&ctx, CORDB_ADDRESS_TO_TADDR(esp));
+
+ // @dbgtodo cross-plat - not cross-platform safe
+ SetIP(&ctx, pfnHijackFunction);
+
+ //
+ // Commit the context.
+ //
+ hr = m_pMutableTarget->SetThreadContext(dwThreadId, sizeof(ctx), reinterpret_cast<BYTE*> (&ctx));
+ IfFailThrow(hr);
+}
+
+// Return the filter CONTEXT on the LS.
+VMPTR_CONTEXT DacDbiInterfaceImpl::GetManagedStoppedContext(VMPTR_Thread vmThread)
+{
+ DD_ENTER_MAY_THROW;
+
+ VMPTR_CONTEXT vmContext = VMPTR_CONTEXT::NullPtr();
+
+ Thread * pThread = vmThread.GetDacPtr();
+ if (pThread->GetInteropDebuggingHijacked())
+ {
+ _ASSERTE(!ISREDIRECTEDTHREAD(pThread));
+ vmContext = VMPTR_CONTEXT::NullPtr();
+ }
+ else
+ {
+ DT_CONTEXT * pLSContext = reinterpret_cast<DT_CONTEXT *>(pThread->GetFilterContext());
+ if (pLSContext != NULL)
+ {
+ _ASSERTE(!ISREDIRECTEDTHREAD(pThread));
+ vmContext.SetHostPtr(pLSContext);
+ }
+ else if (ISREDIRECTEDTHREAD(pThread))
+ {
+ pLSContext = reinterpret_cast<DT_CONTEXT *>(GETREDIRECTEDCONTEXT(pThread));
+ _ASSERTE(pLSContext != NULL);
+
+ if (pLSContext != NULL)
+ {
+ vmContext.SetHostPtr(pLSContext);
+ }
+ }
+ }
+
+ return vmContext;
+}
+
+// Return a TargetBuffer for the raw vararg signature.
+TargetBuffer DacDbiInterfaceImpl::GetVarArgSig(CORDB_ADDRESS VASigCookieAddr,
+ CORDB_ADDRESS * pArgBase)
+{
+ DD_ENTER_MAY_THROW;
+
+ _ASSERTE(pArgBase != NULL);
+ *pArgBase = NULL;
+
+ // First, read the VASigCookie pointer.
+ TADDR taVASigCookie = NULL;
+ SafeReadStructOrThrow(VASigCookieAddr, &taVASigCookie);
+
+ // Now create a DAC copy of VASigCookie.
+ VASigCookie * pVACookie = PTR_VASigCookie(taVASigCookie);
+
+ // Figure out where the first argument is.
+#if defined(_TARGET_X86_) // (STACK_GROWS_DOWN_ON_ARGS_WALK)
+ *pArgBase = VASigCookieAddr + pVACookie->sizeOfArgs;
+#else // !_TARGET_X86_ (STACK_GROWS_UP_ON_ARGS_WALK)
+ *pArgBase = VASigCookieAddr + sizeof(VASigCookie *);
+#endif // !_TARGET_X86_ (STACK_GROWS_UP_ON_ARGS_WALK)
+
+ return TargetBuffer(PTR_TO_CORDB_ADDRESS(pVACookie->signature.GetRawSig()),
+ pVACookie->signature.GetRawSigLen());
+}
+
+// returns TRUE if the type requires 8-byte alignment
+BOOL DacDbiInterfaceImpl::RequiresAlign8(VMPTR_TypeHandle thExact)
+{
+ DD_ENTER_MAY_THROW;
+
+#ifdef FEATURE_64BIT_ALIGNMENT
+ TypeHandle th = TypeHandle::FromPtr(thExact.GetDacPtr());
+ PTR_MethodTable mt = th.AsMethodTable();
+
+ return mt->RequiresAlign8();
+#else
+ ThrowHR(E_NOTIMPL);
+#endif
+}
+
+// Resolve the raw generics token to the real generics type token. The resolution is based on the
+// given index.
+GENERICS_TYPE_TOKEN DacDbiInterfaceImpl::ResolveExactGenericArgsToken(DWORD dwExactGenericArgsTokenIndex,
+ GENERICS_TYPE_TOKEN rawToken)
+{
+ DD_ENTER_MAY_THROW;
+
+ if (dwExactGenericArgsTokenIndex == 0)
+ {
+ // In this case the real generics type token is the MethodTable of the "this" object.
+ // Note that we want the target address here.
+
+ // Incoming rawToken is actually a PTR_Object for the 'this' pointer.
+ // Need to do some casting to convert GENERICS_TYPE_TOKEN --> PTR_Object
+ TADDR addrObjThis = CORDB_ADDRESS_TO_TADDR(rawToken);
+ PTR_Object pObjThis = dac_cast<PTR_Object>(addrObjThis);
+
+
+ PTR_MethodTable pMT = pObjThis->GetMethodTable();
+
+ // Now package up the PTR_MethodTable back into a GENERICS_TYPE_TOKEN
+ TADDR addrMT = dac_cast<TADDR>(pMT);
+ GENERICS_TYPE_TOKEN realToken = (GENERICS_TYPE_TOKEN) addrMT;
+ return realToken;
+ }
+ else if (dwExactGenericArgsTokenIndex == (DWORD)ICorDebugInfo::TYPECTXT_ILNUM)
+ {
+ // rawToken is already initialized correctly. Nothing to do here.
+ return rawToken;
+ }
+
+ // The index of the generics type token should not be anything else.
+ // This is indeed an error condition, and so we throw here.
+ _ASSERTE(!"DDII::REGAT - Unexpected generics type token index.");
+ ThrowHR(CORDBG_E_TARGET_INCONSISTENT);
+}
+
+// Check if the given method is an IL stub or an LCD method.
+IDacDbiInterface::DynamicMethodType DacDbiInterfaceImpl::IsILStubOrLCGMethod(VMPTR_MethodDesc vmMethodDesc)
+{
+ DD_ENTER_MAY_THROW;
+
+ MethodDesc * pMD = vmMethodDesc.GetDacPtr();
+
+ if (pMD->IsILStub())
+ {
+ return kILStub;
+ }
+ else if (pMD->IsLCGMethod())
+ {
+ return kLCGMethod;
+ }
+ else
+ {
+ return kNone;
+ }
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Determine whether the specified thread is at a GC safe place.
+//
+// Arguments:
+// vmThread - the thread to be examined
+//
+// Return Value:
+// Return TRUE if the thread is at a GC safe place.
+// and under what conditions
+//
+// Notes:
+// This function basically does a one-frame stackwalk.
+// The logic is adopted from Debugger::IsThreadAtSafePlace().
+//
+
+BOOL DacDbiInterfaceImpl::IsThreadAtGCSafePlace(VMPTR_Thread vmThread)
+{
+ DD_ENTER_MAY_THROW;
+
+ BOOL fIsGCSafe = FALSE;
+ Thread * pThread = vmThread.GetDacPtr();
+
+ // Check if the runtime has entered "Shutdown for Finalizer" mode.
+ if ((g_fEEShutDown & ShutDown_Finalize2) != 0)
+ {
+ fIsGCSafe = TRUE;
+ }
+ else
+ {
+ T_CONTEXT ctx;
+ REGDISPLAY rd;
+ SetUpRegdisplayForStackWalk(pThread, &ctx, &rd);
+
+ ULONG32 flags = (QUICKUNWIND | HANDLESKIPPEDFRAMES | DISABLE_MISSING_FRAME_DETECTION);
+
+ StackFrameIterator iter;
+ iter.Init(pThread, pThread->GetFrame(), &rd, flags);
+
+ CrawlFrame * pCF = &(iter.m_crawl);
+ if (pCF->IsFrameless() && pCF->IsActiveFunc())
+ {
+ if (pCF->IsGcSafe())
+ {
+ fIsGCSafe = TRUE;
+ }
+ }
+ }
+
+ return fIsGCSafe;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Return a partial user state of the specified thread. The returned user state doesn't contain
+// information about USER_UNSAFE_POINT. The caller needs to call IsThreadAtGCSafePlace() to get
+// the full user state.
+//
+// Arguments:
+// vmThread - the specified thread
+//
+// Return Value:
+// Return the partial user state except for USER_UNSAFE_POINT
+//
+
+CorDebugUserState DacDbiInterfaceImpl::GetPartialUserState(VMPTR_Thread vmThread)
+{
+ DD_ENTER_MAY_THROW;
+
+ Thread * pThread = vmThread.GetDacPtr();
+ Thread::ThreadState ts = pThread->GetSnapshotState();
+
+ UINT result = 0;
+ if (ts & Thread::TS_Background)
+ {
+ result |= USER_BACKGROUND;
+ }
+
+ if (ts & Thread::TS_Unstarted)
+ {
+ result |= USER_UNSTARTED;
+ }
+
+ // Don't report a StopRequested if the thread has actually stopped.
+ if (ts & Thread::TS_Dead)
+ {
+ result |= USER_STOPPED;
+ }
+
+ // The interruptible flag is unreliable (see issue 699245)
+ // The Debugger_SleepWaitJoin is always accurate when it is present, but it is still
+ // just a band-aid fix to cover some of the race conditions interruptible has.
+
+ if (ts & Thread::TS_Interruptible || pThread->HasThreadStateNC(Thread::TSNC_DebuggerSleepWaitJoin))
+ {
+ result |= USER_WAIT_SLEEP_JOIN;
+ }
+
+ // Don't report a SuspendRequested if the thread has actually Suspended.
+ if ((ts & Thread::TS_UserSuspendPending) && (ts & Thread::TS_SyncSuspended))
+ {
+ result |= USER_SUSPENDED;
+ }
+ else if (ts & Thread::TS_UserSuspendPending)
+ {
+ result |= USER_SUSPEND_REQUESTED;
+ }
+
+ if (pThread->IsThreadPoolThread())
+ {
+ result |= USER_THREADPOOL;
+ }
+
+ return (CorDebugUserState)result;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Look up the EnC version number of a particular jitted instance of a managed method.
+//
+// Arguments:
+// pModule - the module containing the managed method
+// vmMethodDesc - the MethodDesc of the managed method
+// mdMethod - the MethodDef metadata token of the managed method
+// pNativeStartAddress - the native start address of the jitted code
+// pJittedInstanceEnCVersion - out parameter; the version number of the version
+// corresponding to the specified native start address
+// pLatestEnCVersion - out parameter; the version number of the latest version
+//
+// Assumptions:
+// vmMethodDesc and mdMethod must match (see below).
+//
+// Notes:
+// mdMethod is not strictly necessary, since we can always get that from vmMethodDesc.
+// It is just a perf optimization since the caller has the metadata token around already.
+//
+// Today, there is no way to retrieve the EnC version number from the RS data structures.
+// This primitive uses DAC to retrieve it from the LS data structures. This function may
+// very well be ripped out in the future if we DACize this information, but the current
+// thinking is that some of the RS data structures will remain, most likely in a reduced form.
+//
+
+void DacDbiInterfaceImpl::LookupEnCVersions(Module* pModule,
+ VMPTR_MethodDesc vmMethodDesc,
+ mdMethodDef mdMethod,
+ CORDB_ADDRESS pNativeStartAddress,
+ SIZE_T * pLatestEnCVersion,
+ SIZE_T * pJittedInstanceEnCVersion /* = NULL */)
+{
+ MethodDesc * pMD = vmMethodDesc.GetDacPtr();
+
+ // make sure the vmMethodDesc and mdMethod match
+ _ASSERTE(pMD->GetMemberDef() == mdMethod);
+
+ _ASSERTE(pLatestEnCVersion != NULL);
+
+ // @dbgtodo inspection - once we do EnC, stop using DMIs.
+ // If the method wasn't EnCed, DMIs may not exist. And since this is DAC, we can't create them.
+
+ // We may not have the memory for the DebuggerMethodInfos in a minidump.
+ // When dump debugging EnC information isn't very useful so just fallback
+ // to default version.
+ DebuggerMethodInfo * pDMI = NULL;
+ DebuggerJitInfo * pDJI = NULL;
+ EX_TRY_ALLOW_DATATARGET_MISSING_MEMORY
+ {
+ pDMI = g_pDebugger->GetOrCreateMethodInfo(pModule, mdMethod);
+ if (pDMI != NULL)
+ {
+ pDJI = pDMI->FindJitInfo(pMD, CORDB_ADDRESS_TO_TADDR(pNativeStartAddress));
+ }
+ }
+ EX_END_CATCH_ALLOW_DATATARGET_MISSING_MEMORY;
+ if (pDJI != NULL)
+ {
+ if (pJittedInstanceEnCVersion != NULL)
+ {
+ *pJittedInstanceEnCVersion = pDJI->m_encVersion;
+ }
+ *pLatestEnCVersion = pDMI->GetCurrentEnCVersion();
+ }
+ else
+ {
+ // If we have no DMI/DJI, then we must never have EnCed. So we can use default EnC info
+ // Several cases where we don't have a DMI/DJI:
+ // - LCG methods
+ // - method was never "touched" by debugger. (DJIs are created lazily).
+ if (pJittedInstanceEnCVersion != NULL)
+ {
+ *pJittedInstanceEnCVersion = CorDB_DEFAULT_ENC_FUNCTION_VERSION;
+ }
+ *pLatestEnCVersion = CorDB_DEFAULT_ENC_FUNCTION_VERSION;
+ }
+}
+
+// Get the address of the Debugger control block on the helper thread
+// Arguments: none
+// Return Value: The remote address of the Debugger control block allocated on the helper thread
+// if it has been successfully allocated or NULL otherwise.
+CORDB_ADDRESS DacDbiInterfaceImpl::GetDebuggerControlBlockAddress()
+{
+ DD_ENTER_MAY_THROW;
+
+ if ((g_pDebugger != NULL) &&
+ (g_pDebugger->m_pRCThread != NULL))
+ {
+ return CORDB_ADDRESS(dac_cast<TADDR>(g_pDebugger->m_pRCThread->GetDCB()));
+ }
+
+ return NULL;
+}
+
+// DacDbi API: Get the context for a particular thread of the target process
+void DacDbiInterfaceImpl::GetContext(VMPTR_Thread vmThread, DT_CONTEXT * pContextBuffer)
+{
+ DD_ENTER_MAY_THROW
+
+ _ASSERTE(pContextBuffer != NULL);
+
+ Thread * pThread = vmThread.GetDacPtr();
+
+ // @dbgtodo Once the filter context is removed, then we should always
+ // start with the leaf CONTEXT.
+ DT_CONTEXT * pFilterContext = reinterpret_cast<DT_CONTEXT *>(pThread->GetFilterContext());
+
+ if (pFilterContext == NULL)
+ {
+ // If the filter context is NULL, then we use the true context of the thread.
+ pContextBuffer->ContextFlags = CONTEXT_ALL;
+ HRESULT hr = m_pTarget->GetThreadContext(pThread->GetOSThreadId(),
+ pContextBuffer->ContextFlags,
+ sizeof(*pContextBuffer),
+ reinterpret_cast<BYTE *>(pContextBuffer));
+ if (hr == E_NOTIMPL)
+ {
+ // GetThreadContext is not implemented on this data target.
+ // That's why we have to make do with context we can obtain from Frames explicitly stored in Thread object.
+ // It suffices for managed debugging stackwalk.
+ REGDISPLAY tmpRd = {};
+ T_CONTEXT tmpContext = {};
+ FillRegDisplay(&tmpRd, &tmpContext);
+
+ // Going through thread Frames and looking for first (deepest one) one that
+ // that has context available for stackwalking (SP and PC)
+ // For example: RedirectedThreadFrame, InlinedCallFrame, HelperMethodFrame, ComPlusMethodFrame
+ Frame *frame = pThread->GetFrame();
+ while (frame != NULL && frame != FRAME_TOP)
+ {
+ frame->UpdateRegDisplay(&tmpRd);
+ if (GetRegdisplaySP(&tmpRd) != 0 && GetControlPC(&tmpRd) != 0)
+ {
+ UpdateContextFromRegDisp(&tmpRd, &tmpContext);
+ CopyMemory(pContextBuffer, &tmpContext, sizeof(*pContextBuffer));
+ pContextBuffer->ContextFlags = DT_CONTEXT_CONTROL;
+ return;
+ }
+ frame = frame->Next();
+ }
+
+ // It looks like this thread is not running managed code.
+ ZeroMemory(pContextBuffer, sizeof(*pContextBuffer));
+ }
+ else
+ {
+ IfFailThrow(hr);
+ }
+ }
+ else
+ {
+ *pContextBuffer = *pFilterContext;
+ }
+
+} // DacDbiInterfaceImpl::GetContext
+
+// Create a VMPTR_Object from a target object address
+// @dbgtodo validate the VMPTR_Object is in fact a object, possibly by DACizing
+// Object::Validate
+VMPTR_Object DacDbiInterfaceImpl::GetObject(CORDB_ADDRESS ptr)
+{
+ DD_ENTER_MAY_THROW;
+
+ VMPTR_Object vmObj = VMPTR_Object::NullPtr();
+ vmObj.SetDacTargetPtr(CORDB_ADDRESS_TO_TADDR(ptr));
+ return vmObj;
+}
+
+HRESULT DacDbiInterfaceImpl::EnableNGENPolicy(CorDebugNGENPolicy ePolicy)
+{
+#ifndef FEATURE_CORECLR
+ DD_ENTER_MAY_THROW;
+
+ // translate from our publicly exposed enum to the appropriate internal value
+ AssemblyUsageLogManager::ASSEMBLY_USAGE_LOG_FLAGS asmFlag = AssemblyUsageLogManager::ASSEMBLY_USAGE_LOG_FLAGS_NONE;
+
+ switch (ePolicy)
+ {
+ case DISABLE_LOCAL_NIC:
+ asmFlag = AssemblyUsageLogManager::ASSEMBLY_USAGE_LOG_FLAGS_APPLOCALNGENDISABLED;
+ break;
+ default:
+ return E_INVALIDARG;
+ }
+
+ // we should have made some selection
+ _ASSERTE(asmFlag != AssemblyUsageLogManager::ASSEMBLY_USAGE_LOG_FLAGS_NONE);
+
+ return AssemblyUsageLogManager::SetUsageLogFlag(asmFlag, TRUE);
+#else
+ return E_NOTIMPL;
+#endif // FEATURE_CORECLR
+}
+
+HRESULT DacDbiInterfaceImpl::SetNGENCompilerFlags(DWORD dwFlags)
+{
+ DD_ENTER_MAY_THROW;
+
+#ifndef FEATURE_PREJIT
+ return CORDBG_E_NGEN_NOT_SUPPORTED;
+#else
+ // verify that we are still early enough in runtime lifecycle to mutate these
+ // flags. Typically this is done in the CreateProcess event though it is possible
+ // to do it even earlier
+ if(!Debugger::s_fCanChangeNgenFlags)
+ return CORDBG_E_MUST_BE_IN_CREATE_PROCESS;
+
+ BOOL fAllowOpt =
+ ((dwFlags & CORDEBUG_JIT_DISABLE_OPTIMIZATION) != CORDEBUG_JIT_DISABLE_OPTIMIZATION);
+ PEFile::SetNGENDebugFlags(fAllowOpt);
+ return S_OK;
+#endif
+}
+
+HRESULT DacDbiInterfaceImpl::GetNGENCompilerFlags(DWORD *pdwFlags)
+{
+ DD_ENTER_MAY_THROW;
+
+#ifndef FEATURE_PREJIT
+ return CORDBG_E_NGEN_NOT_SUPPORTED;
+#else
+ BOOL fAllowOpt = TRUE;
+ PEFile::GetNGENDebugFlags(&fAllowOpt);
+ if(!fAllowOpt)
+ {
+ *pdwFlags = CORDEBUG_JIT_DISABLE_OPTIMIZATION;
+ }
+ else
+ {
+ *pdwFlags = CORDEBUG_JIT_DEFAULT;
+ }
+
+ return S_OK;
+#endif
+}
+
+typedef DPTR(OBJECTREF) PTR_ObjectRef;
+
+// Create a VMPTR_Object from an address which points to a reference to an object
+// @dbgtodo validate the VMPTR_Object is in fact a object, possibly by DACizing
+// Object::Validate
+VMPTR_Object DacDbiInterfaceImpl::GetObjectFromRefPtr(CORDB_ADDRESS ptr)
+{
+ DD_ENTER_MAY_THROW;
+
+ VMPTR_Object vmObj = VMPTR_Object::NullPtr();
+ PTR_ObjectRef objRef = PTR_ObjectRef(CORDB_ADDRESS_TO_TADDR(ptr));
+ vmObj.SetDacTargetPtr(PTR_TO_TADDR(*objRef));
+
+ return vmObj;
+}
+
+// Create a VMPTR_OBJECTHANDLE from a handle
+VMPTR_OBJECTHANDLE DacDbiInterfaceImpl::GetVmObjectHandle(CORDB_ADDRESS handleAddress)
+{
+ DD_ENTER_MAY_THROW;
+
+ VMPTR_OBJECTHANDLE vmObjHandle = VMPTR_OBJECTHANDLE::NullPtr();
+ vmObjHandle.SetDacTargetPtr(CORDB_ADDRESS_TO_TADDR(handleAddress));
+
+ return vmObjHandle;
+}
+
+
+// Validate that the VMPTR_OBJECTHANDLE refers to a legitimate managed object
+BOOL DacDbiInterfaceImpl::IsVmObjectHandleValid(VMPTR_OBJECTHANDLE vmHandle)
+{
+ DD_ENTER_MAY_THROW;
+
+ BOOL ret = FALSE;
+ // this may cause unallocated debuggee memory to be read
+ // SEH exceptions will be caught
+ EX_TRY
+ {
+ OBJECTREF objRef = ObjectFromHandle((OBJECTHANDLE)vmHandle.GetDacPtr());
+
+ // NULL is certinally valid...
+ if (objRef != NULL)
+ {
+ if (objRef->ValidateObjectWithPossibleAV())
+ {
+ ret = TRUE;
+ }
+ }
+ }
+ EX_CATCH
+ {
+ }
+ EX_END_CATCH(SwallowAllExceptions);
+
+ return ret;
+}
+
+// determines if the specified module is a WinRT module
+HRESULT DacDbiInterfaceImpl::IsWinRTModule(VMPTR_Module vmModule, BOOL& isWinRT)
+{
+ DD_ENTER_MAY_THROW;
+
+ HRESULT hr = S_OK;
+ isWinRT = FALSE;
+
+ EX_TRY
+ {
+ Module* pModule = vmModule.GetDacPtr();
+ isWinRT = pModule->GetFile()->GetAssembly()->IsWindowsRuntime();
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+// Determines the app domain id for the object refered to by a given VMPTR_OBJECTHANDLE
+ULONG DacDbiInterfaceImpl::GetAppDomainIdFromVmObjectHandle(VMPTR_OBJECTHANDLE vmHandle)
+{
+ DD_ENTER_MAY_THROW;
+
+ OBJECTHANDLE handle = (OBJECTHANDLE) vmHandle.GetDacPtr();
+ return HndGetHandleADIndex(handle).m_dwIndex;
+}
+
+// Get the target address from a VMPTR_OBJECTHANDLE, i.e., the handle address
+CORDB_ADDRESS DacDbiInterfaceImpl::GetHandleAddressFromVmHandle(VMPTR_OBJECTHANDLE vmHandle)
+{
+ DD_ENTER_MAY_THROW;
+
+ CORDB_ADDRESS handle = vmHandle.GetDacPtr();
+
+ return handle;
+}
+
+// Create a TargetBuffer which describes the location of the object
+TargetBuffer DacDbiInterfaceImpl::GetObjectContents(VMPTR_Object vmObj)
+{
+ DD_ENTER_MAY_THROW;
+ PTR_Object objPtr = vmObj.GetDacPtr();
+
+ _ASSERTE(objPtr->GetSize() <= 0xffffffff);
+ return TargetBuffer(PTR_TO_TADDR(objPtr), (ULONG)objPtr->GetSize());
+}
+
+// ============================================================================
+// functions to get information about objects referenced via an instance of CordbReferenceValue or
+// CordbHandleValue
+// ============================================================================
+
+// DacDbiInterfaceImpl::FastSanityCheckObject
+// Helper function for CheckRef. Sanity check an object.
+// We use a fast and easy check to improve confidence that objPtr points to a valid object.
+// We can't tell cheaply if this is really a valid object (that would require walking the GC heap), but at
+// least we can check if we get an EEClass from the supposed method table and then get the method table from
+// the class. If we can, we have improved the probability that the object is valid.
+// Arguments:
+// input: objPtr - address of the object we are checking
+// Return Value: E_INVALIDARG or S_OK.
+HRESULT DacDbiInterfaceImpl::FastSanityCheckObject(PTR_Object objPtr)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+ // NULL is certainly valid...
+ if (objPtr != NULL)
+ {
+ if (!objPtr->ValidateObjectWithPossibleAV())
+ {
+ LOG((LF_CORDB, LL_INFO10000, "GOI: object methodtable-class invariant doesn't hold.\n"));
+ hr = E_INVALIDARG;
+ }
+ }
+ }
+ EX_CATCH
+ {
+ LOG((LF_CORDB, LL_INFO10000, "GOI: exception indicated ref is bad.\n"));
+ hr = E_INVALIDARG;
+ }
+ EX_END_CATCH(SwallowAllExceptions);
+
+ return hr;
+} // DacDbiInterfaceImpl::FastSanityCheckObject
+
+// Perform a sanity check on an object address to determine if this _could be_ a valid object.
+// We can't tell this for certain without walking the GC heap, but we do some fast tests to rule
+// out clearly invalid object addresses. See code:DacDbiInterfaceImpl::FastSanityCheckObject for more
+// details.
+// Arguments:
+// input: objPtr - address of the object we are checking
+// Return Value:
+// objRefBad - true iff we have determined the address cannot be pointing to a valid object.
+// Note that a value of false doesn't necessarily guarantee the object is really
+// valid
+bool DacDbiInterfaceImpl::CheckRef(PTR_Object objPtr)
+{
+ bool objRefBad = false;
+
+ // Shortcut null references now...
+ if (objPtr == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "D::GOI: ref is NULL.\n"));
+
+ objRefBad = true;
+ }
+ else
+ {
+ // Try to verify the integrity of the object. This is not fool proof.
+ // @todo - this whole idea of expecting AVs is broken, but it does rule
+ // out a fair bit of rubbish. Find another
+ // way to test if the object is valid?
+ if (FAILED(FastSanityCheckObject(objPtr)))
+ {
+ LOG((LF_CORDB, LL_INFO10000, "D::GOI: address is not a valid object.\n"));
+
+ objRefBad = true;
+ }
+ }
+
+ return objRefBad;
+} // DacDbiInterfaceImpl::CheckRef
+
+// DacDbiInterfaceImpl::InitObjectData
+// Initialize basic object information: type handle, object size, offset to fields and expanded type
+// information.
+// Arguments:
+// input: objPtr - address of object of interest
+// vmAppDomain - AppDomain for the type f the object
+// output: pObjectData - object information
+// Note: It is assumed that pObjectData is non-null.
+void DacDbiInterfaceImpl::InitObjectData(PTR_Object objPtr,
+ VMPTR_AppDomain vmAppDomain,
+ DebuggerIPCE_ObjectData * pObjectData)
+{
+ _ASSERTE(pObjectData != NULL);
+ // @todo - this is still dangerous because the object may still be invalid.
+ VMPTR_TypeHandle vmTypeHandle = VMPTR_TypeHandle::NullPtr();
+ vmTypeHandle.SetDacTargetPtr(objPtr->GetGCSafeTypeHandle().AsTAddr());
+
+ // Save basic object info.
+ pObjectData->objSize = objPtr->GetSize();
+ pObjectData->objOffsetToVars = dac_cast<TADDR>((objPtr)->GetData()) - dac_cast<TADDR>(objPtr);
+
+ TypeHandleToExpandedTypeInfo(AllBoxed, vmAppDomain, vmTypeHandle, &(pObjectData->objTypeData));
+
+ // If this is a string object, set the type to ELEMENT_TYPE_STRING.
+ if (objPtr->GetGCSafeMethodTable() == g_pStringClass)
+ {
+ pObjectData->objTypeData.elementType = ELEMENT_TYPE_STRING;
+ if(pObjectData->objSize < MIN_OBJECT_SIZE)
+ {
+ pObjectData->objSize = PtrAlign(pObjectData->objSize);
+ }
+ }
+} // DacDbiInterfaceImpl::InitObjectData
+
+// DAC/DBI API
+
+// Get object information for a TypedByRef object (System.TypedReference).
+
+// These are objects that contain a managed pointer to a location and the type of the value at that location.
+// They are most commonly used for varargs but also may be used for parameters and locals. They are
+// stack-allocated. They provide a means for adding dynamic type information to a value type, whereas boxing
+// provides only static type information. This means they can be passed as reference parameters to
+// polymorphic methods that don't statically restrict the type of arguments they can receive.
+
+// Although they are represented simply as an address, unlike other object references, they don't point
+// directly to the object. Instead, there is an extra level of indirection. The reference points to a struct
+// that contains the address of the object, so we need to treat them differently. They have their own
+// CorElementType (ELEMENT_TYPE_TYPEDBYREF) which makes it possible to identify this special case.
+
+// Example:
+// static int AddABunchOfInts (__arglist)
+// {
+// int result = 0;
+//
+// System.ArgIterator iter = new System.ArgIterator (__arglist);
+// int argCount = iter.GetRemainingCount();
+//
+// for (int i = 0; i < argCount; i++)
+// {
+// System.TypedReference typedRef = iter.GetNextArg();
+// result += (int)TypedReference.ToObject(typedRef);
+// }
+//
+// return result;
+// }
+//
+// static int Main (string[] args)
+// {
+// int result = AddABunchOfInts (__arglist (2, 3, 4));
+// Console.WriteLine ("Answer: {0}", result);
+//
+// if (result != 9)
+// return 1;
+//
+// return 0;
+// }
+
+// Initializes the objRef and typedByRefType fields of pObjectData (type info for the referent).
+void DacDbiInterfaceImpl::GetTypedByRefInfo(CORDB_ADDRESS pTypedByRef,
+ VMPTR_AppDomain vmAppDomain,
+ DebuggerIPCE_ObjectData * pObjectData)
+{
+ DD_ENTER_MAY_THROW;
+
+ // pTypedByRef is really the address of a TypedByRef struct rather than of a normal object.
+ // The data field of the TypedByRef struct is the actual object ref.
+ PTR_TypedByRef refAddr = PTR_TypedByRef(TADDR(pTypedByRef));
+
+ _ASSERTE(refAddr != NULL);
+ _ASSERTE(pObjectData != NULL);
+
+ // The type of the referent is in the type field of the TypedByRef. We need to initialize the object
+ // data type information.
+ TypeHandleToBasicTypeInfo(refAddr->type,
+ &(pObjectData->typedByrefInfo.typedByrefType),
+ vmAppDomain.GetDacPtr());
+
+ // The reference to the object is in the data field of the TypedByRef.
+ CORDB_ADDRESS tempRef = dac_cast<TADDR>(refAddr->data);
+ pObjectData->objRef = CORDB_ADDRESS_TO_PTR(tempRef);
+
+ LOG((LF_CORDB, LL_INFO10000, "D::GASOI: sending REFANY result: "
+ "ref=0x%08x, cls=0x%08x, mod=0x%p\n",
+ pObjectData->objRef,
+ pObjectData->typedByrefType.metadataToken,
+ pObjectData->typedByrefType.vmDomainFile.GetDacPtr()));
+} // DacDbiInterfaceImpl::GetTypedByRefInfo
+
+// Get the string data associated withn obj and put it into the pointers
+// DAC/DBI API
+// Get the string length and offset to string base for a string object
+void DacDbiInterfaceImpl::GetStringData(CORDB_ADDRESS objectAddress, DebuggerIPCE_ObjectData * pObjectData)
+{
+ DD_ENTER_MAY_THROW;
+
+ PTR_Object objPtr = PTR_Object(TADDR(objectAddress));
+ LOG((LF_CORDB, LL_INFO10000, "D::GOI: The referent is a string.\n"));
+
+ if (objPtr->GetGCSafeMethodTable() != g_pStringClass)
+ {
+ ThrowHR(CORDBG_E_TARGET_INCONSISTENT);
+ }
+
+ PTR_StringObject pStrObj = dac_cast<PTR_StringObject>(objPtr);
+
+ _ASSERTE(pStrObj != NULL);
+ pObjectData->stringInfo.length = pStrObj->GetStringLength();
+ pObjectData->stringInfo.offsetToStringBase = (UINT_PTR) pStrObj->GetBufferOffset();
+
+} // DacDbiInterfaceImpl::GetStringData
+
+
+// DAC/DBI API
+// Get information for an array type referent of an objRef, including rank, upper and lower
+// bounds, element size and type, and the number of elements.
+void DacDbiInterfaceImpl::GetArrayData(CORDB_ADDRESS objectAddress, DebuggerIPCE_ObjectData * pObjectData)
+{
+ DD_ENTER_MAY_THROW;
+
+ PTR_Object objPtr = PTR_Object(TADDR(objectAddress));
+ PTR_MethodTable pMT = objPtr->GetGCSafeMethodTable();
+
+ if (!objPtr->GetGCSafeTypeHandle().IsArray())
+ {
+ LOG((LF_CORDB, LL_INFO10000,
+ "D::GASOI: object should be an array.\n"));
+
+ pObjectData->objRefBad = true;
+ }
+ else
+ {
+ PTR_ArrayBase arrPtr = dac_cast<PTR_ArrayBase>(objPtr);
+
+ // this is also returned in the type information for the array - we return both for sanity checking...
+ pObjectData->arrayInfo.rank = arrPtr->GetRank();
+ pObjectData->arrayInfo.componentCount = arrPtr->GetNumComponents();
+ pObjectData->arrayInfo.offsetToArrayBase = arrPtr->GetDataPtrOffset(pMT);
+
+ if (arrPtr->IsMultiDimArray())
+ {
+ pObjectData->arrayInfo.offsetToUpperBounds = SIZE_T(arrPtr->GetBoundsOffset(pMT));
+
+ pObjectData->arrayInfo.offsetToLowerBounds = SIZE_T(arrPtr->GetLowerBoundsOffset(pMT));
+ }
+ else
+ {
+ pObjectData->arrayInfo.offsetToUpperBounds = 0;
+ pObjectData->arrayInfo.offsetToLowerBounds = 0;
+ }
+
+ pObjectData->arrayInfo.elementSize = arrPtr->GetComponentSize();
+
+ LOG((LF_CORDB, LL_INFO10000, "D::GOI: array info: "
+ "baseOff=%d, lowerOff=%d, upperOff=%d, cnt=%d, rank=%d, rank (2) = %d,"
+ "eleSize=%d, eleType=0x%02x\n",
+ pObjectData->arrayInfo.offsetToArrayBase,
+ pObjectData->arrayInfo.offsetToLowerBounds,
+ pObjectData->arrayInfo.offsetToUpperBounds,
+ pObjectData->arrayInfo.componentCount,
+ pObjectData->arrayInfo.rank,
+ pObjectData->objTypeData.ArrayTypeData.arrayRank,
+ pObjectData->arrayInfo.elementSize,
+ pObjectData->objTypeData.ArrayTypeData.arrayTypeArg.elementType));
+ }
+} // DacDbiInterfaceImpl::GetArrayData
+
+// DAC/DBI API: Get information about an object for which we have a reference, including the object size and
+// type information.
+void DacDbiInterfaceImpl::GetBasicObjectInfo(CORDB_ADDRESS objectAddress,
+ CorElementType type,
+ VMPTR_AppDomain vmAppDomain,
+ DebuggerIPCE_ObjectData * pObjectData)
+{
+ DD_ENTER_MAY_THROW;
+
+ PTR_Object objPtr = PTR_Object(TADDR(objectAddress));
+ pObjectData->objRefBad = CheckRef(objPtr);
+ if (pObjectData->objRefBad != true)
+ {
+ // initialize object type, size, offset information. Note: We may have a different element type
+ // after this. For example, we may start with E_T_CLASS but return with something more specific.
+ InitObjectData (objPtr, vmAppDomain, pObjectData);
+ }
+} // DacDbiInterfaceImpl::GetBasicObjectInfo
+
+// This is the data passed to EnumerateBlockingObjectsCallback below
+struct BlockingObjectUserDataWrapper
+{
+ CALLBACK_DATA pUserData;
+ IDacDbiInterface::FP_BLOCKINGOBJECT_ENUMERATION_CALLBACK fpCallback;
+};
+
+// The callback helper used by EnumerateBlockingObjects below, this
+// callback in turn invokes the user's callback with the right arguments
+void EnumerateBlockingObjectsCallback(PTR_DebugBlockingItem obj, VOID* pUserData)
+{
+ BlockingObjectUserDataWrapper* wrapper = (BlockingObjectUserDataWrapper*)pUserData;
+ DacBlockingObject dacObj;
+
+ // init to an arbitrary value to avoid mac compiler error about unintialized use
+ // it will be correctly set in the switch and is never used with only this init here
+ dacObj.blockingReason = DacBlockReason_MonitorCriticalSection;
+
+ dacObj.vmBlockingObject.SetDacTargetPtr(dac_cast<TADDR>(OBJECTREFToObject(obj->pMonitor->GetOwningObject())));
+ dacObj.dwTimeout = obj->dwTimeout;
+ dacObj.vmAppDomain.SetDacTargetPtr(dac_cast<TADDR>(obj->pAppDomain));
+ switch(obj->type)
+ {
+ case DebugBlock_MonitorCriticalSection:
+ dacObj.blockingReason = DacBlockReason_MonitorCriticalSection;
+ break;
+ case DebugBlock_MonitorEvent:
+ dacObj.blockingReason = DacBlockReason_MonitorEvent;
+ break;
+ default:
+ _ASSERTE(!"obj->type has an invalid value");
+ return;
+ }
+
+ wrapper->fpCallback(dacObj, wrapper->pUserData);
+}
+
+// DAC/DBI API:
+// Enumerate all monitors blocking a thread
+void DacDbiInterfaceImpl::EnumerateBlockingObjects(VMPTR_Thread vmThread,
+ FP_BLOCKINGOBJECT_ENUMERATION_CALLBACK fpCallback,
+ CALLBACK_DATA pUserData)
+{
+ DD_ENTER_MAY_THROW;
+
+ Thread * pThread = vmThread.GetDacPtr();
+ _ASSERTE(pThread != NULL);
+
+ BlockingObjectUserDataWrapper wrapper;
+ wrapper.fpCallback = fpCallback;
+ wrapper.pUserData = pUserData;
+
+ pThread->DebugBlockingInfo.VisitBlockingItems((DebugBlockingItemVisitor)EnumerateBlockingObjectsCallback,
+ (VOID*)&wrapper);
+}
+
+// DAC/DBI API:
+// Returns the thread which owns the monitor lock on an object and the acquisition count
+MonitorLockInfo DacDbiInterfaceImpl::GetThreadOwningMonitorLock(VMPTR_Object vmObject)
+{
+ DD_ENTER_MAY_THROW;
+ MonitorLockInfo info;
+ info.lockOwner = VMPTR_Thread::NullPtr();
+ info.acquisitionCount = 0;
+
+ Object* pObj = vmObject.GetDacPtr();
+ DWORD threadId;
+ DWORD acquisitionCount;
+ if(!pObj->GetHeader()->GetThreadOwningMonitorLock(&threadId, &acquisitionCount))
+ {
+ return info;
+ }
+
+ Thread *pThread = ThreadStore::GetThreadList(NULL);
+ while (pThread != NULL)
+ {
+ if(pThread->GetThreadId() == threadId)
+ {
+ info.lockOwner.SetDacTargetPtr(PTR_HOST_TO_TADDR(pThread));
+ info.acquisitionCount = acquisitionCount;
+ return info;
+ }
+ pThread = ThreadStore::GetThreadList(pThread);
+ }
+ _ASSERTE(!"A thread should have been found");
+ return info;
+}
+
+// The data passed to EnumerateThreadsCallback below
+struct ThreadUserDataWrapper
+{
+ CALLBACK_DATA pUserData;
+ IDacDbiInterface::FP_THREAD_ENUMERATION_CALLBACK fpCallback;
+};
+
+// The callback helper used for EnumerateMonitorEventWaitList below. This callback
+// invokes the user's callback with the correct arguments.
+void EnumerateThreadsCallback(PTR_Thread pThread, VOID* pUserData)
+{
+ ThreadUserDataWrapper* wrapper = (ThreadUserDataWrapper*)pUserData;
+ VMPTR_Thread vmThread = VMPTR_Thread::NullPtr();
+ vmThread.SetDacTargetPtr(dac_cast<TADDR>(pThread));
+ wrapper->fpCallback(vmThread, wrapper->pUserData);
+}
+
+// DAC/DBI API:
+// Enumerate all threads waiting on the monitor event for an object
+void DacDbiInterfaceImpl::EnumerateMonitorEventWaitList(VMPTR_Object vmObject,
+ FP_THREAD_ENUMERATION_CALLBACK fpCallback,
+ CALLBACK_DATA pUserData)
+{
+ DD_ENTER_MAY_THROW;
+
+ Object* pObj = vmObject.GetDacPtr();
+ SyncBlock* psb = pObj->PassiveGetSyncBlock();
+
+ // no sync block means no wait list
+ if(psb == NULL)
+ return;
+
+ ThreadUserDataWrapper wrapper;
+ wrapper.fpCallback = fpCallback;
+ wrapper.pUserData = pUserData;
+ ThreadQueue::EnumerateThreads(psb, (FP_TQ_THREAD_ENUMERATION_CALLBACK)EnumerateThreadsCallback, (VOID*) &wrapper);
+}
+
+
+bool DacDbiInterfaceImpl::AreGCStructuresValid()
+{
+ return true;
+}
+
+HeapData::HeapData()
+ : YoungestGenPtr(0), YoungestGenLimit(0), Gen0Start(0), Gen0End(0), SegmentCount(0), Segments(0)
+{
+}
+
+HeapData::~HeapData()
+{
+ if (Segments)
+ delete [] Segments;
+}
+
+LinearReadCache::LinearReadCache()
+ : mCurrPageStart(0), mPageSize(0), mCurrPageSize(0), mPage(0)
+{
+ SYSTEM_INFO si;
+ GetSystemInfo(&si);
+
+ mPageSize = si.dwPageSize;
+ mPage = new (nothrow) BYTE[mPageSize];
+}
+
+LinearReadCache::~LinearReadCache()
+{
+ if (mPage)
+ delete [] mPage;
+}
+
+bool LinearReadCache::MoveToPage(CORDB_ADDRESS addr)
+{
+ mCurrPageStart = addr - (addr % mPageSize);
+ HRESULT hr = g_dacImpl->m_pTarget->ReadVirtual(mCurrPageStart, mPage, mPageSize, &mCurrPageSize);
+
+ if (hr != S_OK)
+ {
+ mCurrPageStart = 0;
+ mCurrPageSize = 0;
+ return false;
+ }
+
+ return true;
+}
+
+
+CORDB_ADDRESS DacHeapWalker::HeapStart = 0;
+CORDB_ADDRESS DacHeapWalker::HeapEnd = ~0;
+
+DacHeapWalker::DacHeapWalker()
+ : mThreadCount(0), mAllocInfo(0), mHeapCount(0), mHeaps(0),
+ mCurrObj(0), mCurrSize(0), mCurrMT(0),
+ mCurrHeap(0), mCurrSeg(0), mStart((TADDR)HeapStart), mEnd((TADDR)HeapEnd)
+{
+}
+
+DacHeapWalker::~DacHeapWalker()
+{
+ if (mAllocInfo)
+ delete [] mAllocInfo;
+
+ if (mHeaps)
+ delete [] mHeaps;
+}
+
+SegmentData *DacHeapWalker::FindSegment(CORDB_ADDRESS obj)
+{
+ for (size_t i = 0; i < mHeapCount; ++i)
+ for (size_t j = 0; j < mHeaps[i].SegmentCount; ++j)
+ if (mHeaps[i].Segments[j].Start <= obj && obj <= mHeaps[i].Segments[j].End)
+ return &mHeaps[i].Segments[j];
+
+ return NULL;
+}
+
+HRESULT DacHeapWalker::Next(CORDB_ADDRESS *pValue, CORDB_ADDRESS *pMT, ULONG64 *pSize)
+{
+ if (!HasMoreObjects())
+ return E_FAIL;
+
+ if (pValue)
+ *pValue = mCurrObj;
+
+ if (pMT)
+ *pMT = (CORDB_ADDRESS)mCurrMT;
+
+ if (pSize)
+ *pSize = (ULONG64)mCurrSize;
+
+ HRESULT hr = MoveToNextObject();
+ return FAILED(hr) ? hr : S_OK;
+}
+
+
+
+HRESULT DacHeapWalker::MoveToNextObject()
+{
+ do
+ {
+ // Move to the next object
+ mCurrObj += mCurrSize;
+
+ // Check to see if we are in the correct bounds.
+ if (mHeaps[mCurrHeap].Gen0Start <= mCurrObj && mHeaps[mCurrHeap].Gen0End > mCurrObj)
+ CheckAllocAndSegmentRange();
+
+ // Check to see if we've moved off the end of a segment
+ if (mCurrObj >= mHeaps[mCurrHeap].Segments[mCurrSeg].End || mCurrObj > mEnd)
+ {
+ HRESULT hr = NextSegment();
+ if (FAILED(hr) || hr == S_FALSE)
+ return hr;
+ }
+
+ // Get the method table pointer
+ if (!mCache.ReadMT(mCurrObj, &mCurrMT))
+ return E_FAIL;
+
+ if (!GetSize(mCurrMT, mCurrSize))
+ return E_FAIL;
+ } while (mCurrObj < mStart);
+
+ _ASSERTE(mStart <= mCurrObj && mCurrObj <= mEnd);
+ return S_OK;
+}
+
+bool DacHeapWalker::GetSize(TADDR tMT, size_t &size)
+{
+ // With heap corruption, it's entierly possible that the MethodTable
+ // we get is bad. This could cause exceptions, which we will catch
+ // and return false. This causes the heapwalker to move to the next
+ // segment.
+ bool ret = true;
+ EX_TRY
+ {
+ MethodTable *mt = PTR_MethodTable(tMT);
+ size_t cs = mt->GetComponentSize();
+
+ if (cs)
+ {
+ DWORD tmp = 0;
+ if (mCache.Read(mCurrObj+sizeof(TADDR), &tmp))
+ cs *= tmp;
+ else
+ ret = false;
+ }
+
+ size = mt->GetBaseSize() + cs;
+
+ // The size is not guaranteed to be aligned, we have to
+ // do that ourself.
+ if (mHeaps[mCurrHeap].Segments[mCurrSeg].Generation == 3)
+ size = AlignLarge(size);
+ else
+ size = Align(size);
+ }
+ EX_CATCH
+ {
+ ret = false;
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ return ret;
+}
+
+
+HRESULT DacHeapWalker::NextSegment()
+{
+ mCurrObj = 0;
+ mCurrMT = 0;
+ mCurrSize = 0;
+
+ do
+ {
+ mCurrSeg++;
+ while (mCurrSeg >= mHeaps[mCurrHeap].SegmentCount)
+ {
+ mCurrSeg = 0;
+ mCurrHeap++;
+
+ if (mCurrHeap >= mHeapCount)
+ {
+ return S_FALSE;
+ }
+ }
+
+ mCurrObj = mHeaps[mCurrHeap].Segments[mCurrSeg].Start;
+
+ if (mHeaps[mCurrHeap].Gen0Start <= mCurrObj && mHeaps[mCurrHeap].Gen0End > mCurrObj)
+ CheckAllocAndSegmentRange();
+
+ if (!mCache.ReadMT(mCurrObj, &mCurrMT))
+ {
+ return E_FAIL;
+ }
+
+ if (!GetSize(mCurrMT, mCurrSize))
+ {
+ return E_FAIL;
+ }
+ } while((mHeaps[mCurrHeap].Segments[mCurrSeg].Start > mEnd) || (mHeaps[mCurrHeap].Segments[mCurrSeg].End < mStart));
+
+ return S_OK;
+}
+
+void DacHeapWalker::CheckAllocAndSegmentRange()
+{
+ const size_t MinObjSize = sizeof(TADDR)*3;
+
+ for (int i = 0; i < mThreadCount; ++i)
+ if (mCurrObj == mAllocInfo[i].Ptr)
+ {
+ mCurrObj = mAllocInfo[i].Limit + Align(MinObjSize);
+ break;
+ }
+
+ if (mCurrObj == mHeaps[mCurrHeap].YoungestGenPtr)
+ {
+ mCurrObj = mHeaps[mCurrHeap].YoungestGenLimit + Align(MinObjSize);
+ }
+}
+
+HRESULT DacHeapWalker::Init(CORDB_ADDRESS start, CORDB_ADDRESS end)
+{
+ // Collect information about the allocation contexts in the process.
+ ThreadStore* threadStore = ThreadStore::s_pThreadStore;
+ if (threadStore != NULL)
+ {
+ int count = (int)threadStore->ThreadCountInEE();
+ mAllocInfo = new (nothrow) AllocInfo[count];
+ if (mAllocInfo == NULL)
+ return E_OUTOFMEMORY;
+
+ Thread *thread = NULL;
+ int j = 0;
+ for (int i = 0; i < count; ++i)
+ {
+ // The thread or allocation context being null is troubling, but not fatal.
+ // We may have stopped the process where the thread list or thread's alloc
+ // context was in an inconsistent state. We will simply skip over affected
+ // segments during the heap walk if we encounter problems due to this.
+ thread = ThreadStore::GetThreadList(thread);
+ if (thread == NULL)
+ continue;
+
+ alloc_context *ctx = thread->GetAllocContext();
+ if (ctx == NULL)
+ continue;
+
+ if ((CORDB_ADDRESS)ctx->alloc_ptr != NULL)
+ {
+ mAllocInfo[j].Ptr = (CORDB_ADDRESS)ctx->alloc_ptr;
+ mAllocInfo[j].Limit = (CORDB_ADDRESS)ctx->alloc_limit;
+ j++;
+ }
+ }
+
+ mThreadCount = j;
+ }
+
+#ifdef FEATURE_SVR_GC
+ HRESULT hr = GCHeap::IsServerHeap() ? InitHeapDataSvr(mHeaps, mHeapCount) : InitHeapDataWks(mHeaps, mHeapCount);
+#else
+ HRESULT hr = InitHeapDataWks(mHeaps, mHeapCount);
+#endif
+
+ // Set up mCurrObj/mCurrMT.
+ if (SUCCEEDED(hr))
+ hr = Reset(start, end);
+
+ // Collect information about GC heaps
+ return hr;
+}
+
+HRESULT DacHeapWalker::Reset(CORDB_ADDRESS start, CORDB_ADDRESS end)
+{
+ _ASSERTE(mHeaps);
+ _ASSERTE(mHeapCount > 0);
+ _ASSERTE(mHeaps[0].Segments);
+ _ASSERTE(mHeaps[0].SegmentCount > 0);
+
+ mStart = start;
+ mEnd = end;
+
+ // Set up first object
+ mCurrObj = mHeaps[0].Segments[0].Start;
+ mCurrMT = 0;
+ mCurrSize = 0;
+ mCurrHeap = 0;
+ mCurrSeg = 0;
+
+ if (!mCache.ReadMT(mCurrObj, &mCurrMT))
+ return E_FAIL;
+
+ if (!GetSize(mCurrMT, mCurrSize))
+ return E_FAIL;
+
+ if (mCurrObj < mStart || mCurrObj > mEnd)
+ MoveToNextObject();
+
+ return S_OK;
+}
+
+HRESULT DacHeapWalker::ListNearObjects(CORDB_ADDRESS obj, CORDB_ADDRESS *pPrev, CORDB_ADDRESS *pContaining, CORDB_ADDRESS *pNext)
+{
+ SegmentData *seg = FindSegment(obj);
+
+ if (seg == NULL)
+ return E_FAIL;
+
+ HRESULT hr = Reset(seg->Start, seg->End);
+ if (SUCCEEDED(hr))
+ {
+ CORDB_ADDRESS prev = 0;
+ CORDB_ADDRESS curr = 0;
+ ULONG64 size = 0;
+ bool found = false;
+
+ while (!found && HasMoreObjects())
+ {
+ prev = curr;
+ hr = Next(&curr, NULL, &size);
+ if (FAILED(hr))
+ break;
+
+ if (obj >= curr && obj < curr + size)
+ found = true;
+ }
+
+ if (found)
+ {
+ if (pPrev)
+ *pPrev = prev;
+
+ if (pContaining)
+ *pContaining = curr;
+
+ if (pNext)
+ {
+ if (HasMoreObjects())
+ {
+ hr = Next(&curr, NULL, NULL);
+ if (SUCCEEDED(hr))
+ *pNext = curr;
+ }
+ else
+ {
+ *pNext = 0;
+ }
+ }
+
+ hr = S_OK;
+ }
+ else if (SUCCEEDED(hr))
+ {
+ hr = E_FAIL;
+ }
+ }
+
+ return hr;
+}
+
+#include "gceewks.cpp"
+HRESULT DacHeapWalker::InitHeapDataWks(HeapData *&pHeaps, size_t &pCount)
+{
+ // Scrape basic heap details
+ pCount = 1;
+ pHeaps = new (nothrow) HeapData[1];
+ if (pHeaps == NULL)
+ return E_OUTOFMEMORY;
+
+ pHeaps[0].YoungestGenPtr = (CORDB_ADDRESS)WKS::generation_table[0].allocation_context.alloc_ptr;
+ pHeaps[0].YoungestGenLimit = (CORDB_ADDRESS)WKS::generation_table[0].allocation_context.alloc_limit;
+
+ pHeaps[0].Gen0Start = (CORDB_ADDRESS)WKS::generation_table[0].allocation_start;
+ pHeaps[0].Gen0End = (CORDB_ADDRESS)WKS::gc_heap::alloc_allocated.GetAddr();
+ pHeaps[0].Gen1Start = (CORDB_ADDRESS)WKS::generation_table[1].allocation_start;
+
+ // Segments
+ int count = GetSegmentCount(WKS::generation_table[NUMBERGENERATIONS-1].start_segment);
+ count += GetSegmentCount(WKS::generation_table[NUMBERGENERATIONS-2].start_segment);
+
+ pHeaps[0].SegmentCount = count;
+ pHeaps[0].Segments = new (nothrow) SegmentData[count];
+ if (pHeaps[0].Segments == NULL)
+ return E_OUTOFMEMORY;
+
+ // Small object heap segments
+ WKS::heap_segment *seg = WKS::generation_table[NUMBERGENERATIONS-2].start_segment;
+ int i = 0;
+ for (; seg && (i < count); ++i)
+ {
+ pHeaps[0].Segments[i].Start = (CORDB_ADDRESS)seg->mem;
+ if (seg == WKS::gc_heap::ephemeral_heap_segment)
+ {
+ pHeaps[0].Segments[i].End = (CORDB_ADDRESS)WKS::gc_heap::alloc_allocated.GetAddr();
+ pHeaps[0].Segments[i].Generation = 1;
+ pHeaps[0].EphemeralSegment = i;
+ }
+ else
+ {
+ pHeaps[0].Segments[i].End = (CORDB_ADDRESS)seg->allocated;
+ pHeaps[0].Segments[i].Generation = 2;
+ }
+
+ seg = seg->next;
+ }
+
+ // Large object heap segments
+ seg = WKS::generation_table[NUMBERGENERATIONS-1].start_segment;
+ for (; seg && (i < count); ++i)
+ {
+ pHeaps[0].Segments[i].Generation = 3;
+ pHeaps[0].Segments[i].Start = (CORDB_ADDRESS)seg->mem;
+ pHeaps[0].Segments[i].End = (CORDB_ADDRESS)seg->allocated;
+
+ seg = seg->next;
+ }
+
+ return S_OK;
+}
+
+ HRESULT DacDbiInterfaceImpl::CreateHeapWalk(IDacDbiInterface::HeapWalkHandle *pHandle)
+{
+ DD_ENTER_MAY_THROW;
+
+ DacHeapWalker *data = new (nothrow) DacHeapWalker;
+ if (data == NULL)
+ return E_OUTOFMEMORY;
+
+ HRESULT hr = data->Init();
+ if (SUCCEEDED(hr))
+ *pHandle = reinterpret_cast<HeapWalkHandle>(data);
+ else
+ delete data;
+
+ return hr;
+}
+
+void DacDbiInterfaceImpl::DeleteHeapWalk(HeapWalkHandle handle)
+{
+ DD_ENTER_MAY_THROW;
+
+ DacHeapWalker *data = reinterpret_cast<DacHeapWalker*>(handle);
+ if (data)
+ delete data;
+}
+
+HRESULT DacDbiInterfaceImpl::WalkHeap(HeapWalkHandle handle,
+ ULONG count,
+ OUT COR_HEAPOBJECT * objects,
+ OUT ULONG *fetched)
+{
+ DD_ENTER_MAY_THROW;
+ if (fetched == NULL)
+ return E_INVALIDARG;
+
+ DacHeapWalker *walk = reinterpret_cast<DacHeapWalker*>(handle);
+ *fetched = 0;
+
+ if (!walk->HasMoreObjects())
+ return S_FALSE;
+
+ CORDB_ADDRESS freeMT = (CORDB_ADDRESS)g_pFreeObjectMethodTable.GetAddr();
+
+ HRESULT hr = S_OK;
+ CORDB_ADDRESS addr, mt;
+ ULONG64 size;
+
+ ULONG i = 0;
+ while (i < count && walk->HasMoreObjects())
+ {
+ hr = walk->Next(&addr, &mt, &size);
+
+ if (FAILED(hr))
+ break;
+
+ if (mt != freeMT)
+ {
+ objects[i].address = addr;
+ objects[i].type.token1 = mt;
+ objects[i].type.token2 = NULL;
+ objects[i].size = size;
+ i++;
+ }
+ }
+
+ if (SUCCEEDED(hr))
+ hr = (i < count) ? S_FALSE : S_OK;
+
+ *fetched = i;
+ return hr;
+}
+
+
+
+HRESULT DacDbiInterfaceImpl::GetHeapSegments(OUT DacDbiArrayList<COR_SEGMENT> *pSegments)
+{
+ DD_ENTER_MAY_THROW;
+
+
+ size_t heapCount = 0;
+ HeapData *heaps = 0;
+
+#ifdef FEATURE_SVR_GC
+ HRESULT hr = GCHeap::IsServerHeap() ? DacHeapWalker::InitHeapDataSvr(heaps, heapCount) : DacHeapWalker::InitHeapDataWks(heaps, heapCount);
+#else
+ HRESULT hr = DacHeapWalker::InitHeapDataWks(heaps, heapCount);
+#endif
+
+ NewArrayHolder<HeapData> _heapHolder = heaps;
+
+ // Count the number of segments to know how much to allocate.
+ int total = 0;
+ for (size_t i = 0; i < heapCount; ++i)
+ {
+ // SegmentCount is +1 due to the ephemeral segment containing more than one
+ // generation (Gen1 + Gen0, and sometimes part of Gen2).
+ total += (int)heaps[i].SegmentCount + 1;
+
+ // It's possible that part of Gen2 lives on the ephemeral segment. If so,
+ // we need to add one more to the output.
+ const size_t eph = heaps[i].EphemeralSegment;
+ _ASSERTE(eph < heaps[i].SegmentCount);
+ if (heaps[i].Segments[eph].Start != heaps[i].Gen1Start)
+ total++;
+ }
+
+ pSegments->Alloc(total);
+
+ // Now walk all segments and write them to the array.
+ int curr = 0;
+ for (size_t i = 0; i < heapCount; ++i)
+ {
+ // Generation 0 is not in the segment list.
+ _ASSERTE(curr < total);
+ {
+ COR_SEGMENT &seg = (*pSegments)[curr++];
+ seg.start = heaps[i].Gen0Start;
+ seg.end = heaps[i].Gen0End;
+ seg.type = CorDebug_Gen0;
+ seg.heap = (ULONG)i;
+ }
+
+ for (size_t j = 0; j < heaps[i].SegmentCount; ++j)
+ {
+ if (heaps[i].Segments[j].Generation == 1)
+ {
+ // This is the ephemeral segment. We have already written Gen0,
+ // now write Gen1.
+ _ASSERTE(heaps[i].Segments[j].Start <= heaps[i].Gen1Start);
+ _ASSERTE(heaps[i].Segments[j].End > heaps[i].Gen1Start);
+
+ {
+ _ASSERTE(curr < total);
+ COR_SEGMENT &seg = (*pSegments)[curr++];
+ seg.start = heaps[i].Gen1Start;
+ seg.end = heaps[i].Gen0Start;
+ seg.type = CorDebug_Gen1;
+ seg.heap = (ULONG)i;
+ }
+
+ // It's possible for Gen2 to take up a portion of the ephemeral segment.
+ // We test for that here.
+ if (heaps[i].Segments[j].Start != heaps[i].Gen1Start)
+ {
+ _ASSERTE(curr < total);
+ COR_SEGMENT &seg = (*pSegments)[curr++];
+ seg.start = heaps[i].Segments[j].Start;
+ seg.end = heaps[i].Gen1Start;
+ seg.type = CorDebug_Gen2;
+ seg.heap = (ULONG)i;
+ }
+ }
+ else
+ {
+ // Otherwise, we have a gen2 or gen3 (LOH) segment
+ _ASSERTE(curr < total);
+ COR_SEGMENT &seg = (*pSegments)[curr++];
+ seg.start = heaps[i].Segments[j].Start;
+ seg.end = heaps[i].Segments[j].End;
+
+ _ASSERTE(heaps[i].Segments[j].Generation <= CorDebug_LOH);
+ seg.type = (CorDebugGenerationTypes)heaps[i].Segments[j].Generation;
+ seg.heap = (ULONG)i;
+ }
+ }
+ }
+
+ _ASSERTE(total == curr);
+ return hr;
+}
+
+bool DacDbiInterfaceImpl::IsValidObject(CORDB_ADDRESS addr)
+{
+ DD_ENTER_MAY_THROW;
+
+ bool isValid = false;
+ EX_TRY
+ {
+ PTR_Object obj(TO_TADDR(addr));
+
+ PTR_MethodTable mt = obj->GetMethodTable();
+ PTR_EEClass cls = mt->GetClass();
+
+ if (mt == cls->GetMethodTable())
+ isValid = true;
+ else if (!mt->IsCanonicalMethodTable())
+ isValid = cls->GetMethodTable()->GetClass() == cls;
+ }
+ EX_CATCH
+ {
+ isValid = false;
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ return isValid;
+}
+
+bool DacDbiInterfaceImpl::GetAppDomainForObject(CORDB_ADDRESS addr, OUT VMPTR_AppDomain * pAppDomain,
+ OUT VMPTR_Module *pModule, OUT VMPTR_DomainFile *pDomainFile)
+{
+ DD_ENTER_MAY_THROW;
+
+ PTR_Object obj(TO_TADDR(addr));
+ MethodTable *mt = obj->GetMethodTable();
+
+ PTR_Module module = mt->GetModule();
+ PTR_Assembly assembly = module->GetAssembly();
+ BaseDomain *baseDomain = assembly->GetDomain();
+
+ if (baseDomain->IsSharedDomain())
+ {
+ pModule->SetDacTargetPtr(PTR_HOST_TO_TADDR(module));
+ *pAppDomain = VMPTR_AppDomain::NullPtr();
+ *pDomainFile = VMPTR_DomainFile::NullPtr();
+ }
+ else if (baseDomain->IsAppDomain())
+ {
+ pAppDomain->SetDacTargetPtr(PTR_HOST_TO_TADDR(baseDomain->AsAppDomain()));
+ pModule->SetDacTargetPtr(PTR_HOST_TO_TADDR(module));
+ pDomainFile->SetDacTargetPtr(PTR_HOST_TO_TADDR(module->GetDomainFile(baseDomain->AsAppDomain())));
+ }
+ else
+ {
+ return false;
+ }
+
+ return true;
+}
+
+HRESULT DacDbiInterfaceImpl::CreateRefWalk(OUT RefWalkHandle * pHandle, BOOL walkStacks, BOOL walkFQ, UINT32 handleWalkMask)
+{
+ DD_ENTER_MAY_THROW;
+
+ DacRefWalker *walker = new (nothrow) DacRefWalker(this, walkStacks, walkFQ, handleWalkMask);
+
+ if (walker == NULL)
+ return E_OUTOFMEMORY;
+
+ HRESULT hr = walker->Init();
+ if (FAILED(hr))
+ {
+ delete walker;
+ }
+ else
+ {
+ *pHandle = reinterpret_cast<RefWalkHandle>(walker);
+ }
+
+ return hr;
+}
+
+
+void DacDbiInterfaceImpl::DeleteRefWalk(IN RefWalkHandle handle)
+{
+ DD_ENTER_MAY_THROW;
+
+ DacRefWalker *walker = reinterpret_cast<DacRefWalker*>(handle);
+
+ if (walker)
+ delete walker;
+}
+
+
+HRESULT DacDbiInterfaceImpl::WalkRefs(RefWalkHandle handle, ULONG count, OUT DacGcReference * objects, OUT ULONG *pFetched)
+{
+ if (objects == NULL || pFetched == NULL)
+ return E_POINTER;
+
+ DD_ENTER_MAY_THROW;
+
+ DacRefWalker *walker = reinterpret_cast<DacRefWalker*>(handle);
+ if (!walker)
+ return E_INVALIDARG;
+
+ return walker->Next(count, objects, pFetched);
+}
+
+HRESULT DacDbiInterfaceImpl::GetTypeID(CORDB_ADDRESS dbgObj, COR_TYPEID *pID)
+{
+ DD_ENTER_MAY_THROW;
+
+ TADDR obj[3];
+ ULONG32 read = 0;
+ HRESULT hr = g_dacImpl->m_pTarget->ReadVirtual(dbgObj, (BYTE*)obj, sizeof(obj), &read);
+ if (FAILED(hr))
+ return hr;
+
+ pID->token1 = (UINT64)(obj[0] & ~1);
+ pID->token2 = 0;
+
+ return hr;
+}
+
+HRESULT DacDbiInterfaceImpl::GetObjectFields(COR_TYPEID id, ULONG32 celt, COR_FIELD *layout, ULONG32 *pceltFetched)
+{
+ if (layout == NULL || pceltFetched == NULL)
+ return E_POINTER;
+
+ if (id.token1 == 0)
+ return CORDBG_E_CLASS_NOT_LOADED;
+
+ DD_ENTER_MAY_THROW;
+
+ HRESULT hr = S_OK;
+
+ TypeHandle typeHandle = TypeHandle::FromPtr(TO_TADDR(id.token1));
+
+ if (typeHandle.IsTypeDesc())
+ return E_INVALIDARG;
+
+ ApproxFieldDescIterator fieldDescIterator(typeHandle.AsMethodTable(), ApproxFieldDescIterator::INSTANCE_FIELDS);
+
+ ULONG32 cFields = fieldDescIterator.Count();
+
+ // Handle case where user only wanted to know the number of fields.
+ if (layout == NULL)
+ {
+ *pceltFetched = cFields;
+ return S_FALSE;
+ }
+
+ if (celt < cFields)
+ {
+ cFields = celt;
+
+ // we are returning less than the total
+ hr = S_FALSE;
+ }
+
+ // This must be non-null due to check at beginning of function.
+ *pceltFetched = celt;
+
+ CorElementType componentType = typeHandle.AsMethodTable()->GetInternalCorElementType();
+ BOOL fReferenceType = CorTypeInfo::IsObjRef_NoThrow(componentType);
+ for (ULONG32 i = 0; i < cFields; ++i)
+ {
+ FieldDesc *pField = fieldDescIterator.Next();
+ layout[i].token = pField->GetMemberDef();
+ layout[i].offset = (ULONG32)pField->GetOffset() + (fReferenceType ? Object::GetOffsetOfFirstField() : 0);
+
+ TypeHandle fieldHandle = pField->LookupFieldTypeHandle();
+
+ if (fieldHandle.IsNull())
+ {
+ layout[i].id.token1 = 0;
+ layout[i].id.token2 = 0;
+ layout[i].fieldType = (CorElementType)0;
+ }
+ else
+ {
+ PTR_MethodTable mt = fieldHandle.GetMethodTable();
+ layout[i].fieldType = mt->GetInternalCorElementType();
+ layout[i].id.token1 = (ULONG64)mt.GetAddr();
+
+ if (!mt->IsArray())
+ {
+ layout[i].id.token2 = 0;
+ }
+ else
+ {
+ TypeHandle hnd = mt->GetApproxArrayElementTypeHandle();
+ PTR_MethodTable cmt = hnd.GetMethodTable();
+ layout[i].id.token2 = (ULONG64)cmt.GetAddr();
+ }
+ }
+ }
+
+ return hr;
+}
+
+
+HRESULT DacDbiInterfaceImpl::GetTypeLayout(COR_TYPEID id, COR_TYPE_LAYOUT *pLayout)
+{
+ if (pLayout == NULL)
+ return E_POINTER;
+
+ if (id.token1 == 0)
+ return CORDBG_E_CLASS_NOT_LOADED;
+
+ DD_ENTER_MAY_THROW;
+
+ PTR_MethodTable mt = PTR_MethodTable(TO_TADDR(id.token1));
+ PTR_MethodTable parentMT = mt->GetParentMethodTable();
+
+ COR_TYPEID parent = {parentMT.GetAddr(), 0};
+ pLayout->parentID = parent;
+
+ DWORD size = mt->GetBaseSize();
+ ApproxFieldDescIterator fieldDescIterator(mt, ApproxFieldDescIterator::INSTANCE_FIELDS);
+
+ pLayout->objectSize = size;
+ pLayout->numFields = fieldDescIterator.Count();
+
+ // Get type
+ CorElementType componentType = mt->IsString() ? ELEMENT_TYPE_STRING : mt->GetInternalCorElementType();
+ pLayout->type = componentType;
+ pLayout->boxOffset = CorTypeInfo::IsObjRef_NoThrow(componentType) ? 0 : sizeof(TADDR);
+
+ return S_OK;
+}
+
+HRESULT DacDbiInterfaceImpl::GetArrayLayout(COR_TYPEID id, COR_ARRAY_LAYOUT *pLayout)
+{
+ if (pLayout == NULL)
+ return E_POINTER;
+
+ if (id.token1 == 0)
+ return CORDBG_E_CLASS_NOT_LOADED;
+
+ DD_ENTER_MAY_THROW;
+
+ PTR_MethodTable mt = PTR_MethodTable(TO_TADDR(id.token1));
+
+ if (!mt->IsStringOrArray())
+ return E_INVALIDARG;
+
+ if (mt->IsString())
+ {
+ COR_TYPEID token;
+ token.token1 = MscorlibBinder::GetElementType(ELEMENT_TYPE_CHAR).GetAddr();
+ token.token2 = 0;
+
+ pLayout->componentID = token;
+
+ pLayout->rankSize = 4;
+ pLayout->numRanks = 1;
+ pLayout->rankOffset = sizeof(TADDR);
+ pLayout->firstElementOffset = sizeof(TADDR) + 4;
+ pLayout->countOffset = sizeof(TADDR);
+ pLayout->componentType = ELEMENT_TYPE_CHAR;
+ pLayout->elementSize = 2;
+ }
+ else
+ {
+ DWORD ranks = mt->GetRank();
+ pLayout->rankSize = 4;
+ pLayout->numRanks = ranks;
+ bool multiDim = (ranks > 1);
+
+ pLayout->rankOffset = multiDim ? sizeof(TADDR)*2 : sizeof(TADDR);
+ pLayout->countOffset = sizeof(TADDR);
+ pLayout->firstElementOffset = ArrayBase::GetDataPtrOffset(mt);
+
+
+ TypeHandle hnd = mt->GetApproxArrayElementTypeHandle();
+ PTR_MethodTable cmt = hnd.GetMethodTable();
+
+ CorElementType componentType = cmt->GetInternalCorElementType();
+ if ((UINT64)cmt.GetAddr() == (UINT64)g_pStringClass.GetAddr())
+ componentType = ELEMENT_TYPE_STRING;
+
+ COR_TYPEID token;
+ token.token1 = cmt.GetAddr(); // This could be type handle
+ token.token2 = 0;
+ pLayout->componentID = token;
+ pLayout->componentType = componentType;
+
+ if (CorTypeInfo::IsObjRef_NoThrow(componentType))
+ pLayout->elementSize = sizeof(TADDR);
+ else if (CorIsPrimitiveType(componentType))
+ pLayout->elementSize = gElementTypeInfo[componentType].m_cbSize;
+ else
+ pLayout->elementSize = cmt->GetNumInstanceFieldBytes();
+ }
+
+ return S_OK;
+}
+
+
+void DacDbiInterfaceImpl::GetGCHeapInformation(COR_HEAPINFO * pHeapInfo)
+{
+ DD_ENTER_MAY_THROW;
+
+ size_t heapCount = 0;
+ pHeapInfo->areGCStructuresValid = GCScan::GetGcRuntimeStructuresValid();
+
+#ifdef FEATURE_SVR_GC
+ if (GCHeap::IsServerHeap())
+ {
+ pHeapInfo->gcType = CorDebugServerGC;
+ pHeapInfo->numHeaps = DacGetNumHeaps();
+ }
+ else
+#endif
+ {
+ pHeapInfo->gcType = CorDebugWorkstationGC;
+ pHeapInfo->numHeaps = 1;
+ }
+
+ pHeapInfo->pointerSize = sizeof(TADDR);
+ pHeapInfo->concurrent = g_pConfig->GetGCconcurrent() ? TRUE : FALSE;
+}
+
+
+HRESULT DacDbiInterfaceImpl::GetPEFileMDInternalRW(VMPTR_PEFile vmPEFile, OUT TADDR* pAddrMDInternalRW)
+{
+ DD_ENTER_MAY_THROW;
+ if (pAddrMDInternalRW == NULL)
+ return E_INVALIDARG;
+ PEFile * pPEFile = vmPEFile.GetDacPtr();
+ *pAddrMDInternalRW = pPEFile->GetMDInternalRWAddress();
+ return S_OK;
+}
+
+HRESULT DacDbiInterfaceImpl::GetReJitInfo(VMPTR_Module vmModule, mdMethodDef methodTk, OUT VMPTR_ReJitInfo* pvmReJitInfo)
+{
+ DD_ENTER_MAY_THROW;
+ if (pvmReJitInfo == NULL)
+ return E_INVALIDARG;
+#ifdef FEATURE_REJIT
+ PTR_Module pModule = vmModule.GetDacPtr();
+ ReJitManager * pReJitMgr = pModule->GetReJitManager();
+ PTR_ReJitInfo pReJitInfoCurrent = pReJitMgr->FindNonRevertedReJitInfo(pModule, methodTk);
+ // if the token lookup failed, we need to search again by method desc
+ // The rejit manager will index by token if the method isn't loaded when RequestReJIT runs
+ // and by methoddesc if it was loaded
+ if (pReJitInfoCurrent == NULL)
+ {
+ MethodDesc* pMD = pModule->LookupMethodDef(methodTk);
+ if (pMD != NULL)
+ {
+ pReJitInfoCurrent = pReJitMgr->FindNonRevertedReJitInfo(dac_cast<PTR_MethodDesc>(pMD));
+ }
+ }
+ pvmReJitInfo->SetDacTargetPtr(PTR_TO_TADDR(pReJitInfoCurrent));
+#else
+ pvmReJitInfo->SetDacTargetPtr(0);
+#endif
+ return S_OK;
+}
+
+HRESULT DacDbiInterfaceImpl::GetReJitInfo(VMPTR_MethodDesc vmMethod, CORDB_ADDRESS codeStartAddress, OUT VMPTR_ReJitInfo* pvmReJitInfo)
+{
+ DD_ENTER_MAY_THROW;
+ if (pvmReJitInfo == NULL)
+ return E_INVALIDARG;
+#ifdef FEATURE_REJIT
+ PTR_MethodDesc pMD = vmMethod.GetDacPtr();
+ ReJitManager * pReJitMgr = pMD->GetReJitManager();
+ PTR_ReJitInfo pReJitInfoCurrent = pReJitMgr->FindReJitInfo(pMD, (PCODE)codeStartAddress, 0);
+ pvmReJitInfo->SetDacTargetPtr(PTR_TO_TADDR(pReJitInfoCurrent));
+#else
+ pvmReJitInfo->SetDacTargetPtr(0);
+#endif
+ return S_OK;
+}
+
+HRESULT DacDbiInterfaceImpl::GetSharedReJitInfo(VMPTR_ReJitInfo vmReJitInfo, OUT VMPTR_SharedReJitInfo* pvmSharedReJitInfo)
+{
+ DD_ENTER_MAY_THROW;
+ if (pvmSharedReJitInfo == NULL)
+ return E_INVALIDARG;
+#ifdef FEATURE_REJIT
+ ReJitInfo* pReJitInfo = vmReJitInfo.GetDacPtr();
+ pvmSharedReJitInfo->SetDacTargetPtr(PTR_TO_TADDR(pReJitInfo->m_pShared));
+#else
+ _ASSERTE(!"You shouldn't be calling this - how did you get a ReJitInfo?");
+ pvmSharedReJitInfo->SetDacTargetPtr(0);
+#endif
+ return S_OK;
+}
+
+HRESULT DacDbiInterfaceImpl::GetSharedReJitInfoData(VMPTR_SharedReJitInfo vmSharedReJitInfo, DacSharedReJitInfo* pData)
+{
+ DD_ENTER_MAY_THROW;
+#ifdef FEATURE_REJIT
+ SharedReJitInfo* pSharedReJitInfo = vmSharedReJitInfo.GetDacPtr();
+ pData->m_state = pSharedReJitInfo->GetState();
+ pData->m_pbIL = PTR_TO_CORDB_ADDRESS(pSharedReJitInfo->m_pbIL);
+ pData->m_dwCodegenFlags = pSharedReJitInfo->m_dwCodegenFlags;
+ pData->m_cInstrumentedMapEntries = (ULONG)pSharedReJitInfo->m_instrumentedILMap.GetCount();
+ pData->m_rgInstrumentedMapEntries = PTR_TO_CORDB_ADDRESS(dac_cast<ULONG_PTR>(pSharedReJitInfo->m_instrumentedILMap.GetOffsets()));
+#else
+ _ASSERTE(!"You shouldn't be calling this - how did you get a SharedReJitInfo?");
+#endif
+ return S_OK;
+}
+
+HRESULT DacDbiInterfaceImpl::GetDefinesBitField(ULONG32 *pDefines)
+{
+ DD_ENTER_MAY_THROW;
+ if (pDefines == NULL)
+ return E_INVALIDARG;
+ *pDefines = g_pDebugger->m_defines;
+ return S_OK;
+}
+
+HRESULT DacDbiInterfaceImpl::GetMDStructuresVersion(ULONG32* pMDStructuresVersion)
+{
+ DD_ENTER_MAY_THROW;
+ if (pMDStructuresVersion == NULL)
+ return E_INVALIDARG;
+ *pMDStructuresVersion = g_pDebugger->m_mdDataStructureVersion;
+ return S_OK;
+}
+
+
+DacRefWalker::DacRefWalker(ClrDataAccess *dac, BOOL walkStacks, BOOL walkFQ, UINT32 handleMask)
+ : mDac(dac), mWalkStacks(walkStacks), mWalkFQ(walkFQ), mHandleMask(handleMask), mStackWalker(NULL),
+ mHandleWalker(NULL), mFQStart(PTR_NULL), mFQEnd(PTR_NULL), mFQCurr(PTR_NULL)
+{
+}
+
+DacRefWalker::~DacRefWalker()
+{
+ Clear();
+}
+
+HRESULT DacRefWalker::Init()
+{
+ HRESULT hr = S_OK;
+ if (mHandleMask)
+ {
+ // Will throw on OOM, which is fine.
+ mHandleWalker = new DacHandleWalker();
+
+ hr = mHandleWalker->Init(GetHandleWalkerMask());
+ }
+
+ if (mWalkStacks && SUCCEEDED(hr))
+ {
+ hr = NextThread();
+ }
+
+ return hr;
+}
+
+void DacRefWalker::Clear()
+{
+ if (mHandleWalker)
+ {
+ delete mHandleWalker;
+ mHandleWalker = NULL;
+ }
+
+ if (mStackWalker)
+ {
+ delete mStackWalker;
+ mStackWalker = NULL;
+ }
+}
+
+
+
+UINT32 DacRefWalker::GetHandleWalkerMask()
+{
+ UINT32 result = 0;
+ if (mHandleMask & CorHandleStrong)
+ result |= (1 << HNDTYPE_STRONG);
+
+ if (mHandleMask & CorHandleStrongPinning)
+ result |= (1 << HNDTYPE_PINNED);
+
+ if (mHandleMask & CorHandleWeakShort)
+ result |= (1 << HNDTYPE_WEAK_SHORT);
+
+ if (mHandleMask & CorHandleWeakLong)
+ result |= (1 << HNDTYPE_WEAK_LONG);
+
+#ifdef FEATURE_COMINTEROP
+ if ((mHandleMask & CorHandleWeakRefCount) || (mHandleMask & CorHandleStrongRefCount))
+ result |= (1 << HNDTYPE_REFCOUNTED);
+
+ if (mHandleMask & CorHandleWeakWinRT)
+ result |= (1 << HNDTYPE_WEAK_WINRT);
+#endif // FEATURE_COMINTEROP
+
+ if (mHandleMask & CorHandleStrongDependent)
+ result |= (1 << HNDTYPE_DEPENDENT);
+
+ if (mHandleMask & CorHandleStrongAsyncPinned)
+ result |= (1 << HNDTYPE_ASYNCPINNED);
+
+ if (mHandleMask & CorHandleStrongSizedByref)
+ result |= (1 << HNDTYPE_SIZEDREF);
+
+ return result;
+}
+
+
+
+HRESULT DacRefWalker::Next(ULONG celt, DacGcReference roots[], ULONG *pceltFetched)
+{
+ if (roots == NULL || pceltFetched == NULL)
+ return E_POINTER;
+
+ ULONG total = 0;
+ HRESULT hr = S_OK;
+
+ if (mHandleWalker)
+ {
+ hr = mHandleWalker->Next(celt, roots, &total);
+
+ if (hr == S_FALSE || FAILED(hr))
+ {
+ delete mHandleWalker;
+ mHandleWalker = NULL;
+
+ if (FAILED(hr))
+ return hr;
+ }
+ }
+
+ if (total < celt)
+ {
+ while (total < celt && mFQCurr < mFQEnd)
+ {
+ DacGcReference &ref = roots[total++];
+
+ ref.vmDomain = VMPTR_AppDomain::NullPtr();
+ ref.objHnd.SetDacTargetPtr(mFQCurr.GetAddr());
+ ref.dwType = (DWORD)CorReferenceFinalizer;
+ ref.i64ExtraData = 0;
+
+ mFQCurr++;
+ }
+ }
+
+ while (total < celt && mStackWalker)
+ {
+ ULONG fetched = 0;
+ hr = mStackWalker->Next(celt-total, roots+total, &fetched);
+
+ if (FAILED(hr))
+ return hr;
+
+ if (hr == S_FALSE)
+ {
+ hr = NextThread();
+
+ if (FAILED(hr))
+ return hr;
+ }
+
+ total += fetched;
+ }
+
+ *pceltFetched = total;
+
+ return total < celt ? S_FALSE : S_OK;
+}
+
+HRESULT DacRefWalker::NextThread()
+{
+ Thread *pThread = NULL;
+ if (mStackWalker)
+ {
+ pThread = mStackWalker->GetThread();
+ delete mStackWalker;
+ mStackWalker = NULL;
+ }
+
+ pThread = ThreadStore::GetThreadList(pThread);
+
+ if (!pThread)
+ return S_FALSE;
+
+ mStackWalker = new DacStackReferenceWalker(mDac, pThread->GetOSThreadId());
+ return mStackWalker->Init();
+}
+
+HRESULT DacHandleWalker::Next(ULONG celt, DacGcReference roots[], ULONG *pceltFetched)
+{
+ SUPPORTS_DAC;
+
+ if (roots == NULL || pceltFetched == NULL)
+ return E_POINTER;
+
+ return DoHandleWalk<DacGcReference, ULONG, DacHandleWalker::EnumCallbackDac>(celt, roots, pceltFetched);
+}
+
+
+void CALLBACK DacHandleWalker::EnumCallbackDac(PTR_UNCHECKED_OBJECTREF handle, uintptr_t *pExtraInfo, uintptr_t param1, uintptr_t param2)
+{
+ SUPPORTS_DAC;
+
+ DacHandleWalkerParam *param = (DacHandleWalkerParam *)param1;
+ HandleChunkHead *curr = param->Curr;
+
+ // If we failed on a previous call (OOM) don't keep trying to allocate, it's not going to work.
+ if (FAILED(param->Result))
+ return;
+
+ // We've moved past the size of the current chunk. We'll allocate a new chunk
+ // and stuff the handles there. These are cleaned up by the destructor
+ if (curr->Count >= (curr->Size/sizeof(DacGcReference)))
+ {
+ if (curr->Next == NULL)
+ {
+ HandleChunk *next = new (nothrow) HandleChunk;
+ if (next != NULL)
+ {
+ curr->Next = next;
+ }
+ else
+ {
+ param->Result = E_OUTOFMEMORY;
+ return;
+ }
+ }
+
+ curr = param->Curr = param->Curr->Next;
+ }
+
+ // Fill the current handle.
+ DacGcReference *dataArray = (DacGcReference*)curr->pData;
+ DacGcReference &data = dataArray[curr->Count++];
+
+ data.objHnd.SetDacTargetPtr(handle.GetAddr());
+ data.vmDomain.SetDacTargetPtr(TO_TADDR(param->AppDomain));
+
+ data.i64ExtraData = 0;
+ unsigned int refCnt = 0;
+
+ switch (param->Type)
+ {
+ case HNDTYPE_STRONG:
+ data.dwType = (DWORD)CorHandleStrong;
+ break;
+
+ case HNDTYPE_PINNED:
+ data.dwType = (DWORD)CorHandleStrongPinning;
+ break;
+
+ case HNDTYPE_WEAK_SHORT:
+ data.dwType = (DWORD)CorHandleWeakShort;
+ break;
+
+ case HNDTYPE_WEAK_LONG:
+ data.dwType = (DWORD)CorHandleWeakLong;
+ break;
+
+#ifdef FEATURE_COMINTEROP
+ case HNDTYPE_REFCOUNTED:
+ data.dwType = (DWORD)(data.i64ExtraData ? CorHandleStrongRefCount : CorHandleWeakRefCount);
+ GetRefCountedHandleInfo((OBJECTREF)*handle, param->Type, &refCnt, NULL, NULL, NULL);
+ data.i64ExtraData = refCnt;
+ break;
+
+ case HNDTYPE_WEAK_WINRT:
+ data.dwType = (DWORD)CorHandleWeakWinRT;
+ break;
+#endif
+
+ case HNDTYPE_DEPENDENT:
+ data.dwType = (DWORD)CorHandleStrongDependent;
+ data.i64ExtraData = GetDependentHandleSecondary(handle.GetAddr()).GetAddr();
+ break;
+
+ case HNDTYPE_ASYNCPINNED:
+ data.dwType = (DWORD)CorHandleStrongAsyncPinned;
+ break;
+
+ case HNDTYPE_SIZEDREF:
+ data.dwType = (DWORD)CorHandleStrongSizedByref;
+ break;
+ }
+}
+
+
+void DacStackReferenceWalker::GCEnumCallbackDac(LPVOID hCallback, OBJECTREF *pObject, uint32_t flags, DacSlotLocation loc)
+{
+ GCCONTEXT *gcctx = (GCCONTEXT *)hCallback;
+ DacScanContext *dsc = (DacScanContext*)gcctx->sc;
+
+ CORDB_ADDRESS obj = 0;
+
+ if (flags & GC_CALL_INTERIOR)
+ {
+ if (loc.targetPtr)
+ obj = (CORDB_ADDRESS)(*PTR_PTR_Object((TADDR)pObject)).GetAddr();
+ else
+ obj = (CORDB_ADDRESS)TO_TADDR(pObject);
+
+ HRESULT hr = dsc->pWalker->mHeap.ListNearObjects(obj, NULL, &obj, NULL);
+
+ // If we failed don't add this instance to the list. ICorDebug doesn't handle invalid pointers
+ // very well, and the only way the heap walker's ListNearObjects will fail is if we have heap
+ // corruption...which ICorDebug doesn't deal with anyway.
+ if (FAILED(hr))
+ return;
+ }
+
+ DacGcReference *data = dsc->pWalker->GetNextObject<DacGcReference>(dsc);
+ if (data != NULL)
+ {
+ data->vmDomain.SetDacTargetPtr(dac_cast<PTR_AppDomain>(dsc->pCurrentDomain).GetAddr());
+ if (obj)
+ data->pObject = obj | 1;
+ else if (loc.targetPtr)
+ data->objHnd.SetDacTargetPtr(TO_TADDR(pObject));
+ else
+ data->pObject = pObject->GetAddr() | 1;
+
+ data->dwType = CorReferenceStack;
+ data->i64ExtraData = 0;
+ }
+}
+
+
+void DacStackReferenceWalker::GCReportCallbackDac(PTR_PTR_Object ppObj, ScanContext *sc, uint32_t flags)
+{
+ DacScanContext *dsc = (DacScanContext*)sc;
+
+ TADDR obj = ppObj.GetAddr();
+ if (flags & GC_CALL_INTERIOR)
+ {
+ CORDB_ADDRESS fixed_addr = 0;
+ HRESULT hr = dsc->pWalker->mHeap.ListNearObjects((CORDB_ADDRESS)obj, NULL, &fixed_addr, NULL);
+
+ // If we failed don't add this instance to the list. ICorDebug doesn't handle invalid pointers
+ // very well, and the only way the heap walker's ListNearObjects will fail is if we have heap
+ // corruption...which ICorDebug doesn't deal with anyway.
+ if (FAILED(hr))
+ return;
+
+ obj = TO_TADDR(fixed_addr);
+ }
+
+ DacGcReference *data = dsc->pWalker->GetNextObject<DacGcReference>(dsc);
+ if (data != NULL)
+ {
+ data->vmDomain.SetDacTargetPtr(dac_cast<PTR_AppDomain>(dsc->pCurrentDomain).GetAddr());
+ data->objHnd.SetDacTargetPtr(obj);
+ data->dwType = CorReferenceStack;
+ data->i64ExtraData = 0;
+ }
+}
+
+
+
+HRESULT DacStackReferenceWalker::Next(ULONG count, DacGcReference stackRefs[], ULONG *pFetched)
+{
+ if (stackRefs == NULL || pFetched == NULL)
+ return E_POINTER;
+
+ HRESULT hr = DoStackWalk<ULONG, DacGcReference,
+ DacStackReferenceWalker::GCReportCallbackDac,
+ DacStackReferenceWalker::GCEnumCallbackDac>
+ (count, stackRefs, pFetched);
+
+ return hr;
+}
diff --git a/src/debug/daccess/dacdbiimpl.h b/src/debug/daccess/dacdbiimpl.h
new file mode 100644
index 0000000000..56d7b0d1d7
--- /dev/null
+++ b/src/debug/daccess/dacdbiimpl.h
@@ -0,0 +1,1152 @@
+// 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.
+//*****************************************************************************
+// DacDbiImpl.h
+//
+
+//
+// Implement the interface between the DAC and DBI.
+//*****************************************************************************
+
+#ifndef _DACDBI_IMPL_H_
+#define _DACDBI_IMPL_H_
+
+// Prototype for creation function
+STDAPI
+DacDbiInterfaceInstance(
+ ICorDebugDataTarget * pTarget,
+ CORDB_ADDRESS baseAddress,
+ IDacDbiInterface::IAllocator * pAllocator,
+ IDacDbiInterface::IMetaDataLookup * pMetaDataLookup,
+ IDacDbiInterface ** ppInterface);
+
+//---------------------------------------------------------------------------------------
+//
+// This implements the DAC/DBI interface. See that interface declaration for
+// full documentation on these methods.
+//
+// Assumptions:
+// This class is free-threaded and provides its own synchronization.
+//
+// Notes:
+// It inherits from ClrDataAccess to get the DAC-management implementation, and to
+// override GetMDImport.
+//
+class DacDbiInterfaceImpl :
+ public ClrDataAccess,
+ public IDacDbiInterface
+{
+public:
+ // Ctor to instantiate a DAC reader around a given data-target.
+ DacDbiInterfaceImpl(ICorDebugDataTarget * pTarget, CORDB_ADDRESS baseAddress, IAllocator * pAllocator, IMetaDataLookup * pLookup);
+
+ // Destructor.
+ virtual ~DacDbiInterfaceImpl(void);
+
+ // Overridden from ClrDataAccess. Gets an internal metadata importer for the file.
+ virtual IMDInternalImport* GetMDImport(
+ const PEFile* pPEFile,
+ const ReflectionModule * pReflectionModule,
+ bool fThrowEx);
+
+
+ // Check whether the version of the DBI matches the version of the runtime.
+ HRESULT CheckDbiVersion(const DbiVersion * pVersion);
+
+ // Flush the DAC cache. This should be called when target memory changes.
+ HRESULT FlushCache();
+
+ // enable or disable DAC target consistency checks
+ void DacSetTargetConsistencyChecks(bool fEnableAsserts);
+
+ // Destroy the interface object. The client should call this when it's done
+ // with the IDacDbiInterface to free up any resources.
+ void Destroy();
+
+ IAllocator * GetAllocator()
+ {
+ return m_pAllocator;
+ }
+
+
+ // Is Left-side started up?
+ BOOL IsLeftSideInitialized();
+
+ // Get an LS Appdomain via an AppDomain unique ID.
+ // Fails if the AD is not found or if the ID is invalid.
+ VMPTR_AppDomain GetAppDomainFromId(ULONG appdomainId);
+
+ // Get the AppDomain ID for an AppDomain.
+ ULONG GetAppDomainId(VMPTR_AppDomain vmAppDomain);
+
+ // Get the managed AppDomain object for an AppDomain.
+ VMPTR_OBJECTHANDLE GetAppDomainObject(VMPTR_AppDomain vmAppDomain);
+
+ // Determine if the specified AppDomain is the default domain
+ BOOL IsDefaultDomain(VMPTR_AppDomain vmAppDomain);
+
+ // Get the full AD friendly name for the appdomain.
+ void GetAppDomainFullName(
+ VMPTR_AppDomain vmAppDomain,
+ IStringHolder * pStrName);
+
+ // Get the values of the JIT Optimization and EnC flags.
+ void GetCompilerFlags (VMPTR_DomainFile vmDomainFile,
+ BOOL * pfAllowJITOpts,
+ BOOL * pfEnableEnC);
+
+ // Helper function for SetCompilerFlags to set EnC status
+ bool CanSetEnCBits(Module * pModule);
+
+ // Set the values of the JIT optimization and EnC flags.
+ HRESULT SetCompilerFlags(VMPTR_DomainFile vmDomainFile,
+ BOOL fAllowJitOpts,
+ BOOL fEnableEnC);
+
+
+ // Initialize the native/IL sequence points and native var info for a function.
+ void GetNativeCodeSequencePointsAndVarInfo(VMPTR_MethodDesc vmMethodDesc,
+ CORDB_ADDRESS startAddr,
+ BOOL fCodeAvailable,
+ NativeVarData * pNativeVarData,
+ SequencePoints * pSequencePoints);
+
+ bool IsThreadSuspendedOrHijacked(VMPTR_Thread vmThread);
+
+
+ bool AreGCStructuresValid();
+ HRESULT CreateHeapWalk(HeapWalkHandle *pHandle);
+ void DeleteHeapWalk(HeapWalkHandle handle);
+
+ HRESULT WalkHeap(HeapWalkHandle handle,
+ ULONG count,
+ OUT COR_HEAPOBJECT * objects,
+ OUT ULONG *fetched);
+
+ HRESULT GetHeapSegments(OUT DacDbiArrayList<COR_SEGMENT> *pSegments);
+
+
+ bool IsValidObject(CORDB_ADDRESS obj);
+
+ bool GetAppDomainForObject(CORDB_ADDRESS obj, OUT VMPTR_AppDomain * pApp, OUT VMPTR_Module *pModule, OUT VMPTR_DomainFile *mod);
+
+
+
+ HRESULT CreateRefWalk(RefWalkHandle * pHandle, BOOL walkStacks, BOOL walkFQ, UINT32 handleWalkMask);
+ void DeleteRefWalk(RefWalkHandle handle);
+ HRESULT WalkRefs(RefWalkHandle handle, ULONG count, OUT DacGcReference * objects, OUT ULONG *pFetched);
+
+ HRESULT GetTypeID(CORDB_ADDRESS obj, COR_TYPEID *pID);
+
+ HRESULT GetObjectFields(COR_TYPEID id, ULONG32 celt, COR_FIELD *layout, ULONG32 *pceltFetched);
+ HRESULT GetTypeLayout(COR_TYPEID id, COR_TYPE_LAYOUT *pLayout);
+ HRESULT GetArrayLayout(COR_TYPEID id, COR_ARRAY_LAYOUT *pLayout);
+ void GetGCHeapInformation(COR_HEAPINFO * pHeapInfo);
+ HRESULT GetPEFileMDInternalRW(VMPTR_PEFile vmPEFile, OUT TADDR* pAddrMDInternalRW);
+ HRESULT GetReJitInfo(VMPTR_Module vmModule, mdMethodDef methodTk, OUT VMPTR_ReJitInfo* pReJitInfo);
+ HRESULT GetReJitInfo(VMPTR_MethodDesc vmMethod, CORDB_ADDRESS codeStartAddress, OUT VMPTR_ReJitInfo* pReJitInfo);
+ HRESULT GetSharedReJitInfo(VMPTR_ReJitInfo vmReJitInfo, VMPTR_SharedReJitInfo* pSharedReJitInfo);
+ HRESULT GetSharedReJitInfoData(VMPTR_SharedReJitInfo sharedReJitInfo, DacSharedReJitInfo* pData);
+ HRESULT GetDefinesBitField(ULONG32 *pDefines);
+ HRESULT GetMDStructuresVersion(ULONG32* pMDStructuresVersion);
+
+private:
+ void TypeHandleToExpandedTypeInfoImpl(AreValueTypesBoxed boxed,
+ VMPTR_AppDomain vmAppDomain,
+ TypeHandle typeHandle,
+ DebuggerIPCE_ExpandedTypeData * pTypeInfo);
+
+ // Get the number of fixed arguments to a function, i.e., the explicit args and the "this" pointer.
+ SIZE_T GetArgCount(MethodDesc * pMD);
+
+ // Get locations and code offsets for local variables and arguments in a function
+ void GetNativeVarData(MethodDesc * pMethodDesc,
+ CORDB_ADDRESS startAddr,
+ SIZE_T fixedArgCount,
+ NativeVarData * pVarInfo);
+
+ // Get the native/IL sequence points for a function
+ void GetSequencePoints(MethodDesc * pMethodDesc,
+ CORDB_ADDRESS startAddr,
+ SequencePoints * pNativeMap);
+
+ // Helper to compose a IL->IL and IL->Native mapping
+ void ComposeMapping(InstrumentedILOffsetMapping profilerILMap, ICorDebugInfo::OffsetMapping nativeMap[], ULONG32* pEntryCount);
+
+ // Helper function to convert an instrumented IL offset to the corresponding original IL offset.
+ ULONG TranslateInstrumentedILOffsetToOriginal(ULONG ilOffset,
+ const InstrumentedILOffsetMapping * pMapping);
+
+public:
+//----------------------------------------------------------------------------------
+ // class MapSortILMap: A template class that will sort an array of DebuggerILToNativeMap.
+ // This class is intended to be instantiated on the stack / in temporary storage, and used
+ // to reorder the sequence map.
+ //----------------------------------------------------------------------------------
+ class MapSortILMap : public CQuickSort<DebuggerILToNativeMap>
+ {
+ public:
+ //Constructor
+ MapSortILMap(DebuggerILToNativeMap * map,
+ int count)
+ : CQuickSort<DebuggerILToNativeMap>(map, count) {}
+
+ // secondary key comparison--if two IL offsets are the same,
+ // we determine order based on native offset
+ int CompareInternal(DebuggerILToNativeMap * first,
+ DebuggerILToNativeMap * second);
+
+ //Comparison operator
+ int Compare(DebuggerILToNativeMap * first,
+ DebuggerILToNativeMap * second);
+ };
+
+
+ // GetILCodeAndSig returns the function's ILCode and SigToken given
+ // a module and a token. The info will come from a MethodDesc, if
+ // one exists or from metadata.
+ //
+ void GetILCodeAndSig(VMPTR_DomainFile vmDomainFile,
+ mdToken functionToken,
+ TargetBuffer * pCodeInfo,
+ mdToken * pLocalSigToken);
+
+ // Gets the following information about the native code blob for a function, if the native
+ // code is available:
+ // its method desc
+ // whether it's an instantiated generic
+ // its EnC version number
+ // hot and cold region information.
+ void GetNativeCodeInfo(VMPTR_DomainFile vmDomainFile,
+ mdToken functionToken,
+ NativeCodeFunctionData * pCodeInfo);
+
+ // Gets the following information about the native code blob for a function
+ // its method desc
+ // whether it's an instantiated generic
+ // its EnC version number
+ // hot and cold region information.
+ void GetNativeCodeInfoForAddr(VMPTR_MethodDesc vmMethodDesc,
+ CORDB_ADDRESS hotCodeStartAddr,
+ NativeCodeFunctionData * pCodeInfo);
+
+private:
+ // Get start addresses and sizes for hot and cold regions for a native code blob
+ void GetMethodRegionInfo(MethodDesc * pMethodDesc,
+ NativeCodeFunctionData * pCodeInfo);
+
+public:
+ // Determine if a type is a ValueType
+ BOOL IsValueType (VMPTR_TypeHandle th);
+
+ // Determine if a type has generic parameters
+ BOOL HasTypeParams (VMPTR_TypeHandle th);
+
+ // Get type information for a class
+ void GetClassInfo (VMPTR_AppDomain vmAppDomain,
+ VMPTR_TypeHandle thExact,
+ ClassInfo * pData);
+
+ // get field information and object size for an instantiated generic type
+ void GetInstantiationFieldInfo (VMPTR_DomainFile vmDomainFile,
+ VMPTR_TypeHandle vmThExact,
+ VMPTR_TypeHandle vmThApprox,
+ DacDbiArrayList<FieldData> * pFieldList,
+ SIZE_T * pObjectSize);
+
+
+ void GetObjectExpandedTypeInfo(AreValueTypesBoxed boxed,
+ VMPTR_AppDomain vmAppDomain,
+ CORDB_ADDRESS addr,
+ DebuggerIPCE_ExpandedTypeData *pTypeInfo);
+
+
+ void GetObjectExpandedTypeInfoFromID(AreValueTypesBoxed boxed,
+ VMPTR_AppDomain vmAppDomain,
+ COR_TYPEID id,
+ DebuggerIPCE_ExpandedTypeData *pTypeInfo);
+
+
+ // @dbgtodo Microsoft inspection: change DebuggerIPCE_ExpandedTypeData to DacDbiStructures type hierarchy
+ // once ICorDebugType and ICorDebugClass are DACized
+ // use a type handle to get the information needed to create the corresponding RS CordbType instance
+ void TypeHandleToExpandedTypeInfo(AreValueTypesBoxed boxed,
+ VMPTR_AppDomain vmAppDomain,
+ VMPTR_TypeHandle vmTypeHandle,
+ DebuggerIPCE_ExpandedTypeData * pTypeInfo);
+
+ // Get type handle for a TypeDef token, if one exists. For generics this returns the open type.
+ VMPTR_TypeHandle GetTypeHandle(VMPTR_Module vmModule,
+ mdTypeDef metadataToken);
+
+ // Get the approximate type handle for an instantiated type. This may be identical to the exact type handle,
+ // but if we have code sharing for generics,it may differ in that it may have canonical type parameters.
+ VMPTR_TypeHandle GetApproxTypeHandle(TypeInfoList * pTypeData);
+
+ // Get the exact type handle from type data
+ HRESULT GetExactTypeHandle(DebuggerIPCE_ExpandedTypeData * pTypeData,
+ ArgInfoList * pArgInfo,
+ VMPTR_TypeHandle& vmTypeHandle);
+
+ // Retrieve the generic type params for a given MethodDesc. This function is specifically
+ // for stackwalking because it requires the generic type token on the stack.
+ void GetMethodDescParams(VMPTR_AppDomain vmAppDomain,
+ VMPTR_MethodDesc vmMethodDesc,
+ GENERICS_TYPE_TOKEN genericsToken,
+ UINT32 * pcGenericClassTypeParams,
+ TypeParamsList * pGenericTypeParams);
+
+ // Get the target field address of a context or thread local static.
+ CORDB_ADDRESS GetThreadOrContextStaticAddress(VMPTR_FieldDesc vmField,
+ VMPTR_Thread vmRuntimeThread);
+
+ // Get the target field address of a collectible types static.
+ CORDB_ADDRESS GetCollectibleTypeStaticAddress(VMPTR_FieldDesc vmField,
+ VMPTR_AppDomain vmAppDomain);
+
+ // Get information about a field added with Edit And Continue.
+ void GetEnCHangingFieldInfo(const EnCHangingFieldInfo * pEnCFieldInfo,
+ FieldData * pFieldData,
+ BOOL * pfStatic);
+
+ // GetTypeHandleParams gets the necessary data for a type handle, i.e. its
+ // type parameters, e.g. "String" and "List<int>" from the type handle
+ // for "Dict<String,List<int>>", and sends it back to the right side.
+ // This should not fail except for OOM
+
+ void GetTypeHandleParams(VMPTR_AppDomain vmAppDomain,
+ VMPTR_TypeHandle vmTypeHandle,
+ TypeParamsList * pParams);
+
+ // DacDbi API: GetSimpleType
+ // gets the metadata token and domain file corresponding to a simple type
+ void GetSimpleType(VMPTR_AppDomain vmAppDomain,
+ CorElementType simpleType,
+ mdTypeDef * pMetadataToken,
+ VMPTR_Module * pVmModule,
+ VMPTR_DomainFile * pVmDomainFile);
+
+ BOOL IsExceptionObject(VMPTR_Object vmObject);
+
+ void GetStackFramesFromException(VMPTR_Object vmObject, DacDbiArrayList<DacExceptionCallStackData>& dacStackFrames);
+
+ // Returns true if the argument is a runtime callable wrapper
+ BOOL IsRcw(VMPTR_Object vmObject);
+
+ // retrieves the list of COM interfaces implemented by vmObject, as it is known at
+ // the time of the call (the list may change as new interface types become available
+ // in the runtime)
+ void GetRcwCachedInterfaceTypes(
+ VMPTR_Object vmObject,
+ VMPTR_AppDomain vmAppDomain,
+ BOOL bIInspectableOnly,
+ OUT DacDbiArrayList<DebuggerIPCE_ExpandedTypeData> * pDacInterfaces);
+
+ // retrieves the list of interfaces pointers implemented by vmObject, as it is known at
+ // the time of the call (the list may change as new interface types become available
+ // in the runtime)
+ void GetRcwCachedInterfacePointers(
+ VMPTR_Object vmObject,
+ BOOL bIInspectableOnly,
+ OUT DacDbiArrayList<CORDB_ADDRESS> * pDacItfPtrs);
+
+ // retrieves a list of interface types corresponding to the passed in
+ // list of IIDs. the interface types are retrieved from an app domain
+ // IID / Type cache, that is updated as new types are loaded. will
+ // have NULL entries corresponding to unknown IIDs in "iids"
+ void GetCachedWinRTTypesForIIDs(
+ VMPTR_AppDomain vmAppDomain,
+ DacDbiArrayList<GUID> & iids,
+ OUT DacDbiArrayList<DebuggerIPCE_ExpandedTypeData> * pTypes);
+
+ // retrieves the whole app domain cache of IID / Type mappings.
+ void GetCachedWinRTTypes(
+ VMPTR_AppDomain vmAppDomain,
+ OUT DacDbiArrayList<GUID> * pGuids,
+ OUT DacDbiArrayList<DebuggerIPCE_ExpandedTypeData> * pTypes);
+
+private:
+ BOOL IsExceptionObject(MethodTable* pMT);
+
+ // Get the approximate and exact type handles for a type
+ void GetTypeHandles(VMPTR_TypeHandle vmThExact,
+ VMPTR_TypeHandle vmThApprox,
+ TypeHandle * pThExact,
+ TypeHandle * pThApprox);
+
+ // Gets the total number of fields for a type.
+ unsigned int GetTotalFieldCount(TypeHandle thApprox);
+
+ // initializes various values of the ClassInfo data structure, including the
+ // field count, generic args count, size and value class flag
+ void InitClassData(TypeHandle thApprox,
+ BOOL fIsInstantiatedType,
+ ClassInfo * pData);
+
+ // Gets the base table addresses for both GC and non-GC statics
+ void GetStaticsBases(TypeHandle thExact,
+ AppDomain * pAppDomain,
+ PTR_BYTE * ppGCStaticsBase,
+ PTR_BYTE * ppNonGCStaticsBase);
+
+ // Computes the field info for pFD and stores it in pcurrentFieldData
+ void ComputeFieldData(PTR_FieldDesc pFD,
+ PTR_BYTE pGCStaticsBase,
+ PTR_BYTE pNonGCStaticsBase,
+ FieldData * pCurrentFieldData);
+
+ // Gets information for all the fields for a given type
+ void CollectFields(TypeHandle thExact,
+ TypeHandle thApprox,
+ AppDomain * pAppDomain,
+ DacDbiArrayList<FieldData> * pFieldList);
+
+ // Gets additional information to convert a type handle to an instance of CordbType if the type is E_T_ARRAY
+ void GetArrayTypeInfo(TypeHandle typeHandle,
+ DebuggerIPCE_ExpandedTypeData * pTypeInfo,
+ AppDomain * pAppDomain);
+
+ // Gets additional information to convert a type handle to an instance of CordbType if the type is
+ // E_T_PTR or E_T_BYREF
+ void GetPtrTypeInfo(AreValueTypesBoxed boxed,
+ TypeHandle typeHandle,
+ DebuggerIPCE_ExpandedTypeData * pTypeInfo,
+ AppDomain * pAppDomain);
+
+ // Gets additional information to convert a type handle to an instance of CordbType if the type is E_T_FNPTR
+ void GetFnPtrTypeInfo(AreValueTypesBoxed boxed,
+ TypeHandle typeHandle,
+ DebuggerIPCE_ExpandedTypeData * pTypeInfo,
+ AppDomain * pAppDomain);
+
+ // Gets additional information to convert a type handle to an instance of CordbType if the type is
+ // E_T_CLASS or E_T_VALUETYPE
+ void GetClassTypeInfo(TypeHandle typeHandle,
+ DebuggerIPCE_ExpandedTypeData * pTypeInfo,
+ AppDomain * pAppDomain);
+
+ // Gets the correct CorElementType value from a type handle
+ CorElementType GetElementType (TypeHandle typeHandle);
+
+ // Gets additional information to convert a type handle to an instance of CordbType for the referent of an
+ // E_T_BYREF or E_T_PTR or for the element type of an E_T_ARRAY or E_T_SZARRAY
+ void TypeHandleToBasicTypeInfo(TypeHandle typeHandle,
+ DebuggerIPCE_BasicTypeData * pTypeInfo,
+ AppDomain * pAppDomain);
+
+ // wrapper routines to set up for a call to ClassLoader functions to retrieve a type handle for a
+ // particular kind of type
+
+ // find a loaded type handle for a primitive type
+ static TypeHandle FindLoadedElementType(CorElementType elementType);
+
+ // find a loaded type handle for an array type (E_T_ARRAY or E_T_SZARRAY)
+ static TypeHandle FindLoadedArrayType(CorElementType elementType, TypeHandle typeArg, unsigned rank);
+
+ // find a loaded type handle for an address type (E_T_PTR or E_T_BYREF)
+ static TypeHandle FindLoadedPointerOrByrefType(CorElementType elementType, TypeHandle typeArg);
+
+ // find a loaded type handle for a function pointer type (E_T_FNPTR)
+ static TypeHandle FindLoadedFnptrType(DWORD numTypeArgs, TypeHandle * pInst);
+
+ // find a loaded type handle for a particular instantiation of a class type (E_T_CLASS or E_T_VALUETYPE)
+ static TypeHandle FindLoadedInstantiation(Module * pModule,
+ mdTypeDef mdToken,
+ DWORD nTypeArgs,
+ TypeHandle * pInst);
+
+
+ // TypeDataWalk
+ // This class provides functionality to allow us to read type handles for generic type parameters or the
+ // argument of an array or address type. It takes code sharing into account and allows us to get the canonical
+ // form where necessary. It operates on a list of type arguments gathered on the RS and passed to the constructor.
+ // See code:CordbType::GatherTypeData for more information.
+ //
+ class TypeDataWalk
+ {
+ private:
+ // list of type arguments
+ DebuggerIPCE_TypeArgData * m_pCurrentData;
+
+ // number of type arguments still to be processed
+ unsigned int m_nRemaining;
+
+ public:
+ typedef enum {kGetExact, kGetCanonical} TypeHandleReadType;
+ // constructor
+ TypeDataWalk(DebuggerIPCE_TypeArgData *pData, unsigned int nData);
+
+ // Compute the type handle for a given type.
+ // This is the top-level function that will return the type handle
+ // for an arbitrary type. It uses mutual recursion with ReadLoadedTypeArg to get
+ // the type handle for a (possibly parameterized) type. Note that the referent of
+ // address types or the element type of an array type are viewed as type parameters.
+ TypeHandle ReadLoadedTypeHandle(TypeHandleReadType retrieveWhich);
+
+ private:
+ // skip a single node from the list of type handles
+ void Skip();
+
+ // read and return a single node from the list of type parameters
+ DebuggerIPCE_TypeArgData * ReadOne();
+
+ //
+ // These are for type arguments. They return null if the item could not be found.
+ // They also optionally find the canonical form for the specified type
+ // (used if generic code sharing is enabled) even if the exact form has not
+ // yet been loaded for some reason
+ //
+
+ // Read a type handle when it is used in the position of a generic argument or
+ // argument of an array type. Take into account generic code sharing if we
+ // have been requested to find the canonical representation amongst a set of shared-
+ // code generic types. That is, if generics code sharing is enabled then return "Object"
+ // for all reference types, and canonicalize underneath value types, e.g. V<string> --> V<object>.
+ //
+ // Return TypeHandle() (null) if any of the type handles are not loaded.
+ TypeHandle ReadLoadedTypeArg(TypeHandleReadType retrieveWhich);
+
+ // Iterate through the type argument data, creating type handles as we go.
+ // Return FALSE if any of the type handles are not loaded.
+ BOOL ReadLoadedTypeHandles(TypeHandleReadType retrieveWhich, unsigned int nTypeArgs, TypeHandle *ppResults);
+
+ // Read an instantiation of a generic type if it has already been created.
+ TypeHandle ReadLoadedInstantiation(TypeHandleReadType retrieveWhich,
+ Module * pModule,
+ mdTypeDef mdToken,
+ unsigned int nTypeArgs);
+
+ // These are helper functions to get the type handle for specific classes of types
+ TypeHandle ArrayTypeArg(DebuggerIPCE_TypeArgData * pData, TypeHandleReadType retrieveWhich);
+ TypeHandle PtrOrByRefTypeArg(DebuggerIPCE_TypeArgData * pData, TypeHandleReadType retrieveWhich);
+ TypeHandle FnPtrTypeArg(DebuggerIPCE_TypeArgData * pData, TypeHandleReadType retrieveWhich);
+ TypeHandle ClassTypeArg(DebuggerIPCE_TypeArgData * pData, TypeHandleReadType retrieveWhich);
+ TypeHandle ObjRefOrPrimitiveTypeArg(DebuggerIPCE_TypeArgData * pData, CorElementType elementType);
+
+ }; // class TypeDataWalk
+
+ // get a typehandle for a class or valuetype from basic type data (metadata token
+ // and domain file
+ TypeHandle GetClassOrValueTypeHandle(DebuggerIPCE_BasicTypeData * pData);
+
+ // get an exact type handle for an array type
+ TypeHandle GetExactArrayTypeHandle(DebuggerIPCE_ExpandedTypeData * pTopLevelTypeData,
+ ArgInfoList * pArgInfo);
+
+ // get an exact type handle for a PTR or BYREF type
+ TypeHandle GetExactPtrOrByRefTypeHandle(DebuggerIPCE_ExpandedTypeData * pTopLevelTypeData,
+ ArgInfoList * pArgInfo);
+
+ // get an exact type handle for a CLASS or VALUETYPE type
+ TypeHandle GetExactClassTypeHandle(DebuggerIPCE_ExpandedTypeData * pTopLevelTypeData,
+ ArgInfoList * pArgInfo);
+
+ // get an exact type handle for a FNPTR type
+ TypeHandle GetExactFnPtrTypeHandle(ArgInfoList * pArgInfo);
+
+ // Convert basic type info for a type parameter that came from a top-level type to
+ // the corresponding type handle. If the type parameter is an array or pointer
+ // type, we simply extract the LS type handle from the VMPTR_TypeHandle that is
+ // part of the type information. If the type parameter is a class or value type,
+ // we use the metadata token and domain file in the type info to look up the
+ // appropriate type handle. If the type parameter is any other types, we get the
+ // type handle by having the loader look up the type handle for the element type.
+ TypeHandle BasicTypeInfoToTypeHandle(DebuggerIPCE_BasicTypeData * pArgTypeData);
+
+ // Convert type information for a top-level type to an exact type handle. This
+ // information includes information about the element type if the top-level type is
+ // an array type, the referent if the top-level type is a pointer type, or actual
+ // parameters if the top-level type is a generic class or value type.
+ TypeHandle ExpandedTypeInfoToTypeHandle(DebuggerIPCE_ExpandedTypeData * pTopLevelTypeData,
+ ArgInfoList * pArgInfo);
+
+ // Initialize information about a field added with EnC
+ void InitFieldData(const FieldDesc * pFD,
+ const PTR_CBYTE pORField,
+ const EnCHangingFieldInfo * pEncFieldData,
+ FieldData * pFieldData);
+
+ // Get the address of a field added with EnC.
+ PTR_CBYTE GetPtrToEnCField(FieldDesc * pFD, const EnCHangingFieldInfo * pEnCFieldInfo);
+
+ // Get the FieldDesc corresponding to a particular EnC field token
+ FieldDesc * GetEnCFieldDesc(const EnCHangingFieldInfo * pEnCFieldInfo);
+
+ // Finds information for a particular class field
+ PTR_FieldDesc FindField(TypeHandle thApprox, mdFieldDef fldToken);
+
+// ============================================================================
+// functions to get information about instances of ICDValue implementations
+// ============================================================================
+
+public:
+ // Get object information for a TypedByRef object. Initializes the objRef and typedByRefType fields of
+ // pObjectData (type info for the referent).
+ void GetTypedByRefInfo(CORDB_ADDRESS pTypedByRef,
+ VMPTR_AppDomain vmAppDomain,
+ DebuggerIPCE_ObjectData * pObjectData);
+
+ // Get the string length and offset to string base for a string object
+ void GetStringData(CORDB_ADDRESS objectAddress, DebuggerIPCE_ObjectData * pObjectData);
+
+ // Get information for an array type referent of an objRef, including rank, upper and lower bounds,
+ // element size and type, and the number of elements.
+ void GetArrayData(CORDB_ADDRESS objectAddress, DebuggerIPCE_ObjectData * pObjectData);
+
+ // Get information about an object for which we have a reference, including the object size and
+ // type information.
+ void GetBasicObjectInfo(CORDB_ADDRESS objectAddress,
+ CorElementType type,
+ VMPTR_AppDomain vmAppDomain,
+ DebuggerIPCE_ObjectData * pObjectData);
+
+ // Returns the thread which owns the monitor lock on an object and the acquisition count
+ MonitorLockInfo GetThreadOwningMonitorLock(VMPTR_Object vmObject);
+
+
+ // Enumerate all threads waiting on the monitor event for an object
+ void EnumerateMonitorEventWaitList(VMPTR_Object vmObject,
+ FP_THREAD_ENUMERATION_CALLBACK fpCallback,
+ CALLBACK_DATA pUserData);
+
+private:
+ // Helper function for CheckRef. Sanity check an object.
+ HRESULT FastSanityCheckObject(PTR_Object objPtr);
+
+ // Perform a sanity check on an object address to determine if this _could be_ a valid object. We can't
+ // tell this for certain without walking the GC heap, but we do some fast tests to rule out clearly
+ // invalid object addresses. See code:DacDbiInterfaceImpl::FastSanityCheckObject for more details.
+ bool CheckRef(PTR_Object objPtr);
+
+ // Initialize basic object information: type handle, object size, offset to fields and expanded type
+ // information.
+ void InitObjectData(PTR_Object objPtr,
+ VMPTR_AppDomain vmAppDomain,
+ DebuggerIPCE_ObjectData * pObjectData);
+
+// ============================================================================
+// Functions to test data safety. In these functions we determine whether a lock
+// is held in a code path we need to execute for inspection. If so, we throw an
+// exception.
+// ============================================================================
+
+#ifdef TEST_DATA_CONSISTENCY
+public:
+ void TestCrst(VMPTR_Crst vmCrst);
+ void TestRWLock(VMPTR_SimpleRWLock vmRWLock);
+#endif
+
+// ============================================================================
+// CordbAssembly, CordbModule
+// ============================================================================
+
+ using ClrDataAccess::GetModuleData;
+ using ClrDataAccess::GetAddressType;
+
+public:
+ // Get the full path and file name to the assembly's manifest module.
+ BOOL GetAssemblyPath(VMPTR_Assembly vmAssembly,
+ IStringHolder * pStrFilename);
+
+ void GetAssemblyFromDomainAssembly(VMPTR_DomainAssembly vmDomainAssembly, VMPTR_Assembly *vmAssembly);
+
+ // Determines whether the runtime security system has assigned full-trust to this assembly.
+ BOOL IsAssemblyFullyTrusted(VMPTR_DomainAssembly vmDomainAssembly);
+
+ // get a type def resolved across modules
+ void ResolveTypeReference(const TypeRefData * pTypeRefInfo,
+ TypeRefData * pTargetRefInfo);
+
+ // Get the full path and file name to the module (if any).
+ BOOL GetModulePath(VMPTR_Module vmModule,
+ IStringHolder * pStrFilename);
+
+ // Get the full path and file name to the ngen image for the module (if any).
+ BOOL GetModuleNGenPath(VMPTR_Module vmModule,
+ IStringHolder * pStrFilename);
+
+ // Implementation of IDacDbiInterface::GetModuleSimpleName
+ void GetModuleSimpleName(VMPTR_Module vmModule, IStringHolder * pStrFilename);
+
+ // Implementation of IDacDbiInterface::GetMetadata
+ void GetMetadata(VMPTR_Module vmModule, TargetBuffer * pTargetBuffer);
+
+ // Implementation of IDacDbiInterface::GetSymbolsBuffer
+ void GetSymbolsBuffer(VMPTR_Module vmModule, TargetBuffer * pTargetBuffer, SymbolFormat * pSymbolFormat);
+
+ // Gets properties for a module
+ void GetModuleData(VMPTR_Module vmModule, ModuleInfo * pData);
+
+ // Gets properties for a domainfile
+ void GetDomainFileData(VMPTR_DomainFile vmDomainFile, DomainFileInfo * pData);
+
+ void GetModuleForDomainFile(VMPTR_DomainFile vmDomainFile, OUT VMPTR_Module * pModule);
+
+ // Yields true if the adddress is a CLR stub.
+ BOOL IsTransitionStub(CORDB_ADDRESS address);
+
+ // Get the "type" of address.
+ AddressType GetAddressType(CORDB_ADDRESS address);
+
+
+ // Enumerate the appdomains
+ void EnumerateAppDomains(FP_APPDOMAIN_ENUMERATION_CALLBACK fpCallback,
+ void * pUserData);
+
+ // Enumerate the assemblies in the appdomain.
+ void EnumerateAssembliesInAppDomain(VMPTR_AppDomain vmAppDomain,
+ FP_ASSEMBLY_ENUMERATION_CALLBACK fpCallback,
+ void * pUserData);
+
+ // Enumerate the moduels in the given assembly.
+ void EnumerateModulesInAssembly(
+ VMPTR_DomainAssembly vmAssembly,
+ FP_MODULE_ENUMERATION_CALLBACK fpCallback,
+ void * pUserData
+ );
+
+ // When stopped at an event, request a synchronization.
+ void RequestSyncAtEvent();
+
+ //sets flag Debugger::m_sendExceptionsOutsideOfJMC on the LS
+ HRESULT SetSendExceptionsOutsideOfJMC(BOOL sendExceptionsOutsideOfJMC);
+
+ // Notify the debuggee that a debugger attach is pending.
+ void MarkDebuggerAttachPending();
+
+ // Notify the debuggee that a debugger is attached.
+ void MarkDebuggerAttached(BOOL fAttached);
+
+ // Enumerate connections in the process.
+ void EnumerateConnections(FP_CONNECTION_CALLBACK fpCallback, void * pUserData);
+
+ void EnumerateThreads(FP_THREAD_ENUMERATION_CALLBACK fpCallback, void * pUserData);
+
+ bool IsThreadMarkedDead(VMPTR_Thread vmThread);
+
+ // Return the handle of the specified thread.
+ HANDLE GetThreadHandle(VMPTR_Thread vmThread);
+
+ // Return the object handle for the managed Thread object corresponding to the specified thread.
+ VMPTR_OBJECTHANDLE GetThreadObject(VMPTR_Thread vmThread);
+
+ // Set and reset the TSNC_DebuggerUserSuspend bit on the state of the specified thread
+ // according to the CorDebugThreadState.
+ void SetDebugState(VMPTR_Thread vmThread,
+ CorDebugThreadState debugState);
+
+ // Returns TRUE if there is a current exception which is unhandled
+ BOOL HasUnhandledException(VMPTR_Thread vmThread);
+
+ // Return the user state of the specified thread.
+ CorDebugUserState GetUserState(VMPTR_Thread vmThread);
+
+ // Returns the user state of the specified thread except for USER_UNSAFE_POINT.
+ CorDebugUserState GetPartialUserState(VMPTR_Thread vmThread);
+
+ // Return the connection ID of the specified thread.
+ CONNID GetConnectionID(VMPTR_Thread vmThread);
+
+ // Return the task ID of the specified thread.
+ TASKID GetTaskID(VMPTR_Thread vmThread);
+
+ // Return the OS thread ID of the specified thread
+ DWORD TryGetVolatileOSThreadID(VMPTR_Thread vmThread);
+
+ // Return the unique thread ID of the specified thread.
+ DWORD GetUniqueThreadID(VMPTR_Thread vmThread);
+
+ // Return the object handle to the managed Exception object of the current exception
+ // on the specified thread. The return value could be NULL if there is no current exception.
+ VMPTR_OBJECTHANDLE GetCurrentException(VMPTR_Thread vmThread);
+
+ // Return the object handle to the managed object for a given CCW pointer.
+ VMPTR_OBJECTHANDLE GetObjectForCCW(CORDB_ADDRESS ccwPtr);
+
+ // Return the object handle to the managed CustomNotification object of the current notification
+ // on the specified thread. The return value could be NULL if there is no current notification.
+ // This will return non-null if and only if we are currently inside a CustomNotification Callback
+ // (or a dump was generated while in this callback)
+ VMPTR_OBJECTHANDLE GetCurrentCustomDebuggerNotification(VMPTR_Thread vmThread);
+
+
+ // Return the current appdomain the specified thread is in.
+ VMPTR_AppDomain GetCurrentAppDomain(VMPTR_Thread vmThread);
+
+ // Given an assembly ref token and metadata scope (via the DomainFile), resolve the assembly.
+ VMPTR_DomainAssembly ResolveAssembly(VMPTR_DomainFile vmScope, mdToken tkAssemblyRef);
+
+
+ // Hijack the thread
+ void Hijack(
+ VMPTR_Thread vmThread,
+ ULONG32 dwThreadId,
+ const EXCEPTION_RECORD * pRecord,
+ T_CONTEXT * pOriginalContext,
+ ULONG32 cbSizeContext,
+ EHijackReason::EHijackReason reason,
+ void * pUserData,
+ CORDB_ADDRESS * pRemoteContextAddr);
+
+ // Return the filter CONTEXT on the LS.
+ VMPTR_CONTEXT GetManagedStoppedContext(VMPTR_Thread vmThread);
+
+ // Create and return a stackwalker on the specified thread.
+ void CreateStackWalk(VMPTR_Thread vmThread,
+ DT_CONTEXT * pInternalContextBuffer,
+ StackWalkHandle * ppSFIHandle);
+
+ // Delete the stackwalk object
+ void DeleteStackWalk(StackWalkHandle ppSFIHandle);
+
+ // Get the CONTEXT of the current frame at which the stackwalker is stopped.
+ void GetStackWalkCurrentContext(StackWalkHandle pSFIHandle,
+ DT_CONTEXT * pContext);
+
+ void GetStackWalkCurrentContext(StackFrameIterator * pIter, DT_CONTEXT * pContext);
+
+ // Set the stackwalker to the specified CONTEXT.
+ void SetStackWalkCurrentContext(VMPTR_Thread vmThread,
+ StackWalkHandle pSFIHandle,
+ CorDebugSetContextFlag flag,
+ DT_CONTEXT * pContext);
+
+ // Unwind the stackwalker to the next frame.
+ BOOL UnwindStackWalkFrame(StackWalkHandle pSFIHandle);
+
+ HRESULT CheckContext(VMPTR_Thread vmThread,
+ const DT_CONTEXT * pContext);
+
+ // Retrieve information about the current frame from the stackwalker.
+ FrameType GetStackWalkCurrentFrameInfo(StackWalkHandle pSFIHandle,
+ DebuggerIPCE_STRData * pFrameData);
+
+ // Return the number of internal frames on the specified thread.
+ ULONG32 GetCountOfInternalFrames(VMPTR_Thread vmThread);
+
+ // Enumerate the internal frames on the specified thread and invoke the provided callback on each of them.
+ void EnumerateInternalFrames(VMPTR_Thread vmThread,
+ FP_INTERNAL_FRAME_ENUMERATION_CALLBACK fpCallback,
+ void * pUserData);
+
+ // Given the FramePointer of the parent frame and the FramePointer of the current frame,
+ // check if the current frame is the parent frame.
+ BOOL IsMatchingParentFrame(FramePointer fpToCheck, FramePointer fpParent);
+
+ // Return the stack parameter size of the given method.
+ ULONG32 GetStackParameterSize(CORDB_ADDRESS controlPC);
+
+ // Return the FramePointer of the current frame at which the stackwalker is stopped.
+ FramePointer GetFramePointer(StackWalkHandle pSFIHandle);
+
+ FramePointer GetFramePointerWorker(StackFrameIterator * pIter);
+
+ // Return TRUE if the specified CONTEXT is the CONTEXT of the leaf frame.
+ // @dbgtodo filter CONTEXT - Currently we check for the filter CONTEXT first.
+ BOOL IsLeafFrame(VMPTR_Thread vmThread,
+ const DT_CONTEXT * pContext);
+
+ // DacDbi API: Get the context for a particular thread of the target process
+ void GetContext(VMPTR_Thread vmThread, DT_CONTEXT * pContextBuffer);
+
+ // This is a simple helper function to convert a CONTEXT to a DebuggerREGDISPLAY. We need to do this
+ // inside DDI because the RS has no notion of REGDISPLAY.
+ void ConvertContextToDebuggerRegDisplay(const DT_CONTEXT * pInContext,
+ DebuggerREGDISPLAY * pOutDRD,
+ BOOL fActive);
+
+ // Check if the given method is an IL stub or an LCD method.
+ DynamicMethodType IsILStubOrLCGMethod(VMPTR_MethodDesc vmMethodDesc);
+
+ // Return a TargetBuffer for the raw vararg signature.
+ TargetBuffer GetVarArgSig(CORDB_ADDRESS VASigCookieAddr,
+ CORDB_ADDRESS * pArgBase);
+
+ // returns TRUE if the type requires 8-byte alignment
+ BOOL RequiresAlign8(VMPTR_TypeHandle thExact);
+
+ // Resolve the raw generics token to the real generics type token. The resolution is based on the
+ // given index.
+ GENERICS_TYPE_TOKEN ResolveExactGenericArgsToken(DWORD dwExactGenericArgsTokenIndex,
+ GENERICS_TYPE_TOKEN rawToken);
+
+ // Enumerate all monitors blocking a thread
+ void EnumerateBlockingObjects(VMPTR_Thread vmThread,
+ FP_BLOCKINGOBJECT_ENUMERATION_CALLBACK fpCallback,
+ CALLBACK_DATA pUserData);
+
+ // Returns a bitfield reflecting the managed debugging state at the time of
+ // the jit attach.
+ CLR_DEBUGGING_PROCESS_FLAGS GetAttachStateFlags();
+
+protected:
+ // This class used to be stateless, but we are relaxing the requirements
+ // slightly to gain perf. We should still be stateless in the sense that an API call
+ // should always return the same result regardless of the internal state. Hence
+ // a caller can not distinguish that we have any state. Internally however we are
+ // allowed to cache pieces of frequently used data to improve the perf of various
+ // operations. All of this cached data should be flushed when the DAC is flushed.
+
+ // But it can have helper methods.
+
+ // The allocator object is conceptually stateless. It lets us allocate data buffers to hand back.
+ IAllocator * m_pAllocator;
+
+ // Callback to DBI to get internal metadata.
+ IMetaDataLookup * m_pMetaDataLookup;
+
+
+ // Metadata lookups is just a property on the PEFile in the normal builds,
+ // and so VM code tends to access the same metadata importer many times in a row.
+ // Cache the most-recently used to avoid excessive redundant lookups.
+
+ // PEFile of Cached Importer. Invalidated between Flush calls. If this is Non-null,
+ // then the importer is m_pCachedImporter, and we can avoid using IMetaDataLookup
+ VMPTR_PEFile m_pCachedPEFile;
+
+ // Value of cached importer, corresponds with m_pCachedPEFile.
+ IMDInternalImport * m_pCachedImporter;
+
+ // Value of cached hijack function list, corresponds to g_pDebugger->m_rgHijackFunction
+ BOOL m_isCachedHijackFunctionValid;
+ TargetBuffer m_pCachedHijackFunction[Debugger::kMaxHijackFunctions];
+
+ // Helper to write structured data to target.
+ template<typename T>
+ void SafeWriteStructOrThrow(CORDB_ADDRESS pRemotePtr, const T * pLocalBuffer);
+
+ // Helper to read structured data from the target process.
+ template<typename T>
+ void SafeReadStructOrThrow(CORDB_ADDRESS pRemotePtr, T * pLocalBuffer);
+
+ TADDR GetHijackAddress();
+
+ void AlignStackPointer(CORDB_ADDRESS * pEsp);
+
+ template <class T>
+ CORDB_ADDRESS PushHelper(CORDB_ADDRESS * pEsp, const T * pData, BOOL fAlignStack);
+
+ // Write an EXCEPTION_RECORD structure to the remote target at the specified address while taking
+ // into account the number of exception parameters.
+ void WriteExceptionRecordHelper(CORDB_ADDRESS pRemotePtr, const EXCEPTION_RECORD * pExcepRecord);
+
+ typedef DPTR(struct DebuggerIPCControlBlock) PTR_DebuggerIPCControlBlock;
+
+ // Get the address of the Debugger control block on the helper thread. Returns
+ // NULL if the control block has not been successfully allocated
+ CORDB_ADDRESS GetDebuggerControlBlockAddress();
+
+ // Creates a VMPTR of an Object from a target address
+ VMPTR_Object GetObject(CORDB_ADDRESS ptr);
+
+ // sets state in the native binder
+ HRESULT EnableNGENPolicy(CorDebugNGENPolicy ePolicy);
+
+ // Sets the NGEN compiler flags. This restricts NGEN to only use images with certain
+ // types of pregenerated code.
+ HRESULT SetNGENCompilerFlags(DWORD dwFlags);
+
+ // Gets the NGEN compiler flags currently in effect.
+ HRESULT GetNGENCompilerFlags(DWORD *pdwFlags);
+
+ // Creates a VMPTR of an Object from a target address pointing to an OBJECTREF
+ VMPTR_Object GetObjectFromRefPtr(CORDB_ADDRESS ptr);
+
+ // Get the target address from a VMPTR_OBJECTHANDLE, i.e., the handle address
+ CORDB_ADDRESS GetHandleAddressFromVmHandle(VMPTR_OBJECTHANDLE vmHandle);
+
+ // Gets the target address of an VMPTR of an Object
+ TargetBuffer GetObjectContents(VMPTR_Object vmObj);
+
+ // Create a VMPTR_OBJECTHANDLE from a CORDB_ADDRESS pointing to an object handle
+ VMPTR_OBJECTHANDLE GetVmObjectHandle(CORDB_ADDRESS handleAddress);
+
+ // Validate that the VMPTR_OBJECTHANDLE refers to a legitimate managed object
+ BOOL IsVmObjectHandleValid(VMPTR_OBJECTHANDLE vmHandle);
+
+ // if the specified module is a WinRT module then isWinRT will equal TRUE
+ HRESULT IsWinRTModule(VMPTR_Module vmModule, BOOL& isWinRT);
+
+ // Determines the app domain id for the object refered to by a given VMPTR_OBJECTHANDLE
+ ULONG GetAppDomainIdFromVmObjectHandle(VMPTR_OBJECTHANDLE vmHandle);
+
+private:
+ bool IsThreadMarkedDeadWorker(Thread * pThread);
+
+ // Check whether the specified thread is at a GC-safe place, i.e. in an interruptible region.
+ BOOL IsThreadAtGCSafePlace(VMPTR_Thread vmThread);
+
+ // Fill in the structure with information about the current frame at which the stackwalker is stopped
+ void InitFrameData(StackFrameIterator * pIter,
+ FrameType ft,
+ DebuggerIPCE_STRData * pFrameData);
+
+ // Helper method to fill in the address and the size of the hot and cold regions.
+ void InitNativeCodeAddrAndSize(TADDR taStartAddr,
+ DebuggerIPCE_JITFuncData * pJITFuncData);
+
+ // Fill in the information about the parent frame.
+ void InitParentFrameInfo(CrawlFrame * pCF,
+ DebuggerIPCE_JITFuncData * pJITFuncData);
+
+ // Return the stack parameter size of the given method.
+ ULONG32 GetStackParameterSize(EECodeInfo * pCodeInfo);
+
+ typedef enum
+ {
+ kFromManagedToUnmanaged,
+ kFromUnmanagedToManaged,
+ } StackAdjustmentDirection;
+
+ // Adjust the stack pointer in the CONTEXT for the stack parameter.
+ void AdjustRegDisplayForStackParameter(REGDISPLAY * pRD,
+ DWORD cbStackParameterSize,
+ BOOL fIsActiveFrame,
+ StackAdjustmentDirection direction);
+
+ // Given an explicit frame, return the corresponding type in terms of CorDebugInternalFrameType.
+ CorDebugInternalFrameType GetInternalFrameType(Frame * pFrame);
+
+ // Helper method to convert a REGDISPLAY to a CONTEXT.
+ void UpdateContextFromRegDisp(REGDISPLAY * pRegDisp,
+ T_CONTEXT * pContext);
+
+ // Check if a control PC is in one of the native functions which require special unwinding.
+ bool IsRuntimeUnwindableStub(PCODE taControlPC);
+
+ // Given the REGDISPLAY of a stack frame for one of the redirect functions, retrieve the original CONTEXT
+ // before the thread redirection.
+ PTR_CONTEXT RetrieveHijackedContext(REGDISPLAY * pRD);
+
+ // Unwind special native stack frame which the runtime knows how to unwind.
+ BOOL UnwindRuntimeStackFrame(StackFrameIterator * pIter);
+
+ // Look up the EnC version number of a particular jitted instance of a managed method.
+ void LookupEnCVersions(Module* pModule,
+ VMPTR_MethodDesc vmMethodDesc,
+ mdMethodDef mdMethod,
+ CORDB_ADDRESS pNativeStartAddress,
+ SIZE_T * pLatestEnCVersion,
+ SIZE_T * pJittedInstanceEnCVersion = NULL);
+
+ // @dbgtodo - This method should be removed once CordbFunctionBreakpoint and SetIP are moved OOP and
+ // no longer use nativeCodeJITInfoToken.
+ void SetDJIPointer(Module * pModule,
+ MethodDesc * pMD,
+ mdMethodDef mdMethod,
+ DebuggerIPCE_JITFuncData * pJITFuncData);
+
+ // This is just a worker function for GetILCodeAndSig. It returns the function's ILCode and SigToken
+ // given a module, a token, and the RVA. If a MethodDesc is provided, it has to be consistent with
+ // the token and the RVA.
+ mdSignature GetILCodeAndSigHelper(Module * pModule,
+ MethodDesc * pMD,
+ mdMethodDef mdMethodToken,
+ RVA methodRVA,
+ TargetBuffer * pIL);
+
+public:
+ // APIs for picking up the info needed for a debugger to look up an ngen image or IL image
+ // from it's search path.
+ bool GetMetaDataFileInfoFromPEFile(VMPTR_PEFile vmPEFile,
+ DWORD &dwTimeStamp,
+ DWORD &dwSize,
+ bool &isNGEN,
+ IStringHolder* pStrFilename);
+
+ bool GetILImageInfoFromNgenPEFile(VMPTR_PEFile vmPEFile,
+ DWORD &dwTimeStamp,
+ DWORD &dwSize,
+ IStringHolder* pStrFilename);
+
+};
+
+
+// Global allocator for DD. Access is protected under the g_dacCritSec lock.
+extern "C" IDacDbiInterface::IAllocator * g_pAllocator;
+
+
+class DDHolder
+{
+public:
+ DDHolder(DacDbiInterfaceImpl* pContainer, bool fAllowReentrant)
+ {
+ EnterCriticalSection(&g_dacCritSec);
+
+ // If we're not re-entrant, then assert.
+ if (!fAllowReentrant)
+ {
+ _ASSERTE(g_dacImpl == NULL);
+ }
+
+ // This cast is safe because ClrDataAccess can't call the DacDbi layer.
+ m_pOldContainer = static_cast<DacDbiInterfaceImpl *> (g_dacImpl);
+ m_pOldAllocator = g_pAllocator;
+
+ g_dacImpl = pContainer;
+ g_pAllocator = pContainer->GetAllocator();
+
+ }
+ ~DDHolder()
+ {
+ // If an exception is being thrown, we won't be in the PAL (but in normal return paths it will).
+
+ g_dacImpl = m_pOldContainer;
+ g_pAllocator = m_pOldAllocator;
+
+ LeaveCriticalSection(&g_dacCritSec);
+ }
+
+protected:
+ DacDbiInterfaceImpl * m_pOldContainer;
+ IDacDbiInterface::IAllocator * m_pOldAllocator;
+};
+
+
+// Use this macro at the start of each DD function.
+// This may nest if a DD primitive takes in a callback that then calls another DD primitive.
+#define DD_ENTER_MAY_THROW \
+ DDHolder __dacHolder(this, true); \
+
+
+// Non-reentrant version of DD_ENTER_MAY_THROW. Asserts non-reentrancy.
+// Use this macro at the start of each DD function.
+// This may nest if a DD primitive takes in a callback that then calls another DD primitive.
+#define DD_NON_REENTRANT_MAY_THROW \
+ DDHolder __dacHolder(this, false); \
+
+#include "dacdbiimpl.inl"
+
+class DacRefWalker
+{
+public:
+ DacRefWalker(ClrDataAccess *dac, BOOL walkStacks, BOOL walkFQ, UINT32 handleMask);
+ ~DacRefWalker();
+
+ HRESULT Init();
+ HRESULT Next(ULONG celt, DacGcReference roots[], ULONG *pceltFetched);
+
+private:
+ UINT32 GetHandleWalkerMask();
+ void Clear();
+ HRESULT NextThread();
+
+private:
+ ClrDataAccess *mDac;
+ BOOL mWalkStacks, mWalkFQ;
+ UINT32 mHandleMask;
+
+ // Stacks
+ DacStackReferenceWalker *mStackWalker;
+
+ // Handles
+ DacHandleWalker *mHandleWalker;
+
+ // FQ
+ PTR_PTR_Object mFQStart;
+ PTR_PTR_Object mFQEnd;
+ PTR_PTR_Object mFQCurr;
+};
+
+#endif // _DACDBI_IMPL_H_
diff --git a/src/debug/daccess/dacdbiimpl.inl b/src/debug/daccess/dacdbiimpl.inl
new file mode 100644
index 0000000000..c9deae7fee
--- /dev/null
+++ b/src/debug/daccess/dacdbiimpl.inl
@@ -0,0 +1,79 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+//*****************************************************************************
+// DacDbiImpl.inl
+//
+
+//
+// Inline functions for DacDbiImpl.h
+//
+//*****************************************************************************
+
+#ifndef _DACDBI_IMPL_INL_
+#define _DACDBI_IMPL_INL_
+
+#include "dacdbiimpl.h"
+
+//---------------------------------------------------------------------------------------
+// Helper to write a structure to the target
+//
+// Arguments:
+// T - type of structure to read.
+// pRemotePtr - remote pointer into target (dest).
+// pLocalBuffer - local buffer to write (Src).
+//
+// Return Value:
+// Throws on error.
+//
+// Notes:
+// This just does a raw Byte copy into the Target, but does not do any Marshalling.
+// This fails if any part of the buffer can't be written.
+//
+//
+//---------------------------------------------------------------------------------------
+template<typename T>
+void DacDbiInterfaceImpl::SafeWriteStructOrThrow(CORDB_ADDRESS pRemotePtr, const T * pLocalBuffer)
+{
+ HRESULT hr = m_pMutableTarget->WriteVirtual(pRemotePtr,
+ (BYTE *)(pLocalBuffer), sizeof(T));
+
+ if (FAILED(hr))
+ {
+ ThrowHR(hr);
+ }
+}
+
+//---------------------------------------------------------------------------------------
+// Helper to read a structure from the target process
+//
+// Arguments:
+// T - type of structure to read
+// pRemotePtr - remote pointer into the target process (src)
+// pLocalBuffer - local buffer to store the structure (dest)
+//
+// Notes:
+// This just does a raw Byte copy into the Target, but does not do any Marshalling.
+// This fails if any part of the buffer can't be written.
+//
+
+template<typename T>
+void DacDbiInterfaceImpl::SafeReadStructOrThrow(CORDB_ADDRESS pRemotePtr, T * pLocalBuffer)
+{
+ ULONG32 cbRead = 0;
+
+ HRESULT hr = m_pTarget->ReadVirtual(pRemotePtr,
+ reinterpret_cast<BYTE *>(pLocalBuffer), sizeof(T), &cbRead);
+
+ if (FAILED(hr))
+ {
+ ThrowHR(CORDBG_E_READVIRTUAL_FAILURE);
+ }
+
+ if (cbRead != sizeof(T))
+ {
+ ThrowWin32(ERROR_PARTIAL_COPY);
+ }
+}
+
+#endif // _DACDBI_IMPL_INL_
diff --git a/src/debug/daccess/dacdbiimpllocks.cpp b/src/debug/daccess/dacdbiimpllocks.cpp
new file mode 100644
index 0000000000..d3fb589e13
--- /dev/null
+++ b/src/debug/daccess/dacdbiimpllocks.cpp
@@ -0,0 +1,43 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+//*****************************************************************************
+// File: DacDbiImplLocks.cpp
+//
+
+//
+// Implement DAC/DBI interface for testing our ability to detect when the LS
+// holds a lock that we encounter while executing in the DAC.
+//
+//*****************************************************************************
+
+#include "stdafx.h"
+#include "dacdbiinterface.h"
+#include "holder.h"
+#include "switches.h"
+#include "dacdbiimpl.h"
+
+// ============================================================================
+// Functions to test data safety. In these functions we determine whether a lock
+// is held in a code path we need to execute for inspection. If so, we throw an
+// exception.
+// ============================================================================
+
+#ifdef TEST_DATA_CONSISTENCY
+#include "crst.h"
+
+void DacDbiInterfaceImpl::TestCrst(VMPTR_Crst vmCrst)
+{
+ DD_ENTER_MAY_THROW;
+
+ DebugTryCrst(vmCrst.GetDacPtr());
+}
+
+void DacDbiInterfaceImpl::TestRWLock(VMPTR_SimpleRWLock vmRWLock)
+{
+ DD_ENTER_MAY_THROW;
+
+ DebugTryRWLock(vmRWLock.GetDacPtr());
+}
+#endif
+
diff --git a/src/debug/daccess/dacdbiimplstackwalk.cpp b/src/debug/daccess/dacdbiimplstackwalk.cpp
new file mode 100644
index 0000000000..d3a2a63bbc
--- /dev/null
+++ b/src/debug/daccess/dacdbiimplstackwalk.cpp
@@ -0,0 +1,1313 @@
+// 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.
+//
+// DacDbiImplStackWalk.cpp
+//
+
+//
+// This file contains the implementation of the stackwalking-related functions on the DacDbiInterface.
+//
+// ======================================================================================
+
+#include "stdafx.h"
+#include "dacdbiinterface.h"
+#include "dacdbiimpl.h"
+#include "excepcpu.h"
+
+#if defined(FEATURE_COMINTEROP)
+#include "comtoclrcall.h"
+#include "comcallablewrapper.h"
+#endif // FEATURE_COMINTEROP
+
+typedef IDacDbiInterface::StackWalkHandle StackWalkHandle;
+
+
+// Persistent data needed to do a stackwalk. This is allocated on the forDbi heap.
+// It can survive across multiple DD calls.
+// However, it has data structures that have raw pointers into the DAC cache, and so it must
+// be re-iniatialized after each time the Dac cache is flushed.
+struct StackWalkData
+{
+public:
+ StackWalkData(Thread * pThread, Frame * pFrame, ULONG32 flags) :
+ m_iterator(pThread, NULL, flags)
+ { SUPPORTS_DAC;
+ }
+
+ // Unwrap a handle to get StackWalkData instance.
+ static StackWalkData * FromHandle(StackWalkHandle handle)
+ {
+ SUPPORTS_DAC;
+ _ASSERTE(handle != NULL);
+ return reinterpret_cast<StackWalkData *>(handle);
+ }
+
+ // The stackwalk iterator. This has lots of pointers into the DAC cache.
+ StackFrameIterator m_iterator;
+
+ // The context buffer, which can be pointed to by the RegDisplay.
+ T_CONTEXT m_context;
+
+ // A regdisplay used by the stackwalker.
+ REGDISPLAY m_regdisplay;
+};
+
+// Helper to allocate stackwalk datastructures for given parameters.
+// This is allocated on the local heap (and not via the forDbi allocatoror on the dac-cache), and then
+// freed via code:DacDbiInterfaceImpl::DeleteStackWalk
+//
+// Throws on error (mainly OOM).
+void AllocateStackwalk(StackWalkHandle * pHandle, Thread * pThread, Frame * pFrame, ULONG32 flags)
+{
+ SUPPORTS_DAC;
+
+ StackWalkData * p = new StackWalkData(pThread, NULL, flags); // throews
+
+ StackWalkHandle h = reinterpret_cast<StackWalkHandle>(p);
+ *pHandle = h;
+}
+void DeleteStackwalk(StackWalkHandle pHandle)
+{
+ SUPPORTS_DAC;
+
+ StackWalkData * pBuffer = (StackWalkData *) pHandle;
+ _ASSERTE(pBuffer != NULL);
+ delete pBuffer;
+}
+
+
+// Helper to get the StackFrameIterator from a Stackwalker handle
+StackFrameIterator * GetIteratorFromHandle(StackWalkHandle pSFIHandle)
+{
+ SUPPORTS_DAC;
+
+ StackWalkData * pBuffer = StackWalkData::FromHandle(pSFIHandle);
+ return &(pBuffer->m_iterator);
+}
+
+// Helper to get a RegDisplay from a Stackwalker handle
+REGDISPLAY * GetRegDisplayFromHandle(StackWalkHandle pSFIHandle)
+{
+ SUPPORTS_DAC;
+ StackWalkData * pBuffer = StackWalkData::FromHandle(pSFIHandle);
+ return &(pBuffer->m_regdisplay);
+}
+
+// Helper to get a Context buffer from a Stackwalker handle
+T_CONTEXT * GetContextBufferFromHandle(StackWalkHandle pSFIHandle)
+{
+ SUPPORTS_DAC;
+ StackWalkData * pBuffer = StackWalkData::FromHandle(pSFIHandle);
+ return &(pBuffer->m_context);
+}
+
+
+// Create and return a stackwalker on the specified thread.
+void DacDbiInterfaceImpl::CreateStackWalk(VMPTR_Thread vmThread,
+ DT_CONTEXT * pInternalContextBuffer,
+ StackWalkHandle * ppSFIHandle)
+{
+ DD_ENTER_MAY_THROW;
+
+ _ASSERTE(ppSFIHandle != NULL);
+
+ Thread * pThread = vmThread.GetDacPtr();
+
+ // Set the stackwalk flags. We pretty much want to stop at everything.
+ DWORD dwFlags = (NOTIFY_ON_U2M_TRANSITIONS |
+ NOTIFY_ON_NO_FRAME_TRANSITIONS |
+ NOTIFY_ON_INITIAL_NATIVE_CONTEXT);
+
+ // allocate memory for various stackwalker buffers (StackFrameIterator, RegDisplay, Context)
+ AllocateStackwalk(ppSFIHandle, pThread, NULL, dwFlags);
+
+ // initialize the the CONTEXT.
+ // SetStackWalk will initial the RegDisplay from this context.
+ GetContext(vmThread, pInternalContextBuffer);
+
+ // initialize the stackwalker
+ SetStackWalkCurrentContext(vmThread,
+ *ppSFIHandle,
+ SET_CONTEXT_FLAG_ACTIVE_FRAME,
+ pInternalContextBuffer);
+}
+
+// Delete the stackwalk object allocated by code:AllocateStackwalk
+void DacDbiInterfaceImpl::DeleteStackWalk(StackWalkHandle ppSFIHandle)
+{
+ DeleteStackwalk(ppSFIHandle);
+}
+
+// Get the CONTEXT of the current frame at which the stackwalker is stopped.
+void DacDbiInterfaceImpl::GetStackWalkCurrentContext(StackWalkHandle pSFIHandle,
+ DT_CONTEXT * pContext)
+{
+ DD_ENTER_MAY_THROW;
+
+ StackFrameIterator * pIter = GetIteratorFromHandle(pSFIHandle);
+
+ GetStackWalkCurrentContext(pIter, pContext);
+}
+
+// Internal Worker for GetStackWalkCurrentContext().
+void DacDbiInterfaceImpl::GetStackWalkCurrentContext(StackFrameIterator * pIter,
+ DT_CONTEXT * pContext)
+{
+ // convert the current REGDISPLAY to a CONTEXT
+ CrawlFrame * pCF = &(pIter->m_crawl);
+ UpdateContextFromRegDisp(pCF->GetRegisterSet(), reinterpret_cast<T_CONTEXT *>(pContext));
+}
+
+
+
+// Set the stackwalker to the specified CONTEXT.
+void DacDbiInterfaceImpl::SetStackWalkCurrentContext(VMPTR_Thread vmThread,
+ StackWalkHandle pSFIHandle,
+ CorDebugSetContextFlag flag,
+ DT_CONTEXT * pContext)
+{
+ DD_ENTER_MAY_THROW;
+
+ StackFrameIterator * pIter = GetIteratorFromHandle(pSFIHandle);
+ REGDISPLAY * pRD = GetRegDisplayFromHandle(pSFIHandle);
+
+#if defined(_DEBUG)
+ // The caller should have checked this already.
+ _ASSERTE(CheckContext(vmThread, pContext) == S_OK);
+#endif // _DEBUG
+
+ // DD can't keep pointers back into the RS address space.
+ // Allocate a context in DDImpl's memory space. DDImpl can't contain raw pointers back into
+ // the client space since that may not marshal.
+ T_CONTEXT * pContext2 = GetContextBufferFromHandle(pSFIHandle);
+ *pContext2 = *reinterpret_cast<T_CONTEXT *>(pContext); // memcpy
+
+ // update the REGDISPLAY with the given CONTEXT.
+ // Be sure that the context is in DDImpl's memory space and not the Right-sides.
+ FillRegDisplay(pRD, pContext2);
+ BOOL fSuccess = pIter->ResetRegDisp(pRD, (flag == SET_CONTEXT_FLAG_ACTIVE_FRAME));
+ if (!fSuccess)
+ {
+ // ResetRegDisp() may fail for the same reason Init() may fail, i.e.
+ // because the stackwalker tries to unwind one frame ahead of time,
+ // or because the stackwalker needs to filter out some frames based on the stackwalk flags.
+ ThrowHR(E_FAIL);
+ }
+}
+
+
+// Unwind the stackwalker to the next frame.
+BOOL DacDbiInterfaceImpl::UnwindStackWalkFrame(StackWalkHandle pSFIHandle)
+{
+ DD_ENTER_MAY_THROW;
+
+ StackFrameIterator * pIter = GetIteratorFromHandle(pSFIHandle);
+
+ CrawlFrame * pCF = &(pIter->m_crawl);
+
+ if ((pIter->GetFrameState() == StackFrameIterator::SFITER_INITIAL_NATIVE_CONTEXT) ||
+ (pIter->GetFrameState() == StackFrameIterator::SFITER_NATIVE_MARKER_FRAME))
+ {
+ if (IsRuntimeUnwindableStub(GetControlPC(pCF->GetRegisterSet())))
+ {
+ // This is a native stack frame which the StackFrameIterator doesn't know how to unwind.
+ // Use our special unwind logic.
+ return UnwindRuntimeStackFrame(pIter);
+ }
+ }
+
+ // On x86, we need to adjust the stack pointer for the callee parameter adjustment.
+ // This requires us to save the number of bytes used for the stack parameters of the callee.
+ // Thus, let's save it here before we unwind.
+ DWORD cbStackParameterSize = 0;
+ if (pIter->GetFrameState() == StackFrameIterator::SFITER_FRAMELESS_METHOD)
+ {
+ cbStackParameterSize = GetStackParameterSize(pCF->GetCodeInfo());
+ }
+
+ // If the stackwalker is invalid to begin with, we'll just say that it is at the end of the stack.
+ BOOL fIsAtEndOfStack = TRUE;
+ while (pIter->IsValid())
+ {
+ StackWalkAction swa = pIter->Next();
+
+ if (swa == SWA_FAILED)
+ {
+ // The stackwalker is valid to begin with, so this must be a failure case.
+ ThrowHR(E_FAIL);
+ }
+ else if (swa == SWA_CONTINUE)
+ {
+ if (pIter->GetFrameState() == StackFrameIterator::SFITER_DONE)
+ {
+ // We are at the end of the stack. We will break at the end of the loop and fIsAtEndOfStack
+ // will be TRUE.
+ }
+ else if ((pIter->GetFrameState() == StackFrameIterator::SFITER_FRAME_FUNCTION) ||
+ (pIter->GetFrameState() == StackFrameIterator::SFITER_SKIPPED_FRAME_FUNCTION))
+ {
+ // If the stackwalker is stopped at an explicit frame, unwind directly to the next frame.
+ // The V3 stackwalker doesn't stop on explicit frames.
+ continue;
+ }
+ else if (pIter->GetFrameState() == StackFrameIterator::SFITER_NO_FRAME_TRANSITION)
+ {
+ // No frame transitions are not exposed in V2.
+ // Just continue onto the next managed stack frame.
+ continue;
+ }
+ else
+ {
+ fIsAtEndOfStack = FALSE;
+ }
+ }
+ else
+ {
+ UNREACHABLE();
+ }
+
+ // If we get here, then we want to stop at this current frame.
+ break;
+ }
+
+ if (fIsAtEndOfStack == FALSE)
+ {
+ // Currently the only case where we adjust the stack pointer is at M2U transitions.
+ if (pIter->GetFrameState() == StackFrameIterator::SFITER_NATIVE_MARKER_FRAME)
+ {
+ _ASSERTE(!pCF->IsActiveFrame());
+ AdjustRegDisplayForStackParameter(pCF->GetRegisterSet(),
+ cbStackParameterSize,
+ pCF->IsActiveFrame(),
+ kFromManagedToUnmanaged);
+ }
+ }
+
+ return (fIsAtEndOfStack == FALSE);
+}
+
+bool g_fSkipStackCheck = false;
+bool g_fSkipStackCheckInit = false;
+
+// Check whether the specified CONTEXT is valid. The only check we perform right now is whether the
+// SP in the specified CONTEXT is in the stack range of the thread.
+HRESULT DacDbiInterfaceImpl::CheckContext(VMPTR_Thread vmThread,
+ const DT_CONTEXT * pContext)
+{
+ DD_ENTER_MAY_THROW;
+
+ // If the SP in the CONTEXT isn't valid, then there's no point in checking.
+ if ((pContext->ContextFlags & CONTEXT_CONTROL) == 0)
+ {
+ return S_OK;
+ }
+
+ if (!g_fSkipStackCheckInit)
+ {
+ g_fSkipStackCheck = (CLRConfig::GetConfigValue(CLRConfig::UNSUPPORTED_DbgSkipStackCheck) != 0);
+ g_fSkipStackCheckInit = true;
+ }
+
+ // Skip this check if the customer has set the reg key/env var. This is necessary for AutoCad. They
+ // enable fiber mode by calling the Win32 API ConvertThreadToFiber(), but when a managed debugger is
+ // attached, they don't actually call into our hosting APIs such as SwitchInLogicalThreadState(). This
+ // leads to the cached stack range on the Thread object being stale.
+ if (!g_fSkipStackCheck)
+ {
+ // We don't have the backing store boundaries stored on the thread, but this is just
+ // a sanity check anyway.
+ Thread * pThread = vmThread.GetDacPtr();
+ PTR_VOID sp = GetSP(reinterpret_cast<const T_CONTEXT *>(pContext));
+
+ if ((sp < pThread->GetCachedStackLimit()) || (pThread->GetCachedStackBase() <= sp))
+ {
+ return CORDBG_E_NON_MATCHING_CONTEXT;
+ }
+ }
+
+ return S_OK;
+}
+
+// Retrieve information about the current frame from the stackwalker.
+IDacDbiInterface::FrameType DacDbiInterfaceImpl::GetStackWalkCurrentFrameInfo(StackWalkHandle pSFIHandle,
+ DebuggerIPCE_STRData * pFrameData)
+{
+ DD_ENTER_MAY_THROW;
+
+ _ASSERTE(pSFIHandle != NULL);
+
+ StackFrameIterator * pIter = GetIteratorFromHandle(pSFIHandle);
+
+ FrameType ftResult = kInvalid;
+ if (pIter->GetFrameState() == StackFrameIterator::SFITER_DONE)
+ {
+ _ASSERTE(!pIter->IsValid());
+ ftResult = kAtEndOfStack;
+ }
+ else
+ {
+ BOOL fInitFrameData = FALSE;
+ switch (pIter->GetFrameState())
+ {
+ case StackFrameIterator::SFITER_UNINITIALIZED:
+ ftResult = kInvalid;
+ break;
+
+ case StackFrameIterator::SFITER_FRAMELESS_METHOD:
+ ftResult = kManagedStackFrame;
+ fInitFrameData = TRUE;
+ break;
+
+ case StackFrameIterator::SFITER_FRAME_FUNCTION:
+ //
+ // fall through
+ //
+ case StackFrameIterator::SFITER_SKIPPED_FRAME_FUNCTION:
+ ftResult = kExplicitFrame;
+ fInitFrameData = TRUE;
+ break;
+
+ case StackFrameIterator::SFITER_NO_FRAME_TRANSITION:
+ // no-frame transition represents an ExInfo for a native exception on x86.
+ // For all intents and purposes this should be treated just like another explicit frame.
+ ftResult = kExplicitFrame;
+ fInitFrameData = TRUE;
+ break;
+
+ case StackFrameIterator::SFITER_NATIVE_MARKER_FRAME:
+ //
+ // fall through
+ //
+ case StackFrameIterator::SFITER_INITIAL_NATIVE_CONTEXT:
+ if (IsRuntimeUnwindableStub(GetControlPC(pIter->m_crawl.GetRegisterSet())))
+ {
+ ftResult = kNativeRuntimeUnwindableStackFrame;
+ fInitFrameData = TRUE;
+ }
+ else
+ {
+ ftResult = kNativeStackFrame;
+ }
+ break;
+
+ default:
+ UNREACHABLE();
+ }
+
+ if ((fInitFrameData == TRUE) && (pFrameData != NULL))
+ {
+ InitFrameData(pIter, ftResult, pFrameData);
+ }
+ }
+
+ return ftResult;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Return the number of internal frames on the specified thread.
+//
+// Arguments:
+// vmThread - the thread to be walked
+//
+// Return Value:
+// Return the number of interesting internal frames on the thread.
+//
+// Notes:
+// Internal frames are interesting if they are not of type STUBFRAME_NONE.
+//
+
+ULONG32 DacDbiInterfaceImpl::GetCountOfInternalFrames(VMPTR_Thread vmThread)
+{
+ DD_ENTER_MAY_THROW;
+
+ Thread * pThread = vmThread.GetDacPtr();
+ Frame * pFrame = pThread->GetFrame();
+
+ // We could call EnumerateInternalFrames() here, but it would be a lot of overhead for what we need.
+ ULONG32 uCount = 0;
+ while (pFrame != FRAME_TOP)
+ {
+ CorDebugInternalFrameType ift = GetInternalFrameType(pFrame);
+ if (ift != STUBFRAME_NONE)
+ {
+ uCount++;
+ }
+ pFrame = pFrame->Next();
+ }
+ return uCount;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Enumerate the internal frames on the specified thread and invoke the provided callback on each of them.
+//
+// Arguments:
+// vmThread - the thread to be walked
+// fpCallback - callback function to be invoked for each interesting internal frame
+// pUserData - user-defined custom data to be passed to the callback
+//
+
+void DacDbiInterfaceImpl::EnumerateInternalFrames(VMPTR_Thread vmThread,
+ FP_INTERNAL_FRAME_ENUMERATION_CALLBACK fpCallback,
+ void * pUserData)
+{
+ DD_ENTER_MAY_THROW;
+
+ DebuggerIPCE_STRData frameData;
+
+ Thread * pThread = vmThread.GetDacPtr();
+ Frame * pFrame = pThread->GetFrame();
+ AppDomain * pAppDomain = pThread->GetDomain(INDEBUG(TRUE));
+
+ // This used to be only true for Enter-Managed chains.
+ // Since we don't have chains anymore, this can always be false.
+ frameData.quicklyUnwound = false;
+ frameData.eType = DebuggerIPCE_STRData::cStubFrame;
+
+ while (pFrame != FRAME_TOP)
+ {
+ // check if the internal frame is interesting
+ frameData.stubFrame.frameType = GetInternalFrameType(pFrame);
+ if (frameData.stubFrame.frameType != STUBFRAME_NONE)
+ {
+ frameData.fp = FramePointer::MakeFramePointer(PTR_HOST_TO_TADDR(pFrame));
+
+ frameData.vmCurrentAppDomainToken.SetHostPtr(pAppDomain);
+
+ MethodDesc * pMD = pFrame->GetFunction();
+#if defined(FEATURE_COMINTEROP)
+ if (frameData.stubFrame.frameType == STUBFRAME_U2M)
+ {
+ _ASSERTE(pMD == NULL);
+
+ // U2M transition frame generally don't store the target MD because we know what the target
+ // is by looking at the callee stack frame. However, for reverse COM interop, we can try
+ // to get the MD for the interface.
+ //
+ // Note that some reverse COM interop cases don't have an intermediate interface MD, so
+ // pMD may still be NULL.
+ //
+ // Even if there is an MD on the ComMethodFrame, it could be in a different appdomain than
+ // the ComMethodFrame itself. The only known scenario is a cross-appdomain reverse COM
+ // interop call. We need to check for this case. The end result is that GetFunction() and
+ // GetFunctionToken() on ICDInternalFrame will return NULL.
+
+ // Minidumps without full memory don't guarantee to capture the CCW since we can do without
+ // it. In this case, pMD will remain NULL.
+ EX_TRY_ALLOW_DATATARGET_MISSING_MEMORY
+ {
+ if (pFrame->GetVTablePtr() == ComMethodFrame::GetMethodFrameVPtr())
+ {
+ ComMethodFrame * pCOMFrame = dac_cast<PTR_ComMethodFrame>(pFrame);
+ PTR_VOID pUnkStackSlot = pCOMFrame->GetPointerToArguments();
+ PTR_IUnknown pUnk = dac_cast<PTR_IUnknown>(*dac_cast<PTR_TADDR>(pUnkStackSlot));
+ ComCallWrapper * pCCW = ComCallWrapper::GetWrapperFromIP(pUnk);
+
+ if (!pCCW->NeedToSwitchDomains(pAppDomain->GetId()))
+ {
+ ComCallMethodDesc * pCMD = NULL;
+ pCMD = dac_cast<PTR_ComCallMethodDesc>(pCOMFrame->ComMethodFrame::GetDatum());
+ pMD = pCMD->GetInterfaceMethodDesc();
+ }
+ }
+ }
+ EX_END_CATCH_ALLOW_DATATARGET_MISSING_MEMORY
+ }
+#endif // FEATURE_COMINTEROP
+
+ Module * pModule = (pMD ? pMD->GetModule() : NULL);
+ DomainFile * pDomainFile = (pModule ? pModule->GetDomainFile(pAppDomain) : NULL);
+
+ if (frameData.stubFrame.frameType == STUBFRAME_FUNC_EVAL)
+ {
+ FuncEvalFrame * pFEF = dac_cast<PTR_FuncEvalFrame>(pFrame);
+ DebuggerEval * pDE = pFEF->GetDebuggerEval();
+
+ frameData.stubFrame.funcMetadataToken = pDE->m_methodToken;
+ frameData.stubFrame.vmDomainFile.SetHostPtr(
+ pDE->m_debuggerModule ? pDE->m_debuggerModule->GetDomainFile() : NULL);
+ frameData.stubFrame.vmMethodDesc = VMPTR_MethodDesc::NullPtr();
+ }
+ else
+ {
+ frameData.stubFrame.funcMetadataToken = (pMD == NULL ? NULL : pMD->GetMemberDef());
+ frameData.stubFrame.vmDomainFile.SetHostPtr(pDomainFile);
+ frameData.stubFrame.vmMethodDesc.SetHostPtr(pMD);
+ }
+
+ // invoke the callback
+ fpCallback(&frameData, pUserData);
+ }
+
+ // update the current appdomain if necessary
+ AppDomain * pRetDomain = pFrame->GetReturnDomain();
+ if (pRetDomain != NULL)
+ {
+ pAppDomain = pRetDomain;
+ }
+
+ // move on to the next internal frame
+ pFrame = pFrame->Next();
+ }
+}
+
+// Given the FramePointer of the parent frame and the FramePointer of the current frame,
+// check if the current frame is the parent frame.
+BOOL DacDbiInterfaceImpl::IsMatchingParentFrame(FramePointer fpToCheck, FramePointer fpParent)
+{
+ DD_ENTER_MAY_THROW;
+
+#ifdef WIN64EXCEPTIONS
+ StackFrame sfToCheck = StackFrame((UINT_PTR)fpToCheck.GetSPValue());
+
+ StackFrame sfParent = StackFrame((UINT_PTR)fpParent.GetSPValue());
+
+ // Ask the ExceptionTracker to figure out the answer.
+ // Don't try to compare the StackFrames/FramePointers ourselves.
+ return ExceptionTracker::IsUnwoundToTargetParentFrame(sfToCheck, sfParent);
+
+#else // !WIN64EXCEPTIONS
+ return FALSE;
+
+#endif // WIN64EXCEPTIONS
+}
+
+// Return the stack parameter size of the given method.
+ULONG32 DacDbiInterfaceImpl::GetStackParameterSize(CORDB_ADDRESS controlPC)
+{
+ DD_ENTER_MAY_THROW;
+
+ PCODE currentPC = PCODE(controlPC);
+
+ EECodeInfo codeInfo(currentPC);
+ return GetStackParameterSize(&codeInfo);
+}
+
+// Return the FramePointer of the current frame at which the stackwalker is stopped.
+FramePointer DacDbiInterfaceImpl::GetFramePointer(StackWalkHandle pSFIHandle)
+{
+ DD_ENTER_MAY_THROW;
+
+ StackFrameIterator * pIter = GetIteratorFromHandle(pSFIHandle);
+ return GetFramePointerWorker(pIter);
+}
+
+// Internal helper for GetFramePointer.
+FramePointer DacDbiInterfaceImpl::GetFramePointerWorker(StackFrameIterator * pIter)
+{
+ CrawlFrame * pCF = &(pIter->m_crawl);
+ REGDISPLAY * pRD = pCF->GetRegisterSet();
+
+ FramePointer fp;
+ switch (pIter->GetFrameState())
+ {
+ // For managed methods, we have the full CONTEXT. Additionally, we also have the caller CONTEXT
+ // on WIN64.
+ case StackFrameIterator::SFITER_FRAMELESS_METHOD:
+ fp = FramePointer::MakeFramePointer(GetRegdisplayStackMark(pRD));
+ break;
+
+ // In these cases, we only have the full CONTEXT, not the caller CONTEXT.
+ case StackFrameIterator::SFITER_NATIVE_MARKER_FRAME:
+ //
+ // fall through
+ //
+ case StackFrameIterator::SFITER_INITIAL_NATIVE_CONTEXT:
+ fp = FramePointer::MakeFramePointer(GetRegdisplayStackMark(pRD));
+ break;
+
+ // In these cases, we use the address of the explicit frame as the frame marker.
+ case StackFrameIterator::SFITER_FRAME_FUNCTION:
+ //
+ // fall through
+ //
+ case StackFrameIterator::SFITER_SKIPPED_FRAME_FUNCTION:
+ fp = FramePointer::MakeFramePointer(PTR_HOST_TO_TADDR(pCF->GetFrame()));
+ break;
+
+ // No-frame transition represents an ExInfo for a native exception on x86.
+ // For all intents and purposes this should be treated just like another explicit frame.
+ case StackFrameIterator::SFITER_NO_FRAME_TRANSITION:
+ fp = FramePointer::MakeFramePointer(pCF->GetNoFrameTransitionMarker());
+ break;
+
+ case StackFrameIterator::SFITER_UNINITIALIZED:
+ //
+ // fall through
+ //
+ default:
+ UNREACHABLE();
+ }
+
+ return fp;
+}
+
+// Return TRUE if the specified CONTEXT is the CONTEXT of the leaf frame.
+// @dbgtodo filter CONTEXT - Currently we check for the filter CONTEXT first.
+BOOL DacDbiInterfaceImpl::IsLeafFrame(VMPTR_Thread vmThread,
+ const DT_CONTEXT * pContext)
+{
+ DD_ENTER_MAY_THROW;
+
+ DT_CONTEXT ctxLeaf;
+ GetContext(vmThread, &ctxLeaf);
+
+ // Call a platform-specific helper to compare the two contexts.
+ return CompareControlRegisters(pContext, &ctxLeaf);
+}
+
+// This is a simple helper function to convert a CONTEXT to a DebuggerREGDISPLAY. We need to do this
+// inside DDI because the RS has no notion of REGDISPLAY.
+void DacDbiInterfaceImpl::ConvertContextToDebuggerRegDisplay(const DT_CONTEXT * pInContext,
+ DebuggerREGDISPLAY * pOutDRD,
+ BOOL fActive)
+{
+ DD_ENTER_MAY_THROW;
+
+ // This is a bit cumbersome. First we need to convert the CONTEXT into a REGDISPLAY. Then we need
+ // to convert the REGDISPLAY to a DebuggerREGDISPLAY.
+ REGDISPLAY rd;
+ FillRegDisplay(&rd, reinterpret_cast<T_CONTEXT *>(const_cast<DT_CONTEXT *>(pInContext)));
+ SetDebuggerREGDISPLAYFromREGDISPLAY(pOutDRD, &rd);
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Fill in the structure with information about the current frame at which the stackwalker is stopped.
+//
+// Arguments:
+// pIter - the stackwalker
+// pFrameData - the structure to be filled out
+//
+
+void DacDbiInterfaceImpl::InitFrameData(StackFrameIterator * pIter,
+ FrameType ft,
+ DebuggerIPCE_STRData * pFrameData)
+{
+ CrawlFrame * pCF = &(pIter->m_crawl);
+
+ //
+ // do common initialization of DebuggerIPCE_STRData for both managed stack frames and explicit frames
+ //
+
+ pFrameData->fp = GetFramePointerWorker(pIter);
+
+ // This used to be only true for Enter-Managed chains.
+ // Since we don't have chains anymore, this can always be false.
+ pFrameData->quicklyUnwound = false;
+
+ AppDomain * pAppDomain = pCF->GetAppDomain();
+ pFrameData->vmCurrentAppDomainToken.SetHostPtr(pAppDomain);
+
+ if (ft == kNativeRuntimeUnwindableStackFrame)
+ {
+ pFrameData->eType = DebuggerIPCE_STRData::cRuntimeNativeFrame;
+
+ GetStackWalkCurrentContext(pIter, &(pFrameData->ctx));
+ }
+ else if (ft == kManagedStackFrame)
+ {
+ MethodDesc * pMD = pCF->GetFunction();
+ Module * pModule = (pMD ? pMD->GetModule() : NULL);
+ // Although MiniDumpNormal tries to dump all AppDomains, it's possible
+ // target corruption will keep one from being present. This should mean
+ // we'll just fail later, but struggle on for now.
+ DomainFile *pDomainFile = NULL;
+ EX_TRY_ALLOW_DATATARGET_MISSING_MEMORY
+ {
+ pDomainFile = (pModule ? pModule->GetDomainFile(pAppDomain) : NULL);
+ _ASSERTE(pDomainFile != NULL);
+ }
+ EX_END_CATCH_ALLOW_DATATARGET_MISSING_MEMORY
+
+ //
+ // This is a managed stack frame.
+ //
+
+ _ASSERTE(pMD != NULL);
+ _ASSERTE(pModule != NULL);
+
+ //
+ // initialize the rest of the DebuggerIPCE_STRData
+ //
+
+ pFrameData->eType = DebuggerIPCE_STRData::cMethodFrame;
+
+ SetDebuggerREGDISPLAYFromREGDISPLAY(&(pFrameData->rd), pCF->GetRegisterSet());
+
+ GetStackWalkCurrentContext(pIter, &(pFrameData->ctx));
+
+ //
+ // initialize the fields in DebuggerIPCE_STRData::v
+ //
+
+ // These fields will be filled in later. We don't have the sequence point mapping information here.
+ pFrameData->v.ILOffset = (SIZE_T)(-1);
+ pFrameData->v.mapping = MAPPING_NO_INFO;
+
+ // Check if this is a vararg method by getting the managed calling convention from the signature.
+ // Strictly speaking, we can do this in CordbJITILFrame::Init(), but it's just easier and more
+ // efficiently to do it here. CordbJITILFrame::Init() will initialize the other vararg-related
+ // fields. We don't have the native var info here to fully initialize everything.
+ pFrameData->v.fVarArgs = (pMD->IsVarArg() == TRUE);
+
+ pFrameData->v.fNoMetadata = (pMD->IsNoMetadata() == TRUE);
+
+ pFrameData->v.taAmbientESP = pCF->GetAmbientSPFromCrawlFrame();
+ if (pMD->IsSharedByGenericInstantiations())
+ {
+ // This method has a generic type token which is required to figure out the exact instantiation
+ // of the method. CrawlFrame::GetExactGenericArgsToken() can't always successfully retrieve
+ // the token because the JIT doesn't generate the required information all the time. As such,
+ // we need to save the variable index of the generic type token in order to do the look up later.
+ ALLOW_DATATARGET_MISSING_MEMORY(
+ pFrameData->v.exactGenericArgsToken = (GENERICS_TYPE_TOKEN)(dac_cast<TADDR>(pCF->GetExactGenericArgsToken()));
+ );
+
+ if (pMD->AcquiresInstMethodTableFromThis())
+ {
+ // The generic type token is the "this" object.
+ pFrameData->v.dwExactGenericArgsTokenIndex = 0;
+ }
+ else
+ {
+ // The generic type token is one of the secret arguments.
+ pFrameData->v.dwExactGenericArgsTokenIndex = (DWORD)ICorDebugInfo::TYPECTXT_ILNUM;
+ }
+ }
+ else
+ {
+ pFrameData->v.exactGenericArgsToken = NULL;
+ pFrameData->v.dwExactGenericArgsTokenIndex = (DWORD)ICorDebugInfo::MAX_ILNUM;
+ }
+
+ //
+ // initialize the DebuggerIPCE_FuncData and DebuggerIPCE_JITFuncData
+ //
+
+ DebuggerIPCE_FuncData * pFuncData = &(pFrameData->v.funcData);
+ DebuggerIPCE_JITFuncData * pJITFuncData = &(pFrameData->v.jitFuncData);
+
+ //
+ // initialize the "easy" fields of DebuggerIPCE_FuncData
+ //
+
+ pFuncData->funcMetadataToken = pMD->GetMemberDef();
+ pFuncData->vmDomainFile.SetHostPtr(pDomainFile);
+
+ // PERF: this is expensive to get so I stopped fetching it eagerly
+ // It is only needed if we haven't already got a cached copy
+ pFuncData->classMetadataToken = mdTokenNil;
+
+ //
+ // initialize the remaining fields of DebuggerIPCE_FuncData to the default values
+ //
+
+ pFuncData->ilStartAddress = NULL;
+ pFuncData->ilSize = 0;
+ pFuncData->currentEnCVersion = CorDB_DEFAULT_ENC_FUNCTION_VERSION;
+ pFuncData->localVarSigToken = mdSignatureNil;
+
+ //
+ // inititalize the fields of DebuggerIPCE_JITFuncData
+ //
+
+ // For MiniDumpNormal, we do not guarantee method region info for all JIT tokens
+ // is present in the dump.
+ ALLOW_DATATARGET_MISSING_MEMORY(
+ pJITFuncData->nativeStartAddressPtr = PCODEToPINSTR(pCF->GetCodeInfo()->GetStartAddress());
+ );
+
+ // PERF: this is expensive to get so I stopped fetching it eagerly
+ // It is only needed if we haven't already got a cached copy
+ pJITFuncData->nativeHotSize = 0;
+ pJITFuncData->nativeStartAddressColdPtr = 0;
+ pJITFuncData->nativeColdSize = 0;
+
+ pJITFuncData->nativeOffset = pCF->GetRelOffset();
+
+ // Here we detect (and set the appropriate flag) if the nativeOffset in the current frame points to the return address of IL_Throw()
+ // (or other exception related JIT helpers like IL_Throw, IL_Rethrow, JIT_RngChkFail, IL_VerificationError, JIT_Overflow etc).
+ // Since return addres point to the next(!) instruction after [call IL_Throw] this sometimes can lead to incorrect exception stacktraces
+ // where a next source line is spotted as an exception origin. This happends when the next instruction after [call IL_Throw] belongs to
+ // a sequence point and a source line different from a sequence point and a source line of [call IL_Throw].
+ // Later on this flag is used in order to adjust nativeOffset and make ICorDebugILFrame::GetIP return IL offset withing
+ // the same sequence point as an actuall IL throw instruction.
+
+ // Here is how we detect it:
+ // We can assume that nativeOffset points to an the instruction after [call IL_Throw] when these conditioins are met:
+ // 1. pCF->IsInterrupted() - Exception has been thrown by this managed frame (frame attr FRAME_ATTR_EXCEPTION)
+ // 2. !pCF->HasFaulted() - It wasn't a "hardware" exception (Access violation, dev by 0, etc.)
+ // 3. !pCF->IsIPadjusted() - It hasn't been previously adjusted to point to [call IL_Throw]
+ // 4. pJITFuncData->nativeOffset != 0 - nativeOffset contains something that looks like a real return address.
+ pJITFuncData->jsutAfterILThrow = pCF->IsInterrupted()
+ && !pCF->HasFaulted()
+ && !pCF->IsIPadjusted()
+ && pJITFuncData->nativeOffset != 0;
+
+ pJITFuncData->nativeCodeJITInfoToken.Set(NULL);
+ pJITFuncData->vmNativeCodeMethodDescToken.SetHostPtr(pMD);
+
+ InitParentFrameInfo(pCF, pJITFuncData);
+
+ ALLOW_DATATARGET_MISSING_MEMORY(
+ pJITFuncData->isInstantiatedGeneric = pMD->HasClassOrMethodInstantiation();
+ );
+ pJITFuncData->enCVersion = CorDB_DEFAULT_ENC_FUNCTION_VERSION;
+
+ // PERF: this is expensive to get so I stopped fetching it eagerly
+ // It is only needed if we haven't already got a cached copy
+ pFuncData->localVarSigToken = 0;
+ pFuncData->ilStartAddress = 0;
+ pFuncData->ilSize = 0;
+
+
+ // See the comment for LookupEnCVersions().
+ // PERF: this is expensive to get so I stopped fetching it eagerly
+ pFuncData->currentEnCVersion = 0;
+ pJITFuncData->enCVersion = 0;
+ }
+ else
+ {
+ _ASSERTE(!"DDII::InitFrameData() - We should never stop at internal frames.");
+ ThrowHR(CORDBG_E_TARGET_INCONSISTENT);
+ }
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Initialize the address and the size of the jitted code, including both hot and cold regions.
+//
+// Arguments:
+// methodToken - METHODTOKEN of the method in question; this should actually be the CodeHeader address
+// pJITFuncData - structure to be filled out
+//
+
+void DacDbiInterfaceImpl::InitNativeCodeAddrAndSize(TADDR taStartAddr,
+ DebuggerIPCE_JITFuncData * pJITFuncData)
+{
+ PTR_CORDB_ADDRESS_TYPE pAddr = dac_cast<PTR_CORDB_ADDRESS_TYPE>(taStartAddr);
+ CodeRegionInfo crInfo = CodeRegionInfo::GetCodeRegionInfo(NULL, NULL, pAddr);
+
+ pJITFuncData->nativeStartAddressPtr = PCODEToPINSTR(crInfo.getAddrOfHotCode());
+ pJITFuncData->nativeHotSize = crInfo.getSizeOfHotCode();
+
+ pJITFuncData->nativeStartAddressColdPtr = PCODEToPINSTR(crInfo.getAddrOfColdCode());
+ pJITFuncData->nativeColdSize = crInfo.getSizeOfColdCode();
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Initialize the funclet-related fields of DebuggerIPCE_JITFuncData. This is an nop on non-WIN64 platforms.
+//
+// Arguments:
+// pCF - the CrawlFrame for the current frame
+// pJITFuncData - the structure to be filled out
+//
+
+void DacDbiInterfaceImpl::InitParentFrameInfo(CrawlFrame * pCF,
+ DebuggerIPCE_JITFuncData * pJITFuncData)
+{
+#ifdef WIN64EXCEPTIONS
+ pJITFuncData->fIsFilterFrame = pCF->IsFilterFunclet();
+
+ if (pCF->IsFunclet())
+ {
+ DWORD dwParentOffset;
+ StackFrame sfParent = ExceptionTracker::FindParentStackFrameEx(pCF, &dwParentOffset, NULL);
+
+ //
+ // For funclets, fpParentOrSelf is the FramePointer of the parent.
+ // Don't mess around with this FramePointer. The only thing we can do with it is to pass it back
+ // to the ExceptionTracker when we are checking if a particular frame is the parent frame.
+ //
+
+ pJITFuncData->fpParentOrSelf = FramePointer::MakeFramePointer(sfParent.SP);
+ pJITFuncData->parentNativeOffset = dwParentOffset;
+ }
+ else
+ {
+ StackFrame sfSelf = ExceptionTracker::GetStackFrameForParentCheck(pCF);
+
+ //
+ // For non-funclets, fpParentOrSelf is the FramePointer of the current frame itself.
+ // Don't mess around with this FramePointer. The only thing we can do with it is to pass it back
+ // to the ExceptionTracker when we are checking if a particular frame is the parent frame.
+ //
+
+ pJITFuncData->fpParentOrSelf = FramePointer::MakeFramePointer(sfSelf.SP);
+ pJITFuncData->parentNativeOffset = 0;
+ }
+#endif // WIN64EXCEPTIONS
+}
+
+// Return the stack parameter size of the given method.
+// Refer to the full comment for the overloaded version.
+ULONG32 DacDbiInterfaceImpl::GetStackParameterSize(EECodeInfo * pCodeInfo)
+{
+ return pCodeInfo->GetCodeManager()->GetStackParameterSize(pCodeInfo);
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Adjust the stack pointer in the CONTEXT for the stack parameters.
+// This is a nop on non-x86 platforms.
+//
+// Arguments:
+// pRD - the REGDISPLAY to be adjusted
+// cbStackParameterSize - the number of bytes for the stack parameters
+// fIsActiveFrame - whether the CONTEXT is for an active frame
+// StackAdjustmentDirection - whether we are changing a CONTEXT from the managed convention
+// to the unmanaged convention
+//
+// Notes:
+// Consider this code:
+//
+// push 1
+// push 2
+// call Foo
+// -> inc eax
+//
+// Here we are assuming that the return instruction in Foo() pops the stack arguments.
+//
+// Suppose the IP in the CONTEXT is at the arrow. The question is, where should the stack pointer be?
+//
+// 0x0 ret addr for Foo
+// 0x4 2
+// 0x8 1
+// 0xc .....
+//
+// If the CONTEXT is the active frame, i.e. the IP is the active instruction,
+// not the instruction at the return address, then the SP should be at 0xc.
+// However, if the CONTEXT is not active, then the SP can be at either 0x4 or 0xc, depending on
+// the convention used by the stackwalker. The managed stackwalker reports 0xc, but dbghelp reports
+// 0x4. To bridge the gap we have to shim it in the DDI.
+//
+// Currently, we have no way to reliably shim the CONTEXT in all cases. Consider this stack,
+// where U* are native stack frames and M* are managed stack frames:
+//
+// [leaf]
+// U2
+// U1
+// ------- (M2U transition)
+// M2
+// M1
+// M0
+// ------- (U2M transition)
+// U0
+// [root]
+//
+// There are only two transition cases where we can reliably adjust for the callee stack parameter size:
+// 1) when the debugger calls SetContext() with the CONTEXT of the first managed stack frame in a
+// managed stack chain (i.e. SetContext() with M2's CONTEXT)
+// - the M2U transition is protected by an explicit frame (aka Frame-chain frame)
+// 2) when the debugger calls GetContext() on the first native stack frame in a native stack chain
+// (i.e. GetContext() at U0)
+// - we unwind from M0 to U0, so we know the stack parameter size of M0
+//
+// If we want to do the adjustment in all cases, we need to ask the JIT to store the callee stack
+// parameter size in either the unwind info.
+//
+
+void DacDbiInterfaceImpl::AdjustRegDisplayForStackParameter(REGDISPLAY * pRD,
+ DWORD cbStackParameterSize,
+ BOOL fIsActiveFrame,
+ StackAdjustmentDirection direction)
+{
+#if defined(_TARGET_X86_)
+ // If the CONTEXT is active then no adjustment is needed.
+ if (!fIsActiveFrame)
+ {
+ UINT_PTR sp = GetRegdisplaySP(pRD);
+ if (direction == kFromManagedToUnmanaged)
+ {
+ // The CONTEXT comes from the managed world.
+ sp -= cbStackParameterSize;
+ }
+ else
+ {
+ _ASSERTE(!"Currently, we should not hit this case.\n");
+
+ // The CONTEXT comes from the unmanaged world.
+ sp += cbStackParameterSize;
+ }
+ SetRegdisplaySP(pRD, reinterpret_cast<LPVOID>(sp));
+ }
+#endif // _TARGET_X86_
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Given an explicit frame, return its frame type in terms of CorDebugInternalFrameType.
+//
+// Arguments:
+// pFrame - the explicit frame in question
+//
+// Return Value:
+// Return the CorDebugInternalFrameType of the explicit frame
+//
+// Notes:
+// I wish this function were simpler, but it's not. The logic in this function is adopted
+// from the logic in the old in-proc debugger stackwalker.
+//
+
+CorDebugInternalFrameType DacDbiInterfaceImpl::GetInternalFrameType(Frame * pFrame)
+{
+ CorDebugInternalFrameType resultType = STUBFRAME_NONE;
+
+ Frame::ETransitionType tt = pFrame->GetTransitionType();
+ Frame::Interception it = pFrame->GetInterception();
+ int ft = pFrame->GetFrameType();
+
+ switch (tt)
+ {
+ case Frame::TT_NONE:
+ if (it == Frame::INTERCEPTION_CLASS_INIT)
+ {
+ resultType = STUBFRAME_CLASS_INIT;
+ }
+ else if (it == Frame::INTERCEPTION_EXCEPTION)
+ {
+ resultType = STUBFRAME_EXCEPTION;
+ }
+ else if (it == Frame::INTERCEPTION_SECURITY)
+ {
+ resultType = STUBFRAME_SECURITY;
+ }
+ else if (it == Frame::INTERCEPTION_PRESTUB)
+ {
+ resultType = STUBFRAME_JIT_COMPILATION;
+ }
+ else
+ {
+ if (ft == Frame::TYPE_FUNC_EVAL)
+ {
+ resultType = STUBFRAME_FUNC_EVAL;
+ }
+ else if (ft == Frame::TYPE_EXIT)
+ {
+ if ((pFrame->GetVTablePtr() != InlinedCallFrame::GetMethodFrameVPtr()) ||
+ InlinedCallFrame::FrameHasActiveCall(pFrame))
+ {
+ resultType = STUBFRAME_M2U;
+ }
+ }
+ }
+ break;
+
+ case Frame::TT_M2U:
+ // Refer to the comment in DebuggerWalkStackProc() for StubDispatchFrame.
+ if (pFrame->GetVTablePtr() != StubDispatchFrame::GetMethodFrameVPtr())
+ {
+ if (it == Frame::INTERCEPTION_SECURITY)
+ {
+ resultType = STUBFRAME_SECURITY;
+ }
+ else
+ {
+ resultType = STUBFRAME_M2U;
+ }
+ }
+ break;
+
+ case Frame::TT_U2M:
+ resultType = STUBFRAME_U2M;
+ break;
+
+ case Frame::TT_AppDomain:
+ resultType = STUBFRAME_APPDOMAIN_TRANSITION;
+ break;
+
+ case Frame::TT_InternalCall:
+ if (it == Frame::INTERCEPTION_EXCEPTION)
+ {
+ resultType = STUBFRAME_EXCEPTION;
+ }
+ else
+ {
+ resultType = STUBFRAME_INTERNALCALL;
+ }
+ break;
+
+ default:
+ UNREACHABLE();
+ break;
+ }
+
+ return resultType;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// This is just a simpler helper function to convert a REGDISPLAY to a CONTEXT.
+//
+// Arguments:
+// pRegDisp - the REGDISPLAY to be converted
+// pContext - the buffer for storing the converted CONTEXT
+//
+
+void DacDbiInterfaceImpl::UpdateContextFromRegDisp(REGDISPLAY * pRegDisp,
+ T_CONTEXT * pContext)
+{
+#if defined(_TARGET_X86_)
+ // Do a partial copy first.
+ pContext->ContextFlags = (CONTEXT_INTEGER | CONTEXT_CONTROL);
+
+ pContext->Edi = *pRegDisp->pEdi;
+ pContext->Esi = *pRegDisp->pEsi;
+ pContext->Ebx = *pRegDisp->pEbx;
+ pContext->Ebp = *pRegDisp->pEbp;
+ pContext->Eax = *pRegDisp->pEax;
+ pContext->Ecx = *pRegDisp->pEcx;
+ pContext->Edx = *pRegDisp->pEdx;
+ pContext->Esp = pRegDisp->Esp;
+ pContext->Eip = pRegDisp->ControlPC;
+
+ // If we still have the pointer to the leaf CONTEXT, and the leaf CONTEXT is the same as the CONTEXT for
+ // the current frame (i.e. the stackwalker is at the leaf frame), then we do a full copy.
+ if ((pRegDisp->pContext != NULL) &&
+ (CompareControlRegisters(const_cast<const DT_CONTEXT *>(reinterpret_cast<DT_CONTEXT *>(pContext)),
+ const_cast<const DT_CONTEXT *>(reinterpret_cast<DT_CONTEXT *>(pRegDisp->pContext)))))
+ {
+ *pContext = *pRegDisp->pContext;
+ }
+#else
+ *pContext = *pRegDisp->pCurrentContext;
+#endif
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Given the REGDISPLAY of a stack frame for one of the redirect functions, retrieve the original CONTEXT
+// before the thread redirection.
+//
+// Arguments:
+// pRD - the REGDISPLAY of the stack frame in question
+//
+// Return Value:
+// Return the original CONTEXT before the thread got redirected.
+//
+// Assumptions:
+// The caller has checked that the REGDISPLAY is indeed for one of the redirect functions.
+//
+
+PTR_CONTEXT DacDbiInterfaceImpl::RetrieveHijackedContext(REGDISPLAY * pRD)
+{
+ CORDB_ADDRESS ContextPointerAddr = NULL;
+
+ TADDR controlPC = PCODEToPINSTR(GetControlPC(pRD));
+
+ // Check which thread redirection mechanism is used.
+ if (g_pDebugger->m_rgHijackFunction[Debugger::kUnhandledException].IsInRange(controlPC))
+ {
+ // The thread is redirected because of an unhandled exception.
+
+ // The CONTEXT pointer is the last thing pushed onto the stack.
+ // So just read the stack slot at ESP. That will be the TADDR to the CONTEXT.
+ ContextPointerAddr = PTR_TO_CORDB_ADDRESS(GetRegdisplaySP(pRD));
+
+ // Read the CONTEXT from OOP.
+ return *dac_cast<PTR_PTR_CONTEXT>((TADDR)ContextPointerAddr);
+ }
+ else
+ {
+ // The thread is redirected by the EE via code:Thread::RedirectThreadAtHandledJITCase.
+
+ // Convert the REGDISPLAY to a CONTEXT;
+ T_CONTEXT * pContext = NULL;
+
+#if defined(_TARGET_X86_)
+ T_CONTEXT ctx;
+ pContext = &ctx;
+ UpdateContextFromRegDisp(pRD, pContext);
+#else
+ pContext = pRD->pCurrentContext;
+#endif
+
+ // Retrieve the original CONTEXT.
+ return GetCONTEXTFromRedirectedStubStackFrame(pContext);
+ }
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Unwind special native stack frame which the runtime knows how to unwind.
+//
+// Arguments:
+// pIter - the StackFrameIterator we are currently using to walk the stack
+//
+// Return Value:
+// Return TRUE if there are more frames to walk, i.e. if we are NOT at the end of the stack.
+//
+// Assumptions:
+// pIter is currently stopped at a special stub which the runtime knows how to unwind.
+//
+// Notes:
+// * Refer to code:DacDbiInterfaceImpl::IsRuntimeUnwindableStub to see how we determine whether a control
+// PC is in a runtime-unwindable stub
+//
+
+BOOL DacDbiInterfaceImpl::UnwindRuntimeStackFrame(StackFrameIterator * pIter)
+{
+ _ASSERTE(IsRuntimeUnwindableStub(GetControlPC(pIter->m_crawl.GetRegisterSet())));
+
+ T_CONTEXT * pContext = NULL;
+ REGDISPLAY * pRD = pIter->m_crawl.GetRegisterSet();
+
+ //
+ // Retrieve the CONTEXT to unwind to and unwind the REGDISPLAY.
+ //
+ pContext = RetrieveHijackedContext(pRD);
+
+ FillRegDisplay(pRD, pContext);
+
+ // Update the StackFrameIterator.
+ BOOL fSuccess = pIter->ResetRegDisp(pRD, true);
+ if (!fSuccess)
+ {
+ // ResetRegDisp() may fail for the same reason Init() may fail, i.e.
+ // because the stackwalker tries to unwind one frame ahead of time,
+ // or because the stackwalker needs to filter out some frames based on the stackwalk flags.
+ ThrowHR(E_FAIL);
+ }
+
+ // Currently we only unwind the hijack function, which will never be the last stack frame.
+ // So return TRUE to indicate that this is not the end of stack.
+ return TRUE;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// To aid in doing the stack walk, the shim needs to know if either TS_SyncSuspended or
+// TS_Hijacked is set on a given thread. This DAC helper provides that access.
+//
+// Arguments:
+// vmThread - Thread on which to check the TS_SyncSuspended & TS_Hijacked states
+//
+// Return Value:
+// Return true iff TS_SyncSuspended or TS_Hijacked is set on the specified thread.
+//
+
+bool DacDbiInterfaceImpl::IsThreadSuspendedOrHijacked(VMPTR_Thread vmThread)
+{
+ DD_ENTER_MAY_THROW;
+
+ Thread * pThread = vmThread.GetDacPtr();
+ Thread::ThreadState ts = pThread->GetSnapshotState();
+ if ((ts & Thread::TS_SyncSuspended) != 0)
+ {
+ return true;
+ }
+
+#ifdef FEATURE_HIJACK
+ if ((ts & Thread::TS_Hijacked) != 0)
+ {
+ return true;
+ }
+#endif
+
+ return false;
+}
diff --git a/src/debug/daccess/dacfn.cpp b/src/debug/daccess/dacfn.cpp
new file mode 100644
index 0000000000..88d45993b3
--- /dev/null
+++ b/src/debug/daccess/dacfn.cpp
@@ -0,0 +1,1505 @@
+// 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.
+//*****************************************************************************
+// File: dacfn.cpp
+//
+
+//
+// Dac function implementations.
+//
+//*****************************************************************************
+
+#include "stdafx.h"
+
+#include <encee.h>
+#ifdef FEATURE_PREJIT
+#include "compile.h"
+#endif // FEATURE_PREJIT
+#ifdef FEATURE_REMOTING
+#include <remoting.h>
+#include "objectclone.h"
+#endif
+#include <virtualcallstub.h>
+#include "peimagelayout.inl"
+
+DacTableInfo g_dacTableInfo;
+DacGlobals g_dacGlobals;
+
+struct DacHostVtPtrs
+{
+#define VPTR_CLASS(name) PVOID name;
+#define VPTR_MULTI_CLASS(name, keyBase) PVOID name##__##keyBase;
+#include <vptr_list.h>
+#undef VPTR_CLASS
+#undef VPTR_MULTI_CLASS
+};
+
+
+const WCHAR *g_dacVtStrings[] =
+{
+#define VPTR_CLASS(name) W(#name),
+#define VPTR_MULTI_CLASS(name, keyBase) W(#name),
+#include <vptr_list.h>
+#undef VPTR_CLASS
+#undef VPTR_MULTI_CLASS
+};
+
+DacHostVtPtrs g_dacHostVtPtrs;
+
+HRESULT
+DacGetHostVtPtrs(void)
+{
+#define VPTR_CLASS(name) \
+ g_dacHostVtPtrs.name = name::VPtrHostVTable();
+#define VPTR_MULTI_CLASS(name, keyBase) \
+ g_dacHostVtPtrs.name##__##keyBase = name::VPtrHostVTable();
+#include <vptr_list.h>
+#undef VPTR_CLASS
+#undef VPTR_MULTI_CLASS
+
+ return S_OK;
+}
+
+bool
+DacExceptionFilter(Exception* ex, ClrDataAccess* access,
+ HRESULT* status)
+{
+ SUPPORTS_DAC_HOST_ONLY;
+
+ // The DAC support functions throw HRExceptions and
+ // the underlying code can throw the normal set of
+ // CLR exceptions. Handle any exception
+ // other than an unexpected SEH exception.
+ // If we're not debugging, handle SEH exceptions also
+ // so that dac absorbs all exceptions by default.
+ if ((access && access->m_debugMode) &&
+ ex->IsType(SEHException::GetType()))
+ {
+ // Indicate this exception should be rethrown.
+ return FALSE;
+ }
+
+ // Indicate this exception is handled.
+ // XXX Microsoft - The C++-based EH has broken the ability
+ // to get proper SEH results. Make sure that the
+ // error returned is actually an error code as
+ // often it's just zero.
+ *status = ex->GetHR();
+ if (!FAILED(*status))
+ {
+ *status = E_FAIL;
+ }
+ return TRUE;
+}
+
+void __cdecl
+DacWarning(__in char* format, ...)
+{
+ char text[256];
+ va_list args;
+
+ va_start(args, format);
+ _vsnprintf_s(text, sizeof(text), _TRUNCATE, format, args);
+ text[sizeof(text) - 1] = 0;
+ va_end(args);
+ OutputDebugStringA(text);
+}
+
+void
+DacNotImpl(void)
+{
+ EX_THROW(HRException, (E_NOTIMPL));
+}
+
+void
+DacError(HRESULT err)
+{
+ EX_THROW(HRException, (err));
+}
+
+// Ideally DacNoImpl and DacError would be marked no-return, but that will require changing a bunch of existing
+// code to avoid "unreachable code" warnings.
+void DECLSPEC_NORETURN
+DacError_NoRet(HRESULT err)
+{
+ EX_THROW(HRException, (err));
+}
+
+TADDR
+DacGlobalBase(void)
+{
+ if (!g_dacImpl)
+ {
+ DacError(E_UNEXPECTED);
+ UNREACHABLE();
+ }
+
+ return g_dacImpl->m_globalBase;
+}
+
+HRESULT
+DacReadAll(TADDR addr, PVOID buffer, ULONG32 size, bool throwEx)
+{
+ if (!g_dacImpl)
+ {
+ DacError(E_UNEXPECTED);
+ UNREACHABLE();
+ }
+
+ ClrSafeInt<TADDR> end = ClrSafeInt<TADDR>(addr) + ClrSafeInt<TADDR>(size);
+ if( end.IsOverflow() )
+ {
+ // Overflow - corrupt data
+ DacError(CORDBG_E_TARGET_INCONSISTENT);
+ }
+
+ HRESULT status;
+ ULONG32 returned;
+
+#if defined(DAC_MEASURE_PERF)
+ unsigned __int64 nStart, nEnd;
+ nStart = GetCycleCount();
+#endif // #if defined(DAC_MEASURE_PERF)
+
+ status = g_dacImpl->m_pTarget->
+ ReadVirtual(addr, (PBYTE)buffer, size, &returned);
+
+#if defined(DAC_MEASURE_PERF)
+ nEnd = GetCycleCount();
+ g_nReadVirtualTotalTime += nEnd - nStart;
+#endif // #if defined(DAC_MEASURE_PERF)
+
+ if (status != S_OK)
+ {
+ // Regardless of what status is, it's very important for dump debugging to
+ // always return CORDBG_E_READVIRTUAL_FAILURE.
+ if (throwEx)
+ {
+ DacError(CORDBG_E_READVIRTUAL_FAILURE);
+ }
+ return CORDBG_E_READVIRTUAL_FAILURE;
+ }
+ if (returned != size)
+ {
+ if (throwEx)
+ {
+ DacError(HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY));
+ }
+ return HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY);
+ }
+
+ return S_OK;
+}
+
+HRESULT
+DacWriteAll(TADDR addr, PVOID buffer, ULONG32 size, bool throwEx)
+{
+ if (!g_dacImpl)
+ {
+ DacError(E_UNEXPECTED);
+ UNREACHABLE();
+ }
+
+ HRESULT status;
+
+ status = g_dacImpl->m_pMutableTarget->
+ WriteVirtual(addr, (PBYTE)buffer, size);
+ if (status != S_OK)
+ {
+ if (throwEx)
+ {
+ DacError(status);
+ }
+ return status;
+ }
+
+ return S_OK;
+}
+
+#if defined(WIN64EXCEPTIONS) && defined(FEATURE_PAL)
+HRESULT
+DacVirtualUnwind(DWORD threadId, PCONTEXT context, PT_KNONVOLATILE_CONTEXT_POINTERS contextPointers)
+{
+ if (!g_dacImpl)
+ {
+ DacError(E_UNEXPECTED);
+ UNREACHABLE();
+ }
+
+ // The DAC code doesn't use these context pointers but zero them out to be safe.
+ if (contextPointers != NULL)
+ {
+ memset(contextPointers, 0, sizeof(T_KNONVOLATILE_CONTEXT_POINTERS));
+ }
+
+ ReleaseHolder<ICorDebugDataTarget4> dt;
+ HRESULT hr = g_dacImpl->m_pTarget->QueryInterface(IID_ICorDebugDataTarget4, (void **)&dt);
+ if (SUCCEEDED(hr))
+ {
+ hr = dt->VirtualUnwind(threadId, sizeof(CONTEXT), (BYTE*)context);
+ }
+
+ return hr;
+}
+#endif // defined(WIN64EXCEPTIONS) && defined(FEATURE_PAL)
+
+// DacAllocVirtual - Allocate memory from the target process
+// Note: this is only available to clients supporting the legacy
+// ICLRDataTarget2 interface. It's currently used by SOS for notification tables.
+HRESULT
+DacAllocVirtual(TADDR addr, ULONG32 size,
+ ULONG32 typeFlags, ULONG32 protectFlags,
+ bool throwEx, TADDR* mem)
+{
+ if (!g_dacImpl)
+ {
+ DacError(E_UNEXPECTED);
+ UNREACHABLE();
+ }
+
+ ICLRDataTarget2 * pTarget2 = g_dacImpl->GetLegacyTarget2();
+ if (pTarget2 == NULL)
+ {
+ DacError(E_NOTIMPL);
+ UNREACHABLE();
+ }
+
+ CLRDATA_ADDRESS cdaMem;
+ HRESULT status = pTarget2->AllocVirtual(
+ TO_CDADDR(addr), size, typeFlags, protectFlags, &cdaMem);
+ if (status != S_OK)
+ {
+ if (throwEx)
+ {
+ DacError(status);
+ UNREACHABLE();
+ }
+
+ return status;
+ }
+
+ *mem = CLRDATA_ADDRESS_TO_TADDR(cdaMem);
+ return S_OK;
+}
+
+// DacFreeVirtual - Free memory from the target process
+// Note: this is only available to clients supporting the legacy
+// ICLRDataTarget2 interface. This is not currently used.
+HRESULT
+DacFreeVirtual(TADDR mem, ULONG32 size, ULONG32 typeFlags,
+ bool throwEx)
+{
+ if (!g_dacImpl)
+ {
+ DacError(E_UNEXPECTED);
+ UNREACHABLE();
+ }
+
+ ICLRDataTarget2 * pTarget2 = g_dacImpl->GetLegacyTarget2();
+ if (pTarget2 == NULL)
+ {
+ DacError(E_NOTIMPL);
+ UNREACHABLE();
+ }
+
+ HRESULT status = pTarget2->FreeVirtual(
+ TO_CDADDR(mem), size, typeFlags);
+
+ if (status != S_OK && throwEx)
+ {
+ DacError(status);
+ UNREACHABLE();
+ }
+
+ return status;
+}
+
+PVOID
+DacInstantiateTypeByAddressHelper(TADDR addr, ULONG32 size, bool throwEx, bool fReport)
+{
+#ifdef _PREFIX_
+
+ // Dac accesses are not interesting for PREfix and cause alot of PREfix noise
+ // so we just return the unmodified pointer for our PREFIX builds
+ return (PVOID)addr;
+
+#else // !_PREFIX_
+
+ if (!g_dacImpl)
+ {
+ DacError(E_UNEXPECTED);
+ UNREACHABLE();
+ }
+
+ // Preserve special pointer values.
+ if (!addr || addr == (TADDR)-1)
+ {
+ return (PVOID)addr;
+ }
+
+ // DacInstanceManager::Alloc will assert (with a non-obvious message) on 0-size instances.
+ // Fail sooner and more obviously here.
+ _ASSERTE_MSG( size > 0, "DAC coding error: instance size cannot be 0" );
+
+ // Do not attempt to allocate more than 64megs for one object instance. While we should
+ // never even come close to this size, in cases of heap corruption or bogus data passed
+ // into the dac, we can allocate huge amounts of data if we are unlucky. This santiy
+ // checks the size to ensure we don't allocate gigs of data.
+ if (size > 0x4000000)
+ {
+ if (throwEx)
+ {
+ DacError(E_OUTOFMEMORY);
+ }
+ return NULL;
+ }
+
+ //
+ // Check the cache for an existing DPTR instance.
+ // It's possible that a previous access may have been
+ // smaller than the current access, so we have to
+ // allow an existing instance to be superseded.
+ //
+
+ DAC_INSTANCE* inst = g_dacImpl->m_instances.Find(addr);
+ DAC_INSTANCE* oldInst = NULL;
+ if (inst)
+ {
+ // If the existing instance is large enough we
+ // can reuse it, otherwise we need to promote.
+ // We cannot promote a VPTR as the VPTR data
+ // has been updated with a host vtable and we
+ // don't want to lose that. This shouldn't
+ // happen anyway.
+ if (inst->size >= size)
+ {
+ return inst + 1;
+ }
+ else
+ {
+ // Existing instance is too small and must
+ // be superseded.
+ if (inst->usage == DAC_VPTR)
+ {
+ // The same address has already been marshalled as a VPTR, now we're trying to marshal as a
+ // DPTR. This is not allowed.
+ _ASSERTE_MSG(false, "DAC coding error: DPTR/VPTR usage conflict");
+ DacError(E_INVALIDARG);
+ UNREACHABLE();
+ }
+
+ // Promote the larger instance into the hash
+ // in place of the smaller, but keep the
+ // smaller instance around in case code still
+ // has a pointer to it. But ensure that we can
+ // create the larger instance and add it to the
+ // hash table before removing the old one.
+ oldInst = inst;
+ }
+ }
+
+ inst = g_dacImpl->m_instances.Alloc(addr, size, DAC_DPTR);
+ if (!inst)
+ {
+ DacError(E_OUTOFMEMORY);
+ UNREACHABLE();
+ }
+
+ if (fReport == false)
+ {
+ // mark the bit if necessary
+ inst->noReport = 1;
+ }
+ else
+ {
+ // clear the bit
+ inst->noReport = 0;
+ }
+ HRESULT status = DacReadAll(addr, inst + 1, size, false);
+ if (status != S_OK)
+ {
+ g_dacImpl->m_instances.ReturnAlloc(inst);
+ if (throwEx)
+ {
+ DacError(status);
+ }
+ return NULL;
+ }
+
+ if (!g_dacImpl->m_instances.Add(inst))
+ {
+ g_dacImpl->m_instances.ReturnAlloc(inst);
+ DacError(E_OUTOFMEMORY);
+ UNREACHABLE();
+ }
+
+ if (oldInst)
+ {
+ g_dacImpl->m_instances.Supersede(oldInst);
+ }
+
+ return inst + 1;
+
+#endif // !_PREFIX_
+}
+
+PVOID DacInstantiateTypeByAddress(TADDR addr, ULONG32 size, bool throwEx)
+{
+ return DacInstantiateTypeByAddressHelper(addr, size, throwEx, true);
+}
+
+PVOID DacInstantiateTypeByAddressNoReport(TADDR addr, ULONG32 size, bool throwEx)
+{
+ return DacInstantiateTypeByAddressHelper(addr, size, throwEx, false);
+}
+
+
+PVOID
+DacInstantiateClassByVTable(TADDR addr, ULONG32 minSize, bool throwEx)
+{
+#ifdef _PREFIX_
+
+ // Dac accesses are not interesting for PREfix and cause alot of PREfix noise
+ // so we just return the unmodified pointer for our PREFIX builds
+ return (PVOID)addr;
+
+#else // !_PREFIX_
+
+ if (!g_dacImpl)
+ {
+ DacError(E_UNEXPECTED);
+ UNREACHABLE();
+ }
+
+ // Preserve special pointer values.
+ if (!addr || addr == (TADDR)-1)
+ {
+ return (PVOID)addr;
+ }
+
+ // Do not attempt to allocate more than 64megs for one object instance. While we should
+ // never even come close to this size, in cases of heap corruption or bogus data passed
+ // into the dac, we can allocate huge amounts of data if we are unlucky. This santiy
+ // checks the size to ensure we don't allocate gigs of data.
+ if (minSize > 0x4000000)
+ {
+ if (throwEx)
+ {
+ DacError(E_OUTOFMEMORY);
+ }
+ return NULL;
+ }
+
+ //
+ // Check the cache for an existing VPTR instance.
+ // If there is an instance we assume that it's
+ // the right object.
+ //
+
+ DAC_INSTANCE* inst = g_dacImpl->m_instances.Find(addr);
+ DAC_INSTANCE* oldInst = NULL;
+ if (inst)
+ {
+ // If the existing instance is a VPTR we can
+ // reuse it, otherwise we need to promote.
+ if (inst->usage == DAC_VPTR)
+ {
+ // Sanity check that the object we're returning is big enough to fill the PTR type it's being
+ // accessed with. For more information, see the similar check below for the case when the
+ // object isn't already cached
+ _ASSERTE_MSG(inst->size >= minSize, "DAC coding error: Attempt to instantiate a VPTR from an object that is too small");
+
+ return inst + 1;
+ }
+ else
+ {
+ // Existing instance is not a match and must
+ // be superseded.
+ // Promote the new instance into the hash
+ // in place of the old, but keep the
+ // old instance around in case code still
+ // has a pointer to it. But ensure that we can
+ // create the larger instance and add it to the
+ // hash table before removing the old one.
+ oldInst = inst;
+ }
+ }
+
+ HRESULT status;
+ TADDR vtAddr;
+ ULONG32 size;
+ PVOID hostVtPtr;
+
+ // Read the vtable pointer to get the actual
+ // implementation class identity.
+ if ((status = DacReadAll(addr, &vtAddr, sizeof(vtAddr), throwEx)) != S_OK)
+ {
+ return NULL;
+ }
+
+ //
+ // Instantiate the right class, using the vtable as
+ // class identity.
+ //
+
+#define VPTR_CLASS(name) \
+ if (vtAddr == g_dacImpl->m_globalBase + \
+ g_dacGlobals.name##__vtAddr) \
+ { \
+ size = sizeof(name); \
+ hostVtPtr = g_dacHostVtPtrs.name; \
+ } \
+ else
+#define VPTR_MULTI_CLASS(name, keyBase) \
+ if (vtAddr == g_dacImpl->m_globalBase + \
+ g_dacGlobals.name##__##keyBase##__mvtAddr) \
+ { \
+ size = sizeof(name); \
+ hostVtPtr = g_dacHostVtPtrs.name##__##keyBase; \
+ } \
+ else
+#include <vptr_list.h>
+#undef VPTR_CLASS
+#undef VPTR_MULTI_CLASS
+
+ {
+ // Can't identify the vtable pointer.
+ if (throwEx)
+ {
+ _ASSERTE_MSG(false,"DAC coding error: Unrecognized vtable pointer in VPTR marshalling code");
+ DacError(E_INVALIDARG);
+ }
+ return NULL;
+ }
+
+ // Sanity check that the object we're returning is big enough to fill the PTR type it's being
+ // accessed with.
+ // If this is not true, it means the type being marshalled isn't a sub-type (or the same type)
+ // as the PTR type it's being used as. For example, trying to marshal an instance of a SystemDomain
+ // object into a PTR_AppDomain will cause this ASSERT to fire (because both SystemDomain and AppDomain
+ // derived from BaseDomain, and SystemDomain is smaller than AppDomain).
+ _ASSERTE_MSG(size >= minSize, "DAC coding error: Attempt to instantiate a VPTR from an object that is too small");
+
+ inst = g_dacImpl->m_instances.Alloc(addr, size, DAC_VPTR);
+ if (!inst)
+ {
+ DacError(E_OUTOFMEMORY);
+ UNREACHABLE();
+ }
+
+ // Copy the object contents into the host instance. Note that this assumes the host and target
+ // have the same exact layout. Specifically, it assumes the host and target vtable pointers are
+ // the same size.
+ if ((status = DacReadAll(addr, inst + 1, size, false)) != S_OK)
+ {
+ g_dacImpl->m_instances.ReturnAlloc(inst);
+ if (throwEx)
+ {
+ DacError(status);
+ }
+ return NULL;
+ }
+
+ // We now have a proper target object with a target
+ // vtable. We need to patch the vtable to the appropriate
+ // host vtable so that the virtual functions can be
+ // called in the host process.
+ *(PVOID*)(inst + 1) = hostVtPtr;
+
+ if (!g_dacImpl->m_instances.Add(inst))
+ {
+ g_dacImpl->m_instances.ReturnAlloc(inst);
+ DacError(E_OUTOFMEMORY);
+ UNREACHABLE();
+ }
+
+ if (oldInst)
+ {
+ g_dacImpl->m_instances.Supersede(oldInst);
+ }
+ return inst + 1;
+
+#endif // !_PREFIX_
+}
+
+#define LOCAL_STR_BUF 256
+
+PSTR
+DacInstantiateStringA(TADDR addr, ULONG32 maxChars, bool throwEx)
+{
+#ifdef _PREFIX_
+
+ // Dac accesses are not interesting for PREfix and cause alot of PREfix noise
+ // so we just return the unmodified pointer for our PREFIX builds
+ return (PSTR)addr;
+
+#else // !_PREFIX_
+
+ HRESULT status;
+
+ if (!g_dacImpl)
+ {
+ DacError(E_UNEXPECTED);
+ UNREACHABLE();
+ }
+
+ // Preserve special pointer values.
+ if (!addr || addr == (TADDR)-1)
+ {
+ return (PSTR)addr;
+ }
+
+
+ // Do not attempt to allocate more than 64megs for a string. While we should
+ // never even come close to this size, in cases of heap corruption or bogus data passed
+ // into the dac, we can allocate huge amounts of data if we are unlucky. This santiy
+ // checks the size to ensure we don't allocate gigs of data.
+ if (maxChars > 0x4000000)
+ {
+ if (throwEx)
+ {
+ DacError(E_OUTOFMEMORY);
+ }
+ return NULL;
+ }
+
+ //
+ // Look for an existing string instance.
+ //
+
+ DAC_INSTANCE* inst = g_dacImpl->m_instances.Find(addr);
+ if (inst && inst->usage == DAC_STRA)
+ {
+ return (PSTR)(inst + 1);
+ }
+
+ //
+ // Determine the length of the string
+ // by iteratively reading blocks and scanning them
+ // for a terminator.
+ //
+
+ char buf[LOCAL_STR_BUF];
+ TADDR scanAddr = addr;
+ ULONG32 curBytes = 0;
+ ULONG32 returned;
+
+ for (;;)
+ {
+ status = g_dacImpl->m_pTarget->
+ ReadVirtual(scanAddr, (PBYTE)buf, sizeof(buf),
+ &returned);
+ if (status != S_OK)
+ {
+ // We hit invalid memory before finding a terminator.
+ if (throwEx)
+ {
+ DacError(CORDBG_E_READVIRTUAL_FAILURE);
+ }
+ return NULL;
+ }
+
+ PSTR scan = (PSTR)buf;
+ PSTR scanEnd = scan + (returned / sizeof(*scan));
+ while (scan < scanEnd)
+ {
+ if (!*scan)
+ {
+ break;
+ }
+
+ scan++;
+ }
+
+ if (!*scan)
+ {
+ // Found a terminator.
+ scanAddr += ((scan + 1) - buf) * sizeof(*scan);
+ break;
+ }
+
+ // Ignore any partial character reads. The character
+ // will be reread on the next loop if necessary.
+ returned &= ~(sizeof(buf[0]) - 1);
+
+ // The assumption is that a memory read cannot wrap
+ // around the address space, thus if we have read to
+ // the top of memory scanAddr cannot wrap farther
+ // than to zero.
+ curBytes += returned;
+ scanAddr += returned;
+
+ if (!scanAddr ||
+ (curBytes + sizeof(buf[0]) - 1) / sizeof(buf[0]) >= maxChars)
+ {
+ // Wrapped around the top of memory or
+ // we didn't find a terminator within the given bound.
+ if (throwEx)
+ {
+ DacError(E_INVALIDARG);
+ }
+ return NULL;
+ }
+ }
+
+ // Now that we know the length we can create a
+ // host copy of the string.
+ PSTR retVal = (PSTR)
+ DacInstantiateTypeByAddress(addr, (ULONG32)(scanAddr - addr), throwEx);
+ if (retVal &&
+ (inst = g_dacImpl->m_instances.Find(addr)))
+ {
+ inst->usage = DAC_STRA;
+ }
+ return retVal;
+
+#endif // !_PREFIX_
+}
+
+PWSTR
+DacInstantiateStringW(TADDR addr, ULONG32 maxChars, bool throwEx)
+{
+#ifdef _PREFIX_
+
+ // Dac accesses are not interesting for PREfix and cause alot of PREfix noise
+ // so we just return the unmodified pointer for our PREFIX builds
+ return (PWSTR)addr;
+
+#else // !_PREFIX_
+
+ HRESULT status;
+
+ if (!g_dacImpl)
+ {
+ DacError(E_UNEXPECTED);
+ UNREACHABLE();
+ }
+
+ // Preserve special pointer values.
+ if (!addr || addr == (TADDR)-1)
+ {
+ return (PWSTR)addr;
+ }
+
+ // Do not attempt to allocate more than 64megs for a string. While we should
+ // never even come close to this size, in cases of heap corruption or bogus data passed
+ // into the dac, we can allocate huge amounts of data if we are unlucky. This santiy
+ // checks the size to ensure we don't allocate gigs of data.
+ if (maxChars > 0x4000000)
+ {
+ if (throwEx)
+ {
+ DacError(E_OUTOFMEMORY);
+ }
+ return NULL;
+ }
+
+
+ //
+ // Look for an existing string instance.
+ //
+
+ DAC_INSTANCE* inst = g_dacImpl->m_instances.Find(addr);
+ if (inst && inst->usage == DAC_STRW)
+ {
+ return (PWSTR)(inst + 1);
+ }
+
+ //
+ // Determine the length of the string
+ // by iteratively reading blocks and scanning them
+ // for a terminator.
+ //
+
+ WCHAR buf[LOCAL_STR_BUF];
+ TADDR scanAddr = addr;
+ ULONG32 curBytes = 0;
+ ULONG32 returned;
+
+ for (;;)
+ {
+ status = g_dacImpl->m_pTarget->
+ ReadVirtual(scanAddr, (PBYTE)buf, sizeof(buf),
+ &returned);
+ if (status != S_OK)
+ {
+ // We hit invalid memory before finding a terminator.
+ if (throwEx)
+ {
+ DacError(CORDBG_E_READVIRTUAL_FAILURE);
+ }
+ return NULL;
+ }
+
+ PWSTR scan = (PWSTR)buf;
+ PWSTR scanEnd = scan + (returned / sizeof(*scan));
+ while (scan < scanEnd)
+ {
+ if (!*scan)
+ {
+ break;
+ }
+
+ scan++;
+ }
+
+ if (!*scan)
+ {
+ // Found a terminator.
+ scanAddr += ((scan + 1) - buf) * sizeof(*scan);
+ break;
+ }
+
+ // Ignore any partial character reads. The character
+ // will be reread on the next loop if necessary.
+ returned &= ~(sizeof(buf[0]) - 1);
+
+ // The assumption is that a memory read cannot wrap
+ // around the address space, thus if we have read to
+ // the top of memory scanAddr cannot wrap farther
+ // than to zero.
+ curBytes += returned;
+ scanAddr += returned;
+
+ if (!scanAddr ||
+ (curBytes + sizeof(buf[0]) - 1) / sizeof(buf[0]) >= maxChars)
+ {
+ // Wrapped around the top of memory or
+ // we didn't find a terminator within the given bound.
+ if (throwEx)
+ {
+ DacError(E_INVALIDARG);
+ }
+ return NULL;
+ }
+ }
+
+ // Now that we know the length we can create a
+ // host copy of the string.
+ PWSTR retVal = (PWSTR)
+ DacInstantiateTypeByAddress(addr, (ULONG32)(scanAddr - addr), throwEx);
+ if (retVal &&
+ (inst = g_dacImpl->m_instances.Find(addr)))
+ {
+ inst->usage = DAC_STRW;
+ }
+ return retVal;
+
+#endif // !_PREFIX_
+}
+
+TADDR
+DacGetTargetAddrForHostAddr(LPCVOID ptr, bool throwEx)
+{
+#ifdef _PREFIX_
+
+ // Dac accesses are not interesting for PREfix and cause alot of PREfix noise
+ // so we just return the unmodified pointer for our PREFIX builds
+ return (TADDR) ptr;
+
+#else // !_PREFIX_
+
+ // Preserve special pointer values.
+ if (ptr == NULL || ((TADDR) ptr == (TADDR)-1))
+ {
+ return 0;
+ }
+ else
+ {
+ TADDR addr = 0;
+ HRESULT status = E_FAIL;
+
+ EX_TRY
+ {
+ DAC_INSTANCE* inst = (DAC_INSTANCE*)ptr - 1;
+ if (inst->sig == DAC_INSTANCE_SIG)
+ {
+ addr = inst->addr;
+ status = S_OK;
+ }
+ else
+ {
+ status = E_INVALIDARG;
+ }
+ }
+ EX_CATCH
+ {
+ status = E_INVALIDARG;
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ if (status != S_OK)
+ {
+ if (g_dacImpl && g_dacImpl->m_debugMode)
+ {
+ DebugBreak();
+ }
+
+ if (throwEx)
+ {
+ // This means a pointer was supplied which doesn't actually point to the beginning of
+ // a marshalled DAC instance.
+ _ASSERTE_MSG(false, "DAC coding error: Attempt to get target address from a host pointer "
+ "which is not an instance marshalled by DAC!");
+ DacError(status);
+ }
+ }
+
+ return addr;
+ }
+
+#endif // !_PREFIX_
+}
+
+// Similar to DacGetTargetAddrForHostAddr above except that ptr can represent any pointer within a host data
+// structure marshalled from the target (rather than just a pointer to the first field).
+TADDR
+DacGetTargetAddrForHostInteriorAddr(LPCVOID ptr, bool throwEx)
+{
+ // Our algorithm for locating the containing DAC instance will search backwards through memory in
+ // DAC_INSTANCE_ALIGN increments looking for a valid header. The following constant determines how many of
+ // these iterations we'll perform before deciding the caller made a mistake and didn't marshal the
+ // containing instance from the target to the host properly. Lower values will determine the maximum
+ // offset from the start of a marshalled structure at which an interior pointer can appear. Higher values
+ // will bound the amount of time it takes to report an error in the case where code has been incorrectly
+ // DAC-ized.
+ const DWORD kMaxSearchIterations = 100;
+
+#ifdef _PREFIX_
+
+ // Dac accesses are not interesting for PREfix and cause alot of PREfix noise
+ // so we just return the unmodified pointer for our PREFIX builds
+ return (TADDR) ptr;
+
+#else // !_PREFIX_
+
+ // Preserve special pointer values.
+ if (ptr == NULL || ((TADDR) ptr == (TADDR)-1))
+ {
+ return 0;
+ }
+ else
+ {
+ TADDR addr = 0;
+ HRESULT status = E_FAIL;
+
+ EX_TRY
+ {
+ // We're going to search backwards through memory from the pointer looking for a valid DAC
+ // instance header. Initialize this search pointer to the first legal value it could hold.
+ // Intuitively this would be ptr - sizeof(DAC_INSTANCE), but DAC_INSTANCE headers are further
+ // constrained to lie on DAC_INSTANCE_ALIGN boundaries. DAC_INSTANCE_ALIGN is large (16 bytes) due
+ // to the need to keep the marshalled structure also aligned for any possible need, so we gain
+ // considerable performance from only needing to test for DAC_INSTANCE headers at
+ // DAC_INSTANCE_ALIGN aligned addresses.
+ DAC_INSTANCE * inst = (DAC_INSTANCE*)(((ULONG_PTR)ptr - sizeof(DAC_INSTANCE)) & ~(DAC_INSTANCE_ALIGN - 1));
+
+ // When code is DAC'ized correctly then our search algorithm is guaranteed to terminate safely
+ // before reading memory that doesn't belong to the containing DAC instance. Since people do make
+ // mistakes we want to limit how long and far we search however. The counter below will let us
+ // assert if we've likely tried to locate an interior host pointer in a non-marshalled structure.
+ DWORD cIterations = 0;
+
+ bool tryAgain = false;
+
+ // Scan backwards in memory looking for a DAC_INSTANCE header.
+ while (true)
+ {
+ // Step back DAC_INSTANCE_ALIGN bytes at a time (the initialization of inst above guarantees
+ // we start with an aligned pointer value. Stop every time our potential DAC_INSTANCE header
+ // has a correct signature value.
+ while (tryAgain || inst->sig != DAC_INSTANCE_SIG)
+ {
+ tryAgain = false;
+ inst = (DAC_INSTANCE*)((BYTE*)inst - DAC_INSTANCE_ALIGN);
+
+ // If we've searched a lot of memory (currently 100 * 16 == 1600 bytes) without success,
+ // then assume this is due to an issue DAC-izing code (if you really do have a field within a
+ // DAC marshalled structure whose offset is >1600 bytes then feel free to update the
+ // constant at the start of this method).
+ if (++cIterations > kMaxSearchIterations)
+ {
+ status = E_INVALIDARG;
+ break;
+ }
+ }
+
+ // Fall through to a DAC error if we searched too long without finding a header candidate.
+ if (status == E_INVALIDARG)
+ break;
+
+ // Validate our candidate header by looking up the target address it claims to map in the
+ // instance hash. The entry should both exist and correspond exactly to our candidate instance
+ // pointer.
+ // TODO: but what if the same memory was marshalled more than once (eg. once as a DPTR, once as a VPTR)?
+ if (inst == g_dacImpl->m_instances.Find(inst->addr))
+ {
+ // We've found a valid DAC instance. Now validate that the marshalled structure it
+ // represents really does enclose the pointer we're asking about. If not, someone hasn't
+ // marshalled a containing structure before trying to map a pointer within that structure
+ // (we've just gone and found the previous, unrelated marshalled structure in host memory).
+ BYTE * parent = (BYTE*)(inst + 1);
+ if (((BYTE*)ptr + sizeof(LPCVOID)) <= (parent + inst->size))
+ {
+ // Everything checks out: we've found a DAC instance header and its address range
+ // encompasses the pointer we're interested in. Compute the corresponding target
+ // address by taking into account the offset of the interior pointer into its
+ // enclosing structure.
+ addr = inst->addr + ((BYTE*)ptr - parent);
+ status = S_OK;
+ }
+ else
+ {
+ // We found a valid DAC instance but it doesn't cover the address range containing our
+ // input pointer. Fall though to report an erroring DAC-izing code.
+ status = E_INVALIDARG;
+ }
+ break;
+ }
+ else
+ {
+ // This must not really be a match, perhaps a coincidence?
+ // Keep searching
+ tryAgain = true;
+ }
+ }
+ }
+ EX_CATCH
+ {
+ status = E_INVALIDARG;
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ if (status != S_OK)
+ {
+ if (g_dacImpl && g_dacImpl->m_debugMode)
+ {
+ DebugBreak();
+ }
+
+ if (throwEx)
+ {
+ // This means a pointer was supplied which doesn't actually point to somewhere in a marshalled
+ // DAC instance.
+ _ASSERTE_MSG(false, "DAC coding error: Attempt to get target address from a host interior "
+ "pointer which is not an instance marshalled by DAC!");
+ DacError(status);
+ }
+ }
+
+ return addr;
+ }
+#endif // !_PREFIX_
+}
+
+PWSTR DacGetVtNameW(TADDR targetVtable)
+{
+ PWSTR pszRet = NULL;
+
+ ULONG *targ = &g_dacGlobals.Thread__vtAddr;
+ ULONG *targStart = targ;
+ for (ULONG i = 0; i < sizeof(g_dacHostVtPtrs) / sizeof(PVOID); i++)
+ {
+ if (targetVtable == (*targ + DacGlobalBase()))
+ {
+ pszRet = (PWSTR) *(g_dacVtStrings + (targ - targStart));
+ break;
+ }
+
+ targ++;
+ }
+ return pszRet;
+}
+
+TADDR
+DacGetTargetVtForHostVt(LPCVOID vtHost, bool throwEx)
+{
+ PVOID* host;
+ ULONG* targ;
+ ULONG i;
+
+ // The host vtable table exactly parallels the
+ // target vtable table, so just iterate to a match
+ // return the matching entry.
+ host = &g_dacHostVtPtrs.Thread;
+ targ = &g_dacGlobals.Thread__vtAddr;
+ for (i = 0; i < sizeof(g_dacHostVtPtrs) / sizeof(PVOID); i++)
+ {
+ if (*host == vtHost)
+ {
+ return *targ + DacGlobalBase();
+ }
+
+ host++;
+ targ++;
+ }
+
+ if (throwEx)
+ {
+ DacError(E_INVALIDARG);
+ }
+ return 0;
+}
+
+//
+// DacEnumMemoryRegion - report a region of memory to the dump generation code
+//
+// Parameters:
+// addr - target address of the beginning of the memory region
+// size - number of bytes to report
+// fExpectSuccess - whether or not ASSERTs should be raised if some memory in this region
+// is found to be unreadable. Generally we should only report readable
+// memory (unless the target is corrupt, in which case we expect asserts
+// if target consistency checking is enabled). Reporting memory that
+// isn't fully readable often indicates an issue that could cause much worse
+// problems (loss of dump data, long/infinite loops in dump generation),
+// so we want to try and catch any such usage. Ocassionally we can't say
+// for sure how much of the reported region will be readable (eg. for the
+// LoaderHeap, we only know the length of the allocated address space, not
+// the size of the commit region for every block). In these special cases,
+// we pass false to indicate that we're happy reporting up to the first
+// unreadable byte. This should be avoided if at all possible.
+//
+bool DacEnumMemoryRegion(TADDR addr, TSIZE_T size, bool fExpectSuccess /*=true*/)
+{
+ if (!g_dacImpl)
+ {
+ DacError(E_UNEXPECTED);
+ UNREACHABLE();
+ }
+
+ return g_dacImpl->ReportMem(addr, size, fExpectSuccess);
+}
+
+//
+// DacUpdateMemoryRegion - updates/poisons a region of memory of generated dump
+//
+// Parameters:
+// addr - target address of the beginning of the memory region
+// bufferSize - number of bytes to update/poison
+// buffer - data to be written at given target address
+//
+bool DacUpdateMemoryRegion(TADDR addr, TSIZE_T bufferSize, BYTE* buffer)
+{
+ if (!g_dacImpl)
+ {
+ DacError(E_UNEXPECTED);
+ UNREACHABLE();
+ }
+
+ return g_dacImpl->DacUpdateMemoryRegion(addr, bufferSize, buffer);
+}
+
+HRESULT
+DacWriteHostInstance(PVOID host, bool throwEx)
+{
+ if (!g_dacImpl)
+ {
+ DacError(E_UNEXPECTED);
+ UNREACHABLE();
+ }
+
+ TADDR addr = DacGetTargetAddrForHostAddr(host, throwEx);
+ if (!addr)
+ {
+ return S_OK;
+ }
+
+ DAC_INSTANCE* inst = (DAC_INSTANCE*)host - 1;
+ return g_dacImpl->m_instances.Write(inst, throwEx);
+}
+
+bool
+DacHostPtrHasEnumMark(LPCVOID host)
+{
+ if (!DacGetTargetAddrForHostAddr(host, false))
+ {
+ // Make it easy to ignore invalid pointers when enumerating.
+ return true;
+ }
+
+ DAC_INSTANCE* inst = ((DAC_INSTANCE*)host) - 1;
+ bool marked = inst->enumMem ? true : false;
+ inst->enumMem = true;
+ return marked;
+}
+
+bool
+DacHasMethodDescBeenEnumerated(LPCVOID pMD)
+{
+ if (!DacGetTargetAddrForHostAddr(pMD, false))
+ {
+ // Make it easy to ignore invalid pointers when enumerating.
+ return true;
+ }
+
+ DAC_INSTANCE* inst = ((DAC_INSTANCE*) pMD) - 1;
+ bool MDEnumed = inst->MDEnumed ? true : false;
+ return MDEnumed;
+}
+
+bool
+DacSetMethodDescEnumerated(LPCVOID pMD)
+{
+ if (!DacGetTargetAddrForHostAddr(pMD, false))
+ {
+ // Make it easy to ignore invalid pointers when enumerating.
+ return true;
+ }
+
+ DAC_INSTANCE* inst = ((DAC_INSTANCE*) pMD) - 1;
+ bool MDEnumed = inst->MDEnumed ? true : false;
+ inst->MDEnumed = true;
+ return MDEnumed;
+}
+
+// This gets called from DAC-ized code in the VM.
+IMDInternalImport*
+DacGetMDImport(const PEFile* peFile, bool throwEx)
+{
+ if (!g_dacImpl)
+ {
+ DacError(E_UNEXPECTED);
+ UNREACHABLE();
+ }
+
+ return g_dacImpl->GetMDImport(peFile, throwEx);
+}
+
+IMDInternalImport*
+DacGetMDImport(const ReflectionModule* reflectionModule, bool throwEx)
+{
+ if (!g_dacImpl)
+ {
+ DacError(E_UNEXPECTED);
+ UNREACHABLE();
+ }
+
+ return g_dacImpl->GetMDImport(reflectionModule, throwEx);
+}
+
+COR_ILMETHOD*
+DacGetIlMethod(TADDR methAddr)
+{
+ ULONG32 methodSize = static_cast<ULONG32>(PEDecoder::ComputeILMethodSize(methAddr));
+
+ // Sometimes when reading from dumps and inspect NGEN images, but we end up reading metadata from IL image
+ // the method RVA could not match and we could read from a random address that will translate in inconsistent
+ // IL code header. If we see the size of the code bigger than 64 Megs we are probably reading a bad IL code header.
+ // For details see issue DevDiv 273199.
+ if (methodSize > 0x4000000)
+ {
+ DacError(CORDBG_E_TARGET_INCONSISTENT);
+ UNREACHABLE();
+ }
+ return (COR_ILMETHOD*)
+ DacInstantiateTypeByAddress(methAddr, methodSize,
+ true);
+}
+
+#ifdef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+void
+DacMdCacheAddEEName(TADDR taEE, const SString& ssEEName)
+{
+ if (!g_dacImpl)
+ {
+ DacError(E_UNEXPECTED);
+ UNREACHABLE();
+ }
+
+ g_dacImpl->MdCacheAddEEName(taEE, ssEEName);
+}
+bool
+DacMdCacheGetEEName(TADDR taEE, SString & eeName)
+{
+ if (!g_dacImpl)
+ {
+ DacError(E_UNEXPECTED);
+ UNREACHABLE();
+ }
+
+ return g_dacImpl->MdCacheGetEEName(taEE, eeName);
+}
+
+#endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+
+PVOID
+DacAllocHostOnlyInstance(ULONG32 size, bool throwEx)
+{
+ SUPPORTS_DAC_HOST_ONLY;
+ if (!g_dacImpl)
+ {
+ DacError(E_UNEXPECTED);
+ UNREACHABLE();
+ }
+
+ DAC_INSTANCE* inst = g_dacImpl->m_instances.Alloc(0, size, DAC_DPTR);
+ if (!inst)
+ {
+ DacError(E_OUTOFMEMORY);
+ UNREACHABLE();
+ }
+
+ g_dacImpl->m_instances.AddSuperseded(inst);
+
+ return inst + 1;
+}
+
+//
+// Queries whether ASSERTs should be raised when inconsistencies in the target are detected
+//
+// Return Value:
+// true if ASSERTs should be raised in DACized code.
+// false if ASSERTs should be ignored.
+//
+// Notes:
+// See code:ClrDataAccess::TargetConsistencyAssertsEnabled for details.
+bool DacTargetConsistencyAssertsEnabled()
+{
+ if (!g_dacImpl)
+ {
+ // No ClrDataAccess instance available (maybe we're still initializing). Any asserts when this is
+ // the case should only be host-asserts (i.e. always bugs), and so we should just return true.
+ return true;
+ }
+
+ return g_dacImpl->TargetConsistencyAssertsEnabled();
+}
+
+//
+// DacEnumCodeForStackwalk
+// This is a helper function to enumerate the instructions around a call site to aid heuristics
+// used by debugger stack walkers.
+//
+// Arguments:
+// taCallEnd - target address of the instruction just after the call instruction for the stack
+// frame we want to examine(i.e. the return address for the next frame).
+//
+// Note that this is shared by our two stackwalks during minidump generation,
+// code:Thread::EnumMemoryRegionsWorker and code:ClrDataAccess::EnumMemWalkStackHelper. Ideally
+// we'd only have one stackwalk, but we currently have two different APIs for stackwalking
+// (CLR StackFrameIterator and IXCLRDataStackWalk), and we must ensure that the memory needed
+// for either is captured in a minidump. Eventually, all clients should get moved over to the
+// arrowhead debugging architecture, at which time we can rip out all the IXCLRData APIs, and
+// so this logic could just be private to the EnumMem code for Thread.
+//
+void DacEnumCodeForStackwalk(TADDR taCallEnd)
+{
+ //
+ // x86 stack walkers often end up having to guess
+ // about what's a return address on the stack.
+ // Doing so involves looking at the code at the
+ // possible call site and seeing if it could
+ // reach the callee. Save enough code and around
+ // the call site to allow this with a dump.
+ //
+ // For whatever reason 64-bit platforms require us to save
+ // the instructions around the call sites on the stack as well.
+ // Otherwise we cannnot show the stack in a minidump.
+ //
+ // Note that everything we do here is a heuristic that won't always work in general.
+ // Eg., part of the 2xMAX_INSTRUCTION_LENGTH range might not be mapped (we could be
+ // right on a page boundary). More seriously, X86 is not necessarily parsable in reverse
+ // (eg. there could be a segment-override prefix in front of the call instruction that
+ // we miss). So we'll dump what we can and ignore any failures. Ideally we'd better
+ // quantify exactly what debuggers need and why, and try and avoid these ugly heuristics.
+ // It seems like these heuristics are too tightly coupled to the implementation details
+ // of some specific debugger stackwalking algorithm.
+ //
+ DacEnumMemoryRegion(taCallEnd - MAX_INSTRUCTION_LENGTH, MAX_INSTRUCTION_LENGTH * 2, false);
+
+#if defined(_TARGET_X86_)
+ // If it was an indirect call we also need to save the data indirected through.
+ // Note that this only handles absolute indirect calls (ModR/M byte of 0x15), all the other forms of
+ // indirect calls are register-relative, and so we'd have to do a much more complicated decoding based
+ // on the register context. Regardless, it seems like this is fundamentally error-prone because it's
+ // aways possible that the call instruction was not 6 bytes long, and we could have some other instructions
+ // that happen to match the pattern we're looking for.
+ PTR_BYTE callCode = PTR_BYTE(taCallEnd - 6);
+ PTR_BYTE callMrm = PTR_BYTE(taCallEnd - 5);
+ PTR_TADDR callInd = PTR_TADDR(taCallEnd - 4);
+ if (callCode.IsValid() &&
+ (*callCode == 0xff) &&
+ callMrm.IsValid() &&
+ (*callMrm == 0x15) &&
+ callInd.IsValid())
+ {
+ DacEnumMemoryRegion(*callInd, sizeof(TADDR), false);
+ }
+#endif // #ifdef _TARGET_X86_
+}
+
+// ----------------------------------------------------------------------------
+// DacReplacePatches
+//
+// Description:
+// Given the address and the size of a memory range which is stored in the buffer, replace all the patches
+// in the buffer with the real opcodes. This is especially important on X64 where the unwinder needs to
+// disassemble the native instructions.
+//
+// Arguments:
+// * range - the address and the size of the memory range
+// * pBuffer - the buffer containting the memory range
+//
+// Return Value:
+// Return S_OK if everything succeeds.
+//
+// Assumptions:
+// * The debuggee has to be stopped.
+//
+// Notes:
+// * @dbgtodo ICDProcess - When we DACize code:CordbProcess::ReadMemory,
+// we should change it to use this function.
+//
+
+HRESULT DacReplacePatchesInHostMemory(MemoryRange range, PVOID pBuffer)
+{
+ SUPPORTS_DAC;
+
+ // If the patch table is invalid, then there is no patch to replace.
+ if (!DebuggerController::GetPatchTableValid())
+ {
+ return S_OK;
+ }
+
+ HASHFIND info;
+
+ DebuggerPatchTable * pTable = DebuggerController::GetPatchTable();
+ DebuggerControllerPatch * pPatch = pTable->GetFirstPatch(&info);
+
+ // <PERF>
+ // The unwinder needs to read the stack very often to restore pushed registers, retrieve the
+ // return addres, etc. However, stack addresses should never be patched.
+ // One way to optimize this code is to pass the stack base and the stack limit of the thread to this
+ // function and use those two values to filter out stack addresses.
+ //
+ // Another thing we can do is instead of enumerating the patches, we could enumerate the address.
+ // This is more efficient when we have a large number of patches and a small memory range. Perhaps
+ // we could do a hybrid approach, i.e. use the size of the range and the number of patches to dynamically
+ // determine which enumeration is more efficient.
+ // </PERF>
+ while (pPatch != NULL)
+ {
+ CORDB_ADDRESS patchAddress = (CORDB_ADDRESS)dac_cast<TADDR>(pPatch->address);
+
+ if (patchAddress != NULL)
+ {
+ PRD_TYPE opcode = pPatch->opcode;
+
+ CORDB_ADDRESS address = (CORDB_ADDRESS)(dac_cast<TADDR>(range.StartAddress()));
+ SIZE_T cbSize = range.Size();
+
+ // Check if the address of the patch is in the specified memory range.
+ if (IsPatchInRequestedRange(address, cbSize, patchAddress))
+ {
+ // Replace the patch in the buffer with the original opcode.
+ CORDbgSetInstructionEx(reinterpret_cast<PBYTE>(pBuffer), address, patchAddress, opcode, cbSize);
+ }
+ }
+
+ pPatch = pTable->GetNextPatch(&info);
+ }
+
+ return S_OK;
+}
diff --git a/src/debug/daccess/dacimpl.h b/src/debug/daccess/dacimpl.h
new file mode 100644
index 0000000000..635be80232
--- /dev/null
+++ b/src/debug/daccess/dacimpl.h
@@ -0,0 +1,4015 @@
+// 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.
+//*****************************************************************************
+// File: dacimpl.h
+//
+
+//
+// Central header file for external data access implementation.
+//
+//*****************************************************************************
+
+
+#ifndef __DACIMPL_H__
+#define __DACIMPL_H__
+
+#if defined(_TARGET_ARM_) || defined(FEATURE_CORESYSTEM) // @ARMTODO: STL breaks the build with current VC headers
+//---------------------------------------------------------------------------------------
+// Setting DAC_HASHTABLE tells the DAC to use the hand rolled hashtable for
+// storing code:DAC_INSTANCE . Otherwise, the DAC uses the STL unordered_map to.
+
+#define DAC_HASHTABLE
+#endif // _TARGET_ARM_|| FEATURE_CORESYSTEM
+
+#ifndef DAC_HASHTABLE
+#pragma push_macro("return")
+#undef return
+#include <unordered_map>
+#pragma pop_macro("return")
+#endif //DAC_HASHTABLE
+extern CRITICAL_SECTION g_dacCritSec;
+
+// Convert between CLRDATA_ADDRESS and TADDR.
+// Note that CLRDATA_ADDRESS is sign-extended (for compat with Windbg and OS conventions). Converting
+// from pointer-size values to CLRDATA_ADDRESS should ALWAYS use this TO_CDADDR macro to avoid bugs when
+// dealing with 3/4GB 32-bit address spaces. You must not rely on the compiler's implicit conversion
+// from ULONG32 to ULONG64 - it is incorrect. Ideally we'd use some compiler tricks or static analysis
+// to help detect such errors (they are nefarious since 3/4GB addresses aren't well tested) .
+//
+// Note: We're in the process of switching the implementation over to CORDB_ADDRESS instead, which is also
+// 64 bits, but 0-extended. This means that conversions between TADDR and CORDB_ADDRESS are simple and natural,
+// but as long as we have some legacy code, conversions involving CLRDATA_ADDRESS are a pain. Eventually we
+// should eliminate CLRDATA_ADDRESS entirely from the implementation, but that will require moving SOS off of
+// the old DAC stuff etc.
+//
+// Here are the possible conversions:
+// TADDR -> CLRDATA_ADDRESS: TO_CDADDR
+// CORDB_ADDRESS -> CLRDATA_ADDRESS: TO_CDADDR
+// CLRDATA_ADDRESS -> TADDR: CLRDATA_ADDRESS_TO_TADDR
+// CORDB_ADDRESS -> TADDR: CORDB_ADDRESS_TO_TADDR
+// TADDR -> CORDB_ADDRESS: implicit
+// CLRDATA_ADDRESS -> CORDB_ADDRESS: CLRDATA_ADDRESS_TO_TADDR
+//
+#define TO_CDADDR(taddr) ((CLRDATA_ADDRESS)(LONG_PTR)(taddr))
+
+// Convert a CLRDATA_ADDRESS (64-bit unsigned sign-extended target address) to a TADDR
+inline TADDR CLRDATA_ADDRESS_TO_TADDR(CLRDATA_ADDRESS cdAddr)
+{
+ SUPPORTS_DAC;
+#ifndef _WIN64
+ static_assert_no_msg(sizeof(TADDR)==sizeof(UINT));
+ INT64 iSignedAddr = (INT64)cdAddr;
+ if (iSignedAddr > INT_MAX || iSignedAddr < INT_MIN)
+ {
+ _ASSERTE_MSG(false, "CLRDATA_ADDRESS out of range for this platform");
+ DacError(E_INVALIDARG);
+ }
+#endif
+ return (TADDR)cdAddr;
+}
+
+// No throw, Convert a CLRDATA_ADDRESS (64-bit unsigned sign-extended target address) to a TADDR
+// Use this in places where we know windbg may pass in bad addresses
+inline HRESULT TRY_CLRDATA_ADDRESS_TO_TADDR(CLRDATA_ADDRESS cdAddr, TADDR* pOutTaddr)
+{
+ SUPPORTS_DAC;
+#ifndef _WIN64
+ static_assert_no_msg(sizeof(TADDR)==sizeof(UINT));
+ INT64 iSignedAddr = (INT64)cdAddr;
+ if (iSignedAddr > INT_MAX || iSignedAddr < INT_MIN)
+ {
+ *pOutTaddr = 0;
+ return E_INVALIDARG;
+ }
+#endif
+ *pOutTaddr = (TADDR)cdAddr;
+ return S_OK;
+}
+
+// Convert a CORDB_ADDRESS (64-bit unsigned 0-extended target address) to a TADDR
+inline TADDR CORDB_ADDRESS_TO_TADDR(CORDB_ADDRESS cdbAddr)
+{
+ SUPPORTS_DAC;
+#ifndef _WIN64
+ static_assert_no_msg(sizeof(TADDR)==sizeof(UINT));
+ if (cdbAddr > UINT_MAX)
+ {
+ _ASSERTE_MSG(false, "CORDB_ADDRESS out of range for this platform");
+ DacError(E_INVALIDARG);
+ }
+#endif
+ return (TADDR)cdbAddr;
+}
+
+// TO_TADDR is the old way of converting CLRDATA_ADDRESSes to TADDRs. Unfortunately,
+// this has been used in many places to also cast pointers (void* etc.) to TADDR, and
+// so we can't actually require the argument to be a valid CLRDATA_ADDRESS. New code
+// should use CLRDATA_ADDRESS_TO_TADDR instead.
+#define TO_TADDR(cdaddr) ((TADDR)(cdaddr))
+
+#define TO_CDENUM(ptr) ((CLRDATA_ENUM)(ULONG_PTR)(ptr))
+#define FROM_CDENUM(type, cdenum) ((type*)(ULONG_PTR)(cdenum))
+
+#define SIMPFRAME_ALL \
+ (CLRDATA_SIMPFRAME_UNRECOGNIZED | \
+ CLRDATA_SIMPFRAME_MANAGED_METHOD | \
+ CLRDATA_SIMPFRAME_RUNTIME_MANAGED_CODE | \
+ CLRDATA_SIMPFRAME_RUNTIME_UNMANAGED_CODE)
+
+enum DAC_USAGE_TYPE
+{
+ DAC_DPTR,
+ DAC_VPTR,
+ DAC_STRA,
+ DAC_STRW,
+};
+
+// mscordacwks's module handle
+extern HINSTANCE g_thisModule;
+
+class ReflectionModule;
+
+struct DAC_MD_IMPORT
+{
+ DAC_MD_IMPORT* next; // list link field
+ TADDR peFile; // a TADDR for a PEFile* or a ReflectionModule*
+ IMDInternalImport* impl; // Associated metadata interface
+ bool isAlternate; // for NGEN images set to true if the metadata corresponds to the IL image
+
+ DAC_MD_IMPORT(TADDR peFile_,
+ IMDInternalImport* impl_,
+ bool isAlt_ = false,
+ DAC_MD_IMPORT* next_ = NULL)
+ : next(next_)
+ , peFile(peFile_)
+ , impl(impl_)
+ , isAlternate(isAlt_)
+ {
+ SUPPORTS_DAC_HOST_ONLY;
+ }
+};
+
+
+// This class maintains a cache of IMDInternalImport* and their corresponding
+// source (a PEFile* or a ReflectionModule*), as a singly-linked list of
+// DAC_MD_IMPORT nodes. The cache is flushed whenever the process state changes
+// by calling its Flush() member function.
+class MDImportsCache
+{
+public:
+
+ MDImportsCache()
+ : m_head(NULL)
+ {}
+
+ ~MDImportsCache()
+ {
+ Flush();
+ }
+
+ FORCEINLINE
+ IMDInternalImport* Get(TADDR key) const
+ {
+ SUPPORTS_DAC;
+ for (DAC_MD_IMPORT* importList = m_head; importList; importList = importList->next)
+ {
+ if (importList->peFile == key)
+ {
+ return importList->impl;
+ }
+ }
+ return NULL;
+ }
+
+ FORCEINLINE
+ DAC_MD_IMPORT* Add(TADDR peFile, IMDInternalImport* impl, bool isAlt)
+ {
+ SUPPORTS_DAC;
+ DAC_MD_IMPORT* importList = new (nothrow) DAC_MD_IMPORT(peFile, impl, isAlt, m_head);
+ if (!importList)
+ {
+ return NULL;
+ }
+
+ m_head = importList;
+ return importList;
+ }
+
+ void Flush()
+ {
+ DAC_MD_IMPORT* importList;
+
+ while (m_head)
+ {
+ importList = m_head;
+ m_head = importList->next;
+ importList->impl->Release();
+ delete importList;
+ }
+ }
+
+private:
+
+ DAC_MD_IMPORT* m_head; // the beginning of the list of cached MD imports
+
+};
+
+struct METH_EXTENTS
+{
+ ULONG32 numExtents;
+ ULONG32 curExtent;
+ // Currently only one is needed.
+ CLRDATA_ADDRESS_RANGE extents[1];
+};
+
+HRESULT ConvertUtf8(__in LPCUTF8 utf8,
+ ULONG32 bufLen,
+ ULONG32* nameLen,
+ __out_ecount_part_opt(bufLen, *nameLen) PWSTR buffer);
+HRESULT AllocUtf8(__in_opt LPCWSTR wstr,
+ ULONG32 srcChars,
+ __deref_out LPUTF8* utf8);
+
+HRESULT GetFullClassNameFromMetadata(IMDInternalImport* mdImport,
+ mdTypeDef classToken,
+ ULONG32 bufferChars,
+ __inout_ecount(bufferChars) LPUTF8 buffer);
+HRESULT GetFullMethodNameFromMetadata(IMDInternalImport* mdImport,
+ mdMethodDef methodToken,
+ ULONG32 bufferChars,
+ __inout_ecount(bufferChars) LPUTF8 buffer);
+
+enum SplitSyntax
+{
+ SPLIT_METHOD,
+ SPLIT_TYPE,
+ SPLIT_FIELD,
+ SPLIT_NO_NAME,
+};
+
+HRESULT SplitFullName(__in_z __in PCWSTR fullName,
+ SplitSyntax syntax,
+ ULONG32 memberDots,
+ __deref_out_opt LPUTF8* namespaceName,
+ __deref_out_opt LPUTF8* typeName,
+ __deref_out_opt LPUTF8* memberName,
+ __deref_out_opt LPUTF8* params);
+
+int CompareUtf8(__in LPCUTF8 str1, __in LPCUTF8 str2, __in ULONG32 nameFlags);
+
+#define INH_STATIC \
+ (CLRDATA_VALUE_ALL_KINDS | \
+ CLRDATA_VALUE_IS_INHERITED | CLRDATA_VALUE_FROM_STATIC)
+
+HRESULT InitFieldIter(DeepFieldDescIterator* fieldIter,
+ TypeHandle typeHandle,
+ bool canHaveFields,
+ ULONG32 flags,
+ IXCLRDataTypeInstance* fromType);
+
+ULONG32 GetTypeFieldValueFlags(TypeHandle typeHandle,
+ FieldDesc* fieldDesc,
+ ULONG32 otherFlags,
+ bool isDeref);
+
+//----------------------------------------------------------------------------
+//
+// MetaEnum.
+//
+//----------------------------------------------------------------------------
+
+class MetaEnum
+{
+public:
+ MetaEnum(void)
+ : m_domainIter(FALSE)
+ {
+ Clear();
+ m_appDomain = NULL;
+ }
+ ~MetaEnum(void)
+ {
+ End();
+ }
+
+ void Clear(void)
+ {
+ m_mdImport = NULL;
+ m_kind = 0;
+ m_lastToken = mdTokenNil;
+ }
+
+ HRESULT Start(IMDInternalImport* mdImport, ULONG32 kind,
+ mdToken container);
+ void End(void);
+
+ HRESULT NextToken(mdToken* token,
+ __deref_opt_out_opt LPCUTF8* namespaceName,
+ __deref_opt_out_opt LPCUTF8* name);
+ HRESULT NextDomainToken(AppDomain** appDomain,
+ mdToken* token);
+ HRESULT NextTokenByName(__in_opt LPCUTF8 namespaceName,
+ __in_opt LPCUTF8 name,
+ ULONG32 nameFlags,
+ mdToken* token);
+ HRESULT NextDomainTokenByName(__in_opt LPCUTF8 namespaceName,
+ __in_opt LPCUTF8 name,
+ ULONG32 nameFlags,
+ AppDomain** appDomain, mdToken* token);
+
+ static HRESULT CdNextToken(CLRDATA_ENUM* handle,
+ mdToken* token)
+ {
+ MetaEnum* iter = FROM_CDENUM(MetaEnum, *handle);
+ if (!iter)
+ {
+ return S_FALSE;
+ }
+
+ return iter->NextToken(token, NULL, NULL);
+ }
+ static HRESULT CdNextDomainToken(CLRDATA_ENUM* handle,
+ AppDomain** appDomain,
+ mdToken* token)
+ {
+ MetaEnum* iter = FROM_CDENUM(MetaEnum, *handle);
+ if (!iter)
+ {
+ return S_FALSE;
+ }
+
+ return iter->NextDomainToken(appDomain, token);
+ }
+ static HRESULT CdEnd(CLRDATA_ENUM handle)
+ {
+ MetaEnum* iter = FROM_CDENUM(MetaEnum, handle);
+ if (iter)
+ {
+ delete iter;
+ return S_OK;
+ }
+ else
+ {
+ return E_INVALIDARG;
+ }
+ }
+
+ IMDInternalImport* m_mdImport;
+ ULONG32 m_kind;
+ HENUMInternal m_enum;
+ AppDomain* m_appDomain;
+ AppDomainIterator m_domainIter;
+ mdToken m_lastToken;
+
+ static HRESULT New(Module* mod,
+ ULONG32 kind,
+ mdToken container,
+ IXCLRDataAppDomain* pubAppDomain,
+ MetaEnum** metaEnum,
+ CLRDATA_ENUM* handle);
+};
+
+//----------------------------------------------------------------------------
+//
+// SplitName.
+//
+//----------------------------------------------------------------------------
+
+class SplitName
+{
+public:
+ // Type of name and splitting being done in this instance.
+ SplitSyntax m_syntax;
+ ULONG32 m_nameFlags;
+ ULONG32 m_memberDots;
+
+ // Split fields.
+ LPUTF8 m_namespaceName;
+ LPUTF8 m_typeName;
+ mdTypeDef m_typeToken;
+ LPUTF8 m_memberName;
+ mdMethodDef m_memberToken;
+ LPUTF8 m_params;
+ // XXX Microsoft - Translated signature.
+
+ // Arbitrary extra data.
+ Thread* m_tlsThread;
+ Module* m_module;
+ MetaEnum m_metaEnum;
+ DeepFieldDescIterator m_fieldEnum;
+ ULONG64 m_objBase;
+ FieldDesc* m_lastField;
+
+ SplitName(SplitSyntax syntax, ULONG32 nameFlags,
+ ULONG32 memberDots);
+ ~SplitName(void)
+ {
+ Delete();
+ }
+
+ void Delete(void);
+ void Clear(void);
+
+ HRESULT SplitString(__in_opt PCWSTR fullName);
+
+ bool FindType(IMDInternalImport* mdInternal);
+ bool FindMethod(IMDInternalImport* mdInternal);
+ bool FindField(IMDInternalImport* mdInternal);
+
+ int Compare(LPCUTF8 str1, LPCUTF8 str2)
+ {
+ return CompareUtf8(str1, str2, m_nameFlags);
+ }
+
+ static HRESULT AllocAndSplitString(__in_opt PCWSTR fullName,
+ SplitSyntax syntax,
+ ULONG32 nameFlags,
+ ULONG32 memberDots,
+ SplitName** split);
+
+ static HRESULT CdStartMethod(__in_opt PCWSTR fullName,
+ ULONG32 nameFlags,
+ Module* mod,
+ mdTypeDef typeToken,
+ AppDomain* appDomain,
+ IXCLRDataAppDomain* pubAppDomain,
+ SplitName** split,
+ CLRDATA_ENUM* handle);
+ static HRESULT CdNextMethod(CLRDATA_ENUM* handle,
+ mdMethodDef* token);
+ static HRESULT CdNextDomainMethod(CLRDATA_ENUM* handle,
+ AppDomain** appDomain,
+ mdMethodDef* token);
+
+ static HRESULT CdStartField(__in_opt PCWSTR fullName,
+ ULONG32 nameFlags,
+ ULONG32 fieldFlags,
+ IXCLRDataTypeInstance* fromTypeInst,
+ TypeHandle typeHandle,
+ Module* mod,
+ mdTypeDef typeToken,
+ ULONG64 objBase,
+ Thread* tlsThread,
+ IXCLRDataTask* pubTlsThread,
+ AppDomain* appDomain,
+ IXCLRDataAppDomain* pubAppDomain,
+ SplitName** split,
+ CLRDATA_ENUM* handle);
+ static HRESULT CdNextField(ClrDataAccess* dac,
+ CLRDATA_ENUM* handle,
+ IXCLRDataTypeDefinition** fieldType,
+ ULONG32* fieldFlags,
+ IXCLRDataValue** value,
+ ULONG32 nameBufRetLen,
+ ULONG32* nameLenRet,
+ __out_ecount_part_opt(nameBufRetLen, *nameLenRet) WCHAR nameBufRet[ ],
+ IXCLRDataModule** tokenScopeRet,
+ mdFieldDef* tokenRet);
+ static HRESULT CdNextDomainField(ClrDataAccess* dac,
+ CLRDATA_ENUM* handle,
+ IXCLRDataValue** value);
+
+ static HRESULT CdStartType(__in_opt PCWSTR fullName,
+ ULONG32 nameFlags,
+ Module* mod,
+ AppDomain* appDomain,
+ IXCLRDataAppDomain* pubAppDomain,
+ SplitName** split,
+ CLRDATA_ENUM* handle);
+ static HRESULT CdNextType(CLRDATA_ENUM* handle,
+ mdTypeDef* token);
+ static HRESULT CdNextDomainType(CLRDATA_ENUM* handle,
+ AppDomain** appDomain,
+ mdTypeDef* token);
+
+ static HRESULT CdEnd(CLRDATA_ENUM handle)
+ {
+ SplitName* split = FROM_CDENUM(SplitName, handle);
+ if (split)
+ {
+ delete split;
+ return S_OK;
+ }
+ else
+ {
+ return E_INVALIDARG;
+ }
+ }
+};
+
+//----------------------------------------------------------------------------
+//
+// ProcessModIter.
+//
+//----------------------------------------------------------------------------
+
+struct ProcessModIter
+{
+ AppDomainIterator m_domainIter;
+ bool m_nextDomain;
+ AppDomain::AssemblyIterator m_assemIter;
+ bool m_iterShared;
+#ifdef FEATURE_LOADER_OPTIMIZATION
+ SharedDomain::SharedAssemblyIterator m_sharedIter;
+#endif
+ Assembly* m_curAssem;
+ Assembly::ModuleIterator m_modIter;
+
+ ProcessModIter(void)
+ : m_domainIter(FALSE)
+ {
+ SUPPORTS_DAC;
+ m_nextDomain = true;
+ m_iterShared = false;
+ m_curAssem = NULL;
+ }
+
+ Assembly * NextAssem()
+ {
+ SUPPORTS_DAC;
+ while (!m_iterShared)
+ {
+ if (m_nextDomain)
+ {
+ if (!m_domainIter.Next())
+ {
+ m_iterShared = true;
+ break;
+ }
+
+ m_nextDomain = false;
+
+ m_assemIter = m_domainIter.GetDomain()->IterateAssembliesEx((AssemblyIterationFlags)(
+ kIncludeLoaded | kIncludeExecution));
+ }
+
+ CollectibleAssemblyHolder<DomainAssembly *> pDomainAssembly;
+ if (!m_assemIter.Next(pDomainAssembly.This()))
+ {
+ m_nextDomain = true;
+ continue;
+ }
+
+ // Note: DAC doesn't need to keep the assembly alive - see code:CollectibleAssemblyHolder#CAH_DAC
+ CollectibleAssemblyHolder<Assembly *> pAssembly = pDomainAssembly->GetLoadedAssembly();
+ if (!pAssembly->IsDomainNeutral())
+ {
+ // We've found a domain-specific assembly, so this is a unique element in the Assembly
+ // iteration.
+ return pAssembly;
+ }
+
+ // Found a shared assembly, which may be duplicated
+ // across app domains. Ignore it now and let
+ // it get picked up in the shared iteration where
+ // it'll only occur once.
+ }
+#ifdef FEATURE_LOADER_OPTIMIZATION
+ if (!m_sharedIter.Next())
+ {
+ return NULL;
+ }
+ return m_sharedIter.GetAssembly();
+#else
+ return NULL;
+#endif
+ }
+
+ Module* NextModule(void)
+ {
+ SUPPORTS_DAC;
+ for (;;)
+ {
+ if (!m_curAssem)
+ {
+ m_curAssem = NextAssem();
+ if (!m_curAssem)
+ {
+ return NULL;
+ }
+
+ m_modIter = m_curAssem->IterateModules();
+ }
+
+ if (!m_modIter.Next())
+ {
+ m_curAssem = NULL;
+ continue;
+ }
+
+ return m_modIter.GetModule();
+ }
+ }
+};
+
+//----------------------------------------------------------------------------
+//
+// DacInstanceManager.
+//
+//----------------------------------------------------------------------------
+
+// The data for an access may have special alignment needs and
+// the cache must provide similar semantics.
+#define DAC_INSTANCE_ALIGN 16
+
+#define DAC_INSTANCE_SIG 0xdac1
+
+// The instance manager allocates large blocks and then
+// suballocates those for particular instances.
+struct DAC_INSTANCE_BLOCK
+{
+ DAC_INSTANCE_BLOCK* next;
+ ULONG32 bytesUsed;
+ ULONG32 bytesFree;
+};
+
+#define DAC_INSTANCE_BLOCK_ALLOCATION 0x40000
+
+// Sufficient memory is allocated to guarantee storage of the
+// instance header plus room for alignment padding.
+// Once the aligned pointer is found, this structure is prepended to
+// the aligned pointer and therefore doesn't affect the alignment
+// of the actual instance data.
+struct DAC_INSTANCE
+{
+ DAC_INSTANCE* next;
+ TADDR addr;
+ ULONG32 size;
+ // Identifying marker to give a simple
+ // check for host->taddr validity.
+ ULONG32 sig:16;
+ // DPTR or VPTR. See code:DAC_USAGE_TYPE
+ ULONG32 usage:2;
+
+ // Marker that can be used to prevent reporting this memory to the callback
+ // object (via ICLRDataEnumMemoryRegionsCallback:EnumMemoryRegion)
+ // more than once. This bit is checked only by the DacEnumHost?PtrMem
+ // macros, so consistent use of those macros ensures that the memory is
+ // reported at most once
+ ULONG32 enumMem:1;
+
+ // Marker to prevent metadata gets reported to mini-dump
+ ULONG32 noReport:1;
+
+ // Marker to determine if EnumMemoryRegions has been called on
+ // a method descriptor
+ ULONG32 MDEnumed:1;
+
+#ifdef _WIN64
+ // Keep DAC_INSTANCE a multiple of DAC_INSTANCE_ALIGN
+ // bytes in size.
+ ULONG32 pad[2];
+#endif
+};
+
+struct DAC_INSTANCE_PUSH
+{
+ DAC_INSTANCE_PUSH* next;
+ DAC_INSTANCE_BLOCK* blocks;
+ ULONG64 blockMemUsage;
+ ULONG32 numInst;
+ ULONG64 instMemUsage;
+};
+
+// The runtime will want the best access locality possible,
+// so it's likely that many instances will be clustered.
+// The hash function needs to spread near addresses across
+// hash entries, so hash on the low bits of the target address.
+// Not all the way down to the LSB, though, as there generally
+// won't be individual accesses at the byte level. Assume that
+// most accesses will be natural-word aligned.
+#define DAC_INSTANCE_HASH_BITS 10
+#define DAC_INSTANCE_HASH_SHIFT 2
+
+#define DAC_INSTANCE_HASH(addr) \
+ (((ULONG32)(ULONG_PTR)(addr) >> DAC_INSTANCE_HASH_SHIFT) & \
+ ((1 << DAC_INSTANCE_HASH_BITS) - 1))
+#define DAC_INSTANCE_HASH_SIZE (1 << DAC_INSTANCE_HASH_BITS)
+
+
+struct DumpMemoryReportStatics
+{
+ TSIZE_T m_cbStack; // number of bytes that we report directly for stack walk
+ TSIZE_T m_cbNgen; // number of bytes that we report directly for ngen images
+ TSIZE_T m_cbModuleList; // number of bytes that we report for module list directly
+ TSIZE_T m_cbClrStatics; // number of bytes that we report for CLR statics
+ TSIZE_T m_cbClrHeapStatics; // number of bytes that we report for CLR heap statics
+ TSIZE_T m_cbImplicity; // number of bytes that we report implicitly
+};
+
+
+class DacInstanceManager
+{
+public:
+ DacInstanceManager(void);
+ ~DacInstanceManager(void);
+
+ DAC_INSTANCE* Add(DAC_INSTANCE* inst);
+
+ DAC_INSTANCE* Alloc(TADDR addr, ULONG32 size, DAC_USAGE_TYPE usage);
+ void ReturnAlloc(DAC_INSTANCE* inst);
+ DAC_INSTANCE* Find(TADDR addr);
+ HRESULT Write(DAC_INSTANCE* inst, bool throwEx);
+ void Supersede(DAC_INSTANCE* inst);
+ void Flush(void);
+ void Flush(bool fSaveBlock);
+ void ClearEnumMemMarker(void);
+
+ void AddSuperseded(DAC_INSTANCE* inst)
+ {
+ SUPPORTS_DAC;
+ inst->next = m_superseded;
+ m_superseded = inst;
+ }
+
+ UINT DumpAllInstances(ICLRDataEnumMemoryRegionsCallback *pCallBack);
+
+private:
+
+ DAC_INSTANCE_BLOCK* FindInstanceBlock(DAC_INSTANCE* inst);
+ void FreeAllBlocks(bool fSaveBlock);
+
+ void InitEmpty(void)
+ {
+ m_blocks = NULL;
+ // m_unusedBlock is not NULLed here; it can contain one block we will use after
+ // a flush is complete.
+ m_blockMemUsage = 0;
+ m_numInst = 0;
+ m_instMemUsage = 0;
+#ifdef DAC_HASHTABLE
+ ZeroMemory(m_hash, sizeof(m_hash));
+#endif
+ m_superseded = NULL;
+ m_instPushed = NULL;
+ }
+
+#if defined(DAC_HASHTABLE)
+
+ typedef struct _HashInstanceKey {
+ TADDR addr;
+ DAC_INSTANCE* instance;
+ } HashInstanceKey;
+
+ typedef struct _HashInstanceKeyBlock {
+ // Blocks are chained in reverse order of allocation so that the most recently allocated
+ // block is searched first.
+ _HashInstanceKeyBlock* next;
+
+ // Entries to a block are added from the max index on down so that recently added
+ // entries are at the start of the block.
+ DWORD firstElement;
+ HashInstanceKey instanceKeys[] ;
+ } HashInstanceKeyBlock;
+
+// The hashing function does a good job of distributing the entries across buckets. To handle a
+// SO on x86, we have under 250 entries in a bucket. A 4K block size allows 511 entries on x86 and
+// about half that on x64. On x64, the number of entries added to the hash table is significantly
+// smaller than on x86 (and the max recursion depth for default stack sizes is also far less), so
+// 4K is generally adequate.
+
+#define HASH_INSTANCE_BLOCK_ALLOC_SIZE (4 * 1024)
+#define HASH_INSTANCE_BLOCK_NUM_ELEMENTS ((HASH_INSTANCE_BLOCK_ALLOC_SIZE - offsetof(_HashInstanceKeyBlock, instanceKeys))/sizeof(HashInstanceKey))
+#endif // #if defined(DAC_HASHTABLE)
+
+ DAC_INSTANCE_BLOCK* m_blocks;
+ DAC_INSTANCE_BLOCK* m_unusedBlock;
+ ULONG64 m_blockMemUsage;
+ ULONG32 m_numInst;
+ ULONG64 m_instMemUsage;
+
+#if defined(DAC_HASHTABLE)
+ HashInstanceKeyBlock* m_hash[DAC_INSTANCE_HASH_SIZE];
+#else //DAC_HASHTABLE
+
+ // We're going to use the STL unordered_map for our instance hash.
+ // This has the benefit of scaling to different workloads appropriately (as opposed to having a
+ // fixed number of buckets).
+
+ class DacHashCompare : public std::hash_compare<TADDR>
+ {
+ public:
+ // Custom hash function
+ // The default hash function uses a pseudo-randomizing function to get a random
+ // distribution. In our case, we'd actually like a more even distribution to get
+ // better access locality (see comments for DAC_INSTANCE_HASH_BITS).
+ //
+ // Also, when enumerating the hash during dump generation, clustering nearby addresses
+ // together can have a significant positive impact on the performance of the minidump
+ // library (at least the un-optimized version 6.5 linked into our dw20.exe - on Vista+
+ // we use the OS's version 6.7+ with radically improved perf characteristics). Having
+ // a random distribution is actually the worst-case because it means most blocks won't
+ // be merged until near the end, and a large number of intermediate blocks will have to
+ // be searched with each call.
+ //
+ // The default pseudo-randomizing function also requires a call to ldiv which shows up as
+ // a 3%-5% perf hit in most perf-sensitive scenarios, so this should also always be
+ // faster.
+ inline size_t operator()(const TADDR& keyval) const
+ {
+ return (unsigned)(keyval >>DAC_INSTANCE_HASH_SHIFT);
+ }
+
+ // Explicitly bring in the two-argument comparison function from the base class (just less-than)
+ // This is necessary because once we override one form of operator() above, we don't automatically
+ // get the others by C++ inheritance rules.
+ using std::hash_compare<TADDR>::operator();
+
+#ifdef NIDUMP_CUSTOMIZED_DAC_HASH // not set
+ //this particular number is supposed to be roughly the same amount of
+ //memory as the old code (buckets * number of entries in the old
+ //blocks.)
+ //disabled for now. May tweak implementation later. It turns out that
+ //having a large number of initial buckets is excellent for nidump, but it
+ // is terrible for most other scenarios due to the cost of clearing them at
+ // every Flush. Once there is a better perf suite, we can tweak these values more.
+ static const size_t min_buckets = DAC_INSTANCE_HASH_SIZE * 256;
+#endif
+
+ };
+ typedef std::unordered_map<TADDR, DAC_INSTANCE*, DacHashCompare > DacInstanceHash;
+ typedef DacInstanceHash::value_type DacInstanceHashValue;
+ typedef DacInstanceHash::iterator DacInstanceHashIterator;
+ DacInstanceHash m_hash;
+#endif //DAC_HASHTABLE
+
+ DAC_INSTANCE* m_superseded;
+ DAC_INSTANCE_PUSH* m_instPushed;
+};
+
+
+#ifdef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+
+class DacStreamManager;
+
+#endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+
+
+//----------------------------------------------------------------------------
+//
+// ClrDataAccess.
+//
+//----------------------------------------------------------------------------
+
+class ClrDataAccess
+ : public IXCLRDataProcess2,
+ public ICLRDataEnumMemoryRegions,
+ public ISOSDacInterface,
+ public ISOSDacInterface2,
+ public ISOSDacInterface3,
+ public ISOSDacInterface4
+{
+public:
+ ClrDataAccess(ICorDebugDataTarget * pTarget, ICLRDataTarget * pLegacyTarget=0);
+ virtual ~ClrDataAccess(void);
+
+ // IUnknown.
+ STDMETHOD(QueryInterface)(THIS_
+ IN REFIID interfaceId,
+ OUT PVOID* iface);
+ STDMETHOD_(ULONG, AddRef)(THIS);
+ STDMETHOD_(ULONG, Release)(THIS);
+
+ //
+ // IXCLRDataProcess.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE Flush( void);
+
+ virtual HRESULT STDMETHODCALLTYPE StartEnumTasks(
+ /* [out] */ CLRDATA_ENUM *handle);
+
+ virtual HRESULT STDMETHODCALLTYPE EnumTask(
+ /* [in, out] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataTask **task);
+
+ virtual HRESULT STDMETHODCALLTYPE EndEnumTasks(
+ /* [in] */ CLRDATA_ENUM handle);
+
+ virtual HRESULT STDMETHODCALLTYPE GetTaskByOSThreadID(
+ /* [in] */ ULONG32 OSThreadID,
+ /* [out] */ IXCLRDataTask **task);
+
+ virtual HRESULT STDMETHODCALLTYPE GetTaskByUniqueID(
+ /* [in] */ ULONG64 uniqueID,
+ /* [out] */ IXCLRDataTask **task);
+
+ virtual HRESULT STDMETHODCALLTYPE GetFlags(
+ /* [out] */ ULONG32 *flags);
+
+ virtual HRESULT STDMETHODCALLTYPE IsSameObject(
+ /* [in] */ IXCLRDataProcess *process);
+
+ virtual HRESULT STDMETHODCALLTYPE GetManagedObject(
+ /* [out] */ IXCLRDataValue **value);
+
+ virtual HRESULT STDMETHODCALLTYPE GetDesiredExecutionState(
+ /* [out] */ ULONG32 *state);
+
+ virtual HRESULT STDMETHODCALLTYPE SetDesiredExecutionState(
+ /* [in] */ ULONG32 state);
+
+ virtual HRESULT STDMETHODCALLTYPE GetAddressType(
+ /* [in] */ CLRDATA_ADDRESS address,
+ /* [out] */ CLRDataAddressType* type);
+
+ virtual HRESULT STDMETHODCALLTYPE GetRuntimeNameByAddress(
+ /* [in] */ CLRDATA_ADDRESS address,
+ /* [in] */ ULONG32 flags,
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_opt(bufLen) WCHAR nameBuf[ ],
+ /* [out] */ CLRDATA_ADDRESS* displacement);
+
+ virtual HRESULT STDMETHODCALLTYPE StartEnumAppDomains(
+ /* [out] */ CLRDATA_ENUM *handle);
+
+ virtual HRESULT STDMETHODCALLTYPE EnumAppDomain(
+ /* [in, out] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataAppDomain **appDomain);
+
+ virtual HRESULT STDMETHODCALLTYPE EndEnumAppDomains(
+ /* [in] */ CLRDATA_ENUM handle);
+
+ virtual HRESULT STDMETHODCALLTYPE GetAppDomainByUniqueID(
+ /* [in] */ ULONG64 uniqueID,
+ /* [out] */ IXCLRDataAppDomain **appDomain);
+
+ virtual HRESULT STDMETHODCALLTYPE StartEnumAssemblies(
+ /* [out] */ CLRDATA_ENUM *handle);
+
+ virtual HRESULT STDMETHODCALLTYPE EnumAssembly(
+ /* [in, out] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataAssembly **assembly);
+
+ virtual HRESULT STDMETHODCALLTYPE EndEnumAssemblies(
+ /* [in] */ CLRDATA_ENUM handle);
+
+ virtual HRESULT STDMETHODCALLTYPE StartEnumModules(
+ /* [out] */ CLRDATA_ENUM *handle);
+
+ virtual HRESULT STDMETHODCALLTYPE EnumModule(
+ /* [in, out] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataModule **mod);
+
+ virtual HRESULT STDMETHODCALLTYPE EndEnumModules(
+ /* [in] */ CLRDATA_ENUM handle);
+
+ virtual HRESULT STDMETHODCALLTYPE GetModuleByAddress(
+ /* [in] */ CLRDATA_ADDRESS address,
+ /* [out] */ IXCLRDataModule** mod);
+
+ virtual HRESULT STDMETHODCALLTYPE StartEnumMethodDefinitionsByAddress(
+ /* [in] */ CLRDATA_ADDRESS address,
+ /* [out] */ CLRDATA_ENUM *handle);
+
+ virtual HRESULT STDMETHODCALLTYPE EnumMethodDefinitionByAddress(
+ /* [in] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataMethodDefinition **method);
+
+ virtual HRESULT STDMETHODCALLTYPE EndEnumMethodDefinitionsByAddress(
+ /* [in] */ CLRDATA_ENUM handle);
+
+ virtual HRESULT STDMETHODCALLTYPE StartEnumMethodInstancesByAddress(
+ /* [in] */ CLRDATA_ADDRESS address,
+ /* [in] */ IXCLRDataAppDomain* appDomain,
+ /* [out] */ CLRDATA_ENUM *handle);
+
+ virtual HRESULT STDMETHODCALLTYPE EnumMethodInstanceByAddress(
+ /* [in] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataMethodInstance **method);
+
+ virtual HRESULT STDMETHODCALLTYPE EndEnumMethodInstancesByAddress(
+ /* [in] */ CLRDATA_ENUM handle);
+
+ virtual HRESULT STDMETHODCALLTYPE GetDataByAddress(
+ /* [in] */ CLRDATA_ADDRESS address,
+ /* [in] */ ULONG32 flags,
+ /* [in] */ IXCLRDataAppDomain* appDomain,
+ /* [in] */ IXCLRDataTask* tlsTask,
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(bufLen, *nameLen) WCHAR nameBuf[ ],
+ /* [out] */ IXCLRDataValue **value,
+ /* [out] */ CLRDATA_ADDRESS *displacement);
+
+ virtual HRESULT STDMETHODCALLTYPE GetExceptionStateByExceptionRecord(
+ /* [in] */ EXCEPTION_RECORD64 *record,
+ /* [out] */ IXCLRDataExceptionState **exception);
+
+ virtual HRESULT STDMETHODCALLTYPE TranslateExceptionRecordToNotification(
+ /* [in] */ EXCEPTION_RECORD64 *record,
+ /* [in] */ IXCLRDataExceptionNotification *notify);
+
+ virtual HRESULT STDMETHODCALLTYPE CreateMemoryValue(
+ /* [in] */ IXCLRDataAppDomain* appDomain,
+ /* [in] */ IXCLRDataTask* tlsTask,
+ /* [in] */ IXCLRDataTypeInstance* type,
+ /* [in] */ CLRDATA_ADDRESS addr,
+ /* [out] */ IXCLRDataValue** value);
+
+ virtual HRESULT STDMETHODCALLTYPE SetAllTypeNotifications(
+ /* [in] */ IXCLRDataModule* mod,
+ /* [in] */ ULONG32 flags);
+
+ virtual HRESULT STDMETHODCALLTYPE SetAllCodeNotifications(
+ /* [in] */ IXCLRDataModule* mod,
+ /* [in] */ ULONG32 flags);
+
+ virtual HRESULT STDMETHODCALLTYPE GetTypeNotifications(
+ /* [in] */ ULONG32 numTokens,
+ /* [in, size_is(numTokens)] */ IXCLRDataModule* mods[],
+ /* [in] */ IXCLRDataModule* singleMod,
+ /* [in, size_is(numTokens)] */ mdTypeDef tokens[],
+ /* [out, size_is(numTokens)] */ ULONG32 flags[]);
+
+ virtual HRESULT STDMETHODCALLTYPE SetTypeNotifications(
+ /* [in] */ ULONG32 numTokens,
+ /* [in, size_is(numTokens)] */ IXCLRDataModule* mods[],
+ /* [in] */ IXCLRDataModule* singleMod,
+ /* [in, size_is(numTokens)] */ mdTypeDef tokens[],
+ /* [in, size_is(numTokens)] */ ULONG32 flags[],
+ /* [in] */ ULONG32 singleFlags);
+
+ virtual HRESULT STDMETHODCALLTYPE GetCodeNotifications(
+ /* [in] */ ULONG32 numTokens,
+ /* [in, size_is(numTokens)] */ IXCLRDataModule* mods[],
+ /* [in] */ IXCLRDataModule* singleMod,
+ /* [in, size_is(numTokens)] */ mdMethodDef tokens[],
+ /* [out, size_is(numTokens)] */ ULONG32 flags[]);
+
+ virtual HRESULT STDMETHODCALLTYPE SetCodeNotifications(
+ /* [in] */ ULONG32 numTokens,
+ /* [in, size_is(numTokens)] */ IXCLRDataModule* mods[],
+ /* [in] */ IXCLRDataModule* singleMod,
+ /* [in, size_is(numTokens)] */ mdMethodDef tokens[],
+ /* [in, size_is(numTokens)] */ ULONG32 flags[],
+ /* [in] */ ULONG32 singleFlags);
+
+ virtual HRESULT STDMETHODCALLTYPE GetOtherNotificationFlags(
+ /* [out] */ ULONG32* flags);
+
+ virtual HRESULT STDMETHODCALLTYPE SetOtherNotificationFlags(
+ /* [in] */ ULONG32 flags);
+
+ virtual HRESULT STDMETHODCALLTYPE FollowStub(
+ /* [in] */ ULONG32 inFlags,
+ /* [in] */ CLRDATA_ADDRESS inAddr,
+ /* [in] */ CLRDATA_FOLLOW_STUB_BUFFER* inBuffer,
+ /* [out] */ CLRDATA_ADDRESS* outAddr,
+ /* [out] */ CLRDATA_FOLLOW_STUB_BUFFER* outBuffer,
+ /* [out] */ ULONG32* outFlags);
+
+ virtual HRESULT STDMETHODCALLTYPE FollowStub2(
+ /* [in] */ IXCLRDataTask* task,
+ /* [in] */ ULONG32 inFlags,
+ /* [in] */ CLRDATA_ADDRESS inAddr,
+ /* [in] */ CLRDATA_FOLLOW_STUB_BUFFER* inBuffer,
+ /* [out] */ CLRDATA_ADDRESS* outAddr,
+ /* [out] */ CLRDATA_FOLLOW_STUB_BUFFER* outBuffer,
+ /* [out] */ ULONG32* outFlags);
+
+ virtual HRESULT STDMETHODCALLTYPE Request(
+ /* [in] */ ULONG32 reqCode,
+ /* [in] */ ULONG32 inBufferSize,
+ /* [size_is][in] */ BYTE *inBuffer,
+ /* [in] */ ULONG32 outBufferSize,
+ /* [size_is][out] */ BYTE *outBuffer);
+
+
+ //
+ // IXCLRDataProcess2.
+ //
+ STDMETHOD(GetGcNotification)(/* [out] */ GcEvtArgs* gcEvtArgs);
+ STDMETHOD(SetGcNotification)(/* [in] */ GcEvtArgs gcEvtArgs);
+
+ //
+ // ICLRDataEnumMemoryRegions.
+ //
+ virtual HRESULT STDMETHODCALLTYPE EnumMemoryRegions(
+ /* [in] */ ICLRDataEnumMemoryRegionsCallback *callback,
+ /* [in] */ ULONG32 miniDumpFlags,
+ /* [in] */ CLRDataEnumMemoryFlags clrFlags);
+
+
+ // ISOSDacInterface
+ virtual HRESULT STDMETHODCALLTYPE GetThreadStoreData(struct DacpThreadStoreData *data);
+ virtual HRESULT STDMETHODCALLTYPE GetAppDomainStoreData(struct DacpAppDomainStoreData *data);
+ virtual HRESULT STDMETHODCALLTYPE GetAppDomainList(unsigned int count, CLRDATA_ADDRESS values[], unsigned int *pNeeded);
+ virtual HRESULT STDMETHODCALLTYPE GetAppDomainData(CLRDATA_ADDRESS addr, struct DacpAppDomainData *data);
+ virtual HRESULT STDMETHODCALLTYPE GetAppDomainName(CLRDATA_ADDRESS addr, unsigned int count, __out_z __inout_ecount(count) wchar_t *name, unsigned int *pNeeded);
+ virtual HRESULT STDMETHODCALLTYPE GetAssemblyList(CLRDATA_ADDRESS appDomain, int count, CLRDATA_ADDRESS values[], int *fetched);
+ virtual HRESULT STDMETHODCALLTYPE GetAssemblyData(CLRDATA_ADDRESS baseDomainPtr, CLRDATA_ADDRESS assembly, struct DacpAssemblyData *data);
+ virtual HRESULT STDMETHODCALLTYPE GetAssemblyName(CLRDATA_ADDRESS assembly, unsigned int count, __out_z __inout_ecount(count) wchar_t *name, unsigned int *pNeeded);
+ virtual HRESULT STDMETHODCALLTYPE GetThreadData(CLRDATA_ADDRESS thread, struct DacpThreadData *data);
+ virtual HRESULT STDMETHODCALLTYPE GetThreadFromThinlockID(UINT thinLockId, CLRDATA_ADDRESS *pThread);
+ virtual HRESULT STDMETHODCALLTYPE GetStackLimits(CLRDATA_ADDRESS threadPtr, CLRDATA_ADDRESS *lower, CLRDATA_ADDRESS *upper, CLRDATA_ADDRESS *fp);
+ virtual HRESULT STDMETHODCALLTYPE GetDomainFromContext(CLRDATA_ADDRESS context, CLRDATA_ADDRESS *domain);
+
+ virtual HRESULT STDMETHODCALLTYPE GetMethodDescData(CLRDATA_ADDRESS methodDesc, CLRDATA_ADDRESS ip, struct DacpMethodDescData *data, ULONG cRevertedRejitVersions, DacpReJitData * rgRevertedRejitData, ULONG * pcNeededRevertedRejitData);
+ virtual HRESULT STDMETHODCALLTYPE GetMethodDescPtrFromIP(CLRDATA_ADDRESS ip, CLRDATA_ADDRESS * ppMD);
+ virtual HRESULT STDMETHODCALLTYPE GetMethodDescName(CLRDATA_ADDRESS methodDesc, unsigned int count, __out_z __inout_ecount(count) wchar_t *name, unsigned int *pNeeded);
+ virtual HRESULT STDMETHODCALLTYPE GetMethodDescPtrFromFrame(CLRDATA_ADDRESS frameAddr, CLRDATA_ADDRESS * ppMD);
+ virtual HRESULT STDMETHODCALLTYPE GetCodeHeaderData(CLRDATA_ADDRESS ip, struct DacpCodeHeaderData *data);
+ virtual HRESULT STDMETHODCALLTYPE GetThreadpoolData(struct DacpThreadpoolData *data);
+ virtual HRESULT STDMETHODCALLTYPE GetWorkRequestData(CLRDATA_ADDRESS addrWorkRequest, struct DacpWorkRequestData *data);
+ virtual HRESULT STDMETHODCALLTYPE GetObjectData(CLRDATA_ADDRESS objAddr, struct DacpObjectData *data);
+ virtual HRESULT STDMETHODCALLTYPE GetObjectStringData(CLRDATA_ADDRESS obj, unsigned int count, __out_z __inout_ecount(count) wchar_t *stringData, unsigned int *pNeeded);
+ virtual HRESULT STDMETHODCALLTYPE GetObjectClassName(CLRDATA_ADDRESS obj, unsigned int count, __out_z __inout_ecount(count) wchar_t *className, unsigned int *pNeeded);
+ virtual HRESULT STDMETHODCALLTYPE GetMethodTableName(CLRDATA_ADDRESS mt, unsigned int count, __out_z __inout_ecount(count) wchar_t *mtName, unsigned int *pNeeded);
+ virtual HRESULT STDMETHODCALLTYPE GetMethodTableData(CLRDATA_ADDRESS mt, struct DacpMethodTableData *data);
+ virtual HRESULT STDMETHODCALLTYPE GetMethodTableFieldData(CLRDATA_ADDRESS mt, struct DacpMethodTableFieldData *data);
+ virtual HRESULT STDMETHODCALLTYPE GetMethodTableTransparencyData(CLRDATA_ADDRESS mt, struct DacpMethodTableTransparencyData *data);
+ virtual HRESULT STDMETHODCALLTYPE GetMethodTableForEEClass(CLRDATA_ADDRESS eeClass, CLRDATA_ADDRESS *value);
+ virtual HRESULT STDMETHODCALLTYPE GetFieldDescData(CLRDATA_ADDRESS fieldDesc, struct DacpFieldDescData *data);
+ virtual HRESULT STDMETHODCALLTYPE GetFrameName(CLRDATA_ADDRESS vtable, unsigned int count, __out_z __inout_ecount(count) wchar_t *frameName, unsigned int *pNeeded);
+ virtual HRESULT STDMETHODCALLTYPE GetModule(CLRDATA_ADDRESS addr, IXCLRDataModule **mod);
+ virtual HRESULT STDMETHODCALLTYPE GetModuleData(CLRDATA_ADDRESS moduleAddr, struct DacpModuleData *data);
+ virtual HRESULT STDMETHODCALLTYPE TraverseModuleMap(ModuleMapType mmt, CLRDATA_ADDRESS moduleAddr, MODULEMAPTRAVERSE pCallback, LPVOID token);
+ virtual HRESULT STDMETHODCALLTYPE GetMethodDescFromToken(CLRDATA_ADDRESS moduleAddr, mdToken token, CLRDATA_ADDRESS *methodDesc);
+ virtual HRESULT STDMETHODCALLTYPE GetPEFileBase(CLRDATA_ADDRESS addr, CLRDATA_ADDRESS *base);
+ virtual HRESULT STDMETHODCALLTYPE GetPEFileName(CLRDATA_ADDRESS addr, unsigned int count, __out_z __inout_ecount(count) wchar_t *fileName, unsigned int *pNeeded);
+ virtual HRESULT STDMETHODCALLTYPE GetAssemblyModuleList(CLRDATA_ADDRESS assembly, unsigned int count, CLRDATA_ADDRESS modules[], unsigned int *pNeeded);
+ virtual HRESULT STDMETHODCALLTYPE GetGCHeapData(struct DacpGcHeapData *data);
+ virtual HRESULT STDMETHODCALLTYPE GetGCHeapList(unsigned int count, CLRDATA_ADDRESS heaps[], unsigned int *pNeeded);
+ virtual HRESULT STDMETHODCALLTYPE GetGCHeapDetails(CLRDATA_ADDRESS heap, struct DacpGcHeapDetails *details);
+ virtual HRESULT STDMETHODCALLTYPE GetGCHeapStaticData(struct DacpGcHeapDetails *data);
+ virtual HRESULT STDMETHODCALLTYPE GetHeapSegmentData(CLRDATA_ADDRESS seg, struct DacpHeapSegmentData *data);
+ virtual HRESULT STDMETHODCALLTYPE GetDomainLocalModuleData(CLRDATA_ADDRESS addr, struct DacpDomainLocalModuleData *data);
+ virtual HRESULT STDMETHODCALLTYPE GetDomainLocalModuleDataFromAppDomain(CLRDATA_ADDRESS appDomainAddr, int moduleID, struct DacpDomainLocalModuleData *data);
+ virtual HRESULT STDMETHODCALLTYPE GetDomainLocalModuleDataFromModule(CLRDATA_ADDRESS moduleAddr, struct DacpDomainLocalModuleData *data);
+ virtual HRESULT STDMETHODCALLTYPE GetSyncBlockData(unsigned int number, struct DacpSyncBlockData *data);
+ virtual HRESULT STDMETHODCALLTYPE GetSyncBlockCleanupData(CLRDATA_ADDRESS addr, struct DacpSyncBlockCleanupData *data);
+ virtual HRESULT STDMETHODCALLTYPE TraverseRCWCleanupList(CLRDATA_ADDRESS cleanupListPtr, VISITRCWFORCLEANUP pCallback, LPVOID token);
+ virtual HRESULT STDMETHODCALLTYPE TraverseEHInfo(CLRDATA_ADDRESS ip, DUMPEHINFO pCallback, LPVOID token);
+ virtual HRESULT STDMETHODCALLTYPE GetStressLogAddress(CLRDATA_ADDRESS *stressLog);
+ virtual HRESULT STDMETHODCALLTYPE GetJitManagerList(unsigned int count, struct DacpJitManagerInfo managers[], unsigned int *pNeeded);
+ virtual HRESULT STDMETHODCALLTYPE GetJitHelperFunctionName(CLRDATA_ADDRESS ip, unsigned int count, __out_z __inout_ecount(count) char *name, unsigned int *pNeeded);
+ virtual HRESULT STDMETHODCALLTYPE GetJumpThunkTarget(T_CONTEXT *ctx, CLRDATA_ADDRESS *targetIP, CLRDATA_ADDRESS *targetMD);
+ virtual HRESULT STDMETHODCALLTYPE TraverseLoaderHeap(CLRDATA_ADDRESS loaderHeapAddr, VISITHEAP pCallback);
+ virtual HRESULT STDMETHODCALLTYPE GetCodeHeapList(CLRDATA_ADDRESS jitManager, unsigned int count, struct DacpJitCodeHeapInfo codeHeaps[], unsigned int *pNeeded);
+ virtual HRESULT STDMETHODCALLTYPE GetMethodTableSlot(CLRDATA_ADDRESS mt, unsigned int slot, CLRDATA_ADDRESS *value);
+ virtual HRESULT STDMETHODCALLTYPE TraverseVirtCallStubHeap(CLRDATA_ADDRESS pAppDomain, VCSHeapType heaptype, VISITHEAP pCallback);
+ virtual HRESULT STDMETHODCALLTYPE GetNestedExceptionData(CLRDATA_ADDRESS exception, CLRDATA_ADDRESS *exceptionObject, CLRDATA_ADDRESS *nextNestedException);
+ virtual HRESULT STDMETHODCALLTYPE GetUsefulGlobals(struct DacpUsefulGlobalsData *data);
+ virtual HRESULT STDMETHODCALLTYPE GetILForModule(CLRDATA_ADDRESS moduleAddr, DWORD rva, CLRDATA_ADDRESS *il);
+ virtual HRESULT STDMETHODCALLTYPE GetClrWatsonBuckets(CLRDATA_ADDRESS thread, void *pGenericModeBlock);
+ virtual HRESULT STDMETHODCALLTYPE GetOOMData(CLRDATA_ADDRESS oomAddr, struct DacpOomData *data);
+ virtual HRESULT STDMETHODCALLTYPE GetOOMStaticData(struct DacpOomData *data);
+ virtual HRESULT STDMETHODCALLTYPE GetHeapAnalyzeData(CLRDATA_ADDRESS addr,struct DacpGcHeapAnalyzeData *data);
+ virtual HRESULT STDMETHODCALLTYPE GetHeapAnalyzeStaticData(struct DacpGcHeapAnalyzeData *data);
+ virtual HRESULT STDMETHODCALLTYPE GetMethodDescTransparencyData(CLRDATA_ADDRESS methodDesc, struct DacpMethodDescTransparencyData *data);
+ virtual HRESULT STDMETHODCALLTYPE GetHillClimbingLogEntry(CLRDATA_ADDRESS addr, struct DacpHillClimbingLogEntry *data);
+ virtual HRESULT STDMETHODCALLTYPE GetThreadLocalModuleData(CLRDATA_ADDRESS thread, unsigned int index, struct DacpThreadLocalModuleData *data);
+ virtual HRESULT STDMETHODCALLTYPE GetRCWData(CLRDATA_ADDRESS addr, struct DacpRCWData *data);
+ virtual HRESULT STDMETHODCALLTYPE GetRCWInterfaces(CLRDATA_ADDRESS rcw, unsigned int count, struct DacpCOMInterfacePointerData interfaces[], unsigned int *pNeeded);
+ virtual HRESULT STDMETHODCALLTYPE GetCCWData(CLRDATA_ADDRESS ccw, struct DacpCCWData *data);
+ virtual HRESULT STDMETHODCALLTYPE GetCCWInterfaces(CLRDATA_ADDRESS ccw, unsigned int count, struct DacpCOMInterfacePointerData interfaces[], unsigned int *pNeeded);
+ virtual HRESULT STDMETHODCALLTYPE GetTLSIndex(ULONG *pIndex);
+ virtual HRESULT STDMETHODCALLTYPE GetDacModuleHandle(HMODULE *phModule);
+
+ virtual HRESULT STDMETHODCALLTYPE GetFailedAssemblyList(CLRDATA_ADDRESS appDomain, int count, CLRDATA_ADDRESS values[], unsigned int *pNeeded);
+ virtual HRESULT STDMETHODCALLTYPE GetPrivateBinPaths(CLRDATA_ADDRESS appDomain, int count, __out_z __inout_ecount(count) wchar_t *paths, unsigned int *pNeeded);
+ virtual HRESULT STDMETHODCALLTYPE GetAssemblyLocation(CLRDATA_ADDRESS assembly, int count, __out_z __inout_ecount(count) wchar_t *location, unsigned int *pNeeded);
+ virtual HRESULT STDMETHODCALLTYPE GetAppDomainConfigFile(CLRDATA_ADDRESS appDomain, int count, __out_z __inout_ecount(count) wchar_t *configFile, unsigned int *pNeeded);
+ virtual HRESULT STDMETHODCALLTYPE GetApplicationBase(CLRDATA_ADDRESS appDomain, int count, __out_z __inout_ecount(count) wchar_t *base, unsigned int *pNeeded);
+
+ virtual HRESULT STDMETHODCALLTYPE GetFailedAssemblyData(CLRDATA_ADDRESS assembly, unsigned int *pContext, HRESULT *pResult);
+ virtual HRESULT STDMETHODCALLTYPE GetFailedAssemblyLocation(CLRDATA_ADDRESS assembly, unsigned int count, __out_z __inout_ecount(count) wchar_t *location, unsigned int *pNeeded);
+ virtual HRESULT STDMETHODCALLTYPE GetFailedAssemblyDisplayName(CLRDATA_ADDRESS assembly, unsigned int count, __out_z __inout_ecount(count) wchar_t *name, unsigned int *pNeeded);
+
+ virtual HRESULT STDMETHODCALLTYPE GetStackReferences(DWORD osThreadID, ISOSStackRefEnum **ppEnum);
+ virtual HRESULT STDMETHODCALLTYPE GetRegisterName(int regNum, unsigned int count, __out_z __inout_ecount(count) wchar_t *buffer, unsigned int *pNeeded);
+
+ virtual HRESULT STDMETHODCALLTYPE GetHandleEnum(ISOSHandleEnum **ppHandleEnum);
+ virtual HRESULT STDMETHODCALLTYPE GetHandleEnumForTypes(unsigned int types[], unsigned int count, ISOSHandleEnum **ppHandleEnum);
+ virtual HRESULT STDMETHODCALLTYPE GetHandleEnumForGC(unsigned int gen, ISOSHandleEnum **ppHandleEnum);
+
+ virtual HRESULT STDMETHODCALLTYPE GetThreadAllocData(CLRDATA_ADDRESS thread, struct DacpAllocData *data);
+ virtual HRESULT STDMETHODCALLTYPE GetHeapAllocData(unsigned int count, struct DacpGenerationAllocData *data, unsigned int *pNeeded);
+
+ // ISOSDacInterface2
+ virtual HRESULT STDMETHODCALLTYPE GetObjectExceptionData(CLRDATA_ADDRESS objAddr, struct DacpExceptionObjectData *data);
+ virtual HRESULT STDMETHODCALLTYPE IsRCWDCOMProxy(CLRDATA_ADDRESS rcwAddr, BOOL* isDCOMProxy);
+
+ // ISOSDacInterface3
+ virtual HRESULT STDMETHODCALLTYPE GetGCInterestingInfoData(CLRDATA_ADDRESS interestingInfoAddr, struct DacpGCInterestingInfoData *data);
+ virtual HRESULT STDMETHODCALLTYPE GetGCInterestingInfoStaticData(struct DacpGCInterestingInfoData *data);
+ virtual HRESULT STDMETHODCALLTYPE GetGCGlobalMechanisms(size_t* globalMechanisms);
+
+ // ISOSDacInterface4
+ virtual HRESULT STDMETHODCALLTYPE GetClrNotification(CLRDATA_ADDRESS arguments[], int count, int *pNeeded);
+
+ //
+ // ClrDataAccess.
+ //
+
+ HRESULT Initialize(void);
+
+ BOOL IsExceptionFromManagedCode(EXCEPTION_RECORD * pExceptionRecord);
+#ifndef FEATURE_PAL
+ HRESULT GetWatsonBuckets(DWORD dwThreadId, GenericModeBlock * pGM);
+#endif // FEATURE_PAL
+
+
+ Thread* FindClrThreadByTaskId(ULONG64 taskId);
+ HRESULT IsPossibleCodeAddress(IN TADDR address);
+
+ PCSTR GetJitHelperName(IN TADDR address,
+ IN bool dynamicHelpersOnly = false);
+ HRESULT GetFullMethodName(IN MethodDesc* methodDesc,
+ IN ULONG32 symbolChars,
+ IN ULONG32* symbolLen,
+ __out_ecount_part_opt(symbolChars, *symbolLen) LPWSTR symbol);
+ HRESULT RawGetMethodName(/* [in] */ CLRDATA_ADDRESS address,
+ /* [in] */ ULONG32 flags,
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_opt(bufLen) WCHAR nameBuf[ ],
+ /* [out] */ CLRDATA_ADDRESS* displacement);
+
+ HRESULT FollowStubStep(
+ /* [in] */ Thread* thread,
+ /* [in] */ ULONG32 inFlags,
+ /* [in] */ TADDR inAddr,
+ /* [in] */ union STUB_BUF* inBuffer,
+ /* [out] */ TADDR* outAddr,
+ /* [out] */ union STUB_BUF* outBuffer,
+ /* [out] */ ULONG32* outFlags);
+
+ DebuggerJitInfo* GetDebuggerJitInfo(MethodDesc* methodDesc,
+ TADDR addr)
+ {
+ if (g_pDebugger)
+ {
+ return g_pDebugger->GetJitInfo(methodDesc, (PBYTE)addr, NULL);
+ }
+
+ return NULL;
+ }
+
+ HRESULT GetMethodExtents(MethodDesc* methodDesc,
+ METH_EXTENTS** extents);
+ HRESULT GetMethodVarInfo(MethodDesc* methodDesc,
+ TADDR address,
+ ULONG32* numVarInfo,
+ ICorDebugInfo::NativeVarInfo** varInfo,
+ ULONG32* codeOffset);
+
+ // If the method has multiple copies of code (because of EnC or code-pitching),
+ // this returns the info corresponding to address.
+ // If 'address' and 'codeOffset' are both non-NULL, *codeOffset gets set to
+ // the offset of 'address' from the start of the method.
+ HRESULT GetMethodNativeMap(MethodDesc* methodDesc,
+ TADDR address,
+ ULONG32* numMap,
+ DebuggerILToNativeMap** map,
+ bool* mapAllocated,
+ CLRDATA_ADDRESS* codeStart,
+ ULONG32* codeOffset);
+
+ // Get the MethodDesc for a function
+ MethodDesc * FindLoadedMethodRefOrDef(Module* pModule, mdToken memberRef);
+
+#ifndef FEATURE_PAL
+ HRESULT GetClrWatsonBucketsWorker(Thread * pThread, GenericModeBlock * pGM);
+#endif // FEATURE_PAL
+
+ HRESULT ServerGCHeapDetails(CLRDATA_ADDRESS heapAddr,
+ DacpGcHeapDetails *detailsData);
+ HRESULT GetServerAllocData(unsigned int count, struct DacpGenerationAllocData *data, unsigned int *pNeeded);
+ HRESULT ServerOomData(CLRDATA_ADDRESS addr, DacpOomData *oomData);
+ HRESULT ServerGCInterestingInfoData(CLRDATA_ADDRESS addr, DacpGCInterestingInfoData *interestingInfoData);
+ HRESULT ServerGCHeapAnalyzeData(CLRDATA_ADDRESS heapAddr,
+ DacpGcHeapAnalyzeData *analyzeData);
+
+ //
+ // Memory enumeration.
+ //
+
+ HRESULT EnumMemoryRegionsWrapper(CLRDataEnumMemoryFlags flags);
+
+ // skinny minidump functions
+ HRESULT EnumMemoryRegionsWorkerSkinny(CLRDataEnumMemoryFlags flags);
+ // triage minidump functions
+ HRESULT EnumMemoryRegionsWorkerMicroTriage(CLRDataEnumMemoryFlags flags);
+ HRESULT EnumMemoryRegionsWorkerHeap(CLRDataEnumMemoryFlags flags);
+
+ HRESULT EnumMemWalkStackHelper(CLRDataEnumMemoryFlags flags, IXCLRDataStackWalk *pStackWalk, Thread * pThread);
+ HRESULT DumpManagedObject(CLRDataEnumMemoryFlags flags, OBJECTREF objRef);
+ HRESULT DumpManagedExcepObject(CLRDataEnumMemoryFlags flags, OBJECTREF objRef);
+ HRESULT DumpManagedStackTraceStringObject(CLRDataEnumMemoryFlags flags, STRINGREF orefStackTrace);
+#ifdef FEATURE_COMINTEROP
+ HRESULT DumpStowedExceptionObject(CLRDataEnumMemoryFlags flags, CLRDATA_ADDRESS ccwPtr);
+ HRESULT EnumMemStowedException(CLRDataEnumMemoryFlags flags);
+#endif
+
+ HRESULT EnumMemWriteDataSegment();
+
+ // Custom Dump
+ HRESULT EnumMemoryRegionsWorkerCustom();
+
+ // helper function for dump code
+ void EnumWksGlobalMemoryRegions(CLRDataEnumMemoryFlags flags);
+ void EnumSvrGlobalMemoryRegions(CLRDataEnumMemoryFlags flags);
+
+ HRESULT EnumMemCollectImages();
+ HRESULT EnumMemCLRStatic(CLRDataEnumMemoryFlags flags);
+ HRESULT EnumMemCLRHeapCrticalStatic(CLRDataEnumMemoryFlags flags);
+ HRESULT EnumMemDumpModuleList(CLRDataEnumMemoryFlags flags);
+ HRESULT EnumMemDumpAppDomainInfo(CLRDataEnumMemoryFlags flags);
+ HRESULT EnumMemDumpAllThreadsStack(CLRDataEnumMemoryFlags flags);
+ HRESULT EnumMemCLRMainModuleInfo();
+
+ bool ReportMem(TADDR addr, TSIZE_T size, bool fExpectSuccess = true);
+ bool DacUpdateMemoryRegion(TADDR addr, TSIZE_T bufferSize, BYTE* buffer);
+
+ void ClearDumpStats();
+ JITNotification* GetHostJitNotificationTable();
+ GcNotification* GetHostGcNotificationTable();
+
+ void* GetMetaDataFromHost(PEFile* peFile,
+ bool* isAlternate);
+
+ virtual
+ interface IMDInternalImport* GetMDImport(const PEFile* peFile,
+ const ReflectionModule* reflectionModule,
+ bool throwEx);
+
+ interface IMDInternalImport* GetMDImport(const PEFile* peFile,
+ bool throwEx)
+ {
+ return GetMDImport(peFile, NULL, throwEx);
+ }
+
+ interface IMDInternalImport* GetMDImport(const ReflectionModule* reflectionModule,
+ bool throwEx)
+ {
+ return GetMDImport(NULL, reflectionModule, throwEx);
+ }
+
+ //ClrDump support
+ HRESULT STDMETHODCALLTYPE DumpNativeImage(CLRDATA_ADDRESS loadedBase,
+ LPCWSTR name,
+ IXCLRDataDisplay *display,
+ IXCLRLibrarySupport *support,
+ IXCLRDisassemblySupport *dis);
+
+ // Set whether inconsistencies in the target should raise asserts.
+ void SetTargetConsistencyChecks(bool fEnableAsserts);
+
+ // Get whether inconsistencies in the target should raise asserts.
+ bool TargetConsistencyAssertsEnabled();
+
+ // Get the ICLRDataTarget2 instance, if any
+ ICLRDataTarget2 * GetLegacyTarget2() { return m_pLegacyTarget2; }
+
+ // Get the ICLRDataTarget3 instance, if any
+ ICLRDataTarget3 * GetLegacyTarget3() { return m_pLegacyTarget3; }
+
+ //
+ // Public Fields
+ // Note that it would be nice if all of these were made private. However, the visibility
+ // model of the DAC implementation is that the public surface area is declared in daccess.h
+ // (implemented in dacfn.cpp), and the private surface area (like ClrDataAccess) is declared
+ // in dacimpl.h which is only included by the DAC infrastructure. Therefore the DAC
+ // infrastructure agressively uses these fields, and we don't get a huge amount of benefit from
+ // reworking this model (since there is some form of encapsulation in place already).
+ //
+
+ // The underlying data target - always present (strong reference)
+ ICorDebugDataTarget * m_pTarget;
+
+ // Mutable version of the data target if any - optional (strong reference)
+ ICorDebugMutableDataTarget * m_pMutableTarget;
+
+ TADDR m_globalBase;
+ DacInstanceManager m_instances;
+ ULONG32 m_instanceAge;
+ bool m_debugMode;
+
+#ifdef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+
+protected:
+ DacStreamManager * m_streams;
+
+public:
+ // Used to mark the point after which enumerated EE structs of interest
+ // will get their names cached in the triage/mini-dump
+ void InitStreamsForWriting(IN CLRDataEnumMemoryFlags flags);
+
+ // Used during triage/mini-dump collection to populate the map of
+ // pointers to EE struct (MethodDesc* for now) to their corresponding
+ // name.
+ bool MdCacheAddEEName(TADDR taEEStruct, const SString& name);
+
+ // Used to mark the end point for the name caching. Will update streams
+ // based on built caches
+ void EnumStreams(IN CLRDataEnumMemoryFlags flags);
+
+ // Used during triage/mini-dump analysis to retrieve the name associated
+ // with an EE struct pointer (MethodDesc* for now).
+ bool MdCacheGetEEName(TADDR taEEStruct, SString & eeName);
+
+#endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+
+private:
+ // Read the DAC table and initialize g_dacGlobals
+ HRESULT GetDacGlobals();
+
+ // Verify the target mscorwks.dll matches the version expected
+ HRESULT VerifyDlls();
+
+ // Check whether a region of memory is fully readable.
+ bool IsFullyReadable(TADDR addr, TSIZE_T size);
+
+ // Legacy target interfaces - optional
+ ICLRDataTarget * m_pLegacyTarget;
+ ICLRDataTarget2 * m_pLegacyTarget2;
+ ICLRDataTarget3 * m_pLegacyTarget3;
+ IXCLRDataTarget3 * m_target3;
+ ICLRMetadataLocator * m_legacyMetaDataLocator;
+
+ LONG m_refs;
+ HRESULT m_memStatus;
+ MDImportsCache m_mdImports;
+ ICLRDataEnumMemoryRegionsCallback* m_enumMemCb;
+ ICLRDataEnumMemoryRegionsCallback2* m_updateMemCb;
+ CLRDataEnumMemoryFlags m_enumMemFlags;
+ JITNotification* m_jitNotificationTable;
+ GcNotification* m_gcNotificationTable;
+ TSIZE_T m_cbMemoryReported;
+ DumpMemoryReportStatics m_dumpStats;
+
+ // If true, inconsistencies in the target will cause ASSERTs to be raised in DEBUG builds
+ bool m_fEnableTargetConsistencyAsserts;
+
+#ifdef _DEBUG
+protected:
+ // If true, a mscorwks/mscordacwks mismatch will trigger a nice assert dialog
+ bool m_fEnableDllVerificationAsserts;
+private:
+#endif
+
+#ifdef FEATURE_COMINTEROP
+protected:
+ // Returns CCW pointer based on a target address.
+ PTR_ComCallWrapper DACGetCCWFromAddress(CLRDATA_ADDRESS addr);
+
+private:
+ // Returns COM interface pointer corresponding to a given CCW and internal vtable
+ // index. Returns NULL if the vtable is unused or not fully laid out.
+ PTR_IUnknown DACGetCOMIPFromCCW(PTR_ComCallWrapper pCCW, int vtableIndex);
+#endif
+
+ static LONG s_procInit;
+
+public:
+ // APIs for picking up the info needed for a debugger to look up an ngen image or IL image
+ // from it's search path.
+ static bool GetMetaDataFileInfoFromPEFile(PEFile *pPEFile,
+ DWORD &dwImageTimestamp,
+ DWORD &dwImageSize,
+ DWORD &dwDataSize,
+ DWORD &dwRvaHint,
+ bool &isNGEN,
+ __out_ecount(cchFilePath) LPWSTR wszFilePath,
+ DWORD cchFilePath);
+
+ static bool GetILImageInfoFromNgenPEFile(PEFile *peFile,
+ DWORD &dwTimeStamp,
+ DWORD &dwSize,
+ __out_ecount(cchPath) LPWSTR wszPath,
+ const DWORD cchPath);
+#if defined(FEATURE_CORESYSTEM)
+ static bool GetILImageNameFromNgenImage(LPCWSTR ilExtension,
+ __out_ecount(cchFilePath) LPWSTR wszFilePath,
+ const DWORD cchFilePath);
+#endif // FEATURE_CORESYSTEM
+};
+
+extern ClrDataAccess* g_dacImpl;
+
+/* DacHandleWalker.
+ *
+ * Iterates over the handle table, enumerating all handles of the requested type on the
+ * handle table. This also will report the handle type, whether the handle is a strong
+ * reference, the AppDomain the handle comes from, as well as the reference count (in
+ * the case of a RefCount handle). Optionally this class can also be used to filter
+ * based on GC generation that would be collected (that is, to emulate a GC scan of the
+ * handle table).
+ *
+ * General implementation details:
+ * We have four sets of variables:
+ * 1. Overhead variables needed to operate in the Dac.
+ * 2. Variables needed to walk the handle table. We walk the handle table one bucket
+ * at a time, filling the array the user gave us until we have either enumerated
+ * all handles, or filled the array.
+ * 3. Storage variables to hold the overflow. That is, we were walking the handle
+ * table, filled the array that the user gave us, then needed to store the extra
+ * handles the handle table continued to enumerate to us. This is implmeneted
+ * as a linked list of arrays (mHead, mHead.Next, etc).
+ * 4. Variables which store the location of where we are in the overflow data.
+ *
+ * Note that "mHead" is a HandleChunkHead where we stuff the user's array. Everything
+ * which follows mHead (mHead.Next, etc) is a HandleChunk containing overflow data.
+ *
+ * Lastly, note this does not do robust error handling. If we fail to allocate a
+ * HandleChunk while walking the handle table, we will miss handles and not report
+ * this to the user. Unfortunately this will have to be fixed in the next iteration
+ * when we add more robust error handling to SOS's interface.
+ */
+ template <class T>
+class DefaultCOMImpl : public T
+{
+public:
+ DefaultCOMImpl()
+ : mRef(0)
+ {
+ }
+
+ virtual ~DefaultCOMImpl() {}
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return ++mRef;
+ }
+
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ ULONG res = mRef--;
+ if (res == 0)
+ delete this;
+ return res;
+ }
+
+ HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppObj)
+ {
+ if (ppObj == NULL)
+ return E_INVALIDARG;
+
+ if (IsEqualIID(riid, IID_IUnknown))
+ {
+ AddRef();
+ *ppObj = static_cast<IUnknown*>(this);
+ return S_OK;
+ }
+ else if (IsEqualIID(riid, __uuidof(T)))
+ {
+ AddRef();
+ *ppObj = static_cast<T*>(this);
+ return S_OK;
+ }
+
+ *ppObj = NULL;
+ return E_NOINTERFACE;
+ }
+
+private:
+ ULONG mRef;
+};
+
+
+// A stuct representing a thread's allocation context.
+struct AllocInfo
+{
+ CORDB_ADDRESS Ptr;
+ CORDB_ADDRESS Limit;
+
+ AllocInfo()
+ : Ptr(0), Limit(0)
+ {
+ }
+};
+
+// A struct representing a segment in the heap.
+struct SegmentData
+{
+ CORDB_ADDRESS Start;
+ CORDB_ADDRESS End;
+
+ // Whether this segment is part of the large object heap.
+ int Generation;
+
+ SegmentData()
+ : Start(0), End(0), Generation(0)
+ {
+ }
+};
+
+// A struct representing a gc heap in the process.
+struct HeapData
+{
+ CORDB_ADDRESS YoungestGenPtr;
+ CORDB_ADDRESS YoungestGenLimit;
+
+ CORDB_ADDRESS Gen0Start;
+ CORDB_ADDRESS Gen0End;
+
+ CORDB_ADDRESS Gen1Start;
+ size_t EphemeralSegment;
+
+ size_t SegmentCount;
+ SegmentData *Segments;
+
+ HeapData();
+ ~HeapData();
+};
+
+/* This cache is used to read data from the target process if the reads are known
+ * to be sequential. This will object will read one page of memory out of the
+ * process at a time, aligned to the page boundary, to
+ */
+class LinearReadCache
+{
+public:
+ LinearReadCache();
+ ~LinearReadCache();
+
+ /* Reads an address out of the target process, caching the page of memory read.
+ * Params:
+ * addr - The address to read out of the target process.
+ * t - A pointer to the data to stuff it in. We will read sizeof(T) data
+ * from the process and write it into the location t points to. This
+ * parameter must be non-null.
+ * Returns:
+ * True if the read succeeded. False if it did not, usually as a result
+ * of the memory simply not being present in the target process.
+ * Note:
+ * The state of *t is undefined if this function returns false. We may
+ * have written partial data to it if we return false, so you must
+ * absolutely NOT use it if Read returns false.
+ */
+ template <class T>
+ bool Read(CORDB_ADDRESS addr, T *t)
+ {
+ _ASSERTE(t);
+
+ // Unfortunately the ctor can fail the alloc for the byte array. In this case
+ // we'll just fall back to non-cached reads.
+ if (mPage == NULL)
+ return MisalignedRead(addr, t);
+
+ // Is addr on the current page? If not read the page of memory addr is on.
+ // If this fails, we will fall back to a raw read out of the process (which
+ // is what MisalignedRead does).
+ if ((addr < mCurrPageStart) || (addr - mCurrPageStart > mCurrPageSize))
+ if (!MoveToPage(addr))
+ return MisalignedRead(addr, t);
+
+ // If MoveToPage succeeds, we MUST be on the right page.
+ _ASSERTE(addr >= mCurrPageStart);
+
+ // However, the amount of data requested may fall off of the page. In that case,
+ // fall back to MisalignedRead.
+ CORDB_ADDRESS offset = addr - mCurrPageStart;
+ if (offset + sizeof(T) > mCurrPageSize)
+ return MisalignedRead(addr, t);
+
+ // If we reach here we know we are on the right page of memory in the cache, and
+ // that the read won't fall off of the end of the page.
+ *t = *reinterpret_cast<T*>(mPage+offset);
+ return true;
+ }
+
+ // helper used to read the MethodTable
+ bool ReadMT(CORDB_ADDRESS addr, TADDR *mt)
+ {
+ if (!Read(addr, mt))
+ return false;
+
+ // clear the GC flag bits off the MethodTable
+ // equivalent to Object::GetGCSafeMethodTable()
+ *mt &= ~3;
+ return true;
+ }
+
+private:
+ /* Sets the cache to the page specified by addr, or false if we could not move to
+ * that page.
+ */
+ bool MoveToPage(CORDB_ADDRESS addr);
+
+ /* Attempts to read from the target process if the data is possibly hanging off
+ * the end of a page.
+ */
+ template<class T>
+ inline bool MisalignedRead(CORDB_ADDRESS addr, T *t)
+ {
+ return SUCCEEDED(DacReadAll(TO_TADDR(addr), t, sizeof(T), false));
+ }
+
+private:
+ CORDB_ADDRESS mCurrPageStart;
+ ULONG32 mPageSize, mCurrPageSize;
+ BYTE *mPage;
+};
+
+DWORD DacGetNumHeaps();
+
+/* The implementation of the dac heap walker. This class will enumerate all objects on
+ * the heap with three important caveats:
+ * - This class will skip all Free objects in the heap. Free objects are an
+ * implementation detail of the GC, and ICorDebug does not have a mechanism
+ * to expose them.
+ * - This class does NOT guarantee that all objects will be enumerated. In
+ * the event that we find heap corruption on a segment, or if the background
+ * GC is modifying a segment, the remainder of that segment will be skipped
+ * by design.
+ * - The GC heap must be in a walkable state before you attempt to use this
+ * class on it. The IDacDbiInterface::AreGCStructuresValid function will
+ * tell you whether it is safe to walk the heap or not.
+ */
+class DacHeapWalker
+{
+ static CORDB_ADDRESS HeapStart;
+ static CORDB_ADDRESS HeapEnd;
+
+public:
+ DacHeapWalker();
+ ~DacHeapWalker();
+
+ /* Initializes the heap walker. This must be called before Next or
+ * HasMoreObjects. Returns false if the initialization was not successful.
+ * (In practice this should only return false if we hit an OOM trying
+ * to allocate space for data structures.) Limits the heap walk to be in the range
+ * [start, end] (inclusive). Use DacHeapWalker::HeapStart, DacHeapWalker::HeapEnd
+ * as start or end to start from the beginning or end.
+ */
+ HRESULT Init(CORDB_ADDRESS start=HeapStart, CORDB_ADDRESS end=HeapEnd);
+
+ /* Returns a CORDB_ADDRESS which points to the next value on the heap.
+ * You must call HasMoreObjects on this class, and it must return true
+ * before calling Next.
+ */
+ HRESULT Next(CORDB_ADDRESS *pValue, CORDB_ADDRESS *pMT, ULONG64 *size);
+
+ /* Returns true if there are more objects on the heap, false otherwise.
+ */
+ inline bool HasMoreObjects() const
+ {
+ return mCurrHeap < mHeapCount;
+ }
+
+ HRESULT Reset(CORDB_ADDRESS start, CORDB_ADDRESS end);
+
+ static HRESULT InitHeapDataWks(HeapData *&pHeaps, size_t &count);
+ static HRESULT InitHeapDataSvr(HeapData *&pHeaps, size_t &count);
+
+ HRESULT GetHeapData(HeapData **ppHeapData, size_t *pNumHeaps);
+
+ SegmentData *FindSegment(CORDB_ADDRESS obj);
+
+ HRESULT ListNearObjects(CORDB_ADDRESS obj, CORDB_ADDRESS *pPrev, CORDB_ADDRESS *pContaining, CORDB_ADDRESS *pNext);
+
+private:
+ HRESULT MoveToNextObject();
+
+ bool GetSize(TADDR tMT, size_t &size);
+
+ inline static size_t Align(size_t size)
+ {
+ if (sizeof(TADDR) == 4)
+ return (size+3) & ~3;
+ else
+ return (size+7) & ~7;
+ }
+
+ inline static size_t AlignLarge(size_t size)
+ {
+ return (size + 7) & ~7;
+ }
+
+ template <class T>
+ static int GetSegmentCount(T seg_start)
+ {
+ int count = 0;
+ while (seg_start)
+ {
+ // If we find this many segments, something is seriously wrong.
+ if (count++ > 4096)
+ break;
+
+ seg_start = seg_start->next;
+ }
+
+ return count;
+ }
+
+ HRESULT NextSegment();
+ void CheckAllocAndSegmentRange();
+
+private:
+ int mThreadCount;
+ AllocInfo *mAllocInfo;
+
+ size_t mHeapCount;
+ HeapData *mHeaps;
+
+ CORDB_ADDRESS mCurrObj;
+ size_t mCurrSize;
+ TADDR mCurrMT;
+
+ size_t mCurrHeap;
+ size_t mCurrSeg;
+
+ CORDB_ADDRESS mStart;
+ CORDB_ADDRESS mEnd;
+
+ LinearReadCache mCache;
+ static CORDB_ADDRESS sFreeMT;
+};
+
+struct DacGcReference;
+struct SOSStackErrorList
+{
+ SOSStackRefError error;
+ SOSStackErrorList *pNext;
+
+ SOSStackErrorList()
+ : pNext(0)
+ {
+ }
+};
+
+class DacStackReferenceWalker;
+class DacStackReferenceErrorEnum : public DefaultCOMImpl<ISOSStackRefErrorEnum>
+{
+public:
+ DacStackReferenceErrorEnum(DacStackReferenceWalker *pEnum, SOSStackErrorList *pErrors);
+ ~DacStackReferenceErrorEnum();
+
+ HRESULT STDMETHODCALLTYPE Skip(unsigned int count);
+ HRESULT STDMETHODCALLTYPE Reset();
+ HRESULT STDMETHODCALLTYPE GetCount(unsigned int *pCount);
+ HRESULT STDMETHODCALLTYPE Next(unsigned int count, SOSStackRefError ref[], unsigned int *pFetched);
+
+private:
+ // The lifetime of the error list is tied to the enum, so we must addref/release it.
+ DacStackReferenceWalker *mEnum;
+ SOSStackErrorList *mHead;
+ SOSStackErrorList *mCurr;
+};
+
+// For GCCONTEXT
+#include "gcenv.h"
+
+ /* DacStackReferenceWalker.
+ */
+class DacStackReferenceWalker : public DefaultCOMImpl<ISOSStackRefEnum>
+{
+ struct DacScanContext : public ScanContext
+ {
+ DacStackReferenceWalker *pWalker;
+ Frame *pFrame;
+ TADDR sp, pc;
+ bool stop;
+ GCEnumCallback pEnumFunc;
+
+ DacScanContext()
+ : pWalker(NULL), pFrame(0), sp(0), pc(0), stop(false), pEnumFunc(0)
+ {
+ }
+ };
+
+ typedef struct _StackRefChunkHead
+ {
+ struct _StackRefChunkHead *next; // Next chunk
+ unsigned int count; // The count of how many StackRefs were written to pData
+ unsigned int size; // The capacity of pData (in bytes)
+ void *pData; // The overflow data
+
+ _StackRefChunkHead()
+ : next(0), count(0), size(0), pData(0)
+ {
+ }
+ } StackRefChunkHead;
+
+ // The actual struct used for storing overflow StackRefs
+ typedef struct _StackRefChunk : public StackRefChunkHead
+ {
+ SOSStackRefData data[64];
+
+ _StackRefChunk()
+ {
+ pData = data;
+ size = sizeof(data);
+ }
+ } StackRefChunk;
+public:
+ DacStackReferenceWalker(ClrDataAccess *dac, DWORD osThreadID);
+ virtual ~DacStackReferenceWalker();
+
+ HRESULT Init();
+
+ HRESULT STDMETHODCALLTYPE Skip(unsigned int count);
+ HRESULT STDMETHODCALLTYPE Reset();
+ HRESULT STDMETHODCALLTYPE GetCount(unsigned int *pCount);
+ HRESULT STDMETHODCALLTYPE Next(unsigned int count,
+ SOSStackRefData refs[],
+ unsigned int *pFetched);
+
+ // Dac-Dbi Functions
+ HRESULT Next(ULONG celt, DacGcReference roots[], ULONG *pceltFetched);
+ Thread *GetThread() const
+ {
+ return mThread;
+ }
+
+ HRESULT STDMETHODCALLTYPE EnumerateErrors(ISOSStackRefErrorEnum **ppEnum);
+
+private:
+ static StackWalkAction Callback(CrawlFrame *pCF, VOID *pData);
+ static void GCEnumCallbackSOS(LPVOID hCallback, OBJECTREF *pObject, uint32_t flags, DacSlotLocation loc);
+ static void GCReportCallbackSOS(PTR_PTR_Object ppObj, ScanContext *sc, uint32_t flags);
+ static void GCEnumCallbackDac(LPVOID hCallback, OBJECTREF *pObject, uint32_t flags, DacSlotLocation loc);
+ static void GCReportCallbackDac(PTR_PTR_Object ppObj, ScanContext *sc, uint32_t flags);
+
+ CLRDATA_ADDRESS ReadPointer(TADDR addr);
+
+ template <class StructType>
+ StructType *GetNextObject(DacScanContext *ctx)
+ {
+ SUPPORTS_DAC;
+
+ // If we failed on a previous call (OOM) don't keep trying to allocate, it's not going to work.
+ if (ctx->stop || !mCurr)
+ return NULL;
+
+ // We've moved past the size of the current chunk. We'll allocate a new chunk
+ // and stuff the references there. These are cleaned up by the destructor.
+ if (mCurr->count >= mCurr->size/sizeof(StructType))
+ {
+ if (mCurr->next == NULL)
+ {
+ StackRefChunk *next = new (nothrow) StackRefChunk;
+ if (next != NULL)
+ {
+ mCurr->next = next;
+ }
+ else
+ {
+ ctx->stop = true;
+ return NULL;
+ }
+ }
+
+ mCurr = mCurr->next;
+ }
+
+ // Fill the current ref.
+ StructType *pData = (StructType*)mCurr->pData;
+ return &pData[mCurr->count++];
+ }
+
+
+ template <class IntType, class StructType>
+ IntType WalkStack(IntType count, StructType refs[], promote_func promote, GCEnumCallback enumFunc)
+ {
+ _ASSERTE(mThread);
+ _ASSERTE(!mEnumerated);
+
+ // If this is the first time we were called, fill local data structures.
+ // This will fill out the user's handles as well.
+ _ASSERTE(mCurr == NULL);
+ _ASSERTE(mHead.next == NULL);
+
+ // Get the current thread's context and set that as the filter context
+ if (mThread->GetFilterContext() == NULL && mThread->GetProfilerFilterContext() == NULL)
+ {
+ T_CONTEXT ctx;
+ mDac->m_pTarget->GetThreadContext(mThread->GetOSThreadId(), CONTEXT_FULL, sizeof(ctx), (BYTE*)&ctx);
+ mThread->SetProfilerFilterContext(&ctx);
+ }
+
+ // Setup GCCONTEXT structs for the stackwalk.
+ GCCONTEXT gcctx;
+ DacScanContext dsc;
+ dsc.pWalker = this;
+ dsc.pEnumFunc = enumFunc;
+ gcctx.f = promote;
+ gcctx.sc = &dsc;
+
+ // Put the user's array/count in the
+ mHead.size = count*sizeof(StructType);
+ mHead.pData = refs;
+ mHead.count = 0;
+
+ mCurr = &mHead;
+
+ // Walk the stack, set mEnumerated to true to ensure we don't do it again.
+ unsigned int flagsStackWalk = ALLOW_INVALID_OBJECTS|ALLOW_ASYNC_STACK_WALK;
+#if defined(WIN64EXCEPTIONS)
+ flagsStackWalk |= GC_FUNCLET_REFERENCE_REPORTING;
+#endif // defined(WIN64EXCEPTIONS)
+
+ mEnumerated = true;
+ mThread->StackWalkFrames(DacStackReferenceWalker::Callback, &gcctx, flagsStackWalk);
+
+ // We have filled the user's array as much as we could. If there's more data than
+ // could fit, mHead.Next will contain a linked list of refs to enumerate.
+ mCurr = mHead.next;
+
+ // Return how many we put in the user's array.
+ return mHead.count;
+ }
+
+ template <class IntType, class StructType, promote_func PromoteFunc, GCEnumCallback EnumFunc>
+ HRESULT DoStackWalk(IntType count, StructType stackRefs[], IntType *pFetched)
+ {
+ HRESULT hr = S_OK;
+ IntType fetched = 0;
+ if (!mEnumerated)
+ {
+ // If this is the first time we were called, fill local data structures.
+ // This will fill out the user's handles as well.
+ fetched = (IntType)WalkStack((unsigned int)count, stackRefs, PromoteFunc, EnumFunc);
+ }
+
+ while (fetched < count)
+ {
+ if (mCurr == NULL)
+ {
+ // Case 1: We have no more refs to walk.
+ hr = S_FALSE;
+ break;
+ }
+ else if (mChunkIndex >= mCurr->count)
+ {
+ // Case 2: We have exhausted the current chunk.
+ mCurr = mCurr->next;
+ mChunkIndex = 0;
+ }
+ else
+ {
+ // Case 3: The last call to "Next" filled the user's array and had some ref
+ // data leftover. Walk the linked-list of arrays copying them into the user's
+ // buffer until we have either exhausted the user's array or the leftover data.
+ IntType toCopy = count - fetched; // Fill the user's buffer...
+
+ // ...unless that would go past the bounds of the current chunk.
+ if (toCopy + mChunkIndex > mCurr->count)
+ toCopy = mCurr->count - mChunkIndex;
+
+ memcpy(stackRefs+fetched, (StructType*)mCurr->pData+mChunkIndex, toCopy*sizeof(StructType));
+ mChunkIndex += toCopy;
+ fetched += toCopy;
+ }
+ }
+
+ *pFetched = fetched;
+
+ return hr;
+ }
+
+private:
+ // Dac variables required for entering/leaving the dac.
+ ClrDataAccess *mDac;
+ ULONG32 m_instanceAge;
+
+ // Operational variables
+ Thread *mThread;
+ SOSStackErrorList *mErrors;
+ bool mEnumerated;
+
+ // Storage variables
+ StackRefChunkHead mHead;
+ unsigned int mChunkIndex;
+
+ // Iterator variables
+ StackRefChunkHead *mCurr;
+ int mIteratorIndex;
+
+ // Heap. Used to resolve interior pointers.
+ DacHeapWalker mHeap;
+};
+
+
+
+struct DacGcReference;
+class DacHandleWalker : public DefaultCOMImpl<ISOSHandleEnum>
+{
+ typedef struct _HandleChunkHead
+ {
+ struct _HandleChunkHead *Next; // Next chunk
+ unsigned int Count; // The count of how many handles were written to pData
+ unsigned int Size; // The capacity of pData
+ void *pData; // The overflow data
+
+ _HandleChunkHead()
+ : Next(0), Count(0), Size(0), pData(0)
+ {
+ }
+ } HandleChunkHead;
+
+ // The actual struct used for storing overflow handles
+ typedef struct _HandleChunk : public HandleChunkHead
+ {
+ SOSHandleData Data[128];
+
+ _HandleChunk()
+ {
+ pData = Data;
+ Size = sizeof(Data);
+ }
+ } HandleChunk;
+
+ // Parameter used in HndEnumHandles callback.
+ struct DacHandleWalkerParam
+ {
+ HandleChunkHead *Curr; // The current chunk to write to
+ HRESULT Result; // HRESULT of the current enumeration
+ CLRDATA_ADDRESS AppDomain; // The AppDomain for the current bucket we are walking
+ unsigned int Type; // The type of handle we are currently walking
+
+ DacHandleWalkerParam(HandleChunk *curr)
+ : Curr(curr), Result(S_OK), AppDomain(0), Type(0)
+ {
+ }
+ };
+
+public:
+ DacHandleWalker();
+ virtual ~DacHandleWalker();
+
+ HRESULT Init(ClrDataAccess *dac, UINT types[], UINT typeCount);
+ HRESULT Init(ClrDataAccess *dac, UINT types[], UINT typeCount, int gen);
+ HRESULT Init(UINT32 typemask);
+
+ // SOS functions
+ HRESULT STDMETHODCALLTYPE Skip(unsigned int count);
+ HRESULT STDMETHODCALLTYPE Reset();
+ HRESULT STDMETHODCALLTYPE GetCount(unsigned int *pCount);
+ HRESULT STDMETHODCALLTYPE Next(unsigned int count,
+ SOSHandleData handles[],
+ unsigned int *pNeeded);
+
+ // Dac-Dbi Functions
+ HRESULT Next(ULONG celt, DacGcReference roots[], ULONG *pceltFetched);
+private:
+ static void CALLBACK EnumCallback(PTR_UNCHECKED_OBJECTREF pref, LPARAM *pExtraInfo, LPARAM userParam, LPARAM type);
+ static void GetRefCountedHandleInfo(
+ OBJECTREF oref, unsigned int uType,
+ unsigned int *pRefCount, unsigned int *pJupiterRefCount, BOOL *pIsPegged, BOOL *pIsStrong);
+ static UINT32 BuildTypemask(UINT types[], UINT typeCount);
+
+private:
+ static void CALLBACK EnumCallbackSOS(PTR_UNCHECKED_OBJECTREF pref, uintptr_t *pExtraInfo, uintptr_t userParam, uintptr_t type);
+ static void CALLBACK EnumCallbackDac(PTR_UNCHECKED_OBJECTREF pref, uintptr_t *pExtraInfo, uintptr_t userParam, uintptr_t type);
+
+ bool FetchMoreHandles(HANDLESCANPROC proc);
+ static inline bool IsAlwaysStrongReference(unsigned int type)
+ {
+ return type == HNDTYPE_STRONG || type == HNDTYPE_PINNED || type == HNDTYPE_ASYNCPINNED || type == HNDTYPE_SIZEDREF;
+ }
+
+ template <class StructType, class IntType, HANDLESCANPROC EnumFunc>
+ HRESULT DoHandleWalk(IntType celt, StructType handles[], IntType *pceltFetched)
+ {
+ SUPPORTS_DAC;
+
+ if (handles == NULL || pceltFetched == NULL)
+ return E_POINTER;
+
+ HRESULT hr = S_OK;
+ IntType fetched = 0;
+ bool done = false;
+
+ // On each iteration of the loop, either fetch more handles (filling in
+ // the user's data structure), or copy handles from previous calls to
+ // FetchMoreHandles which we could not store in the user's data (or simply
+ // advance the current chunk to the next chunk).
+ while (fetched < celt)
+ {
+ if (mCurr == NULL)
+ {
+ // Case 1: We have no overflow data. Stuff the user's array/size into
+ // mHead, fetch more handles. Additionally, if the previous call to
+ // FetchMoreHandles returned false (mMap == NULL), break.
+ if (mMap == NULL)
+ break;
+
+ mHead.pData = handles+fetched;
+ mHead.Size = (celt - fetched)*sizeof(StructType);
+
+ done = !FetchMoreHandles(EnumFunc);
+ fetched += mHead.Count;
+
+ // Sanity check to make sure we haven't overflowed. This should not happen.
+ _ASSERTE(fetched <= celt);
+ }
+ else if (mChunkIndex >= mCurr->Count)
+ {
+ // Case 2: We have overflow data, but the current index into the current
+ // chunk is past the bounds. Move to the next. This could set mCurr to
+ // null, which we'll catch on the next iteration.
+ mCurr = mCurr->Next;
+ mChunkIndex = 0;
+ }
+ else
+ {
+ // Case 3: The last call to "Next" filled the user's array and had some handle
+ // data leftover. Walk the linked-list of arrays copying them into the user's
+ // buffer until we have either exhausted the user's array or the leftover data.
+ unsigned int toCopy = celt - fetched; // Fill the user's buffer...
+
+ // ...unless that would go past the bounds of the current chunk.
+ if (toCopy + mChunkIndex > mCurr->Count)
+ toCopy = mCurr->Count - mChunkIndex;
+
+ memcpy(handles+fetched, ((StructType*)(mCurr->pData))+mChunkIndex, toCopy*sizeof(StructType));
+ mChunkIndex += toCopy;
+ fetched += toCopy;
+ }
+ }
+
+ if (fetched < celt)
+ hr = S_FALSE;
+
+ *pceltFetched = fetched;
+
+ return hr;
+ }
+
+private:
+ // Dac variables required for entering/leaving the dac.
+ ClrDataAccess *mDac;
+ ULONG32 m_instanceAge;
+
+ // Handle table walking variables.
+ HandleTableMap *mMap;
+ int mIndex;
+ UINT32 mTypeMask;
+ int mGenerationFilter;
+
+ // Storage variables
+ HandleChunk mHead;
+ unsigned int mChunkIndex;
+
+ // Iterator variables
+ HandleChunkHead *mCurr;
+ int mIteratorIndex;
+};
+
+
+//----------------------------------------------------------------------------
+//
+// ClrDataAppDomain.
+//
+//----------------------------------------------------------------------------
+
+class ClrDataAppDomain : public IXCLRDataAppDomain
+{
+public:
+ ClrDataAppDomain(ClrDataAccess* dac,
+ AppDomain* appDomain);
+ virtual ~ClrDataAppDomain(void);
+
+ // IUnknown.
+ STDMETHOD(QueryInterface)(THIS_
+ IN REFIID interfaceId,
+ OUT PVOID* iface);
+ STDMETHOD_(ULONG, AddRef)(THIS);
+ STDMETHOD_(ULONG, Release)(THIS);
+
+ //
+ // IXCLRDataAppDomain.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE GetProcess(
+ /* [out] */ IXCLRDataProcess **process);
+
+ virtual HRESULT STDMETHODCALLTYPE GetName(
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(bufLen, *nameLen) WCHAR name[ ]);
+
+ virtual HRESULT STDMETHODCALLTYPE GetUniqueID(
+ /* [out] */ ULONG64 *id);
+
+ virtual HRESULT STDMETHODCALLTYPE GetFlags(
+ /* [out] */ ULONG32 *flags);
+
+ virtual HRESULT STDMETHODCALLTYPE IsSameObject(
+ /* [in] */ IXCLRDataAppDomain *appDomain);
+
+ virtual HRESULT STDMETHODCALLTYPE GetManagedObject(
+ /* [out] */ IXCLRDataValue **value);
+
+ virtual HRESULT STDMETHODCALLTYPE Request(
+ /* [in] */ ULONG32 reqCode,
+ /* [in] */ ULONG32 inBufferSize,
+ /* [size_is][in] */ BYTE *inBuffer,
+ /* [in] */ ULONG32 outBufferSize,
+ /* [size_is][out] */ BYTE *outBuffer);
+
+ AppDomain* GetAppDomain(void)
+ {
+ SUPPORTS_DAC;
+ return m_appDomain;
+ }
+
+private:
+ LONG m_refs;
+ ClrDataAccess* m_dac;
+ ULONG32 m_instanceAge;
+ AppDomain* m_appDomain;
+};
+
+//----------------------------------------------------------------------------
+//
+// ClrDataAssembly.
+//
+//----------------------------------------------------------------------------
+
+class ClrDataAssembly : public IXCLRDataAssembly
+{
+public:
+ ClrDataAssembly(ClrDataAccess* dac,
+ Assembly* assembly);
+ virtual ~ClrDataAssembly(void);
+
+ // IUnknown.
+ STDMETHOD(QueryInterface)(THIS_
+ IN REFIID interfaceId,
+ OUT PVOID* iface);
+ STDMETHOD_(ULONG, AddRef)(THIS);
+ STDMETHOD_(ULONG, Release)(THIS);
+
+ //
+ // IXCLRDataAssembly.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE StartEnumModules(
+ /* [out] */ CLRDATA_ENUM *handle);
+
+ virtual HRESULT STDMETHODCALLTYPE EnumModule(
+ /* [in, out] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataModule **mod);
+
+ virtual HRESULT STDMETHODCALLTYPE EndEnumModules(
+ /* [in] */ CLRDATA_ENUM handle);
+
+ virtual HRESULT STDMETHODCALLTYPE StartEnumAppDomains(
+ /* [out] */ CLRDATA_ENUM *handle);
+
+ virtual HRESULT STDMETHODCALLTYPE EnumAppDomain(
+ /* [in, out] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataAppDomain **appDomain);
+
+ virtual HRESULT STDMETHODCALLTYPE EndEnumAppDomains(
+ /* [in] */ CLRDATA_ENUM handle);
+
+ virtual HRESULT STDMETHODCALLTYPE GetName(
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(bufLen, *nameLen) WCHAR name[ ]);
+
+ virtual HRESULT STDMETHODCALLTYPE GetFileName(
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(bufLen, *nameLen) WCHAR name[ ]);
+
+ virtual HRESULT STDMETHODCALLTYPE GetDisplayName(
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(bufLen, *nameLen) WCHAR name[ ]);
+
+ virtual HRESULT STDMETHODCALLTYPE GetFlags(
+ /* [out] */ ULONG32 *flags);
+
+ virtual HRESULT STDMETHODCALLTYPE IsSameObject(
+ /* [in] */ IXCLRDataAssembly *assembly);
+
+ virtual HRESULT STDMETHODCALLTYPE Request(
+ /* [in] */ ULONG32 reqCode,
+ /* [in] */ ULONG32 inBufferSize,
+ /* [size_is][in] */ BYTE *inBuffer,
+ /* [in] */ ULONG32 outBufferSize,
+ /* [size_is][out] */ BYTE *outBuffer);
+
+private:
+ LONG m_refs;
+ ClrDataAccess* m_dac;
+ ULONG32 m_instanceAge;
+ Assembly* m_assembly;
+};
+
+//----------------------------------------------------------------------------
+//
+// ClrDataModule.
+//
+//----------------------------------------------------------------------------
+
+class ClrDataModule : public IXCLRDataModule, IXCLRDataModule2
+{
+public:
+ ClrDataModule(ClrDataAccess* dac,
+ Module* module);
+ virtual ~ClrDataModule(void);
+
+ // IUnknown.
+ STDMETHOD(QueryInterface)(THIS_
+ IN REFIID interfaceId,
+ OUT PVOID* iface);
+ STDMETHOD_(ULONG, AddRef)(THIS);
+ STDMETHOD_(ULONG, Release)(THIS);
+
+ //
+ // IXCLRDataModule.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE StartEnumAssemblies(
+ /* [out] */ CLRDATA_ENUM *handle);
+
+ virtual HRESULT STDMETHODCALLTYPE EnumAssembly(
+ /* [in, out] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataAssembly **assembly);
+
+ virtual HRESULT STDMETHODCALLTYPE EndEnumAssemblies(
+ /* [in] */ CLRDATA_ENUM handle);
+
+ virtual HRESULT STDMETHODCALLTYPE StartEnumAppDomains(
+ /* [out] */ CLRDATA_ENUM *handle);
+
+ virtual HRESULT STDMETHODCALLTYPE EnumAppDomain(
+ /* [in, out] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataAppDomain **appDomain);
+
+ virtual HRESULT STDMETHODCALLTYPE EndEnumAppDomains(
+ /* [in] */ CLRDATA_ENUM handle);
+
+ virtual HRESULT STDMETHODCALLTYPE StartEnumTypeDefinitions(
+ /* [out] */ CLRDATA_ENUM *handle);
+
+ virtual HRESULT STDMETHODCALLTYPE EnumTypeDefinition(
+ /* [in, out] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataTypeDefinition **typeDefinition);
+
+ virtual HRESULT STDMETHODCALLTYPE EndEnumTypeDefinitions(
+ /* [in] */ CLRDATA_ENUM handle);
+
+ virtual HRESULT STDMETHODCALLTYPE StartEnumTypeInstances(
+ /* [in] */ IXCLRDataAppDomain* appDomain,
+ /* [out] */ CLRDATA_ENUM *handle);
+
+ virtual HRESULT STDMETHODCALLTYPE EnumTypeInstance(
+ /* [in, out] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataTypeInstance **typeInstance);
+
+ virtual HRESULT STDMETHODCALLTYPE EndEnumTypeInstances(
+ /* [in] */ CLRDATA_ENUM handle);
+
+ virtual HRESULT STDMETHODCALLTYPE StartEnumTypeDefinitionsByName(
+ /* [in] */ LPCWSTR name,
+ /* [in] */ ULONG32 flags,
+ /* [out] */ CLRDATA_ENUM *handle);
+
+ virtual HRESULT STDMETHODCALLTYPE EnumTypeDefinitionByName(
+ /* [out][in] */ CLRDATA_ENUM *handle,
+ /* [out] */ IXCLRDataTypeDefinition **type);
+
+ virtual HRESULT STDMETHODCALLTYPE EndEnumTypeDefinitionsByName(
+ /* [in] */ CLRDATA_ENUM handle);
+
+ virtual HRESULT STDMETHODCALLTYPE StartEnumTypeInstancesByName(
+ /* [in] */ LPCWSTR name,
+ /* [in] */ ULONG32 flags,
+ /* [in] */ IXCLRDataAppDomain *appDomain,
+ /* [out] */ CLRDATA_ENUM *handle);
+
+ virtual HRESULT STDMETHODCALLTYPE EnumTypeInstanceByName(
+ /* [out][in] */ CLRDATA_ENUM *handle,
+ /* [out] */ IXCLRDataTypeInstance **type);
+
+ virtual HRESULT STDMETHODCALLTYPE EndEnumTypeInstancesByName(
+ /* [in] */ CLRDATA_ENUM handle);
+
+ virtual HRESULT STDMETHODCALLTYPE GetTypeDefinitionByToken(
+ /* [in] */ mdTypeDef token,
+ /* [out] */ IXCLRDataTypeDefinition **typeDefinition);
+
+ virtual HRESULT STDMETHODCALLTYPE StartEnumMethodDefinitionsByName(
+ /* [in] */ LPCWSTR name,
+ /* [in] */ ULONG32 flags,
+ /* [out] */ CLRDATA_ENUM *handle);
+
+ virtual HRESULT STDMETHODCALLTYPE EnumMethodDefinitionByName(
+ /* [in] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataMethodDefinition **method);
+
+ virtual HRESULT STDMETHODCALLTYPE EndEnumMethodDefinitionsByName(
+ /* [in] */ CLRDATA_ENUM handle);
+
+ virtual HRESULT STDMETHODCALLTYPE StartEnumMethodInstancesByName(
+ /* [in] */ LPCWSTR name,
+ /* [in] */ ULONG32 flags,
+ /* [in] */ IXCLRDataAppDomain* appDomain,
+ /* [out] */ CLRDATA_ENUM *handle);
+
+ virtual HRESULT STDMETHODCALLTYPE EnumMethodInstanceByName(
+ /* [in] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataMethodInstance **method);
+
+ virtual HRESULT STDMETHODCALLTYPE EndEnumMethodInstancesByName(
+ /* [in] */ CLRDATA_ENUM handle);
+
+ virtual HRESULT STDMETHODCALLTYPE GetMethodDefinitionByToken(
+ /* [in] */ mdMethodDef token,
+ /* [out] */ IXCLRDataMethodDefinition **methodDefinition);
+
+ virtual HRESULT STDMETHODCALLTYPE StartEnumDataByName(
+ /* [in] */ LPCWSTR name,
+ /* [in] */ ULONG32 flags,
+ /* [in] */ IXCLRDataAppDomain* appDomain,
+ /* [in] */ IXCLRDataTask* tlsTask,
+ /* [out] */ CLRDATA_ENUM *handle);
+
+ virtual HRESULT STDMETHODCALLTYPE EnumDataByName(
+ /* [in] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataValue **value);
+
+ virtual HRESULT STDMETHODCALLTYPE EndEnumDataByName(
+ /* [in] */ CLRDATA_ENUM handle);
+
+ virtual HRESULT STDMETHODCALLTYPE GetName(
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(bufLen, *nameLen) WCHAR name[ ]);
+
+ virtual HRESULT STDMETHODCALLTYPE GetFileName(
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part(bufLen, *nameLen) WCHAR name[ ]);
+
+ virtual HRESULT STDMETHODCALLTYPE GetVersionId(
+ /* [out] */ GUID* vid);
+
+ virtual HRESULT STDMETHODCALLTYPE GetFlags(
+ /* [out] */ ULONG32 *flags);
+
+ virtual HRESULT STDMETHODCALLTYPE IsSameObject(
+ /* [in] */ IXCLRDataModule *mod);
+
+ virtual HRESULT STDMETHODCALLTYPE StartEnumExtents(
+ /* [out] */ CLRDATA_ENUM *handle);
+
+ virtual HRESULT STDMETHODCALLTYPE EnumExtent(
+ /* [in, out] */ CLRDATA_ENUM* handle,
+ /* [out] */ CLRDATA_MODULE_EXTENT *extent);
+
+ virtual HRESULT STDMETHODCALLTYPE EndEnumExtents(
+ /* [in] */ CLRDATA_ENUM handle);
+
+ virtual HRESULT STDMETHODCALLTYPE Request(
+ /* [in] */ ULONG32 reqCode,
+ /* [in] */ ULONG32 inBufferSize,
+ /* [size_is][in] */ BYTE *inBuffer,
+ /* [in] */ ULONG32 outBufferSize,
+ /* [size_is][out] */ BYTE *outBuffer);
+
+ HRESULT RequestGetModulePtr(IN ULONG32 inBufferSize,
+ IN BYTE* inBuffer,
+ IN ULONG32 outBufferSize,
+ OUT BYTE* outBuffer);
+
+ HRESULT RequestGetModuleData(IN ULONG32 inBufferSize,
+ IN BYTE* inBuffer,
+ IN ULONG32 outBufferSize,
+ OUT BYTE* outBuffer);
+
+ Module* GetModule(void)
+ {
+ return m_module;
+ }
+
+ //
+ // IXCLRDataModule2
+ //
+ virtual HRESULT STDMETHODCALLTYPE SetJITCompilerFlags(
+ /* [in] */ DWORD dwFlags );
+
+private:
+ // Returns an instance of IID_IMetaDataImport.
+ HRESULT GetMdInterface(PVOID* retIface);
+
+ LONG m_refs;
+ ClrDataAccess* m_dac;
+ ULONG32 m_instanceAge;
+ Module* m_module;
+ IMetaDataImport* m_mdImport;
+ bool m_setExtents;
+ CLRDATA_MODULE_EXTENT m_extents[2];
+ CLRDATA_MODULE_EXTENT* m_extentsEnd;
+};
+
+//----------------------------------------------------------------------------
+//
+// ClrDataTypeDefinition.
+//
+//----------------------------------------------------------------------------
+
+class ClrDataTypeDefinition : public IXCLRDataTypeDefinition
+{
+public:
+ ClrDataTypeDefinition(ClrDataAccess* dac,
+ Module* module,
+ mdTypeDef token,
+ TypeHandle typeHandle);
+ virtual ~ClrDataTypeDefinition(void);
+
+ // IUnknown.
+ STDMETHOD(QueryInterface)(THIS_
+ IN REFIID interfaceId,
+ OUT PVOID* iface);
+ STDMETHOD_(ULONG, AddRef)(THIS);
+ STDMETHOD_(ULONG, Release)(THIS);
+
+ //
+ // IXCLRDataTypeDefinition.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE GetModule(
+ /* [out] */ IXCLRDataModule **mod);
+
+ virtual HRESULT STDMETHODCALLTYPE StartEnumMethodDefinitions(
+ /* [out] */ CLRDATA_ENUM *handle);
+
+ virtual HRESULT STDMETHODCALLTYPE EnumMethodDefinition(
+ /* [in, out] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataMethodDefinition **methodDefinition);
+
+ virtual HRESULT STDMETHODCALLTYPE EndEnumMethodDefinitions(
+ /* [in] */ CLRDATA_ENUM handle);
+
+ virtual HRESULT STDMETHODCALLTYPE StartEnumMethodDefinitionsByName(
+ /* [in] */ LPCWSTR name,
+ /* [in] */ ULONG32 flags,
+ /* [out] */ CLRDATA_ENUM *handle);
+
+ virtual HRESULT STDMETHODCALLTYPE EnumMethodDefinitionByName(
+ /* [in] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataMethodDefinition **method);
+
+ virtual HRESULT STDMETHODCALLTYPE EndEnumMethodDefinitionsByName(
+ /* [in] */ CLRDATA_ENUM handle);
+
+ virtual HRESULT STDMETHODCALLTYPE GetMethodDefinitionByToken(
+ /* [in] */ mdMethodDef token,
+ /* [out] */ IXCLRDataMethodDefinition **methodDefinition);
+
+ virtual HRESULT STDMETHODCALLTYPE StartEnumInstances(
+ /* [in] */ IXCLRDataAppDomain* appDomain,
+ /* [out] */ CLRDATA_ENUM *handle);
+
+ virtual HRESULT STDMETHODCALLTYPE EnumInstance(
+ /* [out][in] */ CLRDATA_ENUM *handle,
+ /* [out] */ IXCLRDataTypeInstance **instance);
+
+ virtual HRESULT STDMETHODCALLTYPE EndEnumInstances(
+ /* [in] */ CLRDATA_ENUM handle);
+
+ virtual HRESULT STDMETHODCALLTYPE GetNumFields(
+ /* [in] */ ULONG32 flags,
+ /* [out] */ ULONG32 *numFields);
+
+ virtual HRESULT STDMETHODCALLTYPE StartEnumFields(
+ /* [in] */ ULONG32 flags,
+ /* [out] */ CLRDATA_ENUM *handle);
+
+ virtual HRESULT STDMETHODCALLTYPE EnumField(
+ /* [out][in] */ CLRDATA_ENUM *handle,
+ /* [in] */ ULONG32 nameBufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(nameBufLen, *nameLen) WCHAR nameBuf[ ],
+ /* [out] */ IXCLRDataTypeDefinition **type,
+ /* [out] */ ULONG32 *flags,
+ /* [out] */ mdFieldDef *token);
+
+ virtual HRESULT STDMETHODCALLTYPE EnumField2(
+ /* [out][in] */ CLRDATA_ENUM *handle,
+ /* [in] */ ULONG32 nameBufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(nameBufLen, *nameLen) WCHAR nameBuf[ ],
+ /* [out] */ IXCLRDataTypeDefinition **type,
+ /* [out] */ ULONG32 *flags,
+ /* [out] */ IXCLRDataModule** tokenScope,
+ /* [out] */ mdFieldDef *token);
+
+ virtual HRESULT STDMETHODCALLTYPE EndEnumFields(
+ /* [in] */ CLRDATA_ENUM handle);
+
+ virtual HRESULT STDMETHODCALLTYPE StartEnumFieldsByName(
+ /* [in] */ LPCWSTR name,
+ /* [in] */ ULONG32 nameFlags,
+ /* [in] */ ULONG32 fieldFlags,
+ /* [out] */ CLRDATA_ENUM *handle);
+
+ virtual HRESULT STDMETHODCALLTYPE EnumFieldByName(
+ /* [out][in] */ CLRDATA_ENUM *handle,
+ /* [out] */ IXCLRDataTypeDefinition **type,
+ /* [out] */ ULONG32 *flags,
+ /* [out] */ mdFieldDef *token);
+
+ virtual HRESULT STDMETHODCALLTYPE EnumFieldByName2(
+ /* [out][in] */ CLRDATA_ENUM *handle,
+ /* [out] */ IXCLRDataTypeDefinition **type,
+ /* [out] */ ULONG32 *flags,
+ /* [out] */ IXCLRDataModule** tokenScope,
+ /* [out] */ mdFieldDef *token);
+
+ virtual HRESULT STDMETHODCALLTYPE EndEnumFieldsByName(
+ /* [in] */ CLRDATA_ENUM handle);
+
+ virtual HRESULT STDMETHODCALLTYPE GetFieldByToken(
+ /* [in] */ mdFieldDef token,
+ /* [in] */ ULONG32 nameBufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(nameBufLen, *nameLen) WCHAR nameBuf[ ],
+ /* [out] */ IXCLRDataTypeDefinition **type,
+ /* [out] */ ULONG32* flags);
+
+ virtual HRESULT STDMETHODCALLTYPE GetFieldByToken2(
+ /* [in] */ IXCLRDataModule* tokenScope,
+ /* [in] */ mdFieldDef token,
+ /* [in] */ ULONG32 nameBufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(nameBufLen, *nameLen) WCHAR nameBuf[ ],
+ /* [out] */ IXCLRDataTypeDefinition **type,
+ /* [out] */ ULONG32* flags);
+
+ virtual HRESULT STDMETHODCALLTYPE GetName(
+ /* [in] */ ULONG32 flags,
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(bufLen, *nameLen) WCHAR nameBuf[ ]);
+
+ virtual HRESULT STDMETHODCALLTYPE GetTokenAndScope(
+ /* [out] */ mdTypeDef *token,
+ /* [out] */ IXCLRDataModule **mod);
+
+ virtual HRESULT STDMETHODCALLTYPE GetCorElementType(
+ /* [out] */ CorElementType *type);
+
+ virtual HRESULT STDMETHODCALLTYPE GetFlags(
+ /* [out] */ ULONG32 *flags);
+
+ virtual HRESULT STDMETHODCALLTYPE GetBase(
+ /* [out] */ IXCLRDataTypeDefinition **base);
+
+ virtual HRESULT STDMETHODCALLTYPE IsSameObject(
+ /* [in] */ IXCLRDataTypeDefinition *type);
+
+ virtual HRESULT STDMETHODCALLTYPE Request(
+ /* [in] */ ULONG32 reqCode,
+ /* [in] */ ULONG32 inBufferSize,
+ /* [size_is][in] */ BYTE *inBuffer,
+ /* [in] */ ULONG32 outBufferSize,
+ /* [size_is][out] */ BYTE *outBuffer);
+
+ virtual HRESULT STDMETHODCALLTYPE GetArrayRank(
+ /* [out] */ ULONG32* rank);
+
+ virtual HRESULT STDMETHODCALLTYPE GetTypeNotification(
+ /* [out] */ ULONG32* flags);
+
+ virtual HRESULT STDMETHODCALLTYPE SetTypeNotification(
+ /* [in] */ ULONG32 flags);
+
+ static HRESULT NewFromModule(ClrDataAccess* dac,
+ Module* module,
+ mdTypeDef token,
+ ClrDataTypeDefinition** typeDef,
+ IXCLRDataTypeDefinition** pubTypeDef);
+
+ TypeHandle GetTypeHandle(void)
+ {
+ return m_typeHandle;
+ }
+
+private:
+ LONG m_refs;
+ ClrDataAccess* m_dac;
+ ULONG32 m_instanceAge;
+ Module* m_module;
+ mdTypeDef m_token;
+ TypeHandle m_typeHandle;
+};
+
+//----------------------------------------------------------------------------
+//
+// ClrDataTypeInstance.
+//
+//----------------------------------------------------------------------------
+
+class ClrDataTypeInstance : public IXCLRDataTypeInstance
+{
+public:
+ ClrDataTypeInstance(ClrDataAccess* dac,
+ AppDomain* appDomain,
+ TypeHandle typeHandle);
+ virtual ~ClrDataTypeInstance(void);
+
+ // IUnknown.
+ STDMETHOD(QueryInterface)(THIS_
+ IN REFIID interfaceId,
+ OUT PVOID* iface);
+ STDMETHOD_(ULONG, AddRef)(THIS);
+ STDMETHOD_(ULONG, Release)(THIS);
+
+ //
+ // IXCLRDataTypeInstance.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE StartEnumMethodInstances(
+ /* [out] */ CLRDATA_ENUM *handle);
+
+ virtual HRESULT STDMETHODCALLTYPE EnumMethodInstance(
+ /* [in, out] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataMethodInstance **methodInstance);
+
+ virtual HRESULT STDMETHODCALLTYPE EndEnumMethodInstances(
+ /* [in] */ CLRDATA_ENUM handle);
+
+ virtual HRESULT STDMETHODCALLTYPE StartEnumMethodInstancesByName(
+ /* [in] */ LPCWSTR name,
+ /* [in] */ ULONG32 flags,
+ /* [out] */ CLRDATA_ENUM *handle);
+
+ virtual HRESULT STDMETHODCALLTYPE EnumMethodInstanceByName(
+ /* [in] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataMethodInstance **method);
+
+ virtual HRESULT STDMETHODCALLTYPE EndEnumMethodInstancesByName(
+ /* [in] */ CLRDATA_ENUM handle);
+
+ virtual HRESULT STDMETHODCALLTYPE GetNumStaticFields(
+ /* [out] */ ULONG32 *numFields);
+
+ virtual HRESULT STDMETHODCALLTYPE GetStaticFieldByIndex(
+ /* [in] */ ULONG32 index,
+ /* [in] */ IXCLRDataTask *tlsTask,
+ /* [out] */ IXCLRDataValue **field,
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(bufLen, *nameLen) WCHAR nameBuf[ ],
+ /* [out] */ mdFieldDef *token);
+
+ virtual HRESULT STDMETHODCALLTYPE StartEnumStaticFieldsByName(
+ /* [in] */ LPCWSTR name,
+ /* [in] */ ULONG32 flags,
+ /* [in] */ IXCLRDataTask *tlsTask,
+ /* [out] */ CLRDATA_ENUM *handle);
+
+ virtual HRESULT STDMETHODCALLTYPE EnumStaticFieldByName(
+ /* [out][in] */ CLRDATA_ENUM *handle,
+ /* [out] */ IXCLRDataValue **value);
+
+ virtual HRESULT STDMETHODCALLTYPE EndEnumStaticFieldsByName(
+ /* [in] */ CLRDATA_ENUM handle);
+
+ virtual HRESULT STDMETHODCALLTYPE GetNumStaticFields2(
+ /* [in] */ ULONG32 flags,
+ /* [out] */ ULONG32 *numFields);
+
+ virtual HRESULT STDMETHODCALLTYPE StartEnumStaticFields(
+ /* [in] */ ULONG32 flags,
+ /* [in] */ IXCLRDataTask *tlsTask,
+ /* [out] */ CLRDATA_ENUM *handle);
+
+ virtual HRESULT STDMETHODCALLTYPE EnumStaticField(
+ /* [out][in] */ CLRDATA_ENUM *handle,
+ /* [out] */ IXCLRDataValue **value);
+
+ virtual HRESULT STDMETHODCALLTYPE EnumStaticField2(
+ /* [out][in] */ CLRDATA_ENUM *handle,
+ /* [out] */ IXCLRDataValue **value,
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(bufLen, *nameLen) WCHAR nameBuf[ ],
+ /* [out] */ IXCLRDataModule** tokenScope,
+ /* [out] */ mdFieldDef *token);
+
+ virtual HRESULT STDMETHODCALLTYPE EndEnumStaticFields(
+ /* [in] */ CLRDATA_ENUM handle);
+
+ virtual HRESULT STDMETHODCALLTYPE StartEnumStaticFieldsByName2(
+ /* [in] */ LPCWSTR name,
+ /* [in] */ ULONG32 nameFlags,
+ /* [in] */ ULONG32 fieldFlags,
+ /* [in] */ IXCLRDataTask *tlsTask,
+ /* [out] */ CLRDATA_ENUM *handle);
+
+ virtual HRESULT STDMETHODCALLTYPE EnumStaticFieldByName3(
+ /* [out][in] */ CLRDATA_ENUM *handle,
+ /* [out] */ IXCLRDataValue **value,
+ /* [out] */ IXCLRDataModule** tokenScope,
+ /* [out] */ mdFieldDef *token);
+
+ virtual HRESULT STDMETHODCALLTYPE EnumStaticFieldByName2(
+ /* [out][in] */ CLRDATA_ENUM *handle,
+ /* [out] */ IXCLRDataValue **value);
+
+ virtual HRESULT STDMETHODCALLTYPE EndEnumStaticFieldsByName2(
+ /* [in] */ CLRDATA_ENUM handle);
+
+ virtual HRESULT STDMETHODCALLTYPE GetStaticFieldByToken(
+ /* [in] */ mdFieldDef token,
+ /* [in] */ IXCLRDataTask *tlsTask,
+ /* [out] */ IXCLRDataValue **field,
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(bufLen, *nameLen) WCHAR nameBuf[ ]);
+
+ virtual HRESULT STDMETHODCALLTYPE GetStaticFieldByToken2(
+ /* [in] */ IXCLRDataModule* tokenScope,
+ /* [in] */ mdFieldDef token,
+ /* [in] */ IXCLRDataTask *tlsTask,
+ /* [out] */ IXCLRDataValue **field,
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(bufLen, *nameLen) WCHAR nameBuf[ ]);
+
+ virtual HRESULT STDMETHODCALLTYPE GetName(
+ /* [in] */ ULONG32 flags,
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(bufLen, *nameLen) WCHAR nameBuf[ ]);
+
+ virtual HRESULT STDMETHODCALLTYPE GetModule(
+ /* [out] */ IXCLRDataModule **mod);
+
+ virtual HRESULT STDMETHODCALLTYPE GetDefinition(
+ /* [out] */ IXCLRDataTypeDefinition **typeDefinition);
+
+ virtual HRESULT STDMETHODCALLTYPE GetFlags(
+ /* [out] */ ULONG32 *flags);
+
+ virtual HRESULT STDMETHODCALLTYPE GetBase(
+ /* [out] */ IXCLRDataTypeInstance **base);
+
+ virtual HRESULT STDMETHODCALLTYPE IsSameObject(
+ /* [in] */ IXCLRDataTypeInstance *type);
+
+ virtual HRESULT STDMETHODCALLTYPE GetNumTypeArguments(
+ /* [out] */ ULONG32 *numTypeArgs);
+
+ virtual HRESULT STDMETHODCALLTYPE GetTypeArgumentByIndex(
+ /* [in] */ ULONG32 index,
+ /* [out] */ IXCLRDataTypeInstance **typeArg);
+
+ virtual HRESULT STDMETHODCALLTYPE Request(
+ /* [in] */ ULONG32 reqCode,
+ /* [in] */ ULONG32 inBufferSize,
+ /* [size_is][in] */ BYTE *inBuffer,
+ /* [in] */ ULONG32 outBufferSize,
+ /* [size_is][out] */ BYTE *outBuffer);
+
+ static HRESULT NewFromModule(ClrDataAccess* dac,
+ AppDomain* appDomain,
+ Module* module,
+ mdTypeDef token,
+ ClrDataTypeInstance** typeInst,
+ IXCLRDataTypeInstance** pubTypeInst);
+
+ TypeHandle GetTypeHandle(void)
+ {
+ return m_typeHandle;
+ }
+
+private:
+ LONG m_refs;
+ ClrDataAccess* m_dac;
+ ULONG32 m_instanceAge;
+ AppDomain* m_appDomain;
+ TypeHandle m_typeHandle;
+};
+
+//----------------------------------------------------------------------------
+//
+// ClrDataMethodDefinition.
+//
+//----------------------------------------------------------------------------
+
+class ClrDataMethodDefinition : public IXCLRDataMethodDefinition
+{
+public:
+ ClrDataMethodDefinition(ClrDataAccess* dac,
+ Module* module,
+ mdMethodDef token,
+ MethodDesc* methodDesc);
+ virtual ~ClrDataMethodDefinition(void);
+
+ // IUnknown.
+ STDMETHOD(QueryInterface)(THIS_
+ IN REFIID interfaceId,
+ OUT PVOID* iface);
+ STDMETHOD_(ULONG, AddRef)(THIS);
+ STDMETHOD_(ULONG, Release)(THIS);
+
+ //
+ // IXCLRDataMethodDefinition.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE GetTypeDefinition(
+ /* [out] */ IXCLRDataTypeDefinition **typeDefinition);
+
+ virtual HRESULT STDMETHODCALLTYPE StartEnumInstances(
+ /* [in] */ IXCLRDataAppDomain* appDomain,
+ /* [out] */ CLRDATA_ENUM *handle);
+
+ virtual HRESULT STDMETHODCALLTYPE EnumInstance(
+ /* [out][in] */ CLRDATA_ENUM *handle,
+ /* [out] */ IXCLRDataMethodInstance **instance);
+
+ virtual HRESULT STDMETHODCALLTYPE EndEnumInstances(
+ /* [in] */ CLRDATA_ENUM handle);
+
+ virtual HRESULT STDMETHODCALLTYPE GetName(
+ /* [in] */ ULONG32 flags,
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(bufLen, *nameLen) WCHAR name[ ]);
+
+ virtual HRESULT STDMETHODCALLTYPE GetTokenAndScope(
+ /* [out] */ mdMethodDef *token,
+ /* [out] */ IXCLRDataModule **mod);
+
+ virtual HRESULT STDMETHODCALLTYPE GetFlags(
+ /* [out] */ ULONG32 *flags);
+
+ virtual HRESULT STDMETHODCALLTYPE IsSameObject(
+ /* [in] */ IXCLRDataMethodDefinition *method);
+
+ virtual HRESULT STDMETHODCALLTYPE GetLatestEnCVersion(
+ /* [out] */ ULONG32* version);
+
+ virtual HRESULT STDMETHODCALLTYPE StartEnumExtents(
+ /* [out] */ CLRDATA_ENUM *handle);
+
+ virtual HRESULT STDMETHODCALLTYPE EnumExtent(
+ /* [out][in] */ CLRDATA_ENUM *handle,
+ /* [out] */ CLRDATA_METHDEF_EXTENT *extent);
+
+ virtual HRESULT STDMETHODCALLTYPE EndEnumExtents(
+ /* [in] */ CLRDATA_ENUM handle);
+
+ virtual HRESULT STDMETHODCALLTYPE GetCodeNotification(
+ /* [out] */ ULONG32 *flags);
+
+ virtual HRESULT STDMETHODCALLTYPE SetCodeNotification(
+ /* [in] */ ULONG32 flags);
+
+ virtual HRESULT STDMETHODCALLTYPE GetRepresentativeEntryAddress(
+ /* [out] */ CLRDATA_ADDRESS* addr);
+
+ virtual HRESULT STDMETHODCALLTYPE HasClassOrMethodInstantiation(
+ /*[out]*/ BOOL* bGeneric);
+
+ virtual HRESULT STDMETHODCALLTYPE Request(
+ /* [in] */ ULONG32 reqCode,
+ /* [in] */ ULONG32 inBufferSize,
+ /* [size_is][in] */ BYTE *inBuffer,
+ /* [in] */ ULONG32 outBufferSize,
+ /* [size_is][out] */ BYTE *outBuffer);
+
+ COR_ILMETHOD* GetIlMethod(void);
+
+ static HRESULT NewFromModule(ClrDataAccess* dac,
+ Module* module,
+ mdMethodDef token,
+ ClrDataMethodDefinition** methDef,
+ IXCLRDataMethodDefinition** pubMethDef);
+
+ static HRESULT GetSharedMethodFlags(MethodDesc* methodDesc,
+ ULONG32* flags);
+
+ // We don't need this if we are able to form name using token
+ MethodDesc *GetMethodDesc() { return m_methodDesc;}
+private:
+ LONG m_refs;
+ ClrDataAccess* m_dac;
+ ULONG32 m_instanceAge;
+ Module* m_module;
+ mdMethodDef m_token;
+ MethodDesc* m_methodDesc;
+};
+
+//----------------------------------------------------------------------------
+//
+// ClrDataMethodInstance.
+//
+//----------------------------------------------------------------------------
+
+class ClrDataMethodInstance : public IXCLRDataMethodInstance
+{
+public:
+ ClrDataMethodInstance(ClrDataAccess* dac,
+ AppDomain* appDomain,
+ MethodDesc* methodDesc);
+ virtual ~ClrDataMethodInstance(void);
+
+ // IUnknown.
+ STDMETHOD(QueryInterface)(THIS_
+ IN REFIID interfaceId,
+ OUT PVOID* iface);
+ STDMETHOD_(ULONG, AddRef)(THIS);
+ STDMETHOD_(ULONG, Release)(THIS);
+
+ //
+ // IXCLRDataMethodInstance.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE GetTypeInstance(
+ /* [out] */ IXCLRDataTypeInstance **typeInstance);
+
+ virtual HRESULT STDMETHODCALLTYPE GetDefinition(
+ /* [out] */ IXCLRDataMethodDefinition **methodDefinition);
+
+ virtual HRESULT STDMETHODCALLTYPE GetTokenAndScope(
+ /* [out] */ mdMethodDef *token,
+ /* [out] */ IXCLRDataModule **mod);
+
+ virtual HRESULT STDMETHODCALLTYPE GetName(
+ /* [in] */ ULONG32 flags,
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part(bufLen, *nameLen) WCHAR name[ ]);
+
+ virtual HRESULT STDMETHODCALLTYPE GetFlags(
+ /* [out] */ ULONG32 *flags);
+
+ virtual HRESULT STDMETHODCALLTYPE IsSameObject(
+ /* [in] */ IXCLRDataMethodInstance *method);
+
+ virtual HRESULT STDMETHODCALLTYPE GetEnCVersion(
+ /* [out] */ ULONG32* version);
+
+ virtual HRESULT STDMETHODCALLTYPE GetNumTypeArguments(
+ /* [out] */ ULONG32 *numTypeArgs);
+
+ virtual HRESULT STDMETHODCALLTYPE GetTypeArgumentByIndex(
+ /* [in] */ ULONG32 index,
+ /* [out] */ IXCLRDataTypeInstance **typeArg);
+
+ virtual HRESULT STDMETHODCALLTYPE GetILOffsetsByAddress(
+ /* [in] */ CLRDATA_ADDRESS address,
+ /* [in] */ ULONG32 offsetsLen,
+ /* [out] */ ULONG32 *offsetsNeeded,
+ /* [size_is][out] */ ULONG32 ilOffsets[ ]);
+
+ virtual HRESULT STDMETHODCALLTYPE GetAddressRangesByILOffset(
+ /* [in] */ ULONG32 ilOffset,
+ /* [in] */ ULONG32 rangesLen,
+ /* [out] */ ULONG32 *rangesNeeded,
+ /* [size_is][out] */ CLRDATA_ADDRESS_RANGE addressRanges[ ]);
+
+ virtual HRESULT STDMETHODCALLTYPE GetILAddressMap(
+ /* [in] */ ULONG32 mapLen,
+ /* [out] */ ULONG32 *mapNeeded,
+ /* [size_is][out] */ CLRDATA_IL_ADDRESS_MAP maps[ ]);
+
+ virtual HRESULT STDMETHODCALLTYPE StartEnumExtents(
+ /* [out] */ CLRDATA_ENUM *handle);
+
+ virtual HRESULT STDMETHODCALLTYPE EnumExtent(
+ /* [out][in] */ CLRDATA_ENUM *handle,
+ /* [out] */ CLRDATA_ADDRESS_RANGE *extent);
+
+ virtual HRESULT STDMETHODCALLTYPE EndEnumExtents(
+ /* [in] */ CLRDATA_ENUM handle);
+
+ virtual HRESULT STDMETHODCALLTYPE GetRepresentativeEntryAddress(
+ /* [out] */ CLRDATA_ADDRESS* addr);
+
+ virtual HRESULT STDMETHODCALLTYPE Request(
+ /* [in] */ ULONG32 reqCode,
+ /* [in] */ ULONG32 inBufferSize,
+ /* [size_is][in] */ BYTE *inBuffer,
+ /* [in] */ ULONG32 outBufferSize,
+ /* [size_is][out] */ BYTE *outBuffer);
+
+ static HRESULT NewFromModule(ClrDataAccess* dac,
+ AppDomain* appDomain,
+ Module* module,
+ mdMethodDef token,
+ ClrDataMethodInstance** methInst,
+ IXCLRDataMethodInstance** pubMethInst);
+
+private:
+ friend class ClrDataAccess;
+ LONG m_refs;
+ ClrDataAccess* m_dac;
+ ULONG32 m_instanceAge;
+ AppDomain* m_appDomain;
+ MethodDesc* m_methodDesc;
+};
+
+//----------------------------------------------------------------------------
+//
+// ClrDataTask.
+//
+//----------------------------------------------------------------------------
+
+class ClrDataTask : public IXCLRDataTask
+{
+public:
+ ClrDataTask(ClrDataAccess* dac,
+ Thread* Thread);
+ virtual ~ClrDataTask(void);
+
+ // IUnknown.
+ STDMETHOD(QueryInterface)(THIS_
+ IN REFIID interfaceId,
+ OUT PVOID* iface);
+ STDMETHOD_(ULONG, AddRef)(THIS);
+ STDMETHOD_(ULONG, Release)(THIS);
+
+ //
+ // IXCLRDataTask.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE GetProcess(
+ /* [out] */ IXCLRDataProcess **process);
+
+ virtual HRESULT STDMETHODCALLTYPE GetCurrentAppDomain(
+ /* [out] */ IXCLRDataAppDomain **appDomain);
+
+ virtual HRESULT STDMETHODCALLTYPE GetName(
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(bufLen, *nameLen) WCHAR name[ ]);
+
+ virtual HRESULT STDMETHODCALLTYPE GetUniqueID(
+ /* [out] */ ULONG64 *id);
+
+ virtual HRESULT STDMETHODCALLTYPE GetFlags(
+ /* [out] */ ULONG32 *flags);
+
+ virtual HRESULT STDMETHODCALLTYPE IsSameObject(
+ /* [in] */ IXCLRDataTask *task);
+
+ virtual HRESULT STDMETHODCALLTYPE GetManagedObject(
+ /* [out] */ IXCLRDataValue **value);
+
+ virtual HRESULT STDMETHODCALLTYPE GetDesiredExecutionState(
+ /* [out] */ ULONG32 *state);
+
+ virtual HRESULT STDMETHODCALLTYPE SetDesiredExecutionState(
+ /* [in] */ ULONG32 state);
+
+ virtual HRESULT STDMETHODCALLTYPE CreateStackWalk(
+ /* [in] */ ULONG32 flags,
+ /* [out] */ IXCLRDataStackWalk **stackWalk);
+
+ virtual HRESULT STDMETHODCALLTYPE GetOSThreadID(
+ /* [out] */ ULONG32 *id);
+
+ virtual HRESULT STDMETHODCALLTYPE GetContext(
+ /* [in] */ ULONG32 contextFlags,
+ /* [in] */ ULONG32 contextBufSize,
+ /* [out] */ ULONG32 *contextSize,
+ /* [size_is][out] */ BYTE contextBuf[ ]);
+
+ virtual HRESULT STDMETHODCALLTYPE SetContext(
+ /* [in] */ ULONG32 contextSize,
+ /* [size_is][in] */ BYTE context[ ]);
+
+ virtual HRESULT STDMETHODCALLTYPE GetCurrentExceptionState(
+ /* [out] */ IXCLRDataExceptionState **exception);
+
+ virtual HRESULT STDMETHODCALLTYPE GetLastExceptionState(
+ /* [out] */ IXCLRDataExceptionState **exception);
+
+ virtual HRESULT STDMETHODCALLTYPE Request(
+ /* [in] */ ULONG32 reqCode,
+ /* [in] */ ULONG32 inBufferSize,
+ /* [size_is][in] */ BYTE *inBuffer,
+ /* [in] */ ULONG32 outBufferSize,
+ /* [size_is][out] */ BYTE *outBuffer);
+
+ Thread* GetThread(void)
+ {
+ return m_thread;
+ }
+
+private:
+ LONG m_refs;
+ ClrDataAccess* m_dac;
+ ULONG32 m_instanceAge;
+ Thread* m_thread;
+};
+
+//----------------------------------------------------------------------------
+//
+// ClrDataStackWalk.
+//
+//----------------------------------------------------------------------------
+
+class ClrDataStackWalk : public IXCLRDataStackWalk
+{
+public:
+ ClrDataStackWalk(ClrDataAccess* dac,
+ Thread* Thread,
+ ULONG32 flags);
+ virtual ~ClrDataStackWalk(void);
+
+ // IUnknown.
+ STDMETHOD(QueryInterface)(THIS_
+ IN REFIID interfaceId,
+ OUT PVOID* iface);
+ STDMETHOD_(ULONG, AddRef)(THIS);
+ STDMETHOD_(ULONG, Release)(THIS);
+
+ //
+ // IXCLRDataStackWalk.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE GetContext(
+ /* [in] */ ULONG32 contextFlags,
+ /* [in] */ ULONG32 contextBufSize,
+ /* [out] */ ULONG32 *contextSize,
+ /* [size_is][out] */ BYTE contextBuf[ ]);
+
+ virtual HRESULT STDMETHODCALLTYPE SetContext(
+ /* [in] */ ULONG32 contextSize,
+ /* [size_is][in] */ BYTE context[ ]);
+
+ virtual HRESULT STDMETHODCALLTYPE Next( void);
+
+ virtual HRESULT STDMETHODCALLTYPE GetStackSizeSkipped(
+ /* [out] */ ULONG64 *stackSizeSkipped);
+
+ virtual HRESULT STDMETHODCALLTYPE GetFrameType(
+ /* [out] */ CLRDataSimpleFrameType *simpleType,
+ /* [out] */ CLRDataDetailedFrameType *detailedType);
+
+ virtual HRESULT STDMETHODCALLTYPE GetFrame(
+ /* [out] */ IXCLRDataFrame **frame);
+
+ virtual HRESULT STDMETHODCALLTYPE Request(
+ /* [in] */ ULONG32 reqCode,
+ /* [in] */ ULONG32 inBufferSize,
+ /* [size_is][in] */ BYTE *inBuffer,
+ /* [in] */ ULONG32 outBufferSize,
+ /* [size_is][out] */ BYTE *outBuffer);
+
+ virtual HRESULT STDMETHODCALLTYPE SetContext2(
+ /* [in] */ ULONG32 flags,
+ /* [in] */ ULONG32 contextSize,
+ /* [size_is][in] */ BYTE context[ ]);
+
+ HRESULT Init(void);
+
+private:
+ void FilterFrames(void);
+ void RawGetFrameType(
+ /* [out] */ CLRDataSimpleFrameType* simpleType,
+ /* [out] */ CLRDataDetailedFrameType* detailedType);
+
+ LONG m_refs;
+ ClrDataAccess* m_dac;
+ ULONG32 m_instanceAge;
+ Thread* m_thread;
+ ULONG32 m_walkFlags;
+ StackFrameIterator m_frameIter;
+ REGDISPLAY m_regDisp;
+ T_CONTEXT m_context;
+ TADDR m_stackPrev;
+
+ // This is part of a test hook for debugging. Unless you're code:ClrDataStackWalk::Next
+ // you should never do anything with this member.
+ INDEBUG( int m_framesUnwound; )
+
+};
+
+//----------------------------------------------------------------------------
+//
+// ClrDataFrame.
+//
+//----------------------------------------------------------------------------
+
+class ClrDataFrame : public IXCLRDataFrame,
+ IXCLRDataFrame2
+{
+ friend class ClrDataStackWalk;
+
+public:
+ ClrDataFrame(ClrDataAccess* dac,
+ CLRDataSimpleFrameType simpleType,
+ CLRDataDetailedFrameType detailedType,
+ AppDomain* appDomain,
+ MethodDesc* methodDesc);
+ virtual ~ClrDataFrame(void);
+
+ // IUnknown.
+ STDMETHOD(QueryInterface)(THIS_
+ IN REFIID interfaceId,
+ OUT PVOID* iface);
+ STDMETHOD_(ULONG, AddRef)(THIS);
+ STDMETHOD_(ULONG, Release)(THIS);
+
+ //
+ // IXCLRDataFrame.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE GetContext(
+ /* [in] */ ULONG32 contextFlags,
+ /* [in] */ ULONG32 contextBufSize,
+ /* [out] */ ULONG32 *contextSize,
+ /* [size_is][out] */ BYTE contextBuf[ ]);
+
+ virtual HRESULT STDMETHODCALLTYPE GetFrameType(
+ /* [out] */ CLRDataSimpleFrameType *simpleType,
+ /* [out] */ CLRDataDetailedFrameType *detailedType);
+
+ virtual HRESULT STDMETHODCALLTYPE GetAppDomain(
+ /* [out] */ IXCLRDataAppDomain **appDomain);
+
+ virtual HRESULT STDMETHODCALLTYPE GetNumArguments(
+ /* [out] */ ULONG32 *numParams);
+
+ virtual HRESULT STDMETHODCALLTYPE GetArgumentByIndex(
+ /* [in] */ ULONG32 index,
+ /* [out] */ IXCLRDataValue **arg,
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part(bufLen, *nameLen) WCHAR name[ ]);
+
+ virtual HRESULT STDMETHODCALLTYPE GetNumLocalVariables(
+ /* [out] */ ULONG32 *numLocals);
+
+ virtual HRESULT STDMETHODCALLTYPE GetLocalVariableByIndex(
+ /* [in] */ ULONG32 index,
+ /* [out] */ IXCLRDataValue **localVariable,
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part(bufLen, *nameLen) WCHAR name[ ]);
+
+ virtual HRESULT STDMETHODCALLTYPE GetNumTypeArguments(
+ /* [out] */ ULONG32 *numTypeArgs);
+
+ virtual HRESULT STDMETHODCALLTYPE GetTypeArgumentByIndex(
+ /* [in] */ ULONG32 index,
+ /* [out] */ IXCLRDataTypeInstance **typeArg);
+
+ virtual HRESULT STDMETHODCALLTYPE GetCodeName(
+ /* [in] */ ULONG32 flags,
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_opt(bufLen) WCHAR nameBuf[ ]);
+
+ virtual HRESULT STDMETHODCALLTYPE GetMethodInstance(
+ /* [out] */ IXCLRDataMethodInstance **method);
+
+ virtual HRESULT STDMETHODCALLTYPE Request(
+ /* [in] */ ULONG32 reqCode,
+ /* [in] */ ULONG32 inBufferSize,
+ /* [size_is][in] */ BYTE *inBuffer,
+ /* [in] */ ULONG32 outBufferSize,
+ /* [size_is][out] */ BYTE *outBuffer);
+
+ //
+ // IXCLRDataFrame2.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE GetExactGenericArgsToken(
+ /* [out] */ IXCLRDataValue ** genericToken);
+
+private:
+ HRESULT GetMethodSig(MetaSig** sig,
+ ULONG32* count);
+ HRESULT GetLocalSig(MetaSig** sig,
+ ULONG32* count);
+ HRESULT ValueFromDebugInfo(MetaSig* sig,
+ bool isArg,
+ DWORD sigIndex,
+ DWORD varInfoSlot,
+ IXCLRDataValue** value);
+
+ LONG m_refs;
+ ClrDataAccess* m_dac;
+ ULONG32 m_instanceAge;
+ CLRDataSimpleFrameType m_simpleType;
+ CLRDataDetailedFrameType m_detailedType;
+ AppDomain* m_appDomain;
+ MethodDesc* m_methodDesc;
+ REGDISPLAY m_regDisp;
+ T_CONTEXT m_context;
+ MetaSig* m_methodSig;
+ MetaSig* m_localSig;
+};
+
+//----------------------------------------------------------------------------
+//
+// ClrDataExceptionState.
+//
+//----------------------------------------------------------------------------
+
+#ifdef WIN64EXCEPTIONS
+typedef ExceptionTracker ClrDataExStateType;
+#else // WIN64EXCEPTIONS
+typedef ExInfo ClrDataExStateType;
+#endif // WIN64EXCEPTIONS
+
+
+class ClrDataExceptionState : public IXCLRDataExceptionState
+{
+public:
+ ClrDataExceptionState(ClrDataAccess* dac,
+ AppDomain* appDomain,
+ Thread* thread,
+ ULONG32 flags,
+ ClrDataExStateType* exInfo,
+ OBJECTHANDLE throwable,
+ ClrDataExStateType* prevExInfo);
+ virtual ~ClrDataExceptionState(void);
+
+ // IUnknown.
+ STDMETHOD(QueryInterface)(THIS_
+ IN REFIID interfaceId,
+ OUT PVOID* iface);
+ STDMETHOD_(ULONG, AddRef)(THIS);
+ STDMETHOD_(ULONG, Release)(THIS);
+
+ //
+ // IXCLRDataExceptionState.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE GetFlags(
+ /* [out] */ ULONG32 *flags);
+
+ virtual HRESULT STDMETHODCALLTYPE GetPrevious(
+ /* [out] */ IXCLRDataExceptionState **exState);
+
+ virtual HRESULT STDMETHODCALLTYPE GetManagedObject(
+ /* [out] */ IXCLRDataValue **value);
+
+ virtual HRESULT STDMETHODCALLTYPE GetBaseType(
+ /* [out] */ CLRDataBaseExceptionType *type);
+
+ virtual HRESULT STDMETHODCALLTYPE GetCode(
+ /* [out] */ ULONG32 *code);
+
+ virtual HRESULT STDMETHODCALLTYPE GetString(
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *strLen,
+ /* [size_is][out] */ __out_ecount_part(bufLen, *strLen) WCHAR str[ ]);
+
+ virtual HRESULT STDMETHODCALLTYPE IsSameState(
+ /* [in] */ EXCEPTION_RECORD64 *exRecord,
+ /* [in] */ ULONG32 contextSize,
+ /* [size_is][in] */ BYTE cxRecord[ ]);
+
+ virtual HRESULT STDMETHODCALLTYPE IsSameState2(
+ /* [in] */ ULONG32 flags,
+ /* [in] */ EXCEPTION_RECORD64 *exRecord,
+ /* [in] */ ULONG32 contextSize,
+ /* [size_is][in] */ BYTE cxRecord[ ]);
+
+ virtual HRESULT STDMETHODCALLTYPE GetTask(
+ /* [out] */ IXCLRDataTask** task);
+
+ virtual HRESULT STDMETHODCALLTYPE Request(
+ /* [in] */ ULONG32 reqCode,
+ /* [in] */ ULONG32 inBufferSize,
+ /* [size_is][in] */ BYTE *inBuffer,
+ /* [in] */ ULONG32 outBufferSize,
+ /* [size_is][out] */ BYTE *outBuffer);
+
+ static HRESULT NewFromThread(ClrDataAccess* dac,
+ Thread* thread,
+ ClrDataExceptionState** exception,
+ IXCLRDataExceptionState** pubException);
+
+ PTR_CONTEXT GetCurrentContextRecord();
+ PTR_EXCEPTION_RECORD GetCurrentExceptionRecord();
+
+friend class ClrDataAccess;
+private:
+ LONG m_refs;
+ ClrDataAccess* m_dac;
+ ULONG32 m_instanceAge;
+ AppDomain* m_appDomain;
+ Thread* m_thread;
+ ULONG32 m_flags;
+ ClrDataExStateType* m_exInfo;
+ OBJECTHANDLE m_throwable;
+ ClrDataExStateType* m_prevExInfo;
+};
+
+//----------------------------------------------------------------------------
+//
+// ClrDataValue.
+//
+//----------------------------------------------------------------------------
+
+class ClrDataValue : public IXCLRDataValue
+{
+public:
+ ClrDataValue(ClrDataAccess* dac,
+ AppDomain* appDomain,
+ Thread* thread,
+ ULONG32 flags,
+ TypeHandle typeHandle,
+ ULONG64 baseAddr,
+ ULONG32 numLocs,
+ NativeVarLocation* locs);
+ virtual ~ClrDataValue(void);
+
+ // IUnknown.
+ STDMETHOD(QueryInterface)(THIS_
+ IN REFIID interfaceId,
+ OUT PVOID* iface);
+ STDMETHOD_(ULONG, AddRef)(THIS);
+ STDMETHOD_(ULONG, Release)(THIS);
+
+ //
+ // IXCLRDataValue.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE GetFlags(
+ /* [out] */ ULONG32 *flags);
+
+ virtual HRESULT STDMETHODCALLTYPE GetAddress(
+ /* [out] */ CLRDATA_ADDRESS *address);
+
+ virtual HRESULT STDMETHODCALLTYPE GetSize(
+ /* [out] */ ULONG64 *size);
+
+ virtual HRESULT STDMETHODCALLTYPE GetBytes(
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *dataSize,
+ /* [size_is][out] */ BYTE buffer[ ]);
+
+ virtual HRESULT STDMETHODCALLTYPE SetBytes(
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *dataSize,
+ /* [size_is][in] */ BYTE buffer[ ]);
+
+ virtual HRESULT STDMETHODCALLTYPE GetType(
+ /* [out] */ IXCLRDataTypeInstance **typeInstance);
+
+ virtual HRESULT STDMETHODCALLTYPE GetNumFields(
+ /* [out] */ ULONG32 *numFields);
+
+ virtual HRESULT STDMETHODCALLTYPE GetFieldByIndex(
+ /* [in] */ ULONG32 index,
+ /* [out] */ IXCLRDataValue **field,
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(bufLen, *nameLen) WCHAR nameBuf[ ],
+ /* [out] */ mdFieldDef *token);
+
+ virtual HRESULT STDMETHODCALLTYPE GetNumFields2(
+ /* [in] */ ULONG32 flags,
+ /* [in] */ IXCLRDataTypeInstance *fromType,
+ /* [out] */ ULONG32 *numFields);
+
+ virtual HRESULT STDMETHODCALLTYPE StartEnumFields(
+ /* [in] */ ULONG32 flags,
+ /* [in] */ IXCLRDataTypeInstance *fromType,
+ /* [out] */ CLRDATA_ENUM *handle);
+
+ virtual HRESULT STDMETHODCALLTYPE EnumField(
+ /* [out][in] */ CLRDATA_ENUM *handle,
+ /* [out] */ IXCLRDataValue **field,
+ /* [in] */ ULONG32 nameBufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(nameBufLen, *nameLen) WCHAR nameBuf[ ],
+ /* [out] */ mdFieldDef *token);
+
+ virtual HRESULT STDMETHODCALLTYPE EnumField2(
+ /* [out][in] */ CLRDATA_ENUM *handle,
+ /* [out] */ IXCLRDataValue **field,
+ /* [in] */ ULONG32 nameBufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(nameBufLen, *nameLen) WCHAR nameBuf[ ],
+ /* [out] */ IXCLRDataModule** tokenScope,
+ /* [out] */ mdFieldDef *token);
+
+ virtual HRESULT STDMETHODCALLTYPE EndEnumFields(
+ /* [in] */ CLRDATA_ENUM handle);
+
+ virtual HRESULT STDMETHODCALLTYPE StartEnumFieldsByName(
+ /* [in] */ LPCWSTR name,
+ /* [in] */ ULONG32 nameFlags,
+ /* [in] */ ULONG32 fieldFlags,
+ /* [in] */ IXCLRDataTypeInstance *fromType,
+ /* [out] */ CLRDATA_ENUM *handle);
+
+ virtual HRESULT STDMETHODCALLTYPE EnumFieldByName(
+ /* [out][in] */ CLRDATA_ENUM *handle,
+ /* [out] */ IXCLRDataValue **field,
+ /* [out] */ mdFieldDef *token);
+
+ virtual HRESULT STDMETHODCALLTYPE EnumFieldByName2(
+ /* [out][in] */ CLRDATA_ENUM *handle,
+ /* [out] */ IXCLRDataValue **field,
+ /* [out] */ IXCLRDataModule** tokenScope,
+ /* [out] */ mdFieldDef *token);
+
+ virtual HRESULT STDMETHODCALLTYPE EndEnumFieldsByName(
+ /* [in] */ CLRDATA_ENUM handle);
+
+ virtual HRESULT STDMETHODCALLTYPE GetFieldByToken(
+ /* [in] */ mdFieldDef token,
+ /* [out] */ IXCLRDataValue **field,
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(bufLen, *nameLen) WCHAR nameBuf[ ]);
+
+ virtual HRESULT STDMETHODCALLTYPE GetFieldByToken2(
+ /* [in] */ IXCLRDataModule* tokenScope,
+ /* [in] */ mdFieldDef token,
+ /* [out] */ IXCLRDataValue **field,
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(bufLen, *nameLen) WCHAR nameBuf[ ]);
+
+ virtual HRESULT STDMETHODCALLTYPE GetAssociatedValue(
+ /* [out] */ IXCLRDataValue **assocValue);
+
+ virtual HRESULT STDMETHODCALLTYPE GetAssociatedType(
+ /* [out] */ IXCLRDataTypeInstance **assocType);
+
+ virtual HRESULT STDMETHODCALLTYPE GetString(
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *strLen,
+ /* [size_is][out] */ __out_ecount_part(bufLen, *strLen) WCHAR str[ ]);
+
+ virtual HRESULT STDMETHODCALLTYPE GetArrayProperties(
+ /* [out] */ ULONG32 *rank,
+ /* [out] */ ULONG32 *totalElements,
+ /* [in] */ ULONG32 numDim,
+ /* [size_is][out] */ ULONG32 dims[ ],
+ /* [in] */ ULONG32 numBases,
+ /* [size_is][out] */ LONG32 bases[ ]);
+
+ virtual HRESULT STDMETHODCALLTYPE GetArrayElement(
+ /* [in] */ ULONG32 numInd,
+ /* [size_is][in] */ LONG32 indices[ ],
+ /* [out] */ IXCLRDataValue **value);
+
+ virtual HRESULT STDMETHODCALLTYPE GetNumLocations(
+ /* [out] */ ULONG32* numLocs);
+
+ virtual HRESULT STDMETHODCALLTYPE GetLocationByIndex(
+ /* [in] */ ULONG32 loc,
+ /* [out] */ ULONG32* flags,
+ /* [out] */ CLRDATA_ADDRESS* arg);
+
+ virtual HRESULT STDMETHODCALLTYPE Request(
+ /* [in] */ ULONG32 reqCode,
+ /* [in] */ ULONG32 inBufferSize,
+ /* [size_is][in] */ BYTE *inBuffer,
+ /* [in] */ ULONG32 outBufferSize,
+ /* [size_is][out] */ BYTE *outBuffer);
+
+ HRESULT GetRefAssociatedValue(IXCLRDataValue** assocValue);
+
+ static HRESULT NewFromFieldDesc(ClrDataAccess* dac,
+ AppDomain* appDomain,
+ ULONG32 flags,
+ FieldDesc* fieldDesc,
+ ULONG64 objBase,
+ Thread* tlsThread,
+ ClrDataValue** value,
+ IXCLRDataValue** pubValue,
+ ULONG32 nameBufRetLen,
+ ULONG32* nameLenRet,
+ __out_ecount_part_opt(nameBufRetLen, *nameLenRet) WCHAR nameBufRet[ ],
+ IXCLRDataModule** tokenScopeRet,
+ mdFieldDef* tokenRet);
+
+ HRESULT NewFromSubField(FieldDesc* fieldDesc,
+ ULONG32 flags,
+ ClrDataValue** value,
+ IXCLRDataValue** pubValue,
+ ULONG32 nameBufRetLen,
+ ULONG32* nameLenRet,
+ __out_ecount_part_opt(nameBufRetLen, *nameLenRet) WCHAR nameBufRet[ ],
+ IXCLRDataModule** tokenScopeRet,
+ mdFieldDef* tokenRet)
+ {
+ return ClrDataValue::NewFromFieldDesc(m_dac,
+ m_appDomain,
+ flags,
+ fieldDesc,
+ m_baseAddr,
+ m_thread,
+ value,
+ pubValue,
+ nameBufRetLen,
+ nameLenRet,
+ nameBufRet,
+ tokenScopeRet,
+ tokenRet);
+ }
+
+ bool CanHaveFields(void)
+ {
+ return (m_flags & CLRDATA_VALUE_IS_REFERENCE) == 0;
+ }
+
+ HRESULT IntGetBytes(
+ /* [in] */ ULONG32 bufLen,
+ /* [size_is][out] */ BYTE buffer[ ]);
+
+private:
+ LONG m_refs;
+ ClrDataAccess* m_dac;
+ ULONG32 m_instanceAge;
+ AppDomain* m_appDomain;
+ Thread* m_thread;
+ ULONG32 m_flags;
+ TypeHandle m_typeHandle;
+ ULONG64 m_totalSize;
+ ULONG64 m_baseAddr;
+ ULONG32 m_numLocs;
+ NativeVarLocation m_locs[MAX_NATIVE_VAR_LOCS];
+};
+
+//----------------------------------------------------------------------------
+//
+// EnumMethodDefinitions.
+//
+//----------------------------------------------------------------------------
+
+class EnumMethodDefinitions
+{
+public:
+ HRESULT Start(Module* mod,
+ bool useAddrFilter,
+ CLRDATA_ADDRESS addrFilter);
+ HRESULT Next(ClrDataAccess* dac,
+ IXCLRDataMethodDefinition **method);
+
+ static HRESULT CdStart(Module* mod,
+ bool useAddrFilter,
+ CLRDATA_ADDRESS addrFilter,
+ CLRDATA_ENUM* handle);
+ static HRESULT CdNext(ClrDataAccess* dac,
+ CLRDATA_ENUM* handle,
+ IXCLRDataMethodDefinition** method);
+ static HRESULT CdEnd(CLRDATA_ENUM handle);
+
+ Module* m_module;
+ bool m_useAddrFilter;
+ CLRDATA_ADDRESS m_addrFilter;
+ MetaEnum m_typeEnum;
+ mdToken m_typeToken;
+ bool m_needMethodStart;
+ MetaEnum m_methodEnum;
+};
+
+//----------------------------------------------------------------------------
+//
+// EnumMethodInstances.
+//
+//----------------------------------------------------------------------------
+
+class EnumMethodInstances
+{
+public:
+ EnumMethodInstances(MethodDesc* methodDesc,
+ IXCLRDataAppDomain* givenAppDomain);
+
+ HRESULT Next(ClrDataAccess* dac,
+ IXCLRDataMethodInstance **instance);
+
+ static HRESULT CdStart(MethodDesc* methodDesc,
+ IXCLRDataAppDomain* appDomain,
+ CLRDATA_ENUM* handle);
+ static HRESULT CdNext(ClrDataAccess* dac,
+ CLRDATA_ENUM* handle,
+ IXCLRDataMethodInstance** method);
+ static HRESULT CdEnd(CLRDATA_ENUM handle);
+
+ MethodDesc* m_methodDesc;
+ AppDomain* m_givenAppDomain;
+ bool m_givenAppDomainUsed;
+ AppDomainIterator m_domainIter;
+ AppDomain* m_appDomain;
+ LoadedMethodDescIterator m_methodIter;
+};
+
+//----------------------------------------------------------------------------
+//
+// Internal functions.
+//
+//----------------------------------------------------------------------------
+
+#define DAC_ENTER() \
+ EnterCriticalSection(&g_dacCritSec); \
+ ClrDataAccess* __prevDacImpl = g_dacImpl; \
+ g_dacImpl = this;
+
+// When entering a child object we validate that
+// the process's host instance cache hasn't been flushed
+// since the child was created.
+#define DAC_ENTER_SUB(dac) \
+ EnterCriticalSection(&g_dacCritSec); \
+ if (dac->m_instanceAge != m_instanceAge) \
+ { \
+ LeaveCriticalSection(&g_dacCritSec); \
+ return E_INVALIDARG; \
+ } \
+ ClrDataAccess* __prevDacImpl = g_dacImpl; \
+ g_dacImpl = (dac)
+
+#define DAC_LEAVE() \
+ g_dacImpl = __prevDacImpl; \
+ LeaveCriticalSection(&g_dacCritSec)
+
+
+#define SOSHelperEnter() \
+ DAC_ENTER_SUB(mDac); \
+ HRESULT hr = S_OK; \
+ EX_TRY \
+ {
+
+#define SOSHelperLeave() \
+ } \
+ EX_CATCH \
+ { \
+ if (!DacExceptionFilter(GET_EXCEPTION(), mDac, &hr)) \
+ { \
+ EX_RETHROW; \
+ } \
+ } \
+ EX_END_CATCH(SwallowAllExceptions) \
+ DAC_LEAVE();
+
+HRESULT DacGetHostVtPtrs(void);
+bool DacExceptionFilter(Exception* ex, ClrDataAccess* process,
+ HRESULT* status);
+Thread* __stdcall DacGetThread(ULONG32 osThread);
+BOOL DacGetThreadContext(Thread* thread, T_CONTEXT* context);
+
+// Imports from request_svr.cpp, to provide data we need from the SVR namespace
+int GCHeapCount();
+HRESULT GetServerHeapData(CLRDATA_ADDRESS addr, DacpHeapSegmentData *pSegment);
+HRESULT GetServerHeaps(CLRDATA_ADDRESS pGCHeaps[], ICorDebugDataTarget* pTarget);
+
+#if defined(DAC_MEASURE_PERF)
+
+#if defined(_TARGET_X86_)
+
+// Assume Pentium CPU
+
+#define CCNT_OVERHEAD_32 8
+#define CCNT_OVERHEAD 13
+
+#pragma warning( disable: 4035 ) /* Don't complain about lack of return value */
+
+__inline unsigned __int64 GetCycleCount ()
+{
+__asm _emit 0x0F
+__asm _emit 0x31 /* rdtsc */
+ // return EDX:EAX causes annoying warning
+};
+
+__inline unsigned GetCycleCount32 () // enough for about 40 seconds
+{
+ LIMITED_METHOD_CONTRACT;
+
+__asm push EDX
+__asm _emit 0x0F
+__asm _emit 0x31 /* rdtsc */
+__asm pop EDX
+ // return EAX causes annoying warning
+};
+
+#pragma warning( default: 4035 )
+
+#else // #if defined(_TARGET_X86_)
+
+#define CCNT_OVERHEAD 0 // Don't know
+
+__inline unsigned __int64 GetCycleCount()
+{
+ LIMITED_METHOD_CONTRACT;
+
+ LARGE_INTEGER qwTmp;
+ QueryPerformanceCounter(&qwTmp);
+ return qwTmp.QuadPart;
+}
+
+#endif // #if defined(_TARGET_X86_)
+
+extern unsigned __int64 g_nTotalTime;
+extern unsigned __int64 g_nStackTotalTime;
+extern unsigned __int64 g_nReadVirtualTotalTime;
+extern unsigned __int64 g_nFindTotalTime;
+extern unsigned __int64 g_nFindHashTotalTime;
+extern unsigned __int64 g_nFindHits;
+extern unsigned __int64 g_nFindCalls;
+extern unsigned __int64 g_nFindFails;
+extern unsigned __int64 g_nStackWalk;
+extern unsigned __int64 g_nFindStackTotalTime;
+
+#endif // #if defined(DAC_MEASURE_PERF)
+
+#endif // #ifndef __DACIMPL_H__
diff --git a/src/debug/daccess/datatargetadapter.cpp b/src/debug/daccess/datatargetadapter.cpp
new file mode 100644
index 0000000000..f2a1cc652a
--- /dev/null
+++ b/src/debug/daccess/datatargetadapter.cpp
@@ -0,0 +1,255 @@
+// 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.
+//*****************************************************************************
+// DataTargetAdapter.cpp
+//
+
+//
+// implementation of compatibility adapter for ICLRDataTarget
+//*****************************************************************************
+
+#include "stdafx.h"
+#include "datatargetadapter.h"
+#include <clrdata.h>
+#include "dacimpl.h"
+
+#ifndef IMAGE_FILE_MACHINE_ARM64
+#define IMAGE_FILE_MACHINE_ARM64 0xAA64 // ARM64 Little-Endian
+#endif
+
+//
+// DataTargetAdaptor ctor
+//
+// Instantiate a DataTargetAdapter over the supplied legacy DataTarget interface.
+// Takes a ref on the supplied interface and releases it in our dtor.
+//
+DataTargetAdapter::DataTargetAdapter(ICLRDataTarget * pLegacyTarget) :
+ m_ref(0),
+ m_pLegacyTarget(pLegacyTarget)
+{
+ m_pLegacyTarget->AddRef();
+}
+
+//
+// DataTargetAdapter dtor
+//
+// Releases the underlying DataTarget interface
+//
+DataTargetAdapter::~DataTargetAdapter()
+{
+ m_pLegacyTarget->Release();
+}
+
+// Standard impl of IUnknown::QueryInterface
+HRESULT STDMETHODCALLTYPE
+DataTargetAdapter::QueryInterface(
+ REFIID interfaceId,
+ PVOID* pInterface)
+{
+ if (interfaceId == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown *>(static_cast<ICorDebugDataTarget *>(this));
+ }
+ else if (interfaceId == IID_ICorDebugDataTarget)
+ {
+ *pInterface = static_cast<ICorDebugDataTarget *>(this);
+ }
+ else if (interfaceId == IID_ICorDebugMutableDataTarget)
+ {
+ // Note that we always implement the mutable interface, even though our underlying target
+ // may return E_NOTIMPL for all the functions on this interface. There is no reliable way
+ // to tell apriori whether an ICLRDataTarget instance supports writing or not.
+ *pInterface = static_cast<ICorDebugMutableDataTarget *>(this);
+ }
+ else
+ {
+ // For ICorDebugDataTarget4 and other interfaces directly implemented by the legacy data target.
+ return m_pLegacyTarget->QueryInterface(interfaceId, pInterface);
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+// Standard impl of IUnknown::AddRef
+ULONG STDMETHODCALLTYPE
+DataTargetAdapter::AddRef()
+{
+ LONG ref = InterlockedIncrement(&m_ref);
+ return ref;
+}
+
+// Standard impl of IUnknown::Release
+ULONG STDMETHODCALLTYPE
+DataTargetAdapter::Release()
+{
+ SUPPORTS_DAC_HOST_ONLY;
+ LONG ref = InterlockedDecrement(&m_ref);
+ if (ref == 0)
+ {
+ delete this;
+ }
+ return ref;
+}
+
+// impl of interface method ICorDebugDataTarget::GetPlatform
+HRESULT STDMETHODCALLTYPE
+DataTargetAdapter::GetPlatform(
+ CorDebugPlatform * pPlatform)
+{
+ SUPPORTS_DAC_HOST_ONLY;
+
+ // Get the target machine type, and assume it's Windows
+ HRESULT hr;
+
+ ULONG32 ulMachineType;
+ IfFailRet(m_pLegacyTarget->GetMachineType(&ulMachineType));
+
+ ULONG32 ulExpectedPointerSize;
+ CorDebugPlatform platform;
+
+ switch(ulMachineType)
+ {
+#ifdef FEATURE_PAL
+ case IMAGE_FILE_MACHINE_I386:
+ ulExpectedPointerSize = 4;
+ platform = CORDB_PLATFORM_POSIX_X86;
+ break;
+
+ case IMAGE_FILE_MACHINE_AMD64:
+ ulExpectedPointerSize = 8;
+ platform = CORDB_PLATFORM_POSIX_AMD64;
+ break;
+
+ case IMAGE_FILE_MACHINE_ARMNT:
+ ulExpectedPointerSize = 4;
+ platform = CORDB_PLATFORM_POSIX_ARM;
+ break;
+
+ case IMAGE_FILE_MACHINE_IA64:
+ case IMAGE_FILE_MACHINE_ARM64:
+ _ASSERTE_MSG(false, "Not supported platform.");
+ return E_NOTIMPL;
+
+#else // FEATURE_PAL
+ case IMAGE_FILE_MACHINE_I386:
+ ulExpectedPointerSize = 4;
+ platform = CORDB_PLATFORM_WINDOWS_X86;
+ break;
+
+ case IMAGE_FILE_MACHINE_AMD64:
+ ulExpectedPointerSize = 8;
+ platform = CORDB_PLATFORM_WINDOWS_AMD64;
+ break;
+
+ case IMAGE_FILE_MACHINE_IA64:
+ ulExpectedPointerSize = 8;
+ platform = CORDB_PLATFORM_WINDOWS_IA64;
+ break;
+
+ case IMAGE_FILE_MACHINE_ARMNT:
+ ulExpectedPointerSize = 4;
+ platform = CORDB_PLATFORM_WINDOWS_ARM;
+ break;
+
+ case IMAGE_FILE_MACHINE_ARM64:
+ ulExpectedPointerSize = 8;
+ platform = CORDB_PLATFORM_WINDOWS_ARM64;
+ break;
+#endif // FEATURE_PAL
+
+ default:
+ // No other platforms are current supported
+ return E_NOTIMPL;
+ }
+
+ // Validate that the target pointer size matches
+ ULONG32 ulPointerSize;
+ IfFailRet(m_pLegacyTarget->GetPointerSize(&ulPointerSize));
+
+ if (ulPointerSize != ulExpectedPointerSize)
+ {
+ return E_UNEXPECTED;
+ }
+
+ // Found a match
+ *pPlatform = platform;
+ return S_OK;
+}
+
+// impl of interface method ICorDebugDataTarget::ReadVirtual
+HRESULT STDMETHODCALLTYPE
+DataTargetAdapter::ReadVirtual(
+ CORDB_ADDRESS address,
+ PBYTE pBuffer,
+ ULONG32 cbRequestSize,
+ ULONG32 * pcbRead)
+{
+ SUPPORTS_DAC_HOST_ONLY;
+ CLRDATA_ADDRESS cdAddr = TO_CDADDR(address);
+ return m_pLegacyTarget->ReadVirtual(cdAddr, pBuffer, cbRequestSize, pcbRead);
+}
+
+// impl of interface method ICorDebugMutableDataTarget::WriteVirtual
+HRESULT STDMETHODCALLTYPE
+DataTargetAdapter::WriteVirtual(
+ CORDB_ADDRESS address,
+ const BYTE * pBuffer,
+ ULONG32 cbRequestSize)
+{
+ SUPPORTS_DAC_HOST_ONLY;
+ CLRDATA_ADDRESS cdAddr = TO_CDADDR(address);
+ ULONG32 cbWritten = 0;
+ HRESULT hr = S_OK;
+
+ hr = m_pLegacyTarget->WriteVirtual(cdAddr, const_cast<BYTE *>(pBuffer), cbRequestSize, &cbWritten);
+
+ if (SUCCEEDED(hr) && cbWritten != cbRequestSize)
+ {
+ // This shouldn't happen - existing data target implementations make writes atomic (eg.
+ // WriteProcessMemory), even though that isn't strictly required by the old interface.
+ // If this does happen, we technically leave the process in an inconsistent state, and we make no
+ // attempt to recover from that here.
+ _ASSERTE_MSG(false, "Legacy data target WriteVirtual partial write - target left in inconsistent state");
+ return HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY);
+ }
+ return hr;
+}
+
+
+// impl of interface method ICorDebugDataTarget::GetThreadContext
+HRESULT STDMETHODCALLTYPE
+DataTargetAdapter::GetThreadContext(
+ DWORD dwThreadID,
+ ULONG32 contextFlags,
+ ULONG32 contextSize,
+ PBYTE pContext)
+{
+ SUPPORTS_DAC_HOST_ONLY;
+ return m_pLegacyTarget->GetThreadContext(dwThreadID, contextFlags, contextSize, pContext);
+}
+
+// impl of interface method ICorDebugMutableDataTarget::SetThreadContext
+HRESULT STDMETHODCALLTYPE
+DataTargetAdapter::SetThreadContext(
+ DWORD dwThreadID,
+ ULONG32 contextSize,
+ const BYTE * pContext)
+{
+ SUPPORTS_DAC_HOST_ONLY;
+ return m_pLegacyTarget->SetThreadContext(dwThreadID, contextSize, const_cast<BYTE *>(pContext));
+}
+
+// implementation of ICorDebugMutableDataTarget::ContinueStatusChanged
+HRESULT STDMETHODCALLTYPE
+DataTargetAdapter::ContinueStatusChanged(
+ DWORD dwThreadId,
+ CORDB_CONTINUE_STATUS continueStatus)
+{
+ SUPPORTS_DAC_HOST_ONLY;
+ // No corresponding API in pre-arrowhead ICLRDataTarget* interfaces.
+ // Note that we briefly had a ICLRDataTarget4 with this API, but this was never released outside the CLR so
+ // all existing implementations should now be gone.
+ return E_NOTIMPL;
+}
diff --git a/src/debug/daccess/datatargetadapter.h b/src/debug/daccess/datatargetadapter.h
new file mode 100644
index 0000000000..d2620c88e1
--- /dev/null
+++ b/src/debug/daccess/datatargetadapter.h
@@ -0,0 +1,84 @@
+// 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.
+//*****************************************************************************
+// DataTargetAdapter.h
+//
+
+//
+// header for compatibility adapter for ICLRDataTarget
+//*****************************************************************************
+
+#ifndef DATATARGETADAPTER_H_
+#define DATATARGETADAPTER_H_
+
+#include <cordebug.h>
+
+// Forward decl to avoid including clrdata.h here (it's use is being deprecated)
+interface ICLRDataTarget;
+
+/*
+ * DataTargetAdapter - implements the new ICorDebugDataTarget interfaces
+ * by wrapping legacy ICLRDataTarget implementations. New code should use
+ * ICorDebugDataTarget, but we must continue to support ICLRDataTarget
+ * for dbgeng (watson, windbg, etc.) and for any other 3rd parties since
+ * it is a documented API for dump generation.
+ */
+class DataTargetAdapter : public ICorDebugMutableDataTarget
+{
+public:
+ // Create an adapter over the supplied legacy data target interface
+ DataTargetAdapter(ICLRDataTarget * pLegacyTarget);
+ virtual ~DataTargetAdapter();
+
+ //
+ // IUnknown.
+ //
+ virtual HRESULT STDMETHODCALLTYPE QueryInterface(
+ REFIID riid,
+ void** ppInterface);
+
+ virtual ULONG STDMETHODCALLTYPE AddRef();
+
+ virtual ULONG STDMETHODCALLTYPE Release();
+
+ //
+ // ICorDebugMutableDataTarget.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE GetPlatform(
+ CorDebugPlatform *pPlatform);
+
+ virtual HRESULT STDMETHODCALLTYPE ReadVirtual(
+ CORDB_ADDRESS address,
+ PBYTE pBuffer,
+ ULONG32 request,
+ ULONG32 *pcbRead);
+
+ virtual HRESULT STDMETHODCALLTYPE WriteVirtual(
+ CORDB_ADDRESS address,
+ const BYTE * pBuffer,
+ ULONG32 request);
+
+ virtual HRESULT STDMETHODCALLTYPE GetThreadContext(
+ DWORD dwThreadID,
+ ULONG32 contextFlags,
+ ULONG32 contextSize,
+ PBYTE context);
+
+ virtual HRESULT STDMETHODCALLTYPE SetThreadContext(
+ DWORD dwThreadID,
+ ULONG32 contextSize,
+ const BYTE * context);
+
+ virtual HRESULT STDMETHODCALLTYPE ContinueStatusChanged(
+ DWORD dwThreadId,
+ CORDB_CONTINUE_STATUS continueStatus);
+
+private:
+ LONG m_ref; // Reference count.
+ ICLRDataTarget * m_pLegacyTarget; // underlying data target
+};
+
+
+#endif //DATATARGETADAPTER_H_
diff --git a/src/debug/daccess/dirs.proj b/src/debug/daccess/dirs.proj
new file mode 100644
index 0000000000..795d462778
--- /dev/null
+++ b/src/debug/daccess/dirs.proj
@@ -0,0 +1,19 @@
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <!--Import the settings-->
+ <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\clr.props" />
+
+ <PropertyGroup>
+ <BuildInPhase1>true</BuildInPhase1>
+ <BuildInPhaseDefault>false</BuildInPhaseDefault>
+ <BuildCoreBinaries>true</BuildCoreBinaries>
+ <BuildSysBinaries>true</BuildSysBinaries>
+ </PropertyGroup>
+
+ <!--The following projects will build during PHASE 1-->
+ <ItemGroup Condition="'$(BuildExePhase)' == '1'">
+ <ProjectFile Include="HostLocal\daccess.nativeproj" />
+ </ItemGroup>
+
+ <!--Import the targets-->
+ <Import Project="$(_NTDRIVE)$(_NTROOT)\tools\Microsoft.DevDiv.Traversal.targets" />
+</Project>
diff --git a/src/debug/daccess/enummem.cpp b/src/debug/daccess/enummem.cpp
new file mode 100644
index 0000000000..068c2f2b13
--- /dev/null
+++ b/src/debug/daccess/enummem.cpp
@@ -0,0 +1,2057 @@
+// 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.
+//*****************************************************************************
+// File: enummem.cpp
+//
+
+//
+// ICLRDataEnumMemoryRegions implementation.
+//
+//*****************************************************************************
+
+#include "stdafx.h"
+
+#include <eeconfig.h>
+#include <ecall.h>
+#include <gcinfodecoder.h>
+
+#include "typestring.h"
+#include "daccess.h"
+#include "ipcmanagerinterface.h"
+#include "binder.h"
+#include "win32threadpool.h"
+#include "gcscan.h"
+
+#ifdef FEATURE_APPX
+#include "appxutil.h"
+#endif // FEATURE_APPX
+
+#if defined(DAC_MEASURE_PERF)
+
+unsigned __int64 g_nTotalTime;
+unsigned __int64 g_nStackTotalTime;
+unsigned __int64 g_nReadVirtualTotalTime;
+unsigned __int64 g_nFindTotalTime;
+unsigned __int64 g_nFindHashTotalTime;
+unsigned __int64 g_nFindHits;
+unsigned __int64 g_nFindCalls;
+unsigned __int64 g_nFindFails;
+unsigned __int64 g_nStackWalk;
+unsigned __int64 g_nFindStackTotalTime;
+
+#endif // #if defined(DAC_MEASURE_PERF)
+
+
+//
+// EnumMemCollectImages - collect all images of interest for heap dumps
+//
+// This is used primarily to save ngen images.
+// This is necessary so that heap dumps contain the full native code for the
+// process. Normally mini/heap dump debugging requires that the images be
+// available at debug-time, (in fact, watson explicitly does not want to
+// be downloading 3rd party images). Not including images is the main size
+// advantage of heap dumps over full dumps. However, since ngen images are
+// produced on the client, we can't always ensure that the debugger will
+// have access to the exact ngen image used in the dump. Therefore, managed
+// heap dumps also include full copies of all NGen images in the process.
+//
+// We also currently include in-memory modules (provided by a host, or loaded
+// from a Byte[]).
+//
+HRESULT ClrDataAccess::EnumMemCollectImages()
+{
+ SUPPORTS_DAC;
+
+ ProcessModIter modIter;
+ Module* modDef = NULL;
+ HRESULT status = S_OK;
+ PEFile *file;
+ TADDR pStartAddr = 0;
+ ULONG32 ulSize = 0;
+ ULONG32 ulSizeBlock;
+
+ TSIZE_T cbMemoryReported = m_cbMemoryReported;
+
+ //
+ // Collect the ngen images - Iterating through module list
+ //
+ EX_TRY
+ {
+ while ((modDef = modIter.NextModule()))
+ {
+ EX_TRY
+ {
+ ulSize = 0;
+ file = modDef->GetFile();
+
+ // We want to save all native images
+ if (file->HasNativeImage())
+ {
+ // We should only skip if signed by Microsoft!
+ pStartAddr = PTR_TO_TADDR(file->GetLoadedNative()->GetBase());
+ ulSize = file->GetLoadedNative()->GetSize();
+ }
+ // We also want to save any in-memory images. These show up like mapped files
+ // and so would not be in a heap dump by default. Technically it's not clear we
+ // should include them in the dump - you can often have the files available
+ // after-the-fact. But in-memory modules may be harder to track down at debug time
+ // and people may have come to rely on this - so we'll include them for now.
+ else
+ if (
+ // With Copy On Write feature enabled
+ // IL images would not be dumped as part of the private pages.
+ // We need to explicitly dump them here.
+#ifndef FEATURE_LAZY_COW_PAGES
+ file->GetPath().IsEmpty() && // is in-memory
+#endif // FEATURE_LAZY_COW_PAGES
+ file->HasMetadata() && // skip resource assemblies
+ file->IsLoaded(FALSE) && // skip files not yet loaded
+ !file->IsDynamic()) // skip dynamic (GetLoadedIL asserts anyway)
+ {
+ pStartAddr = PTR_TO_TADDR(file->GetLoadedIL()->GetBase());
+ ulSize = file->GetLoadedIL()->GetSize();
+ }
+
+ // memory are mapped in in OS_PAGE_SIZE size.
+ // Some memory are mapped in but some are not. You cannot
+ // write all in one block. So iterating through page size
+ //
+ while (ulSize > 0)
+ {
+ //
+ // Note that we have talked about not writing IL and Metadata to save size.
+ // It turns out IL was rarely mapped in.
+ // Metadata is needed. The RVA field is needed for it is pointed to a
+ // MethodHeader MethodDesc::GetILHeader. Without this RVA,
+ // all locals are broken. In case, you are asked about this question again.
+ //
+ ulSizeBlock = ulSize > OS_PAGE_SIZE ? OS_PAGE_SIZE : ulSize;
+ ReportMem(pStartAddr, ulSizeBlock, false);
+ pStartAddr += ulSizeBlock;
+ ulSize -= ulSizeBlock;
+ }
+ }
+ EX_CATCH_RETHROW_ONLY_COR_E_OPERATIONCANCELLED
+ }
+ }
+ EX_CATCH_RETHROW_ONLY_COR_E_OPERATIONCANCELLED
+
+ m_dumpStats.m_cbNgen = m_cbMemoryReported - cbMemoryReported;
+ return status;
+}
+
+
+
+//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+//
+// collecting memory for mscorwks's heap dump critical statics
+// This include the stress log, config structure, and IPC block
+//
+//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+HRESULT ClrDataAccess::EnumMemCLRHeapCrticalStatic(IN CLRDataEnumMemoryFlags flags)
+{
+ SUPPORTS_DAC;
+
+ TSIZE_T cbMemoryReported = m_cbMemoryReported;
+
+ // Write out the stress log structure itself
+ DacEnumHostDPtrMem(g_pStressLog);
+
+ // This is pointing to a static buffer
+ DacEnumHostDPtrMem(g_pConfig);
+
+ // dump GC heap structures. Note that the managed heap is not dumped out.
+ // We are just dump the GC heap structures.
+ //
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( EnumWksGlobalMemoryRegions(flags); );
+#ifdef FEATURE_SVR_GC
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( EnumSvrGlobalMemoryRegions(flags); );
+#endif
+
+#ifdef FEATURE_IPCMAN
+ //
+ // Write Out IPC Blocks
+ //
+ EX_TRY
+ {
+ g_pIPCManagerInterface.EnumMem();
+ if (g_pIPCManagerInterface.IsValid())
+ {
+ // write out the instance
+ DacEnumHostDPtrMem(g_pIPCManagerInterface);
+
+ // Then write out the public and private block
+ ReportMem(PTR_TO_TADDR(g_pIPCManagerInterface->GetBlockStart()), g_pIPCManagerInterface->GetBlockSize());
+ ReportMem(PTR_TO_TADDR(g_pIPCManagerInterface->GetBlockTableStart()), g_pIPCManagerInterface->GetBlockTableSize());
+ }
+ }
+ EX_CATCH_RETHROW_ONLY_COR_E_OPERATIONCANCELLED
+#endif // FEATURE_IPCMAN
+
+ m_dumpStats.m_cbClrHeapStatics = m_cbMemoryReported - cbMemoryReported;
+
+ return S_OK;
+}
+
+//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+//
+// collecting memory for mscorwks's statics. This is the minimal
+// set of global and statics that we need to have !threads, !pe, !ClrStack
+// to work.
+//
+//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+HRESULT ClrDataAccess::EnumMemCLRStatic(IN CLRDataEnumMemoryFlags flags)
+{
+ SUPPORTS_DAC;
+
+ TSIZE_T cbMemoryReported = m_cbMemoryReported;
+
+ //
+ // write out the static and global content that we care.
+ //
+
+ // The followig macro will report memory all of the dac related mscorwks static and
+ // global variables. But it won't report the structures that are pointed by
+ // global pointers.
+ //
+#define DEFINE_DACVAR(id_type, size_type, id, var) \
+ ReportMem(m_globalBase + g_dacGlobals.id, sizeof(size_type));
+
+#define DEFINE_DACVAR_SVR(id_type, size_type, id, var) \
+ ReportMem(m_globalBase + g_dacGlobals.id, sizeof(size_type));
+
+ // Cannot use CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED
+ // around conditional preprocessor directives in a sane fashion.
+ EX_TRY
+ {
+#include "dacvars.h"
+ }
+ EX_CATCH
+ {
+ // Catch the exception and keep going unless COR_E_OPERATIONCANCELED
+ // was thrown. Used generating dumps, where rethrow will cancel dump.
+ }
+ EX_END_CATCH(RethrowCancelExceptions)
+
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED
+ (
+ // StressLog is not defined on Rotor build for DAC
+ ReportMem(m_globalBase + g_dacGlobals.dac__g_pStressLog, sizeof(StressLog *));
+ );
+
+ EX_TRY
+ {
+ // These two static pointers are pointed to static data of byte[]
+ // then run constructor in place
+ //
+ ReportMem(m_globalBase + g_dacGlobals.SystemDomain__m_pSystemDomain,
+ sizeof(SystemDomain));
+ ReportMem(m_globalBase + g_dacGlobals.SharedDomain__m_pSharedDomain,
+ sizeof(SharedDomain));
+
+ // We need GCHeap pointer to make EEVersion work
+ ReportMem(m_globalBase + g_dacGlobals.dac__g_pGCHeap,
+ sizeof(GCHeap *));
+
+ // see synblk.cpp, the pointer is pointed to a static byte[]
+ SyncBlockCache::s_pSyncBlockCache.EnumMem();
+
+#ifndef FEATURE_IMPLICIT_TLS
+ ReportMem(m_globalBase + g_dacGlobals.dac__gThreadTLSIndex,
+ sizeof(DWORD));
+ ReportMem(m_globalBase + g_dacGlobals.dac__gAppDomainTLSIndex,
+ sizeof(DWORD));
+#endif
+
+ ReportMem( m_globalBase + g_dacGlobals.dac__g_FCDynamicallyAssignedImplementations,
+ sizeof(TADDR)*ECall::NUM_DYNAMICALLY_ASSIGNED_FCALL_IMPLEMENTATIONS);
+ }
+ EX_CATCH_RETHROW_ONLY_COR_E_OPERATIONCANCELLED
+
+#ifndef FEATURE_PAL
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_runtimeLoadedBaseAddress.EnumMem(); )
+#endif // !FEATURE_PAL
+
+ // These are the structures that are pointed by global pointers and we care.
+ // Some may reside in heap and some may reside as a static byte array in mscorwks.dll
+ // That is ok. We will report them explicitly.
+ //
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pConfig.EnumMem(); )
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pPredefinedArrayTypes.EnumMem(); )
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pObjectClass.EnumMem(); )
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pStringClass.EnumMem(); )
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pArrayClass.EnumMem(); )
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pExceptionClass.EnumMem(); )
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pThreadAbortExceptionClass.EnumMem(); )
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pOutOfMemoryExceptionClass.EnumMem(); )
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pStackOverflowExceptionClass.EnumMem(); )
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pExecutionEngineExceptionClass.EnumMem(); )
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pDelegateClass.EnumMem(); )
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pMulticastDelegateClass.EnumMem(); )
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pValueTypeClass.EnumMem(); )
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pEnumClass.EnumMem(); )
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pThreadClass.EnumMem(); )
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pCriticalFinalizerObjectClass.EnumMem(); )
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pFreeObjectMethodTable.EnumMem(); )
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pObjectCtorMD.EnumMem(); )
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_fHostConfig.EnumMem(); )
+
+ // These two static pointers are pointed to static data of byte[]
+ // then run constructor in place
+ //
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( SystemDomain::m_pSystemDomain.EnumMem(); )
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( SharedDomain::m_pSharedDomain.EnumMem(); )
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pDebugger.EnumMem(); )
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pEEInterface.EnumMem(); )
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pDebugInterface.EnumMem(); )
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pEEDbgInterfaceImpl.EnumMem(); )
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_CORDebuggerControlFlags.EnumMem(); )
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_Mscorlib.EnumMem(); )
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pPredefinedArrayTypes[ELEMENT_TYPE_OBJECT]->EnumMemoryRegions(flags); )
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( StubManager::EnumMemoryRegions(flags); )
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pFinalizerThread.EnumMem(); )
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pSuspensionThread.EnumMem(); )
+
+#ifdef FEATURE_SVR_GC
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED
+ (
+ GCHeap::gcHeapType.EnumMem();
+ );
+#endif // FEATURE_SVR_GC
+
+ m_dumpStats.m_cbClrStatics = m_cbMemoryReported - cbMemoryReported;
+
+ return S_OK;
+}
+
+//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+//
+// This function reports memory that a heap dump need to debug CLR
+// and managed code efficiently.
+//
+// We will write out -
+// 1. mscorwks.dll's image read/write pages
+// 2. IPC blocks - shared memory (needed for debugging service and perf counter)
+// 3. ngen images excluding Metadata and IL for size perf
+// 4. We may want to touch the code pages on the stack - to be safe....
+//
+//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+HRESULT ClrDataAccess::EnumMemoryRegionsWorkerHeap(IN CLRDataEnumMemoryFlags flags)
+{
+ SUPPORTS_DAC;
+
+ HRESULT status = S_OK;
+
+ m_instances.ClearEnumMemMarker();
+
+ // clear all of the previous cached memory
+ Flush();
+
+ // collect ngen image
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemCollectImages(); );
+
+ // collect CLR static
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemCLRStatic(flags); );
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemCLRHeapCrticalStatic(flags); );
+
+ // Note that we do not need to flush out all of the dac instance manager's instance.
+ // This is because it is a heap dump here. Assembly and AppDomain objects will be reported
+ // by the default heap collection mechanism by dbghelp.lib
+ //
+ // Microsoft: I suspect if we have all private read-write pages the preceding statement
+ // would be true, but I don't think we have that guarantee here.
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemDumpModuleList(flags); );
+
+#ifdef FEATURE_LAZY_COW_PAGES
+ // Iterating to all threads' stacks, as we have to collect data stored inside (core)clr.dll
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemDumpAllThreadsStack(flags); )
+
+ // Dump AppDomain-specific info
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemDumpAppDomainInfo(flags); )
+
+ // Dump the Debugger object data needed
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pDebugger->EnumMemoryRegions(flags); )
+
+ // now dump the memory get dragged in by using DAC API implicitly.
+ m_dumpStats.m_cbImplicity = m_instances.DumpAllInstances(m_enumMemCb);
+#endif // FEATURE_LAZY_COW_PAGES
+
+ // end of code
+ status = m_memStatus;
+
+ return S_OK;
+} // EnumMemoryRegionsWorkerHeap
+
+//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+//
+// Helper function for skinny mini-dump
+// Pass in an managed object, this function will dump the EEClass hierachy
+// and field desc of object so SOS's !DumpObj will work
+//
+//
+//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+HRESULT ClrDataAccess::DumpManagedObject(CLRDataEnumMemoryFlags flags, OBJECTREF objRef)
+{
+ SUPPORTS_DAC;
+
+ HRESULT status = S_OK;
+
+ if (objRef == NULL)
+ {
+ return status;
+ }
+
+ if (!GCScan::GetGcRuntimeStructuresValid ())
+ {
+ // GC is in progress, don't dump this object
+ return S_OK;
+ }
+
+ EX_TRY
+ {
+ // write out the current EE class and the direct/indirect inherited EE Classes
+ MethodTable *pMethodTable = objRef->GetGCSafeMethodTable();
+
+ while (pMethodTable)
+ {
+ EX_TRY
+ {
+ pMethodTable->EnumMemoryRegions(flags);
+
+ StackSString s;
+
+ // This might look odd. We are not using variable s after forming it.
+ // That is because our DAC inspecting API is using TypeString to form
+ // full type name. Form the full name here is a implicit reference to needed
+ // memory.
+ //
+ TypeString::AppendType(s, TypeHandle(pMethodTable), TypeString::FormatNamespace|TypeString::FormatFullInst);
+ }
+ EX_CATCH_RETHROW_ONLY_COR_E_OPERATIONCANCELLED
+
+ // Walk up to parent MethodTable
+ pMethodTable = pMethodTable->GetParentMethodTable();
+ }
+
+ // now dump the content for the managed object
+ objRef->EnumMemoryRegions();
+ }
+ EX_CATCH_RETHROW_ONLY_COR_E_OPERATIONCANCELLED
+
+ return status;
+}
+
+
+//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+//
+// Helper function for skinny mini-dump
+// Pass in an managed excption object, this function will dump
+// the managed exception object and some of its fields, such as message, stack trace string,
+// inner exception.
+//
+//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+HRESULT ClrDataAccess::DumpManagedExcepObject(CLRDataEnumMemoryFlags flags, OBJECTREF objRef)
+{
+ SUPPORTS_DAC;
+
+ if (objRef == NULL)
+ {
+ return S_OK;
+ }
+
+ if (!GCScan::GetGcRuntimeStructuresValid ())
+ {
+ // GC is in progress, don't dump this object
+ return S_OK;
+ }
+
+ // write out the managed object for exception. This will only write out the
+ // direct field value. After this, we need to visit some selected fields, such as
+ // exception message and stack trace field, and dump out the object referenced via
+ // the fields.
+ //
+ DumpManagedObject(flags, objRef);
+
+ // If this is not a pre-allocated exception type, then we'll need to dump out enough memory to ensure
+ // that the lookup codepath from the Module to information for the type of this Exception will
+ // be present. Simply dumping the managed object itself isn't enough. Sos doesn't need this.
+ EX_TRY
+ {
+ MethodTable * pMethodTable = objRef->GetGCSafeMethodTable();
+ PTR_Module pModule = pMethodTable->GetModule();
+ mdTypeDef exceptionTypeDef = pMethodTable->GetCl();
+
+ if (TypeFromToken(exceptionTypeDef) != mdtTypeDef)
+ {
+ _ASSERTE(!"Module should have contained a TypeDef, dump will likely be missing exception type lookup!");
+ }
+
+ // The lookup from the Module that contains this TypeDef:
+ pModule->LookupTypeDef(RidFromToken(exceptionTypeDef));
+
+ // If it's a generic class, we need to implicitly enumerate the memory needed to look it up
+ // and enable the calls that ICD will want to make against the TypeHandle when retrieving the
+ // Exception info.
+ TypeHandle th;
+ th = ClassLoader::LookupTypeDefOrRefInModule(pModule, exceptionTypeDef);
+ th.EnumMemoryRegions(flags);
+ }
+ EX_CATCH_RETHROW_ONLY_COR_E_OPERATIONCANCELLED
+
+#ifdef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+ // store the exception type name
+ EX_TRY
+ {
+ MethodTable * pMethodTable = objRef->GetGCSafeMethodTable();
+ StackSString s;
+ TypeString::AppendType(s, TypeHandle(pMethodTable), TypeString::FormatNamespace|TypeString::FormatFullInst);
+ DacMdCacheAddEEName(dac_cast<TADDR>(pMethodTable), s);
+ }
+ EX_CATCH_RETHROW_ONLY_COR_E_OPERATIONCANCELLED
+#endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+
+ EXCEPTIONREF exceptRef = (EXCEPTIONREF)objRef;
+
+ if (flags != CLRDATA_ENUM_MEM_TRIAGE)
+ {
+ // dump the exception message field
+ DumpManagedObject(flags, (OBJECTREF)exceptRef->GetMessage());
+ }
+
+ // dump the exception's stack trace field
+ DumpManagedStackTraceStringObject(flags, exceptRef->GetStackTraceString());
+
+ // dump the exception's remote stack trace field only if we are not generating a triage dump, or
+ // if we are generating a triage dump of an AppX process, or the exception type does not override
+ // the StackTrace getter (see Exception.InternalPreserveStackTrace to understand why)
+ if (flags != CLRDATA_ENUM_MEM_TRIAGE ||
+#ifdef FEATURE_APPX
+ AppX::DacIsAppXProcess() ||
+#endif // FEATURE_APPX
+ !ExceptionTypeOverridesStackTraceGetter(exceptRef->GetGCSafeMethodTable()))
+ {
+ DumpManagedStackTraceStringObject(flags, exceptRef->GetRemoteStackTraceString());
+ }
+
+ // Dump inner exception
+ DumpManagedExcepObject(flags, exceptRef->GetInnerException());
+
+ // Dump the stack trace array object and its underlying type
+ I1ARRAYREF stackTraceArrayObj = exceptRef->GetStackTraceArrayObject();
+
+ // There are cases where a managed exception does not have a stack trace.
+ // These cases are:
+ // * exception was thrown by VM and no managed frames are on the thread.
+ // * exception thrown is a preallocated exception.
+ if (stackTraceArrayObj != NULL)
+ {
+ // first dump the array's element type
+ TypeHandle arrayTypeHandle = stackTraceArrayObj->GetTypeHandle();
+ ArrayTypeDesc* pArrayTypeDesc = arrayTypeHandle.AsArray();
+ TypeHandle elementTypeHandle = pArrayTypeDesc->GetArrayElementTypeHandle();
+ elementTypeHandle.AsMethodTable()->EnumMemoryRegions(flags);
+ elementTypeHandle.AsMethodTable()->GetClass()->EnumMemoryRegions(flags, elementTypeHandle.AsMethodTable());
+
+ // now dump the actual stack trace array object
+ DumpManagedObject(flags, (OBJECTREF)stackTraceArrayObj);
+ }
+
+ // Dump the stack trace native structure. Unfortunately, we need to write out the
+ // native structure and also dump the MethodDesc that we care about!
+ // We need to ensure the entire _stackTrace from the Exception is enumerated and
+ // included in the dump. When we touch the header and each element looking for the
+ // MD this happens.
+ StackTraceArray stackTrace;
+ exceptRef->GetStackTrace(stackTrace);
+ for(size_t i = 0; i < stackTrace.Size(); i++)
+ {
+ MethodDesc* pMD = stackTrace[i].pFunc;
+ if (!DacHasMethodDescBeenEnumerated(pMD) && DacValidateMD(pMD))
+ {
+ pMD->EnumMemoryRegions(flags);
+
+ // The following calls are to ensure that mscordacwks!DacDbiInterfaceImpl::GetNativeCodeInfo
+ // will succeed for all dumps.
+
+ // Pulls in data to translate from token to MethodDesc
+ FindLoadedMethodRefOrDef(pMD->GetMethodTable()->GetModule(), pMD->GetMemberDef());
+
+ // Pulls in sequence points.
+ DebugInfoManager::EnumMemoryRegionsForMethodDebugInfo(flags, pMD);
+ PCODE addr = pMD->GetNativeCode();
+ if (addr != NULL)
+ {
+ IJitManager::MethodRegionInfo methodRegionInfo = { NULL, 0, NULL, 0 };
+ EECodeInfo codeInfo(addr);
+ codeInfo.GetMethodRegionInfo(&methodRegionInfo);
+ }
+ }
+
+ // Enumerate the code around call site to help SOS resolve the source lines
+ TADDR callEnd = PCODEToPINSTR(stackTrace[i].ip);
+ DacEnumCodeForStackwalk(callEnd);
+ }
+
+ return S_OK;
+}
+
+//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+//
+// Helper function for skinny mini-dump
+// Pass in a string object representing a managed stack trace, this function will
+// dump it and "poison" the contents with a PII-free version of the stack trace.
+//
+//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+HRESULT ClrDataAccess::DumpManagedStackTraceStringObject(CLRDataEnumMemoryFlags flags, STRINGREF orefStackTrace)
+{
+ SUPPORTS_DAC;
+
+ if (orefStackTrace == NULL)
+ {
+ return S_OK;
+ }
+
+ // dump the stack trace string object
+ DumpManagedObject(flags, (OBJECTREF)orefStackTrace);
+
+ if (flags == CLRDATA_ENUM_MEM_TRIAGE)
+ {
+ // StringObject::GetSString does not support DAC, use GetBuffer/GetStringLength
+ SString stackTrace(dac_cast<PTR_WSTR>((TADDR)orefStackTrace->GetBuffer()), orefStackTrace->GetStringLength());
+
+ StripFileInfoFromStackTrace(stackTrace);
+
+ COUNT_T traceCharCount = stackTrace.GetCount();
+ _ASSERTE(traceCharCount <= orefStackTrace->GetStringLength());
+
+ // fill the rest of the string with \0
+ WCHAR *buffer = stackTrace.OpenUnicodeBuffer(orefStackTrace->GetStringLength());
+ memset(buffer + traceCharCount, 0, sizeof(WCHAR) * (orefStackTrace->GetStringLength() - traceCharCount));
+
+ // replace the string
+ DacUpdateMemoryRegion(dac_cast<TADDR>(orefStackTrace) + StringObject::GetBufferOffset(), sizeof(WCHAR) * orefStackTrace->GetStringLength(), (BYTE *)buffer);
+ }
+
+ return S_OK;
+}
+
+//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+//
+// Iterating through module list and report the memory.
+// Remember to call
+// m_instances.DumpAllInstances(m_enumMemCb);
+// when all memory enumeration are done if you call this function!
+// This is because using ProcessModIter will drag in some memory implicitly.
+//
+//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+HRESULT ClrDataAccess::EnumMemDumpModuleList(CLRDataEnumMemoryFlags flags)
+{
+ SUPPORTS_DAC;
+
+ ProcessModIter modIter;
+ Module* modDef;
+ TADDR base;
+ ULONG32 length;
+ PEFile *file;
+ TSIZE_T cbMemoryReported = m_cbMemoryReported;
+#ifdef FEATURE_PREJIT
+ COUNT_T count;
+#endif // FEATURE_PREJIT
+
+ //
+ // Iterating through module list
+ //
+
+ // Cannot use CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED around
+ // conditional pre-processor directives in a sane fashion
+ EX_TRY
+ {
+ while ((modDef = modIter.NextModule()))
+ {
+ // We also want to dump the link from the Module back to the AppDomain,
+ // since the stackwalker uses it to find the AD.
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED
+ (
+ // Pass false to ensure we force enumeration of this module's references.
+ modDef->EnumMemoryRegions(flags, false);
+ );
+
+ EX_TRY
+ {
+ // To enable a debugger to check on whether a module is an NI or IL image, they need
+ // the DOS header, PE headers, and IMAGE_COR20_HEADER for the Flags member.
+ // We expose no API today to find this out.
+ PTR_PEFile pPEFile = modDef->GetFile();
+ PEImage * pILImage = pPEFile->GetILimage();
+ PEImage * pNIImage = pPEFile->GetNativeImage();
+
+ // Implicitly gets the COR header.
+ if ((pILImage) && (pILImage->HasLoadedLayout()))
+ {
+ pILImage->GetCorHeaderFlags();
+ }
+ if ((pNIImage) && (pNIImage->HasLoadedLayout()))
+ {
+ pNIImage->GetCorHeaderFlags();
+ }
+ }
+ EX_CATCH_RETHROW_ONLY_COR_E_OPERATIONCANCELLED
+
+
+ EX_TRY
+ {
+ file = modDef->GetFile();
+ base = PTR_TO_TADDR(file->GetLoadedImageContents(&length));
+ file->EnumMemoryRegions(flags);
+#ifdef FEATURE_PREJIT
+
+ // If module has native image and it has debug map, we need to get the debug map.
+ //
+ if (modDef->HasNativeImage() && modDef->GetNativeImage()->HasNativeDebugMap())
+ {
+ modDef->GetNativeImage()->GetNativeDebugMap(&count);
+ }
+#endif // FEATURE_PREJIT
+ }
+ EX_CATCH
+ {
+ // Catch the exception and keep going unless COR_E_OPERATIONCANCELED
+ // was thrown. Used generating dumps, where rethrow will cancel dump.
+ }
+ EX_END_CATCH(RethrowCancelExceptions)
+ }
+ }
+ EX_CATCH
+ {
+ // Catch the exception and keep going unless COR_E_OPERATIONCANCELED
+ // was thrown. Used generating dumps, where rethrow will cancel dump.
+ }
+ EX_END_CATCH(RethrowCancelExceptions)
+
+ m_dumpStats.m_cbModuleList = m_cbMemoryReported - cbMemoryReported;
+
+ return S_OK;
+}
+
+//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+//
+// Iterate through AppDomains and report specific memory needed
+// for all dumps, such as the Module lookup path.
+// This is intended for MiniDumpNormal and should be kept small.
+//
+//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+HRESULT ClrDataAccess::EnumMemDumpAppDomainInfo(CLRDataEnumMemoryFlags flags)
+{
+ SUPPORTS_DAC;
+
+ AppDomainIterator adIter(FALSE);
+ EX_TRY
+ {
+ while (adIter.Next())
+ {
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED
+ (
+ // Note that the flags being CLRDATA_ENUM_MEM_MINI prevents
+ // you from pulling entire files loaded into memory into the dump.
+ adIter.GetDomain()->EnumMemoryRegions(flags, true);
+ );
+ }
+ }
+ EX_CATCH_RETHROW_ONLY_COR_E_OPERATIONCANCELLED
+
+ return S_OK;
+}
+
+//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+//
+// Iterating through each frame to make sure
+// we dump out MethodDesc, DJI etc related info
+// This is a generic helper for walking stack. However, if you call
+// this function, make sure to flush instance in the DAC Instance manager.
+//
+//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+HRESULT ClrDataAccess::EnumMemWalkStackHelper(CLRDataEnumMemoryFlags flags,
+ IXCLRDataStackWalk *pStackWalk,
+ Thread * pThread)
+{
+ SUPPORTS_DAC;
+
+#if defined(DAC_MEASURE_PERF)
+ g_nStackWalk = 1;
+ unsigned __int64 nStart= GetCycleCount();
+#endif
+
+ HRESULT status = S_OK;
+ ReleaseHolder<IXCLRDataFrame> pFrame(NULL);
+ ReleaseHolder<IXCLRDataMethodInstance> pMethod(NULL);
+ ReleaseHolder<IXCLRDataMethodDefinition> pMethodDefinition(NULL);
+ ReleaseHolder<IXCLRDataTypeInstance> pTypeInstance(NULL);
+
+ MethodDesc * pMethodDesc = NULL;
+ EX_TRY
+ {
+ TADDR previousSP = 0; //start at zero; this allows first check to always succeed.
+ TADDR currentSP;
+ currentSP = dac_cast<TADDR>(pThread->GetCachedStackLimit()) + sizeof(TADDR);
+
+ // exhaust the frames using DAC api
+ bool frameHadContext;
+ for (; status == S_OK; )
+ {
+ frameHadContext = false;
+ status = pStackWalk->GetFrame(&pFrame);
+ PCODE addr = NULL;
+ if (status == S_OK && pFrame != NULL)
+ {
+ // write out the code that ip pointed to
+ T_CONTEXT context;
+ REGDISPLAY regDisp;
+ if ((status=pFrame->GetContext(CONTEXT_ALL, sizeof(T_CONTEXT),
+ NULL, (BYTE *)&context))==S_OK)
+ {
+ // Enumerate the code around the call site to help debugger stack walking heuristics
+ ::FillRegDisplay(&regDisp, &context);
+ addr = GetControlPC(&regDisp);
+ TADDR callEnd = PCODEToPINSTR(addr);
+ DacEnumCodeForStackwalk(callEnd);
+ frameHadContext = true;
+ }
+
+ //
+ // There are identical stack pointer checking semantics in code:Thread::EnumMemoryRegionsWorker
+ // See that code for comments.
+ // You ***MUST*** maintain identical semantics for both checks!
+ //
+ CLRDataSimpleFrameType simpleFrameType;
+ CLRDataDetailedFrameType detailedFrameType;
+ if (SUCCEEDED(pFrame->GetFrameType(&simpleFrameType, &detailedFrameType)))
+ {
+ if (!frameHadContext)
+ {
+ _ASSERTE(!"Stack frame should always have an associated context!");
+ break;
+ }
+
+ // This is StackFrameIterator::SFITER_FRAMELESS_METHOD, initialized by Code:ClrDataStackWalk::GetFrame
+ // from code:ClrDataStackWalk::RawGetFrameType
+ if (simpleFrameType == CLRDATA_SIMPFRAME_MANAGED_METHOD)
+ {
+ currentSP = (TADDR)GetRegdisplaySP(&regDisp);
+
+ if (currentSP <= previousSP)
+ {
+ _ASSERTE(!"Target stack has been corrupted, SP for current frame must be larger than previous frame.");
+ break;
+ }
+
+ if (currentSP % sizeof(TADDR) != 0)
+ {
+ _ASSERTE(!"Target stack has been corrupted, SP must be aligned.");
+ break;
+ }
+
+ if (!pThread->IsAddressInStack(currentSP))
+ {
+ _ASSERTE(!"Target stack has been corrupted, SP must in in the stack range.");
+ break;
+ }
+ }
+ }
+ else
+ {
+ _ASSERTE(!"The stack frame should always know what type it is!");
+ break;
+ }
+
+ status = pFrame->GetMethodInstance(&pMethod);
+ if (status == S_OK && pMethod != NULL)
+ {
+ // managed frame
+ if (SUCCEEDED(pMethod->GetTypeInstance(&pTypeInstance)) &&
+ (pTypeInstance != NULL))
+ {
+ pTypeInstance.Clear();
+ }
+
+ if(SUCCEEDED(pMethod->GetDefinition(&pMethodDefinition)) &&
+ (pMethodDefinition != NULL))
+ {
+ pMethodDesc = ((ClrDataMethodDefinition *)pMethodDefinition.GetValue())->GetMethodDesc();
+ if (pMethodDesc)
+ {
+
+ // If this is a generic, we'll need to pull in enough extra info that
+ // we get decent results later when stackwalking. Note that we do not guarantee
+ // we'll always get an exact type for any reference type; most of the time the
+ // stack walk will just show System.__Canon, which is the level of support we
+ // guarantee for minidumps without full memory.
+ EX_TRY
+ {
+ if ((pMethodDesc->AcquiresInstMethodTableFromThis()) ||
+ (pMethodDesc->RequiresInstMethodTableArg()))
+ {
+ // MethodTable
+ ReleaseHolder<IXCLRDataValue> pDV(NULL);
+ ReleaseHolder<IXCLRDataValue> pAssociatedValue(NULL);
+ CLRDATA_ADDRESS address;
+ PTR_Object pObjThis = NULL;
+
+ if (SUCCEEDED(pFrame->GetArgumentByIndex(0, &pDV, 0, NULL, NULL)) &&
+ SUCCEEDED(pDV->GetAssociatedValue(&pAssociatedValue)) &&
+ SUCCEEDED(pAssociatedValue->GetAddress(&address)))
+ {
+ // Implicitly enumerate the object itself.
+ TADDR addrObjThis = CLRDATA_ADDRESS_TO_TADDR(address);
+ pObjThis = dac_cast<PTR_Object>(addrObjThis);
+ }
+
+ // And now get the extra info we need for the AcquiresInstMethodTableFromThis case.
+ if (pMethodDesc->AcquiresInstMethodTableFromThis())
+ {
+ // When working with the 'this' case, we need to pick up the MethodTable from
+ // object lookup.
+ PTR_MethodTable pMT = NULL;
+ if (pObjThis != NULL)
+ {
+ pMT = pObjThis->GetMethodTable();
+ }
+
+ TypeHandle th;
+ if (pMT != NULL)
+ {
+ th = TypeHandle(pMT);
+ }
+
+ Instantiation classInst = pMethodDesc->GetExactClassInstantiation(th);
+ Instantiation methodInst = pMethodDesc->GetMethodInstantiation();
+ }
+
+ }
+ else if (pMethodDesc->RequiresInstMethodDescArg())
+ {
+ // This method has a generic type token which is required to figure out the exact instantiation
+ // of the method.
+ // We need to to use the variable index of the generic type token in order to do the look up.
+ CLRDATA_ADDRESS address = NULL;
+ DWORD dwExactGenericArgsTokenIndex = 0;
+ ReleaseHolder<IXCLRDataValue> pDV(NULL);
+ ReleaseHolder<IXCLRDataValue> pAssociatedValue(NULL);
+ ReleaseHolder<IXCLRDataFrame2> pFrame2(NULL);
+
+ if (SUCCEEDED(pFrame->QueryInterface(__uuidof(IXCLRDataFrame2), (void**)&pFrame2)) &&
+ SUCCEEDED(pFrame2->GetExactGenericArgsToken(&pDV)) &&
+ SUCCEEDED(pDV->GetAssociatedValue(&pAssociatedValue)) &&
+ SUCCEEDED(pAssociatedValue->GetAddress(&address)))
+ {
+ TADDR addrMD = CLRDATA_ADDRESS_TO_TADDR(address);
+ PTR_MethodDesc pMD = dac_cast<PTR_MethodDesc>(addrMD);
+ pMD->EnumMemoryRegions(flags);
+ }
+
+ pMethodDesc->EnumMemoryRegions(flags);
+ MethodTable * pCanonicalMT = pMethodDesc->GetCanonicalMethodTable();
+ MethodTable * pNormalMT = pMethodDesc->GetMethodTable();
+ pCanonicalMT->EnumMemoryRegions(flags);
+ pNormalMT->EnumMemoryRegions(flags);
+ }
+ }
+ EX_CATCH_RETHROW_ONLY_COR_E_OPERATIONCANCELLED
+
+ pMethodDesc->EnumMemoryRegions(flags);
+
+ // The following calls are to ensure that mscordacwks!DacDbiInterfaceImpl::GetNativeCodeSequencePointsAndVarInfo
+ // will succeed for all dumps. Local variable info usefulness is somewhat questionable
+ // since most dumps will be for optimized targets. However, being able to map
+ // back to source lines for functions on stacks is very useful and we don't
+ // want to allow the function to fail for all targets.
+
+ // Pulls in sequence points and local variable info
+ DebugInfoManager::EnumMemoryRegionsForMethodDebugInfo(flags, pMethodDesc);
+
+#ifdef WIN64EXCEPTIONS
+
+ if (addr != NULL)
+ {
+ EECodeInfo codeInfo(addr);
+
+ // We want IsFilterFunclet to work for anything on the stack
+ codeInfo.GetJitManager()->IsFilterFunclet(&codeInfo);
+
+ // The stackwalker needs GC info to find the parent 'stack pointer' or PSP
+ GCInfoToken gcInfoToken = codeInfo.GetGCInfoToken();
+ PTR_BYTE pGCInfo = dac_cast<PTR_BYTE>(gcInfoToken.Info);
+ if (pGCInfo != NULL)
+ {
+ GcInfoDecoder gcDecoder(gcInfoToken, DECODE_PSP_SYM, 0);
+ DacEnumMemoryRegion(dac_cast<TADDR>(pGCInfo), gcDecoder.GetNumBytesRead(), true);
+ }
+ }
+#endif // WIN64EXCEPTIONS
+ }
+ pMethodDefinition.Clear();
+ }
+ pMethod.Clear();
+ }
+ pFrame.Clear();
+ }
+
+ previousSP = currentSP;
+ status = pStackWalk->Next();
+ }
+
+ }
+ EX_CATCH
+ {
+ status = E_FAIL;
+ // Catch the exception and keep going unless a COR_E_OPERATIONCANCELED
+ // was thrown. In which case, rethrow to cancel the dump gathering
+ }
+ EX_END_CATCH(RethrowCancelExceptions)
+
+#if defined(DAC_MEASURE_PERF)
+ unsigned __int64 nEnd = GetCycleCount();
+ g_nStackTotalTime += nEnd - nStart;
+ g_nStackWalk = 0;
+#endif // #if defined(DAC_MEASURE_PERF)
+
+ return status;
+}
+
+// code: ClrDataAccess::EnumMemDumpAllThreadsStack needs a trivial implementation of
+// an un-DACized container class to track what exceptions have happened so far.
+// It shouldn't get used anywhere else.
+class DebuggingExceptionTrackerList
+{
+private:
+
+ struct TrivialTADDRNode
+ {
+ TADDR m_exceptionAddress;
+ TrivialTADDRNode * m_pNext;
+
+ TrivialTADDRNode(TrivialTADDRNode *pNext, TADDR address)
+ : m_exceptionAddress(address), m_pNext(pNext)
+ {
+ SUPPORTS_DAC_HOST_ONLY;
+ }
+
+ private:
+ TrivialTADDRNode() { _ASSERTE(!"You should never call this ctor."); }
+ };
+
+ TrivialTADDRNode *m_pHead;
+
+ bool Find(TADDR address)
+ {
+ SUPPORTS_DAC_HOST_ONLY;
+ for (TrivialTADDRNode *pFind = m_pHead; pFind != NULL; pFind = pFind->m_pNext)
+ if (pFind->m_exceptionAddress == address)
+ return true;
+
+ return false;
+ }
+
+public:
+ DebuggingExceptionTrackerList()
+ : m_pHead(NULL)
+ {
+ SUPPORTS_DAC_HOST_ONLY;
+ }
+
+ bool AddNewAddressOnly(TADDR address)
+ {
+ SUPPORTS_DAC_HOST_ONLY;
+ if (Find(address))
+ {
+ return false;
+ }
+ else
+ {
+ TrivialTADDRNode *pNew = new TrivialTADDRNode(m_pHead, address);
+ m_pHead = pNew;
+ return true;
+ }
+ }
+
+ ~DebuggingExceptionTrackerList()
+ {
+ SUPPORTS_DAC_HOST_ONLY;
+ for (TrivialTADDRNode *pTemp = m_pHead; m_pHead != NULL; pTemp = m_pHead)
+ {
+ m_pHead = m_pHead->m_pNext;
+ delete pTemp;
+ }
+ }
+};
+
+
+//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+//
+// This function will walk all threads, all the context in the
+// exception state to report memory. This can also drag in memory implicitly.
+// So do call
+// m_instances.DumpAllInstances(m_enumMemCb);
+// when function is done.
+//
+//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+HRESULT ClrDataAccess::EnumMemDumpAllThreadsStack(CLRDataEnumMemoryFlags flags)
+{
+ SUPPORTS_DAC;
+
+#ifdef FEATURE_COMINTEROP
+ // Dump the exception object stored in the WinRT stowed exception
+ EnumMemStowedException(flags);
+#endif
+
+ HRESULT status = S_OK;
+ TSIZE_T cbMemoryReported = m_cbMemoryReported;
+
+#ifdef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+
+ // Duplicate the enumeration code below, to allow Exception stacks to be enumerated first.
+ // These exception stacks will get MethodDesc names cached to the DacStreamManager before
+ // MethodDescs residing on the "regular" callstacks
+ EX_TRY
+ {
+ DebuggingExceptionTrackerList exceptionTrackingInner;
+
+ CLRDATA_ENUM handle;
+ ReleaseHolder<IXCLRDataTask> pIXCLRDataTask(NULL);
+ ReleaseHolder<IXCLRDataExceptionState> pExcepState(NULL);
+ Thread *pThread = NULL;
+
+ // enumerating through each thread
+ StartEnumTasks(&handle);
+ status = EnumTask(&handle, &pIXCLRDataTask);
+ for (unsigned nbThreads = 0; status == S_OK && pIXCLRDataTask != NULL; nbThreads++)
+ {
+ // Avoid infinite loop if target process is corrupted.
+ if (nbThreads > 100000)
+ {
+ break;
+ }
+ EX_TRY
+ {
+ // get Thread *
+ pThread = ((ClrDataTask *)pIXCLRDataTask.GetValue())->GetThread();
+
+ // dump the exception object
+ DumpManagedExcepObject(flags, pThread->LastThrownObject());
+
+ // Now probe into the exception info
+ status = pIXCLRDataTask->GetCurrentExceptionState(&pExcepState);
+ while (status == S_OK && pExcepState != NULL)
+ {
+ EX_TRY
+ {
+ // touch the throwable in exception state
+ PTR_UNCHECKED_OBJECTREF throwRef(((ClrDataExceptionState *)pExcepState.GetValue())->m_throwable);
+
+ // If we've already attempted enumeration for this exception, it's time to quit.
+ if (!exceptionTrackingInner.AddNewAddressOnly(throwRef.GetAddr()))
+ {
+ break;
+ }
+
+ DumpManagedExcepObject(flags, *throwRef);
+ }
+ EX_CATCH_RETHROW_ONLY_COR_E_OPERATIONCANCELLED
+
+ // get the previous exception
+ IXCLRDataExceptionState * pExcepStatePrev = NULL;
+ status = pExcepState->GetPrevious(&pExcepStatePrev);
+
+ // Release our current exception object, and transfer ref ownership of the previous
+ // exception object into the holder.
+ pExcepState = pExcepStatePrev;
+ }
+ }
+ EX_CATCH_RETHROW_ONLY_COR_E_OPERATIONCANCELLED
+
+ // get next thread
+ pIXCLRDataTask.Clear();
+ status = EnumTask(&handle, &pIXCLRDataTask);
+ }
+ EndEnumTasks(handle);
+ }
+ EX_CATCH_RETHROW_ONLY_COR_E_OPERATIONCANCELLED
+
+#endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+
+ // exceptionTracking is used for exactly that; it is a per-dump list of the
+ // addresses of all exceptions enumerated for this dump. If an exception is
+ // enumerated more than once it indicates that we have multiple threads pointing to
+ // the same object, or the same thread has an InnerException chain with a cycle.
+ // In either case, we need to terminate exception reporting.
+ DebuggingExceptionTrackerList exceptionTracking;
+
+ EX_TRY
+ {
+ CLRDATA_ENUM handle;
+ ReleaseHolder<IXCLRDataTask> pIXCLRDataTask(NULL);
+ ReleaseHolder<IXCLRDataExceptionState> pExcepState(NULL);
+ ReleaseHolder<IXCLRDataStackWalk> pStackWalk(NULL);
+ Thread *pThread = NULL;
+
+ // enumerating through each thread's each frame, dump out some interesting
+ // code memory needed to debugger to recognize frame
+ //
+ ThreadStore::EnumMemoryRegions(flags);
+
+ // enumerating through each thread
+ StartEnumTasks(&handle);
+ status = EnumTask(&handle, &pIXCLRDataTask);
+ for (unsigned nbThreads = 0; status == S_OK && pIXCLRDataTask != NULL; nbThreads++)
+ {
+ // Avoid infinite loop if target process is corrupted.
+ if (nbThreads > 100000)
+ {
+ break;
+ }
+ EX_TRY
+ {
+ // get Thread *
+ pThread = ((ClrDataTask *)pIXCLRDataTask.GetValue())->GetThread();
+
+ // Write out the Thread instance
+ DacEnumHostDPtrMem(pThread);
+
+ // Write out the context pointed by the thread
+ DacEnumHostDPtrMem(pThread->GetContext());
+
+ // @TODO
+ // write TEB pointed by the thread
+ // DacEnumHostDPtrMem(pThread->GetTEB());
+
+ // @TODO
+ // If CLR is hosted, we want to write out fiber data
+
+ // Dump the managed thread object
+ DumpManagedObject(flags, pThread->GetExposedObjectRaw());
+
+#ifndef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+ // dump the exception object
+ DumpManagedExcepObject(flags, pThread->LastThrownObject());
+#endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+
+ // Stack Walking
+ // We need for the ClrDataTask::CreateStackWalk from IXCLRDataTask to work, which is the
+ // following walk. However, the CordbStackWalk code requires some different (extra) data
+ // to walk the stack, such as info being present for
+ // mscordacwks!DacDbiInterfaceImpl::GetNativeCodeSequencePointsAndVarInfo.
+ status = pIXCLRDataTask->CreateStackWalk(CLRDATA_SIMPFRAME_UNRECOGNIZED | CLRDATA_SIMPFRAME_MANAGED_METHOD | CLRDATA_SIMPFRAME_RUNTIME_MANAGED_CODE | CLRDATA_SIMPFRAME_RUNTIME_UNMANAGED_CODE,
+ &pStackWalk);
+ if (status == S_OK && pStackWalk != NULL)
+ {
+ status = EnumMemWalkStackHelper(flags, pStackWalk, pThread);
+ pStackWalk.Clear();
+ }
+
+ // Now probe into the exception info
+ status = pIXCLRDataTask->GetCurrentExceptionState(&pExcepState);
+ while (status == S_OK && pExcepState != NULL)
+ {
+ EX_TRY
+ {
+ // touch the throwable in exception state
+ PTR_UNCHECKED_OBJECTREF throwRef(((ClrDataExceptionState *)pExcepState.GetValue())->m_throwable);
+
+ // If we've already attempted enumeration for this exception, it's time to quit.
+ if (!exceptionTracking.AddNewAddressOnly(throwRef.GetAddr()))
+ {
+ break;
+ }
+
+#ifndef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+ DumpManagedExcepObject(flags, *throwRef);
+#endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+
+ // get the type of the exception
+ ReleaseHolder<IXCLRDataValue> pValue(NULL);
+ status = pExcepState->GetManagedObject(&pValue);
+ if (status == S_OK && pValue != NULL)
+ {
+ ReleaseHolder<IXCLRDataTypeInstance> pTypeInstance(NULL);
+ // Make sure that we can get back a TypeInstance during inspection
+ status = pValue->GetType(&pTypeInstance);
+ pValue.Clear();
+ }
+
+ // If Exception state has a new context, we will walk with the stashed context as well.
+ // Note that in stack overflow exception's case, m_pContext is null.
+ //
+ // It is possible that we are in exception's catch clause when we
+ // try to walk the stack below. This is a very weird situation where
+ // stack is logically unwind and not physically unwind. We may not be able
+ // to walk the stack correctly here. Anyway, we try to catch exception thrown
+ // by bad stack walk in EnumMemWalkStackHelper.
+ //
+ PTR_CONTEXT pContext = ((ClrDataExceptionState*)pExcepState.GetValue())->GetCurrentContextRecord();
+ if (pContext != NULL)
+ {
+ T_CONTEXT newContext;
+ newContext = *pContext;
+
+ // We need to trigger stack walk again using the exception's context!
+ status = pIXCLRDataTask->CreateStackWalk(CLRDATA_SIMPFRAME_UNRECOGNIZED | CLRDATA_SIMPFRAME_MANAGED_METHOD | CLRDATA_SIMPFRAME_RUNTIME_MANAGED_CODE | CLRDATA_SIMPFRAME_RUNTIME_UNMANAGED_CODE,
+ &pStackWalk);
+ if (status == S_OK && pStackWalk != NULL)
+ {
+ status = pStackWalk->SetContext2(CLRDATA_STACK_SET_CURRENT_CONTEXT, sizeof(T_CONTEXT), (BYTE *) &newContext);
+ if (status == S_OK)
+ {
+ status = EnumMemWalkStackHelper(flags, pStackWalk, pThread);
+ }
+ pStackWalk.Clear();
+ }
+ }
+ }
+ EX_CATCH_RETHROW_ONLY_COR_E_OPERATIONCANCELLED
+
+ // get the previous exception
+ IXCLRDataExceptionState * pExcepStatePrev = NULL;
+ status = pExcepState->GetPrevious(&pExcepStatePrev);
+
+ // Release our current exception object, and transfer ref ownership of the previous
+ // exception object into the holder.
+ pExcepState = pExcepStatePrev;
+ }
+ }
+ EX_CATCH_RETHROW_ONLY_COR_E_OPERATIONCANCELLED
+
+ // get next thread
+ pIXCLRDataTask.Clear();
+ status = EnumTask(&handle, &pIXCLRDataTask);
+ }
+ EndEnumTasks(handle);
+ }
+ EX_CATCH_RETHROW_ONLY_COR_E_OPERATIONCANCELLED
+
+ // updating the statistics
+ m_dumpStats.m_cbStack = m_cbMemoryReported - cbMemoryReported;
+
+ return status;
+}
+
+
+#ifdef FEATURE_COMINTEROP
+//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+//
+// WinRT stowed exception holds the (CCW)pointer to a managed exception object.
+// We should check for the presence of a such an exception object and dump it if available.
+// This can also drag in memory implicitly.
+// So do call
+// m_instances.DumpAllInstances(m_enumMemCb);
+// when function is done.
+//
+//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+HRESULT ClrDataAccess::EnumMemStowedException(CLRDataEnumMemoryFlags flags)
+{
+ SUPPORTS_DAC;
+
+ ICLRDataTarget3 *pTarget3 = GetLegacyTarget3();
+ if (pTarget3 == NULL)
+ return S_OK;
+
+ // get the thread that raised the exception
+ ULONG32 exThreadID = 0;
+ if (FAILED(pTarget3->GetExceptionThreadID(&exThreadID)) || exThreadID == 0)
+ return S_OK;
+
+ //
+ // check that the thread is one of the known managed threads
+ //
+ BOOL foundThread = FALSE;
+ CLRDATA_ENUM handle;
+ ReleaseHolder<IXCLRDataTask> pIXCLRDataTask(NULL);
+
+ // enumerate through each thread
+ StartEnumTasks(&handle);
+ HRESULT status = EnumTask(&handle, &pIXCLRDataTask);
+ for (unsigned nbThreads = 0; status == S_OK && pIXCLRDataTask != NULL; ++nbThreads)
+ {
+ // Avoid infinite loop if target process is corrupted.
+ if (nbThreads > 100000)
+ {
+ break;
+ }
+ EX_TRY
+ {
+ if (((ClrDataTask *)pIXCLRDataTask.GetValue())->GetThread()->GetOSThreadId() == exThreadID)
+ {
+ // found the thread
+ foundThread = TRUE;
+ break;
+ }
+ }
+ EX_CATCH_RETHROW_ONLY_COR_E_OPERATIONCANCELLED
+
+ // get next thread
+ pIXCLRDataTask.Clear();
+ status = EnumTask(&handle, &pIXCLRDataTask);
+ }
+ EndEnumTasks(handle);
+
+ if (!foundThread)
+ return S_OK;
+
+
+ //
+ // Read the remote stowed exceptions.
+ //
+ // EXCEPTION_RECORD.ExceptionCode: STATUS_STOWED_EXCEPTION.
+ // EXCEPTION_RECORD.NumberParameters: 2.
+ // EXCEPTION_RECORD.ExceptionInformation[0]: pointer to an array of pointers
+ // to STOWED_EXCEPTION_INFORMATION structures.
+ // EXCEPTION_RECORD.ExceptionInformation[1]: count of elements in the array.
+ //
+ ULONG32 bytesRead = 0;
+ MINIDUMP_EXCEPTION minidumpException = { 0 };
+ if (FAILED(pTarget3->GetExceptionRecord(sizeof(MINIDUMP_EXCEPTION), &bytesRead, (PBYTE)&minidumpException)))
+ return S_OK;
+
+ TADDR remoteStowedExceptionArray = (TADDR)minidumpException.ExceptionInformation[0];
+ ULONG stowedExceptionCount = (ULONG)minidumpException.ExceptionInformation[1];
+ if (bytesRead != sizeof(MINIDUMP_EXCEPTION)
+ || minidumpException.ExceptionCode != STATUS_STOWED_EXCEPTION
+ || minidumpException.NumberParameters != 2
+ || stowedExceptionCount < 1 // there must atleast be 1 stowed exception
+ || stowedExceptionCount > 256 // upper bound: 256
+ || remoteStowedExceptionArray == NULL)
+ {
+ return S_OK;
+ }
+
+ for (ULONG i = 0; i < stowedExceptionCount; ++i)
+ {
+ // Read the i-th stowed exception
+ TADDR remoteStowedException = NULL;
+ if (FAILED(m_pTarget->ReadVirtual(TO_CDADDR(remoteStowedExceptionArray + (i * sizeof(TADDR))),
+ (PBYTE)&remoteStowedException, sizeof(TADDR), &bytesRead))
+ || bytesRead != sizeof(TADDR)
+ || remoteStowedException == NULL)
+ {
+ continue;
+ }
+
+ // check if this is a v2 stowed exception
+ STOWED_EXCEPTION_INFORMATION_V2 stowedException = { 0 };
+ if (FAILED(m_pTarget->ReadVirtual(TO_CDADDR(remoteStowedException),
+ (PBYTE)&stowedException, sizeof(STOWED_EXCEPTION_INFORMATION_HEADER), &bytesRead))
+ || bytesRead != sizeof(STOWED_EXCEPTION_INFORMATION_HEADER)
+ || stowedException.Header.Signature != STOWED_EXCEPTION_INFORMATION_V2_SIGNATURE)
+ {
+ continue;
+ }
+
+ // Read the full v2 stowed exception and get the CCW pointer out of it
+ if (FAILED(m_pTarget->ReadVirtual(TO_CDADDR(remoteStowedException),
+ (PBYTE)&stowedException, sizeof(STOWED_EXCEPTION_INFORMATION_V2), &bytesRead))
+ || bytesRead != sizeof(STOWED_EXCEPTION_INFORMATION_V2)
+ || stowedException.NestedExceptionType != STOWED_EXCEPTION_NESTED_TYPE_LEO
+ || stowedException.NestedException == NULL)
+ {
+ continue;
+ }
+
+ // Find out if NestedException is a pointer to CCW and then dump the exception object in it
+ DumpStowedExceptionObject(flags, TO_CDADDR(stowedException.NestedException));
+ }
+
+ return S_OK;
+}
+
+HRESULT ClrDataAccess::DumpStowedExceptionObject(CLRDataEnumMemoryFlags flags, CLRDATA_ADDRESS ccwPtr)
+{
+ SUPPORTS_DAC;
+ if (ccwPtr == NULL)
+ return S_OK;
+
+ // dump the managed exception object wrapped in CCW
+ // memory of the CCW object itself is dumped later by DacInstanceManager::DumpAllInstances
+ DacpCCWData ccwData;
+ GetCCWData(ccwPtr, &ccwData); // this call collects some memory implicitly
+ DumpManagedExcepObject(flags, OBJECTREF(TO_TADDR(ccwData.managedObject)));
+
+ // dump memory of the 2nd slot in the CCW's vtable
+ // this is used in DACGetCCWFromAddress to identify if the passed in pointer is a valid CCW.
+ ULONG32 bytesRead = 0;
+ TADDR vTableAddress = NULL;
+ if (FAILED(m_pTarget->ReadVirtual(ccwPtr, (PBYTE)&vTableAddress, sizeof(TADDR), &bytesRead))
+ || bytesRead != sizeof (TADDR)
+ || vTableAddress == NULL)
+ {
+ return S_OK;
+ }
+
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED
+ (
+ ReportMem(vTableAddress + sizeof(PBYTE)* TEAR_OFF_SLOT, sizeof(TADDR));
+ );
+
+ return S_OK;
+}
+#endif
+
+#define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // Resource Directory
+#define IMAGE_DIRECTORY_ENTRY_DEBUG 6 // Debug Directory
+
+//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+//
+// Reports critical data from the CLR main module
+// that needs to be present in all minidumps.
+// Implicitly reports memory, so remember to call
+// m_instances.DumpAllInstances(m_enumMemCb);
+// after this function completes.
+//
+//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+HRESULT ClrDataAccess::EnumMemCLRMainModuleInfo()
+{
+ SUPPORTS_DAC;
+
+ HRESULT status = S_OK;
+
+ // PEDecoder is DACized, so we just need to touch what we want to
+ // make subsequent lookup work.
+ PEDecoder pe(m_globalBase);
+
+ // We currently only actually have one debug directory entry.
+ // Post-processing, such as optimization, may add an extra directory.
+ // These directories are of type IMAGE_DEBUG_TYPE_RESERVED10, while our
+ // standard CodeView directory with pdb info is IMAGE_DEBUG_TYPE_CODEVIEW.
+ UINT i;
+ for (i = 0; pe.GetDebugDirectoryEntry(i); i++)
+ {
+ }
+
+ if (i < 1)
+ {
+ status = E_UNEXPECTED;
+ _ASSERTE(!"Collecting dump of target with no debug directory entries!");
+ }
+
+ // For CLRv4+, the resource directory contains the necessary info
+ // to retrieve the DBI/DAC from a symbol server.
+ // Specifically, in v4 it contains a mscoree!PE_FIXEDFILEINFO.
+ // This is also required since OpenVirtualProcess will check against
+ // this content to determine if a target module is indeed a CLR
+ // main module.
+
+ // Retrieve all resources in clr.dll. Right now, the entire resource
+ // content is very small (~0x600 bytes of raw data), so getting all is
+ // the easy thing to do. If resources become larger in later
+ // releases, we'll have to specifically get just the debugging-related resources.
+ _ASSERTE(pe.HasDirectoryEntry(IMAGE_DIRECTORY_ENTRY_RESOURCE));
+ if (pe.HasDirectoryEntry(IMAGE_DIRECTORY_ENTRY_RESOURCE))
+ {
+ COUNT_T size = 0;
+ TADDR pResourceDirData = pe.GetDirectoryEntryData(IMAGE_DIRECTORY_ENTRY_RESOURCE, &size);
+
+ _ASSERTE(size < 0x2000);
+ ReportMem((TADDR)pResourceDirData, size, true);
+ }
+ else
+ {
+ // In later releases, we should log the ERROR_RESOURCE_DATA_NOT_FOUND.
+ status = E_UNEXPECTED;
+ }
+
+ return status;
+}
+
+
+//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+//
+// Generating skinny mini-dump. Skinny mini-dump will only support stack trace, module list,
+// and Exception list viewing.
+//
+//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+HRESULT ClrDataAccess::EnumMemoryRegionsWorkerSkinny(IN CLRDataEnumMemoryFlags flags)
+{
+ SUPPORTS_DAC;
+
+ HRESULT status = S_OK;
+
+ // clear all of the previous cached memory
+ Flush();
+
+#ifdef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+ // Enable caching enumerated metadata of interest
+ InitStreamsForWriting(flags);
+#endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+
+ //TODO: actually *do* something with potential failures. It would be relatively easy to
+ // hook up an official dump stream to put info on our failures and other 'metadata'
+ // about dumping into in a generic sort of way. Our code doesn't have access to
+ // MDWD's callbacks, so we can't just do it ourselves. Thus we could have useful info
+ // baked into the dump, like we failed to enumerate mem for certain threads, etc.
+
+ // Each enumeration function below should be wrapped in a try/catch
+ // so that we have a chance to create a debuggable dump in the face of target problems.
+
+ // Iterating to all threads' stacks
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemDumpAllThreadsStack(flags); )
+
+ // Iterating to module list.
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemDumpModuleList(flags); )
+
+ //
+ // iterating through static that we care
+ //
+ // collect CLR static
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemCLRStatic(flags); )
+
+ // now dump the memory get dragged in by using DAC API implicitly.
+ m_dumpStats.m_cbImplicity = m_instances.DumpAllInstances(m_enumMemCb);
+ status = m_memStatus;
+
+ // Dump AppDomain-specific info needed for MiniDumpNormal.
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemDumpAppDomainInfo(flags); )
+
+ // Dump the Debugger object data needed
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pDebugger->EnumMemoryRegions(flags); )
+
+#ifdef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+ // Dump the extra data needed for metadata-free debugging
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( EnumStreams(flags); )
+#endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+
+ // Do not let any remaining implicitly enumerated memory leak out.
+ Flush();
+
+ return S_OK;
+}
+
+//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+//
+// Generating triage micro-dump. Triage dumps will only support stack trace
+// and Exception viewing.More than that triage dumps have to be PII free,
+// so all exception messages have to be poisoned with 0xcc mask.
+//
+//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+HRESULT ClrDataAccess::EnumMemoryRegionsWorkerMicroTriage(IN CLRDataEnumMemoryFlags flags)
+{
+ SUPPORTS_DAC;
+
+ HRESULT status = S_OK;
+
+ // clear all of the previous cached memory
+ Flush();
+
+#ifdef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+ // Enable caching enumerated metadata of interest
+ InitStreamsForWriting(flags);
+#endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+
+ // Iterating to all threads' stacks
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemDumpAllThreadsStack(flags); )
+
+ // Iterating to module list.
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemDumpModuleList(flags); )
+
+ // collect CLR static
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemCLRStatic(flags); )
+
+ // now dump the memory get dragged in by using DAC API implicitly.
+ m_dumpStats.m_cbImplicity = m_instances.DumpAllInstances(m_enumMemCb);
+ status = m_memStatus;
+
+ // Dump AppDomain-specific info needed for triage dumps methods enumeration (k command).
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemDumpAppDomainInfo(flags); )
+
+ // Dump the Debugger object data needed
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( g_pDebugger->EnumMemoryRegions(flags); )
+
+#ifdef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+ // Dump the extra data needed for metadata-free debugging
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( EnumStreams(flags); )
+#endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+
+ // Do not let any remaining implicitly enumerated memory leak out.
+ Flush();
+
+ return S_OK;
+}
+
+//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+//
+// Write out mscorwks's data segment. This will write out the whole
+// data segment for mscorwks. It is about 200 or 300K. Most of it (90%) are
+// vtable definition that we don't really care. But we don't have a
+// good walk to just write out all globals and statics.
+//
+//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+HRESULT ClrDataAccess::EnumMemWriteDataSegment()
+{
+ SUPPORTS_DAC;
+
+ NewHolder<PEDecoder> pedecoder(NULL);
+
+ EX_TRY
+ {
+ // Collecting mscorwks's data segment
+ {
+ // m_globalBase is the base address of target process's mscorwks module
+ pedecoder = new PEDecoder(dac_cast<PTR_VOID>(m_globalBase));
+
+ PTR_IMAGE_SECTION_HEADER pSection = (PTR_IMAGE_SECTION_HEADER) pedecoder->FindFirstSection();
+ PTR_IMAGE_SECTION_HEADER pSectionEnd = pSection + VAL16(pedecoder->GetNumberOfSections());
+
+ while (pSection < pSectionEnd)
+ {
+ if (pSection->Name[0] == '.' &&
+ pSection->Name[1] == 'd' &&
+ pSection->Name[2] == 'a' &&
+ pSection->Name[3] == 't' &&
+ pSection->Name[4] == 'a')
+ {
+ // This is the .data section of mscorwks
+ ReportMem(m_globalBase + pSection->VirtualAddress, pSection->Misc.VirtualSize);
+ }
+ pSection++;
+ }
+ }
+ }
+ EX_CATCH_RETHROW_ONLY_COR_E_OPERATIONCANCELLED
+
+ return S_OK;
+}
+
+//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+//
+// Custom Dump. Depending on the value of g_ECustomDumpFlavor, different dump
+// will be taken. You can set this global variable using hosting API
+// ICLRErrorReportingManager::BeginCustomDump.
+//
+//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+HRESULT ClrDataAccess::EnumMemoryRegionsWorkerCustom()
+{
+ SUPPORTS_DAC;
+
+ HRESULT status = S_OK;
+
+ ECustomDumpFlavor eFlavor;
+
+#ifdef FEATURE_INCLUDE_ALL_INTERFACES
+ eFlavor = CCLRErrorReportingManager::g_ECustomDumpFlavor;
+#else
+ eFlavor = DUMP_FLAVOR_Default;
+#endif
+
+ m_enumMemFlags = CLRDATA_ENUM_MEM_MINI;
+
+ // clear all of the previous cached memory
+ Flush();
+
+ if (eFlavor == DUMP_FLAVOR_Mini)
+ {
+ // Iterating to all threads' stacks
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemDumpAllThreadsStack(m_enumMemFlags); )
+
+ // Iterating to module list.
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemDumpModuleList(m_enumMemFlags); )
+
+ //
+ // iterating through static that we care
+ //
+ // collect CLR static
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemCLRStatic(m_enumMemFlags); )
+
+ // we are done...
+
+ // now dump the memory get dragged in implicitly
+ m_dumpStats.m_cbImplicity = m_instances.DumpAllInstances(m_enumMemCb);
+
+ }
+ else if (eFlavor == DUMP_FLAVOR_CriticalCLRState)
+ {
+ // We need to walk Threads stack to view managed frames.
+ // Iterating through module list
+
+ // Iterating to all threads' stacks
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemDumpAllThreadsStack(m_enumMemFlags); )
+
+ // Iterating to module list.
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemDumpModuleList(m_enumMemFlags); )
+
+ //
+ // iterating through static that we care
+ //
+ // collect CLR static
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemCLRStatic(m_enumMemFlags); )
+
+ // Collecting some CLR secondary critical data
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemCLRHeapCrticalStatic(m_enumMemFlags); )
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemWriteDataSegment(); )
+
+ // we are done...
+
+ // now dump the memory get dragged in implicitly
+ m_dumpStats.m_cbImplicity = m_instances.DumpAllInstances(m_enumMemCb);
+
+ }
+ else if (eFlavor == DUMP_FLAVOR_NonHeapCLRState)
+ {
+ // since all CLR hosted heap will be dump by the host,
+ // the EE structures that are not loaded using LoadLibrary will
+ // be included by the host.
+ //
+ // Thus we only need to include mscorwks's critical data and ngen images
+
+ m_enumMemFlags = CLRDATA_ENUM_MEM_HEAP;
+
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemCLRStatic(m_enumMemFlags); )
+
+ // Collecting some CLR secondary critical data
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemCLRHeapCrticalStatic(m_enumMemFlags); )
+
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemWriteDataSegment(); )
+ CATCH_ALL_EXCEPT_RETHROW_COR_E_OPERATIONCANCELLED( status = EnumMemCollectImages(); )
+ }
+ else
+ {
+ status = E_INVALIDARG;
+ }
+
+ status = m_memStatus;
+
+ return S_OK;
+}
+
+//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+//
+// Minidumps traverse a giant static calltree. We already try to catch
+// exceptions at various lower level places and continue to report memory.
+//
+// However, if we'll jump to the top-level catcher and skip the rest of the tree,
+// that may mean some key data may not get emitted to the minidump.
+// In the case that a user requests a dump is canceled, we should skip the rest
+// of the tree. When a COR_E_OPERATIONCANCELED exception is thrown, is allowed to
+// escape all the way to this function. If any exception makes it here and is not
+// COR_E_OPERATIONCANCELED that indicates an issue, and the assert is meant to catch that.
+// Unfortunately the stack unwind will already have happened.
+//
+// Internal API to support minidump and heap dump. It just delegate
+// to proper function but with a top level catch.
+//
+//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+HRESULT ClrDataAccess::EnumMemoryRegionsWrapper(IN CLRDataEnumMemoryFlags flags)
+{
+ // This is infrastructure code - we don't want DacCop complaining about the calls as a result
+ // of the use of EX_CATCH_HRESULT here. We're careful to mark EnumMemoryRegionsWorkerSkinny
+ // and EnumMemoryRegionsWorkerHeap as just SUPPORTS_DAC so that we still get analysis.
+ SUPPORTS_DAC_HOST_ONLY;
+
+ HRESULT status = S_OK;
+ m_enumMemFlags = flags;
+ EX_TRY
+ {
+ // The various EnumMemoryRegions() implementations should understand
+ // CLRDATA_ENUM_MEM_MINI to mean that the bare minimimum memory
+ // to make a MiniDumpNormal work should be included.
+ if ( flags == CLRDATA_ENUM_MEM_MINI)
+ {
+ // skinny mini-dump
+ status = EnumMemoryRegionsWorkerSkinny(flags);
+ }
+ else if ( flags == CLRDATA_ENUM_MEM_TRIAGE)
+ {
+ // triage micro-dump
+ status = EnumMemoryRegionsWorkerMicroTriage(flags);
+ }
+ else if ( flags == CLRDATA_ENUM_MEM_HEAP)
+ {
+ status = EnumMemoryRegionsWorkerHeap(flags);
+ }
+ else
+ {
+ _ASSERTE(!"Bad flags passing to EnumMemoryRegionsWrapper!");
+ }
+ }
+ EX_CATCH_HRESULT(status);
+
+ // The only exception that should reach here is the cancel exception
+ _ASSERTE(SUCCEEDED(status) || status == COR_E_OPERATIONCANCELED);
+
+ return status;
+}
+
+#define MiniDumpWithPrivateReadWriteMemory 0x00000200
+#define MiniDumpWithFullAuxiliaryState 0x00008000
+#define MiniDumpFilterTriage 0x00100000
+
+
+
+//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+//
+// Entry function for generating CLR aware dump. This function is called
+// for minidump, heap dump, and custom dumps. CLR specific memory will
+// be reported to outer level dumper (usually dbghelp's MiniDumpWriteDump api)
+// through the callback. We do not write out to file directly.
+//
+// N.B.: The CLR may report duplicate memory chunks and it's up to
+// the debugger to coalesce memory. *However* the debugger's current
+// implementation coalesces memory we enumerate and memory that
+// they enumerate; the two sets of memory are not guaranteed to be
+// coalesced. The dump produced may thus have memory blocks in the
+// MemoryListStream that overlap or are totally contained in other blocks.
+// This issue was resolved by-design by dbgteam. Win7 #407019.
+// Note also that Memory64ListStream (when passing MiniDumpWithFullMemory)
+// will have no duplicates, be sorted, etc. In that case, none of
+// our code is called.
+//
+//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+STDMETHODIMP
+ClrDataAccess::EnumMemoryRegions(IN ICLRDataEnumMemoryRegionsCallback* callback,
+ IN ULONG32 miniDumpFlags,
+ IN CLRDataEnumMemoryFlags flags) // reserved not used
+{
+ SUPPORTS_DAC;
+ HRESULT status;
+
+#if defined(DAC_MEASURE_PERF)
+
+ g_nTotalTime = 0;
+ g_nStackTotalTime = 0;
+ g_nReadVirtualTotalTime = 0;
+ g_nFindTotalTime = 0;
+ g_nFindHashTotalTime = 0;
+ g_nFindHits = 0;
+ g_nFindCalls = 0;
+ g_nFindFails = 0;
+ g_nStackWalk = 0;
+ g_nFindStackTotalTime = 0;
+
+ LARGE_INTEGER nClockFrequency;
+ unsigned __int64 nStart = 0;
+ unsigned __int64 nEnd = 0;
+
+ QueryPerformanceFrequency(&nClockFrequency);
+
+ FILE* fp = fopen("c:\\dumpLog.txt", "a");
+ if (fp)
+ {
+ fprintf(fp, "\nMinidumpFlags = %d\n", miniDumpFlags);
+ fclose(fp);
+ }
+
+ nStart = GetCycleCount();
+
+#endif // #if defined(DAC_MEASURE_PERF)
+
+ DAC_ENTER();
+
+ // We should not be trying to enumerate while we have an enumeration outstanding
+ _ASSERTE(m_enumMemCb==NULL);
+ m_memStatus = S_OK;
+ m_enumMemCb = callback;
+
+ // QI for ICLRDataEnumMemoryRegionsCallback2 will succeed only for Win8+.
+ // It is expected to fail on pre Win8 OSes.
+ callback->QueryInterface(IID_ICLRDataEnumMemoryRegionsCallback2, (void **)&m_updateMemCb);
+
+ EX_TRY
+ {
+ ClearDumpStats();
+ if (miniDumpFlags & MiniDumpWithPrivateReadWriteMemory)
+ {
+ // heap dump
+ status = EnumMemoryRegionsWrapper(CLRDATA_ENUM_MEM_HEAP);
+ }
+ else if (miniDumpFlags & MiniDumpWithFullAuxiliaryState)
+ {
+ // This is the host custom dump.
+ status = EnumMemoryRegionsWorkerCustom();
+ }
+ else if (miniDumpFlags & MiniDumpFilterTriage)
+ {
+ // triage micro-dump
+ status = EnumMemoryRegionsWrapper(CLRDATA_ENUM_MEM_TRIAGE);
+ }
+ else
+ {
+ // minidump
+ status = EnumMemoryRegionsWrapper(CLRDATA_ENUM_MEM_MINI);
+ }
+
+ // For all dump types, we need to capture the chain to the IMAGE_DIRECTORY_ENTRY_DEBUG
+ // contents, so that DAC can validate against the TimeDateStamp even if the
+ // debugger can't find the main CLR module on disk.
+ // If we already failed, don't bother.
+ if (SUCCEEDED(status))
+ {
+ // In case there's implicitly enumerated memory hanging around
+ // let's not accidentally pick it up.
+ Flush();
+ if (SUCCEEDED(status = EnumMemCLRMainModuleInfo()))
+ {
+ m_instances.DumpAllInstances(m_enumMemCb);
+ }
+ }
+
+ Flush();
+ }
+ EX_CATCH
+ {
+ m_enumMemCb = NULL;
+
+ // We should never actually be here b/c none of the EMR functions should throw.
+ // They should all either be written robustly w/ ptr.IsValid() and catching their
+ // own exceptions.
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ _ASSERTE_MSG(false, "Got unexpected exception in EnumMemoryRegions");
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ // fix for issue 866100: DAC is too late in releasing ICLRDataEnumMemoryRegionsCallback2*
+ if (m_updateMemCb)
+ {
+ m_updateMemCb->Release();
+ m_updateMemCb = NULL;
+ }
+ m_enumMemCb = NULL;
+
+ DAC_LEAVE();
+
+#if defined(DAC_MEASURE_PERF)
+
+ nEnd = GetCycleCount();
+ g_nTotalTime= nEnd - nStart;
+ fp = fopen("c:\\dumpLog.txt", "a");
+ fprintf(fp, "Total = %g msec\n"
+ "ReadVirtual = %g msec\n"
+ "StackWalk = %g msec; Find: %g msec\n"
+ "Find = %g msec; Hash = %g msec; Calls = %I64u; Hits = %I64u; Not found = %I64u\n\n=====\n",
+ (float) (1000*g_nTotalTime/nClockFrequency.QuadPart),
+ (float) (1000*g_nReadVirtualTotalTime/nClockFrequency.QuadPart),
+ (float) (1000*g_nStackTotalTime/nClockFrequency.QuadPart), (float) (1000*g_nFindStackTotalTime/nClockFrequency.QuadPart),
+ (float) (1000*g_nFindTotalTime/nClockFrequency.QuadPart), (float) (1000*g_nFindHashTotalTime/nClockFrequency.QuadPart),
+ g_nFindCalls, g_nFindHits, g_nFindFails
+ );
+ fclose(fp);
+
+#endif // #if defined(DAC_MEASURE_PERF)
+
+ return status;
+}
+
+
+//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+//
+// Clear the statistics for the dump. For each dump generation, we
+// clear the dump statistics. At the end of the dump generation, you can
+// view the statics data member m_dumpStats and see how many bytes that
+// we have reported to our debugger host.
+//
+//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+void ClrDataAccess::ClearDumpStats()
+{
+ SUPPORTS_DAC;
+
+ m_cbMemoryReported = 0;
+ memset(&m_dumpStats, 0, sizeof(DumpMemoryReportStatics));
+}
diff --git a/src/debug/daccess/fntableaccess.cpp b/src/debug/daccess/fntableaccess.cpp
new file mode 100644
index 0000000000..e7b96ec3e0
--- /dev/null
+++ b/src/debug/daccess/fntableaccess.cpp
@@ -0,0 +1,461 @@
+// 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.
+// ===========================================================================
+
+//
+// File: DebugSupport.cpp
+//
+// Support routines for debugging the CLR
+// ===========================================================================
+
+#include "stdafx.h"
+
+#ifndef _TARGET_X86_
+
+//
+//
+// @TODO: This is old code that should be easy to implement on top of the existing DAC support.
+// This code was originally written prior to DAC.
+//
+//
+
+#include <winwrap.h>
+#include <windows.h>
+#include <winnt.h>
+#include <clrnt.h>
+#include <stddef.h> // offsetof
+#include "nibblemapmacros.h"
+#include "stdmacros.h"
+
+#include "fntableaccess.h"
+
+#define move(dst, src) \
+{ \
+ if (!fpReadMemory(pUserContext, (LPCVOID)(src), &(dst), sizeof(dst), NULL)) \
+ { \
+ _ASSERTE(!"MSCORDBG ERROR: ReadProcessMemory failed!!"); \
+ return STATUS_UNSUCCESSFUL; \
+ } \
+}
+
+#define move_field(dst, src, cls, fld) \
+ move(dst, (SIZE_T)(src) + FIELD_OFFSET(cls, fld))
+
+static NTSTATUS OutOfProcessFindHeader(ReadMemoryFunction fpReadMemory,PVOID pUserContext, DWORD_PTR pMapIn, DWORD_PTR addr, DWORD_PTR &codeHead)
+{
+ codeHead = 0;
+
+ DWORD tmp; // must be a DWORD, not a DWORD_PTR
+ DWORD_PTR startPos = ADDR2POS(addr); // align to 128 byte buckets ( == index into the array of nibbles)
+ DWORD_PTR offset = ADDR2OFFS(addr); // this is the offset inside the bucket + 1
+ DWORD * pMap = (DWORD *) pMapIn; // make this a pointer type so our pointer math is correct w/o adding sizeof(DWORD) everywhere
+
+ _ASSERTE(offset == (offset & NIBBLE_MASK)); // the offset must fit in a nibble
+
+ pMap += (startPos >> LOG2_NIBBLES_PER_DWORD); // points to the proper DWORD of the map
+
+ //
+ // get DWORD and shift down our nibble
+ //
+ move(tmp, pMap);
+ tmp = tmp >> POS2SHIFTCOUNT(startPos);
+
+ // don't allow equality in the next check (tmp & NIBBLE_MASK == offset)
+ // there are code blocks that terminate with a call instruction
+ // (like call throwobject), i.e. their return address is
+ // right behind the code block. If the memory manager allocates
+ // heap blocks w/o gaps, we could find the next header in such
+ // cases. Therefore we exclude the first DWORD of the header
+ // from our search, but since we call this function for code
+ // anyway (which starts at the end of the header) this is not
+ // a problem.
+ if ((tmp & NIBBLE_MASK) && ((tmp & NIBBLE_MASK) < offset) )
+ {
+ codeHead = POSOFF2ADDR(startPos, tmp & NIBBLE_MASK) - sizeof(CodeHeader);
+ return STATUS_SUCCESS;
+ }
+
+ // is there a header in the remainder of the DWORD ?
+ tmp = tmp >> NIBBLE_SIZE;
+
+ if (tmp)
+ {
+ startPos--;
+ while (!(tmp & NIBBLE_MASK))
+ {
+ tmp = tmp >> NIBBLE_SIZE;
+ startPos--;
+ }
+
+ codeHead = POSOFF2ADDR(startPos, tmp & NIBBLE_MASK) - sizeof(CodeHeader);
+ return STATUS_SUCCESS;
+ }
+
+ // we skipped the remainder of the DWORD,
+ // so we must set startPos to the highest position of
+ // previous DWORD
+
+ startPos = ((startPos >> LOG2_NIBBLES_PER_DWORD) << LOG2_NIBBLES_PER_DWORD) - 1;
+
+ if ((INT_PTR)startPos < 0)
+ {
+ return STATUS_SUCCESS;
+ }
+
+ // skip "headerless" DWORDS
+
+ pMap--;
+ move(tmp, pMap);
+ while (!tmp)
+ {
+ startPos -= NIBBLES_PER_DWORD;
+ if ((INT_PTR)startPos < 0)
+ {
+ return STATUS_SUCCESS;
+ }
+ pMap--;
+ move (tmp, pMap);
+ }
+
+
+ while (!(tmp & NIBBLE_MASK))
+ {
+ tmp = tmp >> NIBBLE_SIZE;
+ startPos--;
+ }
+
+ codeHead = POSOFF2ADDR(startPos, tmp & NIBBLE_MASK) - sizeof(CodeHeader);
+ return STATUS_SUCCESS;
+}
+
+#define CODE_HEADER FakeRealCodeHeader
+#define ResolveCodeHeader(pHeader) \
+ if (pHeader) \
+ { \
+ DWORD_PTR tmp = pHeader; \
+ tmp += offsetof (FakeCodeHeader, pRealCodeHeader); \
+ move (tmp, tmp); \
+ pHeader = tmp; \
+ }
+
+static NTSTATUS OutOfProcessFunctionTableCallback_JIT(IN ReadMemoryFunction fpReadMemory,
+ IN PVOID pUserContext,
+ IN PVOID TableAddress,
+ OUT PULONG pnEntries,
+ OUT PT_RUNTIME_FUNCTION* ppFunctions)
+{
+ if (NULL == pnEntries) { return STATUS_INVALID_PARAMETER_3; }
+ if (NULL == ppFunctions) { return STATUS_INVALID_PARAMETER_4; }
+
+ DYNAMIC_FUNCTION_TABLE * pTable = (DYNAMIC_FUNCTION_TABLE *) TableAddress;
+
+ PVOID pvContext;
+ move(pvContext, &pTable->Context);
+
+ DWORD_PTR JitMan = (((DWORD_PTR)pvContext) & ~3);
+
+ DWORD_PTR MinAddress = (DWORD_PTR) &(pTable->MinimumAddress);
+ move(MinAddress, MinAddress);
+
+ *ppFunctions = 0;
+ *pnEntries = 0;
+
+ DWORD_PTR pHp = JitMan + (DWORD_PTR)offsetof(FakeEEJitManager, m_pCodeHeap);
+
+ move(pHp, pHp);
+
+ while (pHp)
+ {
+ FakeHeapList Hp;
+
+ move(Hp, pHp);
+
+ if (pHp == MinAddress)
+ {
+ DWORD_PTR pThisHeader;
+ DWORD_PTR hdrOffset;
+ DWORD_PTR hdrOffsetInitial;
+ DWORD nEntries;
+ DWORD index;
+ DWORD_PTR pUnwindInfo;
+ PT_RUNTIME_FUNCTION pFunctions;
+ LONG64 lSmallestOffset;
+
+ //
+ // walk the header map and count functions with unwind info
+ //
+ nEntries = 0;
+ hdrOffset = Hp.endAddress - Hp.mapBase;
+ lSmallestOffset = (LONG64)(Hp.startAddress - Hp.mapBase);
+
+ // Save the initial offset at which we start our enumeration (from the end to the beginning).
+ // The target process could be running when this function is called. New methods could be
+ // added after we have started our enumeration, but their code headers would be added after
+ // this initial offset. Methods could also be deleted, but the memory would still be there.
+ // It just wouldn't be marked as the beginning of a method, and we would collect fewer entries
+ // than we have anticipated.
+ hdrOffsetInitial = hdrOffset;
+
+ _ASSERTE(((LONG64)hdrOffset) >= lSmallestOffset);
+ OutOfProcessFindHeader(fpReadMemory, pUserContext, Hp.pHdrMap, hdrOffset, hdrOffset);
+
+ while (((LONG64)hdrOffset) >= lSmallestOffset) // MUST BE A SIGNED COMPARISON
+ {
+ pThisHeader = Hp.mapBase + hdrOffset;
+ ResolveCodeHeader(pThisHeader);
+
+ if (pThisHeader > FAKE_STUB_CODE_BLOCK_LAST)
+ {
+ DWORD nUnwindInfos;
+ move_field(nUnwindInfos, pThisHeader, CODE_HEADER, nUnwindInfos);
+
+ nEntries += nUnwindInfos;
+ }
+
+ _ASSERTE(((LONG64)hdrOffset) >= lSmallestOffset);
+ OutOfProcessFindHeader(fpReadMemory, pUserContext, Hp.pHdrMap, hdrOffset, hdrOffset);
+ }
+
+ pFunctions = (PT_RUNTIME_FUNCTION)ClrHeapAlloc(ClrGetProcessHeap(), HEAP_ZERO_MEMORY, S_SIZE_T(nEntries) * S_SIZE_T(sizeof(T_RUNTIME_FUNCTION)));
+ *ppFunctions = pFunctions;
+ *pnEntries = nEntries;
+
+ //
+ // walk the header map and copy the function tables
+ //
+
+ index = 0;
+ hdrOffset = hdrOffsetInitial;
+
+ _ASSERTE(((LONG64)hdrOffset) >= lSmallestOffset);
+ OutOfProcessFindHeader(fpReadMemory, pUserContext, Hp.pHdrMap, hdrOffset, hdrOffset);
+
+ while (((LONG64)hdrOffset) >= lSmallestOffset) // MUST BE A SIGNED COMPARISON
+ {
+ pThisHeader = Hp.mapBase + hdrOffset;
+ ResolveCodeHeader(pThisHeader);
+
+ if (pThisHeader > FAKE_STUB_CODE_BLOCK_LAST)
+ {
+ DWORD nUnwindInfos;
+ move_field(nUnwindInfos, pThisHeader, CODE_HEADER, nUnwindInfos);
+
+ if ((index + nUnwindInfos) > nEntries)
+ {
+ break;
+ }
+ for (DWORD iUnwindInfo = 0; iUnwindInfo < nUnwindInfos; iUnwindInfo++)
+ {
+ move(pFunctions[index], pThisHeader + offsetof(CODE_HEADER, unwindInfos[iUnwindInfo]));
+ index++;
+ }
+ }
+
+ _ASSERTE(((LONG64)hdrOffset) >= lSmallestOffset);
+ OutOfProcessFindHeader(fpReadMemory, pUserContext, Hp.pHdrMap, hdrOffset, hdrOffset);
+ }
+
+ // Return the final count.
+ *pnEntries = index;
+ break;
+ }
+
+ pHp = (DWORD_PTR)Hp.hpNext;
+ }
+
+ return STATUS_SUCCESS;
+}
+
+
+#ifdef DEBUGSUPPORT_STUBS_HAVE_UNWIND_INFO
+
+static NTSTATUS OutOfProcessFunctionTableCallback_Stub(IN ReadMemoryFunction fpReadMemory,
+ IN PVOID pUserContext,
+ IN PVOID TableAddress,
+ OUT PULONG pnEntries,
+ OUT PT_RUNTIME_FUNCTION* ppFunctions)
+{
+ if (NULL == pnEntries) { return STATUS_INVALID_PARAMETER_3; }
+ if (NULL == ppFunctions) { return STATUS_INVALID_PARAMETER_4; }
+
+ *ppFunctions = 0;
+ *pnEntries = 0;
+
+ PVOID pvContext;
+ move_field(pvContext, TableAddress, DYNAMIC_FUNCTION_TABLE, Context);
+
+ SIZE_T pStubHeapSegment = ((SIZE_T)pvContext & ~3);
+
+ FakeStubUnwindInfoHeapSegment stubHeapSegment;
+ move(stubHeapSegment, pStubHeapSegment);
+
+ UINT nEntries = 0;
+ UINT nEntriesAllocated = 0;
+ PT_RUNTIME_FUNCTION rgFunctions = NULL;
+
+ for (int pass = 1; pass <= 2; pass++)
+ {
+ // Use the same initial header for both passes. The process may still be running,
+ // and so new entries could be added at the beginning of the list. Using the initial header
+ // makes sure new entries are not picked up in the second pass. Entries could also be deleted,
+ // and there is a small time window here where we could read invalid memory. This just means
+ // that ReadProcessMemory() may fail. As long as we don't crash the host process (e.g. WER)
+ // we are fine.
+ SIZE_T pHeader = (SIZE_T)stubHeapSegment.pUnwindHeaderList;
+
+ while (pHeader)
+ {
+ FakeStubUnwindInfoHeader unwindInfoHeader;
+ move(unwindInfoHeader, pHeader);
+#if defined(_TARGET_AMD64_)
+ // Consistency checks to detect corrupted process state
+ if (unwindInfoHeader.FunctionEntry.BeginAddress > unwindInfoHeader.FunctionEntry.EndAddress ||
+ unwindInfoHeader.FunctionEntry.EndAddress > stubHeapSegment.cbSegment)
+ {
+ _ASSERTE(1 == pass);
+ return STATUS_UNSUCCESSFUL;
+ }
+
+ if ((SIZE_T)stubHeapSegment.pbBaseAddress + unwindInfoHeader.FunctionEntry.UnwindData !=
+ pHeader + FIELD_OFFSET(FakeStubUnwindInfoHeader, UnwindInfo))
+ {
+ _ASSERTE(1 == pass);
+ return STATUS_UNSUCCESSFUL;
+ }
+#elif defined(_TARGET_ARM_)
+
+ // Skip checking the corrupted process stateon ARM
+
+#elif defined(_TARGET_ARM64_)
+ // Compute the function length
+ ULONG64 functionLength = 0;
+ ULONG64 unwindData = unwindInfoHeader.FunctionEntry.UnwindData;
+ if (( unwindData & 3) != 0) {
+ // the unwindData contains the function length, retrieve it directly from unwindData
+ functionLength = (unwindInfoHeader.FunctionEntry.UnwindData >> 2) & 0x7ff;
+ } else {
+ // the unwindData is an RVA to the .xdata record which contains the function length
+ DWORD xdataHeader=0;
+ if ((SIZE_T)stubHeapSegment.pbBaseAddress + unwindData != pHeader + FIELD_OFFSET(FakeStubUnwindInfoHeader, UnwindInfo))
+ {
+ _ASSERTE(1 == pass);
+ return STATUS_UNSUCCESSFUL;
+ }
+ move(xdataHeader, stubHeapSegment.pbBaseAddress + unwindData);
+ functionLength = (xdataHeader & 0x3ffff) << 2;
+ }
+ if (unwindInfoHeader.FunctionEntry.BeginAddress + functionLength > stubHeapSegment.cbSegment)
+ {
+ _ASSERTE(1 == pass);
+ return STATUS_UNSUCCESSFUL;
+ }
+#else
+ PORTABILITY_ASSERT("OutOfProcessFunctionTableCallback_Stub");
+#endif
+ if (nEntriesAllocated)
+ {
+ if (nEntries >= nEntriesAllocated)
+ break;
+ rgFunctions[nEntries] = unwindInfoHeader.FunctionEntry;
+ }
+ nEntries++;
+
+ pHeader = (SIZE_T)unwindInfoHeader.pNext;
+ }
+
+ if (1 == pass)
+ {
+ if (!nEntries)
+ break;
+
+ _ASSERTE(!nEntriesAllocated);
+ nEntriesAllocated = nEntries;
+ rgFunctions = (PT_RUNTIME_FUNCTION)ClrHeapAlloc(ClrGetProcessHeap(), HEAP_ZERO_MEMORY, S_SIZE_T(nEntries) * S_SIZE_T(sizeof(T_RUNTIME_FUNCTION)));
+ nEntries = 0;
+ }
+ else
+ {
+ _ASSERTE(nEntriesAllocated >= nEntries);
+ }
+ }
+
+ *ppFunctions = rgFunctions;
+ *pnEntries = nEntries; // return the final count
+
+ return STATUS_SUCCESS;
+}
+
+#endif // DEBUGSUPPORT_STUBS_HAVE_UNWIND_INFO
+
+BOOL ReadMemory(PVOID pUserContext, LPCVOID lpBaseAddress, PVOID lpBuffer, SIZE_T nSize, SIZE_T* lpNumberOfBytesRead)
+{
+ HANDLE hProcess = (HANDLE)pUserContext;
+ return ReadProcessMemory(hProcess, lpBaseAddress, lpBuffer, nSize, lpNumberOfBytesRead);
+}
+
+extern "C" NTSTATUS OutOfProcessFunctionTableCallback(IN HANDLE hProcess,
+ IN PVOID TableAddress,
+ OUT PULONG pnEntries,
+ OUT PT_RUNTIME_FUNCTION* ppFunctions)
+{
+ return OutOfProcessFunctionTableCallbackEx(&ReadMemory, hProcess, TableAddress, pnEntries, ppFunctions);
+}
+
+extern "C" NTSTATUS OutOfProcessFunctionTableCallbackEx(IN ReadMemoryFunction fpReadMemory,
+ IN PVOID pUserContext,
+ IN PVOID TableAddress,
+ OUT PULONG pnEntries,
+ OUT PT_RUNTIME_FUNCTION* ppFunctions)
+{
+ if (NULL == pnEntries) { return STATUS_INVALID_PARAMETER_3; }
+ if (NULL == ppFunctions) { return STATUS_INVALID_PARAMETER_4; }
+
+ DYNAMIC_FUNCTION_TABLE * pTable = (DYNAMIC_FUNCTION_TABLE *) TableAddress;
+ PVOID pvContext;
+
+ move(pvContext, &pTable->Context);
+
+ FakeEEDynamicFunctionTableType type = (FakeEEDynamicFunctionTableType)((SIZE_T)pvContext & 3);
+
+ switch (type)
+ {
+ case FAKEDYNFNTABLE_JIT:
+ return OutOfProcessFunctionTableCallback_JIT(
+ fpReadMemory,
+ pUserContext,
+ TableAddress,
+ pnEntries,
+ ppFunctions);
+
+#ifdef DEBUGSUPPORT_STUBS_HAVE_UNWIND_INFO
+ case FAKEDYNFNTABLE_STUB:
+ return OutOfProcessFunctionTableCallback_Stub(
+ fpReadMemory,
+ pUserContext,
+ TableAddress,
+ pnEntries,
+ ppFunctions);
+#endif // DEBUGSUPPORT_STUBS_HAVE_UNWIND_INFO
+ default:
+ break;
+ }
+
+ return STATUS_UNSUCCESSFUL;
+}
+
+#else
+
+extern "C" NTSTATUS OutOfProcessFunctionTableCallback()
+{
+ return STATUS_UNSUCCESSFUL;
+}
+
+extern "C" NTSTATUS OutOfProcessFunctionTableCallbackEx()
+{
+ return STATUS_UNSUCCESSFUL;
+}
+
+
+
+#endif // !_TARGET_X86_
diff --git a/src/debug/daccess/fntableaccess.h b/src/debug/daccess/fntableaccess.h
new file mode 100644
index 0000000000..0dbabdf8c9
--- /dev/null
+++ b/src/debug/daccess/fntableaccess.h
@@ -0,0 +1,216 @@
+// 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.
+
+//
+
+#ifdef _MSC_VER
+#pragma once
+#endif
+
+#ifndef _FN_TABLE_ACCESS_H
+#define _FN_TABLE_ACCESS_H
+
+
+#if !defined(_TARGET_X86_)
+
+#ifndef FEATURE_PAL
+#define DEBUGSUPPORT_STUBS_HAVE_UNWIND_INFO
+#endif // !FEATURE_PAL
+
+#ifndef USE_INDIRECT_CODEHEADER
+#define USE_INDIRECT_CODEHEADER
+#endif // USE_INDIRECT_CODEHEADER
+#endif
+
+
+struct FakeEEJitManager
+{
+ LPVOID __VFN_table;
+ LPVOID m_runtimeSupport;
+ LPVOID m_pCodeHeap;
+ // Nothing after this point matters: we only need the correct offset of m_pCodeHeap.
+};
+
+struct FakeHeapList
+{
+ FakeHeapList* hpNext;
+ LPVOID pHeap; // changed type from LoaderHeap*
+ DWORD_PTR startAddress; // changed from PBYTE
+ DWORD_PTR endAddress; // changed from PBYTE
+ DWORD_PTR mapBase; // changed from PBYTE
+ DWORD_PTR pHdrMap; // changed from DWORD*
+ size_t maxCodeHeapSize;
+ DWORD cBlocks;
+ bool bFull; // Heap is considered full do not use for new allocations
+ bool bFullForJumpStubs; // Heap is considered full do not use for new allocations of jump stubs
+};
+
+typedef struct _FakeHpRealCodeHdr
+{
+ LPVOID phdrDebugInfo;
+ LPVOID phdrJitEHInfo; // changed from EE_ILEXCEPTION*
+ LPVOID phdrJitGCInfo; // changed from BYTE*
+ LPVOID hdrMDesc; // changed from MethodDesc*
+ DWORD nUnwindInfos;
+ T_RUNTIME_FUNCTION unwindInfos[0];
+} FakeRealCodeHeader;
+
+typedef struct _FakeHpCodeHdr
+{
+ LPVOID pRealCodeHeader;
+} FakeCodeHeader;
+
+#define FAKE_STUB_CODE_BLOCK_LAST 0xF
+
+#ifdef DEBUGSUPPORT_STUBS_HAVE_UNWIND_INFO
+
+struct FakeStubUnwindInfoHeaderSuffix
+{
+ UCHAR nUnwindInfoSize;
+};
+
+// Variable-sized struct that preceeds a Stub when the stub requires unwind
+// information. Followed by a StubUnwindInfoHeaderSuffix.
+struct FakeStubUnwindInfoHeader
+{
+ FakeStubUnwindInfoHeader *pNext;
+ T_RUNTIME_FUNCTION FunctionEntry;
+ UNWIND_INFO UnwindInfo; // variable length
+};
+
+// List of stub address ranges, in increasing address order.
+struct FakeStubUnwindInfoHeapSegment
+{
+ PBYTE pbBaseAddress;
+ SIZE_T cbSegment;
+ FakeStubUnwindInfoHeader *pUnwindHeaderList;
+ FakeStubUnwindInfoHeapSegment *pNext;
+};
+
+#define FAKE_STUB_EXTERNAL_ENTRY_BIT 0x40000000
+#define FAKE_STUB_INTERCEPT_BIT 0x10000000
+#define FAKE_STUB_UNWIND_INFO_BIT 0x08000000
+
+#ifdef _DEBUG
+#define FAKE_STUB_SIGNATURE 0x42555453
+#endif
+
+struct FakeStub
+{
+ ULONG m_refcount;
+ ULONG m_patchOffset;
+
+ UINT m_numCodeBytes;
+#ifdef _DEBUG
+ UINT32 m_signature;
+#else
+#ifdef _WIN64
+ //README ALIGNEMENT: in retail mode UINT m_numCodeBytes does not align to 16byte for the code
+ // after the Stub struct. This is to pad properly
+ UINT m_pad_code_bytes;
+#endif // _WIN64
+#endif // _DEBUG
+};
+
+#endif // DEBUGSUPPORT_STUBS_HAVE_UNWIND_INFO
+
+
+enum FakeEEDynamicFunctionTableType
+{
+ FAKEDYNFNTABLE_JIT = 0,
+ FAKEDYNFNTABLE_STUB = 1,
+};
+
+
+#ifdef CHECK_DUPLICATED_STRUCT_LAYOUTS
+
+//
+// These are the fields of the above structs that we use.
+// We need to assert that their layout matches the layout
+// in the EE.
+//
+class CheckDuplicatedStructLayouts
+{
+#define CHECK_OFFSET(cls, fld) CPP_ASSERT(cls##fld, offsetof(Fake##cls, fld) == offsetof(cls, fld))
+
+ CHECK_OFFSET(EEJitManager, m_pCodeHeap);
+
+ CHECK_OFFSET(HeapList, hpNext);
+ CHECK_OFFSET(HeapList, startAddress);
+ CHECK_OFFSET(HeapList, endAddress);
+ CHECK_OFFSET(HeapList, mapBase);
+ CHECK_OFFSET(HeapList, pHdrMap);
+
+#if !defined(_TARGET_X86_)
+ CHECK_OFFSET(RealCodeHeader, nUnwindInfos);
+ CHECK_OFFSET(RealCodeHeader, unwindInfos);
+#endif // !_TARGET_X86_
+
+#ifdef DEBUGSUPPORT_STUBS_HAVE_UNWIND_INFO
+ CHECK_OFFSET(StubUnwindInfoHeader, pNext);
+
+ CHECK_OFFSET(StubUnwindInfoHeapSegment, pbBaseAddress);
+ CHECK_OFFSET(StubUnwindInfoHeapSegment, cbSegment);
+ CHECK_OFFSET(StubUnwindInfoHeapSegment, pUnwindHeaderList);
+ CHECK_OFFSET(StubUnwindInfoHeapSegment, pNext);
+
+
+ CHECK_OFFSET(Stub, m_refcount);
+ CHECK_OFFSET(Stub, m_patchOffset);
+ CHECK_OFFSET(Stub, m_numCodeBytes);
+#ifdef _DEBUG
+ CHECK_OFFSET(Stub, m_signature);
+#endif // _DEBUG
+
+#endif // DEBUGSUPPORT_STUBS_HAVE_UNWIND_INFO
+
+#undef CHECK_OFFSET
+
+#ifdef DEBUGSUPPORT_STUBS_HAVE_UNWIND_INFO
+
+ static_assert_no_msg( Stub::EXTERNAL_ENTRY_BIT
+ == FAKE_STUB_EXTERNAL_ENTRY_BIT);
+
+ static_assert_no_msg( Stub::INTERCEPT_BIT
+ == FAKE_STUB_INTERCEPT_BIT);
+
+ static_assert_no_msg( Stub::UNWIND_INFO_BIT
+ == FAKE_STUB_UNWIND_INFO_BIT);
+
+#ifdef _DEBUG
+ static_assert_no_msg( FAKE_STUB_SIGNATURE
+ == Stub::kUsedStub);
+#endif
+
+#endif // DEBUGSUPPORT_STUBS_HAVE_UNWIND_INFO
+};
+
+#ifdef DEBUGSUPPORT_STUBS_HAVE_UNWIND_INFO
+
+static_assert_no_msg( FAKEDYNFNTABLE_JIT
+ == DYNFNTABLE_JIT);
+
+static_assert_no_msg( FAKEDYNFNTABLE_STUB
+ == DYNFNTABLE_STUB);
+
+#endif // DEBUGSUPPORT_STUBS_HAVE_UNWIND_INFO
+
+#else // CHECK_DUPLICATED_STRUCT_LAYOUTS
+
+BOOL WINAPI DllMain(HINSTANCE hDLL, DWORD dwReason, LPVOID pReserved);
+//NTSTATUS OutOfProcessFindHeader(HANDLE hProcess, DWORD_PTR pMapIn, DWORD_PTR addr, DWORD_PTR &codeHead);
+extern "C" NTSTATUS OutOfProcessFunctionTableCallback(IN HANDLE hProcess, IN PVOID TableAddress, OUT PULONG pnEntries, OUT PT_RUNTIME_FUNCTION* ppFunctions);
+
+
+// OutOfProcessFunctionTableCallbackEx is like the standard OS-defined OutOfProcessFunctionTableCallback, but rather
+// than take a handle to a process, it takes a callback function which can read from the target. This allows the API to work on
+// targets other than live processes (such as TTT trace files).
+// pUserContext is passed directly to fpReadMemory, and the semantics of all other ReadMemoryFunction arguments (and return value) are
+// the same as those for kernel32!ReadProcessMemory.
+typedef BOOL (ReadMemoryFunction)(PVOID pUserContext, LPCVOID lpBaseAddress, PVOID lpBuffer, SIZE_T nSize, SIZE_T* lpNumberOfBytesRead);
+extern "C" NTSTATUS OutOfProcessFunctionTableCallbackEx(IN ReadMemoryFunction fpReadMemory, IN PVOID pUserContext, IN PVOID TableAddress, OUT PULONG pnEntries, OUT PT_RUNTIME_FUNCTION* ppFunctions);
+
+#endif // CHECK_DUPLICATED_STRUCT_LAYOUTS
+
+#endif //_FN_TABLE_ACCESS_H
diff --git a/src/debug/daccess/i386/.gitmirror b/src/debug/daccess/i386/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/debug/daccess/i386/.gitmirror
@@ -0,0 +1 @@
+Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror. \ No newline at end of file
diff --git a/src/debug/daccess/i386/primitives.cpp b/src/debug/daccess/i386/primitives.cpp
new file mode 100644
index 0000000000..e4d3c6cb76
--- /dev/null
+++ b/src/debug/daccess/i386/primitives.cpp
@@ -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.
+
+//
+
+#include "stdafx.h"
+
+#include "../../shared/i386/primitives.cpp"
+
+
diff --git a/src/debug/daccess/inspect.cpp b/src/debug/daccess/inspect.cpp
new file mode 100644
index 0000000000..5276626dcb
--- /dev/null
+++ b/src/debug/daccess/inspect.cpp
@@ -0,0 +1,3840 @@
+// 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.
+//*****************************************************************************
+// File: inspect.cpp
+//
+
+//
+// ClrData object inspection.
+//
+//*****************************************************************************
+
+#include "stdafx.h"
+
+
+HRESULT
+InitFieldIter(DeepFieldDescIterator* fieldIter,
+ TypeHandle typeHandle,
+ bool canHaveFields,
+ ULONG32 flags,
+ IXCLRDataTypeInstance* fromType)
+{
+ // Currently we can't filter on kinds so
+ // require them all.
+ if ((flags & ~CLRDATA_FIELD_ALL_FIELDS) != 0 ||
+ (flags & CLRDATA_TYPE_ALL_KINDS) != CLRDATA_TYPE_ALL_KINDS ||
+ (flags & CLRDATA_FIELD_ALL_LOCATIONS) == 0)
+ {
+ return E_INVALIDARG;
+ }
+
+ if (!canHaveFields)
+ {
+ // Leave default empty initialization.
+ return S_OK;
+ }
+
+ int fieldIterFlags = ApproxFieldDescIterator::ALL_FIELDS;
+
+ if ((flags & CLRDATA_FIELD_FROM_INSTANCE) == 0)
+ {
+ fieldIterFlags &= ~ApproxFieldDescIterator::INSTANCE_FIELDS;
+ }
+ if ((flags & CLRDATA_FIELD_FROM_STATIC) == 0)
+ {
+ fieldIterFlags &= ~ApproxFieldDescIterator::STATIC_FIELDS;
+ }
+
+ bool includeParents;
+
+ if ((flags & CLRDATA_FIELD_IS_INHERITED) == 0)
+ {
+ if (fromType)
+ {
+ typeHandle = ((ClrDataTypeInstance*)fromType)->GetTypeHandle();
+ }
+ includeParents = false;
+ }
+ else if (fromType)
+ {
+ return E_INVALIDARG;
+ }
+ else
+ {
+ includeParents = true;
+ }
+
+ if (typeHandle.IsNull() ||
+ !typeHandle.GetMethodTable() ||
+ !typeHandle.IsRestored())
+ {
+ return E_INVALIDARG;
+ }
+
+ fieldIter->Init(typeHandle.GetMethodTable(), fieldIterFlags, includeParents);
+
+ return S_OK;
+}
+
+ULONG32
+GetTypeFieldValueFlags(TypeHandle typeHandle,
+ FieldDesc* fieldDesc,
+ ULONG32 otherFlags,
+ bool isDeref)
+{
+ otherFlags &= ~(CLRDATA_VALUE_IS_PRIMITIVE |
+ CLRDATA_VALUE_IS_VALUE_TYPE |
+ CLRDATA_VALUE_IS_STRING |
+ CLRDATA_VALUE_IS_ARRAY |
+ CLRDATA_VALUE_IS_REFERENCE |
+ CLRDATA_VALUE_IS_POINTER |
+ CLRDATA_VALUE_IS_ENUM);
+
+ CorElementType eltType;
+
+ if (fieldDesc)
+ {
+ eltType = fieldDesc->GetFieldType();
+ }
+ else
+ {
+ _ASSERTE(!typeHandle.IsNull());
+ eltType = typeHandle.GetInternalCorElementType();
+ }
+
+ if (!isDeref && CorTypeInfo::IsObjRef_NoThrow(eltType))
+ {
+ otherFlags |= CLRDATA_VALUE_IS_REFERENCE;
+ }
+ else if (typeHandle.IsEnum())
+ {
+ otherFlags |= CLRDATA_VALUE_IS_ENUM;
+ }
+ else if (eltType == ELEMENT_TYPE_STRING)
+ {
+ otherFlags |= CLRDATA_VALUE_IS_STRING;
+ }
+ else if (eltType == ELEMENT_TYPE_PTR)
+ {
+ otherFlags |= CLRDATA_VALUE_IS_POINTER;
+ }
+ else if (CorTypeInfo::IsPrimitiveType_NoThrow(eltType))
+ {
+ otherFlags |= CLRDATA_VALUE_IS_PRIMITIVE;
+ }
+ else if (typeHandle.IsArray())
+ {
+ otherFlags |= CLRDATA_VALUE_IS_ARRAY;
+ }
+ else if (typeHandle.IsValueType())
+ {
+ otherFlags |= CLRDATA_VALUE_IS_VALUE_TYPE;
+ }
+ else if (eltType == ELEMENT_TYPE_CLASS)
+ {
+ //
+ // Perform extra checks to identify well-known classes.
+ //
+
+ if ((&g_Mscorlib)->IsClass(typeHandle.GetMethodTable(), CLASS__STRING))
+ {
+ otherFlags |= CLRDATA_VALUE_IS_STRING;
+ }
+ }
+
+ if (fieldDesc)
+ {
+ otherFlags &= ~(CLRDATA_VALUE_IS_LITERAL |
+ CLRDATA_VALUE_FROM_INSTANCE |
+ CLRDATA_VALUE_FROM_TASK_LOCAL |
+ CLRDATA_VALUE_FROM_STATIC);
+
+ if ((isDeref ||
+ (otherFlags & CLRDATA_VALUE_IS_REFERENCE) == 0) &&
+ IsFdLiteral(fieldDesc->GetAttributes()))
+ {
+ otherFlags |= CLRDATA_VALUE_IS_LITERAL;
+ }
+
+ if (fieldDesc->IsStatic())
+ {
+ otherFlags |= CLRDATA_VALUE_FROM_STATIC;
+ }
+ else if (fieldDesc->IsThreadStatic())
+ {
+ otherFlags |= CLRDATA_VALUE_FROM_TASK_LOCAL;
+ }
+ else
+#ifdef FEATURE_REMOTING
+ if (!fieldDesc->IsContextStatic())
+#endif
+ {
+ otherFlags |= CLRDATA_VALUE_FROM_INSTANCE;
+ }
+ }
+
+ return otherFlags;
+}
+
+//----------------------------------------------------------------------------
+//
+// ClrDataValue.
+//
+//----------------------------------------------------------------------------
+
+ClrDataValue::ClrDataValue(ClrDataAccess* dac,
+ AppDomain* appDomain,
+ Thread* thread,
+ ULONG32 flags,
+ TypeHandle typeHandle,
+ ULONG64 baseAddr,
+ ULONG32 numLocs,
+ NativeVarLocation* locs)
+{
+ m_dac = dac;
+ m_dac->AddRef();
+ m_instanceAge = m_dac->m_instanceAge;
+ m_refs = 1;
+ m_appDomain = appDomain;
+ m_thread = thread;
+ m_flags = flags;
+ m_typeHandle = typeHandle;
+ m_baseAddr = baseAddr;
+ m_numLocs = numLocs;
+ if (numLocs)
+ {
+ memcpy(m_locs, locs, numLocs * sizeof(m_locs[0]));
+ }
+
+ if (numLocs && (m_flags & CLRDATA_VALUE_IS_REFERENCE) != 0)
+ {
+ m_totalSize = sizeof(TADDR);
+ }
+ else
+ {
+ m_totalSize = 0;
+ for (ULONG32 i = 0; i < m_numLocs; i++)
+ {
+ m_totalSize += m_locs[i].size;
+ }
+ }
+}
+
+ClrDataValue::~ClrDataValue(void)
+{
+ m_dac->Release();
+}
+
+STDMETHODIMP
+ClrDataValue::QueryInterface(THIS_
+ IN REFIID interfaceId,
+ OUT PVOID* iface)
+{
+ if (IsEqualIID(interfaceId, IID_IUnknown) ||
+ IsEqualIID(interfaceId, __uuidof(IXCLRDataValue)))
+ {
+ AddRef();
+ *iface = static_cast<IUnknown*>
+ (static_cast<IXCLRDataValue*>(this));
+ return S_OK;
+ }
+ else
+ {
+ *iface = NULL;
+ return E_NOINTERFACE;
+ }
+}
+
+STDMETHODIMP_(ULONG)
+ClrDataValue::AddRef(THIS)
+{
+ return InterlockedIncrement(&m_refs);
+}
+
+STDMETHODIMP_(ULONG)
+ClrDataValue::Release(THIS)
+{
+ SUPPORTS_DAC_HOST_ONLY;
+ LONG newRefs = InterlockedDecrement(&m_refs);
+ if (newRefs == 0)
+ {
+ delete this;
+ }
+ return newRefs;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataValue::GetFlags(
+ /* [out] */ ULONG32 *flags)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ *flags = m_flags;
+ status = S_OK;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataValue::GetAddress(
+ /* [out] */ CLRDATA_ADDRESS *address)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ // This query can only be answered if there's a
+ // single non-register address.
+ if (m_numLocs == 1 &&
+ !m_locs[0].contextReg)
+ {
+ *address = TO_CDADDR(m_locs[0].addr);
+ status = S_OK;
+ }
+ else
+ {
+ status = E_NOINTERFACE;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataValue::GetSize(
+ /* [out] */ ULONG64 *size)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if (m_totalSize)
+ {
+ *size = m_totalSize;
+ status = S_OK;
+ }
+ else
+ {
+ status = E_NOINTERFACE;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT
+ClrDataValue::IntGetBytes(
+ /* [in] */ ULONG32 bufLen,
+ /* [size_is][out] */ BYTE buffer[ ])
+{
+ HRESULT status;
+
+ NativeVarLocation* loc = m_locs;
+ for (ULONG32 i = 0; i < m_numLocs; i++)
+ {
+ if (loc->contextReg)
+ {
+ memcpy(buffer, (void*)(ULONG_PTR)loc->addr, loc->size);
+ buffer += loc->size;
+ }
+ else
+ {
+ ULONG32 done;
+
+ _ASSERTE(FitsIn<ULONG32>(loc->size));
+ status = m_dac->m_pTarget->
+ ReadVirtual(loc->addr, buffer, static_cast<ULONG32>(loc->size),
+ &done);
+ if (status != S_OK)
+ {
+ return CORDBG_E_READVIRTUAL_FAILURE;
+ }
+ if (done != loc->size)
+ {
+ return HRESULT_FROM_WIN32(ERROR_READ_FAULT);
+ }
+
+ buffer += loc->size;
+ }
+
+ loc++;
+ }
+
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataValue::GetBytes(
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *dataSize,
+ /* [size_is][out] */ BYTE buffer[ ])
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if (!m_totalSize)
+ {
+ status = E_NOINTERFACE;
+ goto Exit;
+ }
+
+ if (dataSize)
+ {
+ _ASSERTE(FitsIn<ULONG32>(m_totalSize));
+ *dataSize = static_cast<ULONG32>(m_totalSize);
+ }
+
+ if (bufLen < m_totalSize)
+ {
+ status = HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW);
+ goto Exit;
+ }
+
+ status = IntGetBytes(bufLen, buffer);
+
+ Exit: ;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataValue::SetBytes(
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *dataSize,
+ /* [size_is][in] */ BYTE buffer[ ])
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ NativeVarLocation* loc = NULL;
+
+ if (!m_totalSize)
+ {
+ status = E_NOINTERFACE;
+ goto Exit;
+ }
+
+ if (dataSize)
+ {
+ _ASSERTE(FitsIn<ULONG32>(m_totalSize));
+ *dataSize = static_cast<ULONG32>(m_totalSize);
+ }
+
+ if (bufLen < m_totalSize)
+ {
+ status = HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW);
+ goto Exit;
+ }
+
+ loc = m_locs;
+ for (ULONG32 i = 0; i < m_numLocs; i++)
+ {
+ if (loc->contextReg)
+ {
+ // XXX Microsoft - Context update?
+ // memcpy(buffer, (void*)(ULONG_PTR)loc->addr, loc->size);
+ // buffer += loc->size;
+ // until drew decides, return notimpl
+ status = E_NOTIMPL;
+ goto Exit;
+ }
+ else
+ {
+ _ASSERT(FitsIn<ULONG32>(loc->size));
+ status = m_dac->m_pMutableTarget->
+ WriteVirtual(loc->addr, buffer, static_cast<ULONG32>(loc->size));
+ if (status != S_OK)
+ {
+ goto Exit;
+ }
+
+ buffer += loc->size;
+ }
+
+ loc++;
+ }
+
+ status = S_OK;
+
+ Exit: ;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataValue::GetType(
+ /* [out] */ IXCLRDataTypeInstance **typeInstance)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if ((m_flags & CLRDATA_VALUE_IS_REFERENCE) != 0)
+ {
+ *typeInstance = NULL;
+ status = S_FALSE;
+ }
+ else if (!m_appDomain ||
+ m_typeHandle.IsNull())
+ {
+ status = E_NOTIMPL;
+ }
+ else
+ {
+ *typeInstance = new (nothrow)
+ ClrDataTypeInstance(m_dac, m_appDomain, m_typeHandle);
+ status = *typeInstance ? S_OK : E_OUTOFMEMORY;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataValue::GetNumFields(
+ /* [out] */ ULONG32 *numFields)
+{
+ // XXX Microsoft - Obsolete method, never implemented.
+ return E_UNEXPECTED;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataValue::GetFieldByIndex(
+ /* [in] */ ULONG32 index,
+ /* [out] */ IXCLRDataValue **field,
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(bufLen, *nameLen) WCHAR nameBuf[ ],
+ /* [out] */ mdFieldDef *token)
+{
+ // XXX Microsoft - Obsolete method, never implemented.
+ return E_UNEXPECTED;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataValue::GetNumFields2(
+ /* [in] */ ULONG32 flags,
+ /* [in] */ IXCLRDataTypeInstance *fromType,
+ /* [out] */ ULONG32 *numFields)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ DeepFieldDescIterator fieldIter;
+
+ if ((status = InitFieldIter(&fieldIter, m_typeHandle, CanHaveFields(),
+ flags, fromType)) == S_OK)
+ {
+ *numFields = fieldIter.Count();
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataValue::StartEnumFields(
+ /* [in] */ ULONG32 flags,
+ /* [in] */ IXCLRDataTypeInstance *fromType,
+ /* [out] */ CLRDATA_ENUM *handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = SplitName::
+ CdStartField(NULL,
+ 0,
+ flags,
+ fromType,
+ m_typeHandle,
+ NULL,
+ mdTypeDefNil,
+ m_baseAddr,
+ m_thread,
+ NULL,
+ m_appDomain,
+ NULL,
+ NULL,
+ handle);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataValue::EnumField(
+ /* [out][in] */ CLRDATA_ENUM *handle,
+ /* [out] */ IXCLRDataValue **field,
+ /* [in] */ ULONG32 nameBufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(nameBufLen, *nameLen) WCHAR nameBuf[ ],
+ /* [out] */ mdFieldDef *token)
+{
+ return EnumField2(handle, field, nameBufLen, nameLen, nameBuf,
+ NULL, token);
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataValue::EnumField2(
+ /* [out][in] */ CLRDATA_ENUM *handle,
+ /* [out] */ IXCLRDataValue **field,
+ /* [in] */ ULONG32 nameBufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(nameBufLen, *nameLen) WCHAR nameBuf[ ],
+ /* [out] */ IXCLRDataModule** tokenScope,
+ /* [out] */ mdFieldDef *token)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = SplitName::CdNextField(m_dac, handle, NULL, NULL, field,
+ nameBufLen, nameLen, nameBuf,
+ tokenScope, token);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataValue::EndEnumFields(
+ /* [in] */ CLRDATA_ENUM handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = SplitName::CdEnd(handle);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataValue::StartEnumFieldsByName(
+ /* [in] */ LPCWSTR name,
+ /* [in] */ ULONG32 nameFlags,
+ /* [in] */ ULONG32 fieldFlags,
+ /* [in] */ IXCLRDataTypeInstance *fromType,
+ /* [out] */ CLRDATA_ENUM *handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = SplitName::
+ CdStartField(name,
+ nameFlags,
+ fieldFlags,
+ fromType,
+ m_typeHandle,
+ NULL,
+ mdTypeDefNil,
+ m_baseAddr,
+ m_thread,
+ NULL,
+ m_appDomain,
+ NULL,
+ NULL,
+ handle);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataValue::EnumFieldByName(
+ /* [out][in] */ CLRDATA_ENUM *handle,
+ /* [out] */ IXCLRDataValue **field,
+ /* [out] */ mdFieldDef *token)
+{
+ return EnumFieldByName2(handle, field, NULL, token);
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataValue::EnumFieldByName2(
+ /* [out][in] */ CLRDATA_ENUM *handle,
+ /* [out] */ IXCLRDataValue **field,
+ /* [out] */ IXCLRDataModule** tokenScope,
+ /* [out] */ mdFieldDef *token)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = SplitName::CdNextField(m_dac, handle, NULL, NULL, field,
+ 0, NULL, NULL,
+ tokenScope, token);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataValue::EndEnumFieldsByName(
+ /* [in] */ CLRDATA_ENUM handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = SplitName::CdEnd(handle);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataValue::GetFieldByToken(
+ /* [in] */ mdFieldDef token,
+ /* [out] */ IXCLRDataValue **field,
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(bufLen, *nameLen) WCHAR nameBuf[ ])
+{
+ return GetFieldByToken2(NULL, token, field, bufLen, nameLen, nameBuf);
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataValue::GetFieldByToken2(
+ /* [in] */ IXCLRDataModule* tokenScope,
+ /* [in] */ mdFieldDef token,
+ /* [out] */ IXCLRDataValue **field,
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(bufLen, *nameLen) WCHAR nameBuf[ ])
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ DeepFieldDescIterator fieldIter;
+
+ if ((status = InitFieldIter(&fieldIter, m_typeHandle, CanHaveFields(),
+ CLRDATA_VALUE_ALL_FIELDS, NULL)) == S_OK)
+ {
+ FieldDesc* fieldDesc;
+
+ status = E_INVALIDARG;
+ while ((fieldDesc = fieldIter.Next()))
+ {
+ if ((!tokenScope ||
+ PTR_HOST_TO_TADDR(((ClrDataModule*)tokenScope)->
+ GetModule()) ==
+ PTR_HOST_TO_TADDR(fieldDesc->GetModule())) &&
+ fieldDesc->GetMemberDef() == token)
+ {
+ status = NewFromSubField(fieldDesc,
+ fieldIter.
+ IsFieldFromParentClass() ?
+ CLRDATA_VALUE_IS_INHERITED : 0,
+ NULL, field,
+ bufLen, nameLen, nameBuf,
+ NULL, NULL);
+ break;
+ }
+ }
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT
+ClrDataValue::GetRefAssociatedValue(IXCLRDataValue** assocValue)
+{
+ HRESULT status;
+
+ if (m_typeHandle.IsNull())
+ {
+ return E_NOINTERFACE;
+ }
+
+ TADDR refAddr;
+
+ _ASSERTE(m_totalSize == sizeof(refAddr));
+
+ if ((status = IntGetBytes(sizeof(refAddr),
+ (PBYTE)&refAddr)) != S_OK)
+ {
+ return status;
+ }
+
+ // We assume that objrefs always refer
+ // to objects so there is no ref chain.
+ ULONG32 valueFlags =
+ GetTypeFieldValueFlags(m_typeHandle, NULL,
+ m_flags & CLRDATA_VALUE_ALL_LOCATIONS, true);
+
+ NativeVarLocation loc;
+
+ loc.addr = TO_CDADDR(refAddr);
+ // XXX Microsoft - Good way to get the right size for everything?
+ loc.size = (m_typeHandle.GetMethodTable())->GetBaseSize();
+ loc.contextReg = false;
+
+ *assocValue = new (nothrow)
+ ClrDataValue(m_dac,
+ m_appDomain,
+ m_thread,
+ valueFlags,
+ m_typeHandle,
+ loc.addr,
+ 1,
+ &loc);
+ return *assocValue ? S_OK : E_OUTOFMEMORY;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataValue::GetAssociatedValue(
+ /* [out] */ IXCLRDataValue **assocValue)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if (m_numLocs && (m_flags & CLRDATA_VALUE_IS_REFERENCE) != 0)
+ {
+ status = GetRefAssociatedValue(assocValue);
+ }
+ else
+ {
+ status = E_NOINTERFACE;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataValue::GetAssociatedType(
+ /* [out] */ IXCLRDataTypeInstance **assocType)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ TypeHandle dacType;
+
+ if ((m_flags & CLRDATA_VALUE_IS_REFERENCE) != 0)
+ {
+ dacType = m_typeHandle;
+ }
+ else if ((m_flags & CLRDATA_VALUE_IS_ARRAY) != 0)
+ {
+ ArrayBase* arrayBase = PTR_ArrayBase(CLRDATA_ADDRESS_TO_TADDR(m_baseAddr));
+ dacType = arrayBase->GetArrayElementTypeHandle();
+ }
+
+ if (dacType.IsNull())
+ {
+ status = E_NOINTERFACE;
+ }
+ else
+ {
+ *assocType = new (nothrow)
+ ClrDataTypeInstance(m_dac,
+ m_appDomain,
+ dacType);
+ status = *assocType ? S_OK : E_OUTOFMEMORY;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataValue::GetString(
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *strLen,
+ /* [size_is][out] */ __out_ecount_part(bufLen, *strLen) WCHAR str[ ])
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if ((m_flags & CLRDATA_VALUE_IS_STRING) != 0)
+ {
+ STRINGREF message = STRINGREF(TO_TADDR(m_baseAddr));
+
+ PWSTR msgStr = DacInstantiateStringW((TADDR)message->GetBuffer(),
+ message->GetStringLength(),
+ true);
+
+ if (strLen)
+ {
+ *strLen = static_cast<ULONG32>(wcslen(msgStr) + 1);
+ }
+ status = StringCchCopy(str, bufLen, msgStr) == S_OK ?
+ S_OK : S_FALSE;
+ }
+ else
+ {
+ status = E_INVALIDARG;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataValue::GetArrayProperties(
+ /* [out] */ ULONG32 *rank,
+ /* [out] */ ULONG32 *totalElements,
+ /* [in] */ ULONG32 numDim,
+ /* [size_is][out] */ ULONG32 dims[ ],
+ /* [in] */ ULONG32 numBases,
+ /* [size_is][out] */ LONG32 bases[ ])
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if ((m_flags & CLRDATA_VALUE_IS_ARRAY) != 0)
+ {
+ ArrayBase* arrayBase = PTR_ArrayBase(CLRDATA_ADDRESS_TO_TADDR(m_baseAddr));
+ unsigned baseRank = arrayBase->GetRank();
+ unsigned i;
+
+ if (rank)
+ {
+ *rank = baseRank;
+ }
+
+ if (totalElements)
+ {
+ *totalElements = arrayBase->GetNumComponents();
+ }
+
+ if (numDim)
+ {
+ PTR_INT32 bounds = arrayBase->GetBoundsPtr();
+
+ for (i = 0; i < baseRank; i++)
+ {
+ if (i < numDim)
+ {
+ dims[i] = bounds[i];
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+
+ if (numBases)
+ {
+ PTR_INT32 lowBounds = arrayBase->GetLowerBoundsPtr();
+
+ for (i = 0; i < baseRank; i++)
+ {
+ if (i < numBases)
+ {
+ bases[i] = lowBounds[i];
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+
+ status = S_OK;
+ }
+ else
+ {
+ status = E_INVALIDARG;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataValue::GetArrayElement(
+ /* [in] */ ULONG32 numInd,
+ /* [size_is][in] */ LONG32 indices[ ],
+ /* [out] */ IXCLRDataValue **value)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ PTR_INT32 bounds, lowBounds;
+ TypeHandle eltType;
+
+ if ((m_flags & CLRDATA_VALUE_IS_ARRAY) == 0)
+ {
+ status = E_INVALIDARG;
+ goto Exit;
+ }
+
+ ArrayBase* arrayBase;
+ unsigned baseRank;
+
+ arrayBase = PTR_ArrayBase(CLRDATA_ADDRESS_TO_TADDR(m_baseAddr));
+ baseRank = arrayBase->GetRank();
+ if (numInd != baseRank)
+ {
+ status = E_INVALIDARG;
+ goto Exit;
+ }
+
+ eltType = arrayBase->GetArrayElementTypeHandle();
+ if (eltType.IsNull())
+ {
+ status = E_INVALIDARG;
+ goto Exit;
+ }
+
+ unsigned dim;
+ ULONG64 offs;
+ SIZE_T dimSize;
+
+ dim = baseRank;
+ offs = TO_CDADDR(PTR_TO_TADDR(arrayBase->GetDataPtr()));
+ dimSize = arrayBase->GetComponentSize();
+ bounds = arrayBase->GetBoundsPtr();
+ lowBounds = arrayBase->GetLowerBoundsPtr();
+
+ while (dim-- > 0)
+ {
+ if (indices[dim] < lowBounds[dim])
+ {
+ status = E_INVALIDARG;
+ goto Exit;
+ }
+
+ UINT32 uindex = (UINT32)(indices[dim] - lowBounds[dim]);
+ if (uindex >= (UINT32)bounds[dim])
+ {
+ status = E_INVALIDARG;
+ goto Exit;
+ }
+
+ offs += dimSize * uindex;
+
+ dimSize *= (UINT64)bounds[dim];
+ }
+
+ NativeVarLocation loc;
+
+ loc.addr = offs;
+ loc.size = eltType.GetSize();
+ loc.contextReg = false;
+
+ *value = new (nothrow)
+ ClrDataValue(m_dac, m_appDomain, m_thread,
+ GetTypeFieldValueFlags(eltType, NULL, 0, false),
+ eltType, offs, 1, &loc);
+ status = *value ? S_OK : E_OUTOFMEMORY;
+
+ Exit: ;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataValue::GetNumLocations(
+ /* [out] */ ULONG32* numLocs)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ *numLocs = m_numLocs;
+ status = S_OK;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataValue::GetLocationByIndex(
+ /* [in] */ ULONG32 loc,
+ /* [out] */ ULONG32* flags,
+ /* [out] */ CLRDATA_ADDRESS* arg)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if (loc < m_numLocs)
+ {
+ if (m_locs[loc].contextReg)
+ {
+ *flags = CLRDATA_VLOC_REGISTER;
+ *arg = 0;
+ }
+ else
+ {
+ *flags = CLRDATA_VLOC_MEMORY;
+ *arg = TO_CDADDR(m_locs[loc].addr);
+ }
+
+ status = S_OK;
+ }
+ else
+ {
+ status = E_INVALIDARG;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataValue::Request(
+ /* [in] */ ULONG32 reqCode,
+ /* [in] */ ULONG32 inBufferSize,
+ /* [size_is][in] */ BYTE *inBuffer,
+ /* [in] */ ULONG32 outBufferSize,
+ /* [size_is][out] */ BYTE *outBuffer)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ switch(reqCode)
+ {
+ case CLRDATA_REQUEST_REVISION:
+ if (inBufferSize != 0 ||
+ inBuffer ||
+ outBufferSize != sizeof(ULONG32))
+ {
+ status = E_INVALIDARG;
+ }
+ else
+ {
+ *(ULONG32*)outBuffer = 3;
+ status = S_OK;
+ }
+ break;
+
+ default:
+ status = E_INVALIDARG;
+ break;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT
+ClrDataValue::NewFromFieldDesc(ClrDataAccess* dac,
+ AppDomain* appDomain,
+ ULONG32 flags,
+ FieldDesc* fieldDesc,
+ ULONG64 objBase,
+ Thread* tlsThread,
+ ClrDataValue** value,
+ IXCLRDataValue** pubValue,
+ ULONG32 nameBufRetLen,
+ ULONG32 *nameLenRet,
+ __out_ecount_part_opt(nameBufRetLen, *nameLenRet) WCHAR nameBufRet[ ],
+ IXCLRDataModule** tokenScopeRet,
+ mdFieldDef *tokenRet)
+{
+ HRESULT status;
+ ClrDataValue* field;
+ ULONG numLocs = 1;
+ NativeVarLocation varLoc, *locs = &varLoc;
+ ULONG64 baseAddr;
+ LPCUTF8 szFieldName;
+
+ status = fieldDesc->GetName_NoThrow(&szFieldName);
+ if (status != S_OK)
+ {
+ return status;
+ }
+
+ status = ConvertUtf8(
+ szFieldName,
+ nameBufRetLen,
+ nameLenRet,
+ nameBufRet);
+ if (status != S_OK)
+ {
+ return status;
+ }
+
+ if (tokenRet != NULL)
+ {
+ *tokenRet = fieldDesc->GetMemberDef();
+ }
+
+ if (fieldDesc->GetEnclosingMethodTable()->ContainsGenericVariables())
+ {
+ // This field is for a generic type definition and
+ // so doesn't have a real location. Produce
+ // a placeholder no-data value.
+ numLocs = 0;
+ locs = NULL;
+ baseAddr = 0;
+ }
+ else if (fieldDesc->IsThreadStatic())
+ {
+ if (!tlsThread)
+ {
+ return E_INVALIDARG;
+ }
+
+ baseAddr =
+ TO_CDADDR(tlsThread->GetStaticFieldAddrNoCreate(fieldDesc, NULL));
+ }
+#ifdef FEATURE_REMOTING
+ else if (fieldDesc->IsContextStatic())
+ {
+ // XXX Microsoft - Microsoft says Context is going away.
+ return E_NOTIMPL;
+ }
+#endif
+ else if (fieldDesc->IsStatic())
+ {
+ baseAddr = TO_CDADDR
+ (PTR_TO_TADDR(fieldDesc->GetStaticAddressHandle
+ (fieldDesc->GetBaseInDomain(appDomain))));
+ }
+ else
+ {
+ // objBase is basically a CLRDATA_ADDRESS, which is a pointer-sized target address sign-extened to
+ // 64-bit. We need to get a TADDR here, which is a pointer-size unsigned value.
+ baseAddr = TO_CDADDR(PTR_TO_TADDR(fieldDesc->GetAddress(PTR_VOID(CLRDATA_ADDRESS_TO_TADDR(objBase)))));
+ }
+
+ if (locs)
+ {
+ locs->addr = baseAddr;
+ locs->size = fieldDesc->GetSize();
+ locs->contextReg = false;
+ }
+
+ TypeHandle typeHandle = fieldDesc->LookupFieldTypeHandle();
+
+ // We allow no-type situations for reference fields
+ // as they can still be useful even though they cannot
+ // be expanded. This is also a common case where the
+ // referred-to type may not be loaded if the field
+ // is holding null.
+ if (typeHandle.IsNull() && !fieldDesc->IsObjRef())
+ {
+ return E_INVALIDARG;
+ }
+
+ flags = GetTypeFieldValueFlags(typeHandle, fieldDesc, flags, false);
+
+ if (tokenScopeRet)
+ {
+ *tokenScopeRet = new (nothrow)
+ ClrDataModule(dac, fieldDesc->GetModule());
+ if (!*tokenScopeRet)
+ {
+ return E_OUTOFMEMORY;
+ }
+ }
+
+ field = new (nothrow) ClrDataValue(dac,
+ appDomain,
+ tlsThread,
+ flags,
+ typeHandle,
+ baseAddr,
+ numLocs,
+ locs);
+ if (value)
+ {
+ *value = field;
+ }
+ if (pubValue)
+ {
+ *pubValue = field;
+ }
+
+ if (!field)
+ {
+ if (tokenScopeRet)
+ {
+ delete (ClrDataModule*)*tokenScopeRet;
+ }
+ return E_OUTOFMEMORY;
+ }
+
+ return S_OK;
+}
+
+//----------------------------------------------------------------------------
+//
+// ClrDataTypeDefinition.
+//
+//----------------------------------------------------------------------------
+
+ClrDataTypeDefinition::ClrDataTypeDefinition(ClrDataAccess* dac,
+ Module* module,
+ mdTypeDef token,
+ TypeHandle typeHandle)
+{
+ m_dac = dac;
+ m_dac->AddRef();
+ m_instanceAge = m_dac->m_instanceAge;
+ m_refs = 1;
+ m_module = module;
+ m_token = token;
+ m_typeHandle = typeHandle;
+}
+
+ClrDataTypeDefinition::~ClrDataTypeDefinition(void)
+{
+ m_dac->Release();
+}
+
+STDMETHODIMP
+ClrDataTypeDefinition::QueryInterface(THIS_
+ IN REFIID interfaceId,
+ OUT PVOID* iface)
+{
+ if (IsEqualIID(interfaceId, IID_IUnknown) ||
+ IsEqualIID(interfaceId, __uuidof(IXCLRDataTypeDefinition)))
+ {
+ AddRef();
+ *iface = static_cast<IUnknown*>
+ (static_cast<IXCLRDataTypeDefinition*>(this));
+ return S_OK;
+ }
+ else
+ {
+ *iface = NULL;
+ return E_NOINTERFACE;
+ }
+}
+
+STDMETHODIMP_(ULONG)
+ClrDataTypeDefinition::AddRef(THIS)
+{
+ return InterlockedIncrement(&m_refs);
+}
+
+STDMETHODIMP_(ULONG)
+ClrDataTypeDefinition::Release(THIS)
+{
+ SUPPORTS_DAC_HOST_ONLY;
+ LONG newRefs = InterlockedDecrement(&m_refs);
+ if (newRefs == 0)
+ {
+ delete this;
+ }
+ return newRefs;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeDefinition::GetModule(
+ /* [out] */ IXCLRDataModule **mod)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ *mod = new (nothrow)
+ ClrDataModule(m_dac, m_module);
+ status = *mod ? S_OK : E_OUTOFMEMORY;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeDefinition::StartEnumMethodDefinitions(
+ /* [out] */ CLRDATA_ENUM* handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = MetaEnum::New(m_module,
+ mdtMethodDef,
+ m_token,
+ NULL,
+ NULL,
+ handle);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeDefinition::EnumMethodDefinition(
+ /* [in, out] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataMethodDefinition **methodDefinition)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ mdMethodDef token;
+
+ if ((status = MetaEnum::CdNextToken(handle, &token)) == S_OK)
+ {
+ status = ClrDataMethodDefinition::
+ NewFromModule(m_dac,
+ m_module,
+ token,
+ NULL,
+ methodDefinition);
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeDefinition::EndEnumMethodDefinitions(
+ /* [in] */ CLRDATA_ENUM handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = MetaEnum::CdEnd(handle);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeDefinition::StartEnumMethodDefinitionsByName(
+ /* [in] */ LPCWSTR name,
+ /* [in] */ ULONG32 flags,
+ /* [out] */ CLRDATA_ENUM* handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = SplitName::CdStartMethod(name,
+ flags,
+ m_module,
+ m_token,
+ NULL,
+ NULL,
+ NULL,
+ handle);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeDefinition::EnumMethodDefinitionByName(
+ /* [out][in] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataMethodDefinition **method)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ mdMethodDef token;
+
+ if ((status = SplitName::CdNextMethod(handle, &token)) == S_OK)
+ {
+ status = ClrDataMethodDefinition::
+ NewFromModule(m_dac,
+ m_module,
+ token,
+ NULL,
+ method);
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeDefinition::EndEnumMethodDefinitionsByName(
+ /* [in] */ CLRDATA_ENUM handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = SplitName::CdEnd(handle);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeDefinition::GetMethodDefinitionByToken(
+ /* [in] */ mdMethodDef token,
+ /* [out] */ IXCLRDataMethodDefinition **methodDefinition)
+{
+ HRESULT status;
+
+ // This isn't critically necessary but it prevents
+ // an assert in the metadata code.
+ if (TypeFromToken(token) != mdtMethodDef)
+ {
+ return E_INVALIDARG;
+ }
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = ClrDataMethodDefinition::
+ NewFromModule(m_dac,
+ m_module,
+ token,
+ NULL,
+ methodDefinition);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeDefinition::StartEnumInstances(
+ /* [in] */ IXCLRDataAppDomain* appDomain,
+ /* [out] */ CLRDATA_ENUM *handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ // XXX Microsoft.
+ status = E_NOTIMPL;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeDefinition::EnumInstance(
+ /* [out][in] */ CLRDATA_ENUM *handle,
+ /* [out] */ IXCLRDataTypeInstance **instance)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ // XXX Microsoft.
+ status = E_NOTIMPL;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeDefinition::EndEnumInstances(
+ /* [in] */ CLRDATA_ENUM handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ // XXX Microsoft.
+ status = E_NOTIMPL;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeDefinition::GetNumFields(
+ /* [in] */ ULONG32 flags,
+ /* [out] */ ULONG32 *numFields)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if (m_typeHandle.IsNull())
+ {
+ status = E_NOTIMPL;
+ }
+ else
+ {
+ DeepFieldDescIterator fieldIter;
+
+ if ((status = InitFieldIter(&fieldIter, m_typeHandle, true,
+ flags, NULL)) == S_OK)
+ {
+ *numFields = fieldIter.Count();
+ }
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeDefinition::StartEnumFields(
+ /* [in] */ ULONG32 flags,
+ /* [out] */ CLRDATA_ENUM *handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if (m_typeHandle.IsNull())
+ {
+ *handle = 0;
+ status = E_NOTIMPL;
+ }
+ else
+ {
+ status = SplitName::
+ CdStartField(NULL,
+ 0,
+ flags,
+ NULL,
+ m_typeHandle,
+ NULL,
+ mdTypeDefNil,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ handle);
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeDefinition::EnumField(
+ /* [out][in] */ CLRDATA_ENUM *handle,
+ /* [in] */ ULONG32 nameBufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(nameBufLen, *nameLen) WCHAR nameBuf[ ],
+ /* [out] */ IXCLRDataTypeDefinition **type,
+ /* [out] */ ULONG32 *flags,
+ /* [out] */ mdFieldDef *token)
+{
+ return EnumField2(handle, nameBufLen, nameLen, nameBuf,
+ type, flags, NULL, token);
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeDefinition::EnumField2(
+ /* [out][in] */ CLRDATA_ENUM *handle,
+ /* [in] */ ULONG32 nameBufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(nameBufLen, *nameLen) WCHAR nameBuf[ ],
+ /* [out] */ IXCLRDataTypeDefinition **type,
+ /* [out] */ ULONG32 *flags,
+ /* [out] */ IXCLRDataModule** tokenScope,
+ /* [out] */ mdFieldDef *token)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = SplitName::CdNextField(m_dac, handle, type, flags, NULL,
+ nameBufLen, nameLen, nameBuf,
+ tokenScope, token);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeDefinition::EndEnumFields(
+ /* [in] */ CLRDATA_ENUM handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = SplitName::CdEnd(handle);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeDefinition::StartEnumFieldsByName(
+ /* [in] */ LPCWSTR name,
+ /* [in] */ ULONG32 nameFlags,
+ /* [in] */ ULONG32 fieldFlags,
+ /* [out] */ CLRDATA_ENUM *handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if (m_typeHandle.IsNull())
+ {
+ *handle = 0;
+ status = E_NOTIMPL;
+ }
+ else
+ {
+ status = SplitName::
+ CdStartField(name,
+ nameFlags,
+ fieldFlags,
+ NULL,
+ m_typeHandle,
+ NULL,
+ mdTypeDefNil,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ handle);
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeDefinition::EnumFieldByName(
+ /* [out][in] */ CLRDATA_ENUM *handle,
+ /* [out] */ IXCLRDataTypeDefinition **type,
+ /* [out] */ ULONG32 *flags,
+ /* [out] */ mdFieldDef *token)
+{
+ return EnumFieldByName2(handle, type, flags, NULL, token);
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeDefinition::EnumFieldByName2(
+ /* [out][in] */ CLRDATA_ENUM *handle,
+ /* [out] */ IXCLRDataTypeDefinition **type,
+ /* [out] */ ULONG32 *flags,
+ /* [out] */ IXCLRDataModule** tokenScope,
+ /* [out] */ mdFieldDef *token)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = SplitName::CdNextField(m_dac, handle, type, flags, NULL,
+ 0, NULL, NULL,
+ tokenScope, token);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeDefinition::EndEnumFieldsByName(
+ /* [in] */ CLRDATA_ENUM handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = SplitName::CdEnd(handle);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeDefinition::GetFieldByToken(
+ /* [in] */ mdFieldDef token,
+ /* [in] */ ULONG32 nameBufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(nameBufLen, *nameLen) WCHAR nameBuf[ ],
+ /* [out] */ IXCLRDataTypeDefinition **type,
+ /* [out] */ ULONG32 *flags)
+{
+ return GetFieldByToken2(NULL, token, nameBufLen, nameLen, nameBuf,
+ type, flags);
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeDefinition::GetFieldByToken2(
+ /* [in] */ IXCLRDataModule* tokenScope,
+ /* [in] */ mdFieldDef token,
+ /* [in] */ ULONG32 nameBufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(nameBufLen, *nameLen) WCHAR nameBuf[ ],
+ /* [out] */ IXCLRDataTypeDefinition **type,
+ /* [out] */ ULONG32 *flags)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ DeepFieldDescIterator fieldIter;
+
+ if (m_typeHandle.IsNull())
+ {
+ status = E_NOTIMPL;
+ goto Exit;
+ }
+
+ if ((status = InitFieldIter(&fieldIter, m_typeHandle, true,
+ CLRDATA_VALUE_ALL_FIELDS, NULL)) == S_OK)
+ {
+ FieldDesc* fieldDesc;
+
+ status = E_INVALIDARG;
+ while ((fieldDesc = fieldIter.Next()))
+ {
+ if ((!tokenScope ||
+ PTR_HOST_TO_TADDR(((ClrDataModule*)tokenScope)->
+ GetModule()) ==
+ PTR_HOST_TO_TADDR(fieldDesc->GetModule())) &&
+ fieldDesc->GetMemberDef() == token)
+ {
+ if (flags)
+ {
+ *flags = GetTypeFieldValueFlags(m_typeHandle,
+ fieldDesc,
+ fieldIter.IsFieldFromParentClass() ?
+ CLRDATA_VALUE_IS_INHERITED : 0,
+ false);
+ }
+
+ status = ConvertUtf8(fieldDesc->GetName(),
+ nameBufLen, nameLen, nameBuf);
+
+ if (SUCCEEDED(status) && type)
+ {
+ TypeHandle fieldTypeHandle =
+ fieldDesc->LookupFieldTypeHandle();
+ *type = new (nothrow)
+ ClrDataTypeDefinition(m_dac,
+ fieldTypeHandle.GetModule(),
+ fieldTypeHandle.
+ GetMethodTable()->GetCl(),
+ fieldTypeHandle);
+ status = *type ? S_OK : E_OUTOFMEMORY;
+ }
+
+ break;
+ }
+ }
+ }
+
+ Exit: ;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeDefinition::GetName(
+ /* [in] */ ULONG32 flags,
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(bufLen, *nameLen) WCHAR nameBuf[ ])
+{
+ HRESULT status = S_OK;
+
+ if (flags != 0)
+ {
+ return E_INVALIDARG;
+ }
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ char classNameBuf[MAX_CLASSNAME_LENGTH];
+
+ if (m_typeHandle.IsNull())
+ {
+ if ((status =
+ GetFullClassNameFromMetadata(m_module->GetMDImport(),
+ m_token,
+ NumItems(classNameBuf),
+ classNameBuf)) == S_OK)
+ {
+ status = ConvertUtf8(classNameBuf, bufLen, nameLen, nameBuf);
+ }
+ }
+ else
+ {
+ StackSString ssClassNameBuf;
+ m_typeHandle.GetName(ssClassNameBuf);
+ if (wcsncpy_s(nameBuf, bufLen, ssClassNameBuf.GetUnicode(), _TRUNCATE) == STRUNCATE)
+ {
+ status = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
+ }
+ if (nameLen != NULL)
+ {
+ size_t cchName = ssClassNameBuf.GetCount() + 1;
+ if (FitsIn<ULONG32>(cchName))
+ {
+ *nameLen = (ULONG32) cchName;
+ }
+ else
+ {
+ status = COR_E_OVERFLOW;
+ }
+ }
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeDefinition::GetTokenAndScope(
+ /* [out] */ mdTypeDef *token,
+ /* [out] */ IXCLRDataModule **mod)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = S_OK;
+
+ if (token)
+ {
+ *token = m_token;
+ }
+
+ if (mod)
+ {
+ *mod = new (nothrow)
+ ClrDataModule(m_dac, m_module);
+ status = *mod ? S_OK : E_OUTOFMEMORY;
+ }
+
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeDefinition::GetCorElementType(
+ /* [out] */ CorElementType *type)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if (m_typeHandle.IsNull())
+ {
+ status = E_NOTIMPL;
+ }
+ else
+ {
+ *type = m_typeHandle.GetInternalCorElementType();
+ status = S_OK;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeDefinition::GetFlags(
+ /* [out] */ ULONG32 *flags)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ *flags = CLRDATA_TYPE_DEFAULT;
+ status = S_OK;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeDefinition::GetBase(
+ /* [out] */ IXCLRDataTypeDefinition **base)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ mdTypeDef token;
+ TypeHandle typeHandle;
+
+ if (m_typeHandle.IsNull())
+ {
+ ULONG attr;
+
+ status = m_module->GetMDImport()->GetTypeDefProps(m_token, &attr, &token);
+ if (FAILED(status))
+ {
+ goto Exit;
+ }
+ }
+ else
+ {
+ typeHandle = m_typeHandle.GetParent();
+ if (typeHandle.IsNull() ||
+ !typeHandle.GetMethodTable())
+ {
+ status = E_NOINTERFACE;
+ goto Exit;
+ }
+
+ token = typeHandle.GetMethodTable()->GetCl();
+ }
+
+ *base = new (nothrow)
+ ClrDataTypeDefinition(m_dac, m_module, token, typeHandle);
+ status = *base ? S_OK : E_OUTOFMEMORY;
+
+ Exit: ;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeDefinition::GetArrayRank(
+ /* [out] */ ULONG32* rank)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if (m_typeHandle.IsNull())
+ {
+ status = E_NOTIMPL;
+ }
+ else
+ {
+ MethodTable* pMT = m_typeHandle.GetMethodTable();
+
+ if (!m_typeHandle.IsArray() ||
+ (pMT == NULL))
+ {
+ status = E_NOINTERFACE;
+ }
+ else
+ {
+ *rank = pMT->GetRank();
+ status = S_OK;
+ }
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeDefinition::IsSameObject(
+ /* [in] */ IXCLRDataTypeDefinition* type)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if (m_typeHandle.IsNull())
+ {
+ status = (PTR_HOST_TO_TADDR(m_module) ==
+ PTR_HOST_TO_TADDR(((ClrDataTypeDefinition*)type)->
+ m_module) &&
+ m_token == ((ClrDataTypeDefinition*)type)->m_token) ?
+ S_OK : S_FALSE;
+ }
+ else
+ {
+ status = (m_typeHandle.AsTAddr() ==
+ ((ClrDataTypeDefinition*)type)->m_typeHandle.AsTAddr()) ?
+ S_OK : S_FALSE;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeDefinition::GetTypeNotification(
+ /* [out] */ ULONG32* flags)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ // XXX Microsoft.
+ status = E_NOTIMPL;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeDefinition::SetTypeNotification(
+ /* [in] */ ULONG32 flags)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ // XXX Microsoft.
+ status = E_NOTIMPL;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeDefinition::Request(
+ /* [in] */ ULONG32 reqCode,
+ /* [in] */ ULONG32 inBufferSize,
+ /* [size_is][in] */ BYTE *inBuffer,
+ /* [in] */ ULONG32 outBufferSize,
+ /* [size_is][out] */ BYTE *outBuffer)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ switch(reqCode)
+ {
+ case CLRDATA_REQUEST_REVISION:
+ if (inBufferSize != 0 ||
+ inBuffer ||
+ outBufferSize != sizeof(ULONG32))
+ {
+ status = E_INVALIDARG;
+ }
+ else
+ {
+ *(ULONG32*)outBuffer = 2;
+ status = S_OK;
+ }
+ break;
+
+ default:
+ status = E_INVALIDARG;
+ break;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT
+ClrDataTypeDefinition::NewFromModule(ClrDataAccess* dac,
+ Module* module,
+ mdTypeDef token,
+ ClrDataTypeDefinition** typeDef,
+ IXCLRDataTypeDefinition** pubTypeDef)
+{
+ // The type may not be loaded yet so the
+ // absence of a TypeHandle is not fatal.
+ // If the type isn't loaded a metadata-query
+ // TypeDefinition is produced.
+ TypeHandle typeHandle = module->LookupTypeDef(token);
+ if (!typeHandle.IsNull() &&
+ !typeHandle.IsRestored())
+ {
+ // The type isn't fully usable so just go with metadata.
+ typeHandle = TypeHandle();
+ }
+
+ ClrDataTypeDefinition* def = new (nothrow)
+ ClrDataTypeDefinition(dac, module, token, typeHandle);
+ if (!def)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ PREFIX_ASSUME(typeDef || pubTypeDef);
+
+ if (typeDef)
+ {
+ *typeDef = def;
+ }
+ if (pubTypeDef)
+ {
+ *pubTypeDef = def;
+ }
+
+ return S_OK;
+}
+
+//----------------------------------------------------------------------------
+//
+// ClrDataTypeInstance.
+//
+//----------------------------------------------------------------------------
+
+ClrDataTypeInstance::ClrDataTypeInstance(ClrDataAccess* dac,
+ AppDomain* appDomain,
+ TypeHandle typeHandle)
+{
+ m_dac = dac;
+ m_dac->AddRef();
+ m_instanceAge = m_dac->m_instanceAge;
+ m_refs = 1;
+ m_appDomain = appDomain;
+ m_typeHandle = typeHandle;
+}
+
+ClrDataTypeInstance::~ClrDataTypeInstance(void)
+{
+ m_dac->Release();
+}
+
+STDMETHODIMP
+ClrDataTypeInstance::QueryInterface(THIS_
+ IN REFIID interfaceId,
+ OUT PVOID* iface)
+{
+ if (IsEqualIID(interfaceId, IID_IUnknown) ||
+ IsEqualIID(interfaceId, __uuidof(IXCLRDataTypeInstance)))
+ {
+ AddRef();
+ *iface = static_cast<IUnknown*>
+ (static_cast<IXCLRDataTypeInstance*>(this));
+ return S_OK;
+ }
+ else
+ {
+ *iface = NULL;
+ return E_NOINTERFACE;
+ }
+}
+
+STDMETHODIMP_(ULONG)
+ClrDataTypeInstance::AddRef(THIS)
+{
+ return InterlockedIncrement(&m_refs);
+}
+
+STDMETHODIMP_(ULONG)
+ClrDataTypeInstance::Release(THIS)
+{
+ SUPPORTS_DAC_HOST_ONLY;
+ LONG newRefs = InterlockedDecrement(&m_refs);
+ if (newRefs == 0)
+ {
+ delete this;
+ }
+ return newRefs;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeInstance::StartEnumMethodInstances(
+ /* [out] */ CLRDATA_ENUM* handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if (!m_typeHandle.GetMethodTable())
+ {
+ *handle = 0;
+ status = S_FALSE;
+ goto Exit;
+ }
+
+ status = MetaEnum::New(m_typeHandle.GetModule(),
+ mdtMethodDef,
+ m_typeHandle.GetCl(),
+ NULL,
+ NULL,
+ handle);
+
+ Exit: ;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeInstance::EnumMethodInstance(
+ /* [in, out] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataMethodInstance **method)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ for (;;)
+ {
+ mdMethodDef token;
+
+ if ((status = MetaEnum::CdNextToken(handle, &token)) != S_OK)
+ {
+ break;
+ }
+
+ // If the method doesn't have a MethodDesc or hasn't
+ // been JIT'ed yet it's not an instance and should
+ // just be skipped.
+ if ((status = ClrDataMethodInstance::
+ NewFromModule(m_dac,
+ m_appDomain,
+ m_typeHandle.GetModule(),
+ token,
+ NULL,
+ method)) != E_INVALIDARG)
+ {
+ break;
+ }
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeInstance::EndEnumMethodInstances(
+ /* [in] */ CLRDATA_ENUM handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = MetaEnum::CdEnd(handle);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeInstance::StartEnumMethodInstancesByName(
+ /* [in] */ LPCWSTR name,
+ /* [in] */ ULONG32 flags,
+ /* [out] */ CLRDATA_ENUM* handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if (!m_typeHandle.GetMethodTable())
+ {
+ *handle = 0;
+ status = S_FALSE;
+ goto Exit;
+ }
+
+ status = SplitName::CdStartMethod(name,
+ flags,
+ m_typeHandle.GetModule(),
+ m_typeHandle.GetCl(),
+ m_appDomain,
+ NULL,
+ NULL,
+ handle);
+
+ Exit: ;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeInstance::EnumMethodInstanceByName(
+ /* [out][in] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataMethodInstance **method)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ for (;;)
+ {
+ mdMethodDef token;
+
+ if ((status = SplitName::CdNextMethod(handle, &token)) != S_OK)
+ {
+ break;
+ }
+
+ // If the method doesn't have a MethodDesc or hasn't
+ // been JIT'ed yet it's not an instance and should
+ // just be skipped.
+ if ((status = ClrDataMethodInstance::
+ NewFromModule(m_dac,
+ m_appDomain,
+ m_typeHandle.GetModule(),
+ token,
+ NULL,
+ method)) != E_INVALIDARG)
+ {
+ break;
+ }
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeInstance::EndEnumMethodInstancesByName(
+ /* [in] */ CLRDATA_ENUM handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = SplitName::CdEnd(handle);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeInstance::GetNumStaticFields(
+ /* [out] */ ULONG32 *numFields)
+{
+ return GetNumStaticFields2(INH_STATIC, numFields);
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeInstance::GetStaticFieldByIndex(
+ /* [in] */ ULONG32 index,
+ /* [in] */ IXCLRDataTask *tlsTask,
+ /* [out] */ IXCLRDataValue **field,
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(bufLen, *nameLen) WCHAR nameBuf[ ],
+ /* [out] */ mdFieldDef *token)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ DeepFieldDescIterator fieldIter;
+
+ if ((status = InitFieldIter(&fieldIter, m_typeHandle, true,
+ INH_STATIC, NULL)) == S_OK)
+ {
+ ULONG32 count = 0;
+ FieldDesc* fieldDesc;
+
+ status = E_INVALIDARG;
+ while ((fieldDesc = fieldIter.Next()))
+ {
+ if (count++ == index)
+ {
+ Thread* tlsThread = tlsTask ?
+ ((ClrDataTask*)tlsTask)->GetThread() : NULL;
+
+ status = ClrDataValue::
+ NewFromFieldDesc(m_dac,
+ m_appDomain,
+ fieldIter.IsFieldFromParentClass() ?
+ CLRDATA_VALUE_IS_INHERITED : 0,
+ fieldDesc,
+ 0,
+ tlsThread,
+ NULL,
+ field,
+ bufLen,
+ nameLen,
+ nameBuf,
+ NULL,
+ token);
+ break;
+ }
+ }
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeInstance::StartEnumStaticFieldsByName(
+ /* [in] */ LPCWSTR name,
+ /* [in] */ ULONG32 flags,
+ /* [in] */ IXCLRDataTask* tlsTask,
+ /* [out] */ CLRDATA_ENUM* handle)
+{
+ return StartEnumStaticFieldsByName2(name, flags, INH_STATIC, tlsTask,
+ handle);
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeInstance::EnumStaticFieldByName(
+ /* [out][in] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataValue **value)
+{
+ return EnumStaticFieldByName2(handle, value);
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeInstance::EndEnumStaticFieldsByName(
+ /* [in] */ CLRDATA_ENUM handle)
+{
+ return EndEnumStaticFieldsByName2(handle);
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeInstance::GetNumStaticFields2(
+ /* [in] */ ULONG32 flags,
+ /* [out] */ ULONG32 *numFields)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ DeepFieldDescIterator fieldIter;
+
+ if ((status = InitFieldIter(&fieldIter, m_typeHandle, true,
+ flags, NULL)) == S_OK)
+ {
+ *numFields = fieldIter.Count();
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeInstance::StartEnumStaticFields(
+ /* [in] */ ULONG32 flags,
+ /* [in] */ IXCLRDataTask* tlsTask,
+ /* [out] */ CLRDATA_ENUM* handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = SplitName::
+ CdStartField(NULL,
+ 0,
+ flags,
+ NULL,
+ m_typeHandle,
+ NULL,
+ mdTypeDefNil,
+ 0,
+ NULL,
+ tlsTask,
+ m_appDomain,
+ NULL,
+ NULL,
+ handle);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeInstance::EnumStaticField(
+ /* [out][in] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataValue **value)
+{
+ return EnumStaticField2(handle, value, 0, NULL, NULL,
+ NULL, NULL);
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeInstance::EnumStaticField2(
+ /* [out][in] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataValue **value,
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(bufLen, *nameLen) WCHAR nameBuf[ ],
+ /* [out] */ IXCLRDataModule** tokenScope,
+ /* [out] */ mdFieldDef *token)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = SplitName::CdNextField(m_dac, handle, NULL, NULL, value,
+ bufLen, nameLen, nameBuf,
+ tokenScope, token);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeInstance::EndEnumStaticFields(
+ /* [in] */ CLRDATA_ENUM handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = SplitName::CdEnd(handle);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeInstance::StartEnumStaticFieldsByName2(
+ /* [in] */ LPCWSTR name,
+ /* [in] */ ULONG32 nameFlags,
+ /* [in] */ ULONG32 fieldFlags,
+ /* [in] */ IXCLRDataTask* tlsTask,
+ /* [out] */ CLRDATA_ENUM* handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = SplitName::
+ CdStartField(name,
+ nameFlags,
+ fieldFlags,
+ NULL,
+ m_typeHandle,
+ NULL,
+ mdTypeDefNil,
+ 0,
+ NULL,
+ tlsTask,
+ m_appDomain,
+ NULL,
+ NULL,
+ handle);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeInstance::EnumStaticFieldByName2(
+ /* [out][in] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataValue **value)
+{
+ return EnumStaticFieldByName3(handle, value, NULL, NULL);
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeInstance::EnumStaticFieldByName3(
+ /* [out][in] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataValue **value,
+ /* [out] */ IXCLRDataModule** tokenScope,
+ /* [out] */ mdFieldDef *token)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = SplitName::CdNextField(m_dac, handle, NULL, NULL, value,
+ 0, NULL, NULL,
+ tokenScope, token);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeInstance::EndEnumStaticFieldsByName2(
+ /* [in] */ CLRDATA_ENUM handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = SplitName::CdEnd(handle);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeInstance::GetStaticFieldByToken(
+ /* [in] */ mdFieldDef token,
+ /* [in] */ IXCLRDataTask *tlsTask,
+ /* [out] */ IXCLRDataValue **field,
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(bufLen, *nameLen) WCHAR nameBuf[ ])
+{
+ return GetStaticFieldByToken2(NULL, token, tlsTask, field,
+ bufLen, nameLen, nameBuf);
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeInstance::GetStaticFieldByToken2(
+ /* [in] */ IXCLRDataModule* tokenScope,
+ /* [in] */ mdFieldDef token,
+ /* [in] */ IXCLRDataTask *tlsTask,
+ /* [out] */ IXCLRDataValue **field,
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(bufLen, *nameLen) WCHAR nameBuf[ ])
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ DeepFieldDescIterator fieldIter;
+
+ if ((status = InitFieldIter(&fieldIter, m_typeHandle, true,
+ INH_STATIC, NULL)) == S_OK)
+ {
+ FieldDesc* fieldDesc;
+
+ status = E_INVALIDARG;
+ while ((fieldDesc = fieldIter.Next()))
+ {
+ if ((!tokenScope ||
+ PTR_HOST_TO_TADDR(((ClrDataModule*)tokenScope)->
+ GetModule()) ==
+ PTR_HOST_TO_TADDR(fieldDesc->GetModule())) &&
+ fieldDesc->GetMemberDef() == token)
+ {
+ Thread* tlsThread = tlsTask ?
+ ((ClrDataTask*)tlsTask)->GetThread() : NULL;
+
+ status = ClrDataValue::
+ NewFromFieldDesc(m_dac,
+ m_appDomain,
+ fieldIter.IsFieldFromParentClass() ?
+ CLRDATA_VALUE_IS_INHERITED : 0,
+ fieldDesc,
+ 0,
+ tlsThread,
+ NULL,
+ field,
+ bufLen,
+ nameLen,
+ nameBuf,
+ NULL,
+ NULL);
+ break;
+ }
+ }
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeInstance::GetName(
+ /* [in] */ ULONG32 flags,
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(bufLen, *nameLen) WCHAR nameBuf[ ])
+{
+ HRESULT status = S_OK;
+
+ if (flags != 0)
+ {
+ return E_INVALIDARG;
+ }
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ StackSString ssClassNameBuf;
+
+#ifdef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+ PAL_CPP_TRY
+ {
+#endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+
+ m_typeHandle.GetName(ssClassNameBuf);
+
+#ifdef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+ }
+ PAL_CPP_CATCH_ALL
+ {
+ // if metadata is unavailable try the DAC's MdCache
+ ssClassNameBuf.Clear();
+ PTR_MethodTable pMT = m_typeHandle.AsMethodTable();
+ if (pMT != NULL)
+ {
+ if (!DacMdCacheGetEEName(dac_cast<TADDR>(pMT), ssClassNameBuf))
+ {
+ ssClassNameBuf.Clear();
+ }
+ }
+ if (ssClassNameBuf.IsEmpty())
+ {
+ PAL_CPP_RETHROW;
+ }
+ }
+ PAL_CPP_ENDTRY
+#endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+
+ if (wcsncpy_s(nameBuf, bufLen, ssClassNameBuf.GetUnicode(), _TRUNCATE) == STRUNCATE)
+ {
+ status = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
+ }
+ if (nameLen != NULL)
+ {
+ size_t cchName = ssClassNameBuf.GetCount() + 1;
+ if (FitsIn<ULONG32>(cchName))
+ {
+ *nameLen = (ULONG32) cchName;
+ }
+ else
+ {
+ status = COR_E_OVERFLOW;
+ }
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeInstance::GetModule(
+ /* [out] */ IXCLRDataModule **mod)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ *mod = new (nothrow)
+ ClrDataModule(m_dac, m_typeHandle.GetModule());
+ status = *mod ? S_OK : E_OUTOFMEMORY;
+
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeInstance::GetDefinition(
+ /* [out] */ IXCLRDataTypeDefinition **typeDefinition)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ TypeHandle defType;
+
+ if (m_typeHandle.IsArray() || m_typeHandle.IsFnPtrType())
+ {
+ // Arrays don't necessarily have metadata so
+ // we can't rely on being able to look up
+ // a definition that way.
+
+ // Also ByRef and FUNC_PTR does not have typedef token backing it.
+
+ // Instead, just use the same type handle.
+ // XXX Microsoft - Generics issues?
+
+ // Question - what does the GetCl return return here? The underlying element type?
+ // If so, we are lossing informaiton.
+ //
+ defType = m_typeHandle;
+ *typeDefinition = new (nothrow)
+ ClrDataTypeDefinition(m_dac,
+ defType.GetModule(),
+ defType.GetCl(),
+ defType);
+ }
+
+ else if (m_typeHandle.IsTypeDesc() && m_typeHandle.AsTypeDesc()->HasTypeParam())
+ {
+ // HasTypeParam is true for - ParamTypeDesc (ARRAY, SZARRAY, BYREF, PTR)
+ defType = m_typeHandle.AsTypeDesc()->GetTypeParam();
+
+ // The DefinitionType won't contain ByRef, PTR.
+ *typeDefinition = new (nothrow)
+ ClrDataTypeDefinition(m_dac,
+ defType.GetModule(),
+ defType.GetCl(),
+ defType);
+ }
+ else
+ {
+ // @TODO:: Should this be only for generic?
+ //
+ defType = m_typeHandle.GetModule()->
+ LookupTypeDef(m_typeHandle.GetCl());
+ *typeDefinition = new (nothrow)
+ ClrDataTypeDefinition(m_dac,
+ m_typeHandle.GetModule(),
+ m_typeHandle.GetCl(),
+ defType);
+ }
+
+ status = *typeDefinition ? S_OK : E_OUTOFMEMORY;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeInstance::GetFlags(
+ /* [out] */ ULONG32 *flags)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ *flags = CLRDATA_TYPE_DEFAULT;
+ status = S_OK;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeInstance::GetBase(
+ /* [out] */ IXCLRDataTypeInstance **base)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ *base = new (nothrow)
+ ClrDataTypeInstance(m_dac, m_appDomain, m_typeHandle.GetParent());
+ status = *base ? S_OK : E_OUTOFMEMORY;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeInstance::IsSameObject(
+ /* [in] */ IXCLRDataTypeInstance* type)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = (PTR_HOST_TO_TADDR(m_appDomain) ==
+ PTR_HOST_TO_TADDR(((ClrDataTypeInstance*)type)->
+ m_appDomain) &&
+ m_typeHandle == ((ClrDataTypeInstance*)type)->
+ m_typeHandle) ?
+ S_OK : S_FALSE;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeInstance::GetNumTypeArguments(
+ /* [out] */ ULONG32 *numTypeArgs)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ // XXX Microsoft.
+ status = E_NOTIMPL;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeInstance::GetTypeArgumentByIndex(
+ /* [in] */ ULONG32 index,
+ /* [out] */ IXCLRDataTypeInstance **typeArg)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ // XXX Microsoft.
+ status = E_NOTIMPL;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTypeInstance::Request(
+ /* [in] */ ULONG32 reqCode,
+ /* [in] */ ULONG32 inBufferSize,
+ /* [size_is][in] */ BYTE *inBuffer,
+ /* [in] */ ULONG32 outBufferSize,
+ /* [size_is][out] */ BYTE *outBuffer)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ switch(reqCode)
+ {
+ case CLRDATA_REQUEST_REVISION:
+ if (inBufferSize != 0 ||
+ inBuffer ||
+ outBufferSize != sizeof(ULONG32))
+ {
+ status = E_INVALIDARG;
+ }
+ else
+ {
+ *(ULONG32*)outBuffer = 2;
+ status = S_OK;
+ }
+ break;
+
+ default:
+ status = E_INVALIDARG;
+ break;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT
+ClrDataTypeInstance::NewFromModule(ClrDataAccess* dac,
+ AppDomain* appDomain,
+ Module* module,
+ mdTypeDef token,
+ ClrDataTypeInstance** typeInst,
+ IXCLRDataTypeInstance** pubTypeInst)
+{
+ TypeHandle typeHandle = module->LookupTypeDef(token);
+ if (typeHandle.IsNull() ||
+ !typeHandle.IsRestored())
+ {
+ return E_INVALIDARG;
+ }
+
+ ClrDataTypeInstance* inst = new (nothrow)
+ ClrDataTypeInstance(dac, appDomain, typeHandle);
+ if (!inst)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ PREFIX_ASSUME(typeInst || pubTypeInst);
+
+ if (typeInst)
+ {
+ *typeInst = inst;
+ }
+ if (pubTypeInst)
+ {
+ *pubTypeInst = inst;
+ }
+
+ return S_OK;
+}
+
+
diff --git a/src/debug/daccess/nidump.cpp b/src/debug/daccess/nidump.cpp
new file mode 100644
index 0000000000..32eab498d3
--- /dev/null
+++ b/src/debug/daccess/nidump.cpp
@@ -0,0 +1,9579 @@
+// 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.
+
+
+//
+ /*vim: set foldmethod=marker: */
+#include <stdafx.h>
+
+#if defined(FEATURE_PREJIT)
+#include "nidump.h"
+
+#include <metadataexports.h>
+
+#include <comcallablewrapper.h>
+#include <gcdump.h>
+
+#if !defined(FEATURE_CORESYSTEM)
+#include <algorithm>
+#endif
+
+#include <constrainedexecutionregion.h>
+
+#include <formattype.h>
+
+#include <pedecoder.h>
+
+#ifdef FEATURE_REMOTING
+#include <crossdomaincalls.h>
+#endif
+
+#include <mdfileformat.h>
+
+#if !defined(FEATURE_CORESYSTEM)
+#include <cassert>
+#undef _ASSERTE
+#define _ASSERTE(x) assert(x)
+#endif
+
+#include <compile.h>
+
+#ifdef USE_GC_INFO_DECODER
+#include <gcinfodecoder.h>
+#endif
+
+#include <ngenhash.inl>
+
+#define FEATURE_MSDIS
+
+//----------------------------------------------------------------------------
+//
+// ClrDump functionality
+//
+//----------------------------------------------------------------------------
+
+/////////////////////////////////////////////////////////////////////////////////////////////
+//
+// Given a compressed integer(*pData), expand the compressed int to *pDataOut.
+// Return value is the number of bytes that the integer occupies in the compressed format
+// It is caller's responsibility to ensure pDataOut has at least 4 bytes to be written to.
+//
+// This function returns -1 if pass in with an incorrectly compressed data, such as
+// (*pBytes & 0xE0) == 0XE0.
+/////////////////////////////////////////////////////////////////////////////////////////////
+/* XXX Wed 09/14/2005
+ * copied from cor.h. Modified to operate on PTR_PCCOR_SIGNATUREs
+ */
+inline ULONG DacSigUncompressBigData(
+ PTR_CCOR_SIGNATURE &pData) // [IN,OUT] compressed data
+{
+ ULONG res;
+
+ // 1 byte data is handled in DacSigUncompressData
+ // _ASSERTE(*pData & 0x80);
+
+ // Medium.
+ if ((*pData & 0xC0) == 0x80) // 10?? ????
+ {
+ res = (ULONG)((*pData++ & 0x3f) << 8);
+ res |= *pData++;
+ }
+ else // 110? ????
+ {
+ res = (*pData++ & 0x1f) << 24;
+ res |= *pData++ << 16;
+ res |= *pData++ << 8;
+ res |= *pData++;
+ }
+ return res;
+}
+FORCEINLINE ULONG DacSigUncompressData(
+ PTR_CCOR_SIGNATURE &pData) // [IN,OUT] compressed data
+{
+ // Handle smallest data inline.
+ if ((*pData & 0x80) == 0x00) // 0??? ????
+ return *pData++;
+ return DacSigUncompressBigData(pData);
+}
+//const static mdToken g_tkCorEncodeToken[4] ={mdtTypeDef, mdtTypeRef, mdtTypeSpec, mdtBaseType};
+
+// uncompress a token
+inline mdToken DacSigUncompressToken( // return the token.
+ PTR_CCOR_SIGNATURE &pData) // [IN,OUT] compressed data
+{
+ mdToken tk;
+ mdToken tkType;
+
+ tk = DacSigUncompressData(pData);
+ tkType = g_tkCorEncodeToken[tk & 0x3];
+ tk = TokenFromRid(tk >> 2, tkType);
+ return tk;
+}
+// uncompress encoded element type
+FORCEINLINE CorElementType DacSigUncompressElementType(//Element type
+ PTR_CCOR_SIGNATURE &pData) // [IN,OUT] compressed data
+{
+ return (CorElementType)*pData++;
+}
+
+
+
+const char * g_helperNames[] =
+{
+#define JITHELPER(val, fn, sig) # val,
+#include <jithelpers.h>
+#undef JITHELPER
+};
+
+
+
+#define dim(x) (sizeof(x)/sizeof((x)[0]))
+
+void EnumFlagsToString( DWORD value,
+ const NativeImageDumper::EnumMnemonics * table,
+ int count, const WCHAR * sep, SString& output )
+{
+ bool firstValue = true;
+ for( int i = 0; i < count; ++i )
+ {
+ bool match = false;
+ const NativeImageDumper::EnumMnemonics& entry = table[i];
+ if( entry.mask != 0 )
+ match = ((entry.mask & value) == entry.value);
+ else
+ match = (entry.value == value);
+
+ if( match )
+ {
+ if( !firstValue )
+ output.Append(sep);
+ firstValue = false;
+
+ output.Append( table[i].mnemonic );
+
+ value &= ~entry.value;
+ }
+ }
+}
+
+const NativeImageDumper::EnumMnemonics s_ImageSections[] =
+{
+#define IS_ENTRY(v, s) NativeImageDumper::EnumMnemonics(v, s)
+ IS_ENTRY(IMAGE_SCN_MEM_READ, W("read")),
+ IS_ENTRY(IMAGE_SCN_MEM_WRITE, W("write")),
+ IS_ENTRY(IMAGE_SCN_MEM_EXECUTE, W("execute")),
+ IS_ENTRY(IMAGE_SCN_CNT_CODE, W("code")),
+ IS_ENTRY(IMAGE_SCN_CNT_INITIALIZED_DATA, W("init data")),
+ IS_ENTRY(IMAGE_SCN_CNT_UNINITIALIZED_DATA, W("uninit data")),
+#undef IS_ENTRY
+};
+inline int CheckFlags( DWORD source, DWORD flags )
+{
+ return (source & flags) == flags;
+}
+
+HRESULT ClrDataAccess::DumpNativeImage(CLRDATA_ADDRESS loadedBase,
+ LPCWSTR name,
+ IXCLRDataDisplay * display,
+ IXCLRLibrarySupport * support,
+ IXCLRDisassemblySupport *dis)
+{
+ DAC_ENTER();
+ /* REVISIT_TODO Fri 09/09/2005
+ * catch exceptions
+ */
+ NativeImageDumper dump(dac_cast<PTR_VOID>(CLRDATA_ADDRESS_TO_TADDR(loadedBase)), name, display,
+ support, dis);
+ dump.DumpNativeImage();
+ DAC_LEAVE();
+ return S_OK;
+}
+
+
+
+static ULONG bigBufferSize = 8192;
+static WCHAR bigBuffer[8192];
+static BYTE bigByteBuffer[1024];
+
+//----------------------------------------------------------------------------
+//
+// NativeImageDumper
+//
+//----------------------------------------------------------------------------
+template<typename T>
+inline T combine(T a, T b)
+{
+ return (T)(((DWORD)a) | ((DWORD)b));
+}
+template<typename T>
+inline T combine(T a, T b, T c)
+{
+ return (T)(((DWORD)a) | ((DWORD)b) | ((DWORD)c));
+}
+#define CLRNATIVEIMAGE_ALWAYS ((CLRNativeImageDumpOptions)~0)
+#define CHECK_OPT(opt) CheckOptions(CLRNATIVEIMAGE_ ## opt)
+#define IF_OPT(opt) if( CHECK_OPT(opt) )
+#define IF_OPT_AND(opt1, opt2) if( CHECK_OPT(opt1) && CHECK_OPT(opt2) )
+#define IF_OPT_OR(opt1, opt2) if( CHECK_OPT(opt1) || CHECK_OPT(opt2) )
+#define IF_OPT_OR3(opt1, opt2, opt3) if( CHECK_OPT(opt1) || CHECK_OPT(opt2) || CHECK_OPT(opt3) )
+#define IF_OPT_OR4(opt1, opt2, opt3, opt4) if( CHECK_OPT(opt1) || CHECK_OPT(opt2) || CHECK_OPT(opt3) || CHECK_OPT(opt4) )
+#define IF_OPT_OR5(opt1, opt2, opt3, opt4, opt5) if( CHECK_OPT(opt1) || CHECK_OPT(opt2) || CHECK_OPT(opt3) || CHECK_OPT(opt4) || CHECK_OPT(opt5) )
+
+#define fieldsize(type, field) (sizeof(((type*)NULL)->field))
+
+
+/*{{{Display helpers*/
+#define DisplayStartCategory(name, filter)\
+ do { IF_OPT(filter) m_display->StartCategory(name); } while(0)
+#define DisplayEndCategory(filter)\
+ do { IF_OPT(filter) m_display->EndCategory(); } while(0)
+#define DisplayStartArray(name, fmt, filter) \
+ do { IF_OPT(filter) m_display->StartArray(name, fmt); } while(0)
+#define DisplayStartArrayWithOffset(field, fmt, type, filter) \
+ do { IF_OPT(filter) m_display->StartArrayWithOffset( # field, offsetof(type, field), fieldsize(type, field), fmt); } while(0)
+
+#define DisplayStartElement( name, filter ) \
+ do { IF_OPT(filter) m_display->StartElement( name ); } while(0)
+#define DisplayStartStructure( name, ptr, size, filter ) \
+ do { IF_OPT(filter) m_display->StartStructure( name, ptr, size ); } while(0)
+#define DisplayStartList(fmt, filter) \
+ do { IF_OPT(filter) m_display->StartList(fmt); } while(0)
+
+#define DisplayStartStructureWithOffset( field, ptr, size, type, filter ) \
+ do { IF_OPT(filter) m_display->StartStructureWithOffset( # field, offsetof(type, field), fieldsize(type, field), ptr, size ); } while(0)
+#define DisplayStartVStructure( name, filter ) \
+ do { IF_OPT(filter) m_display->StartVStructure( name ); } while(0)
+#define DisplayEndVStructure( filter ) \
+ do { IF_OPT(filter) m_display->EndVStructure(); } while(0)
+
+#define DisplayEndList(filter) \
+ do { IF_OPT(filter) m_display->EndList(); } while(0)
+#define DisplayEndArray(footer, filter) \
+ do { IF_OPT(filter) m_display->EndArray(footer); } while(0)
+#define DisplayEndStructure(filter) \
+ do { IF_OPT(filter) m_display->EndStructure(); } while(0)
+
+#define DisplayEndElement(filter) \
+ do { IF_OPT(filter) m_display->EndElement(); } while(0)
+
+#define DisplayWriteElementString(name, value, filter) \
+ do { IF_OPT(filter) m_display->WriteElementString(name, value); } while(0)
+#define DisplayWriteElementStringW(name, value, filter) \
+ do { IF_OPT(filter) m_display->WriteElementStringW(name, value); } while(0)
+
+#define DisplayWriteElementStringW(name, value, filter) \
+ do { IF_OPT(filter) m_display->WriteElementStringW(name, value); } while(0)
+
+#define DisplayWriteElementInt(name, value, filter) \
+ do { IF_OPT(filter) m_display->WriteElementInt(name, value); } while(0)
+#define DisplayWriteElementIntWithSuppress(name, value, defVal, filter) \
+ do { IF_OPT(filter) m_display->WriteElementIntWithSuppress(name, value, defVal); } while(0)
+#define DisplayWriteElementUInt(name, value, filter) \
+ do { IF_OPT(filter) m_display->WriteElementUInt(name, value); } while(0)
+#define DisplayWriteElementFlag(name, value, filter) \
+ do { IF_OPT(filter) m_display->WriteElementFlag(name, value); } while(0)
+#define DisplayWriteElementPointer(name, value, filter) \
+ do { IF_OPT(filter) m_display->WriteElementPointer(name, value); } while(0)
+#define DisplayWriteElementPointerAnnotated(name, value, annotation, filter) \
+ do { IF_OPT(filter) m_display->WriteElementPointerAnnotated(name, value, annotation ); } while(0)
+#define DisplayWriteEmptyElement(name, filter) \
+ do { IF_OPT(filter) m_display->WriteEmptyElement(name); } while(0)
+
+#define DisplayWriteElementEnumerated(name, value, mnemonics, sep, filter) \
+ do { \
+ IF_OPT(filter) { \
+ TempBuffer buf; \
+ EnumFlagsToString(value, mnemonics, _countof(mnemonics), sep, buf);\
+ m_display->WriteElementEnumerated( name, value, (const WCHAR*)buf ); \
+ }\
+ }while(0)
+#define DisplayWriteFieldEnumerated(field, value, type, mnemonics, sep, filter)\
+ do { \
+ IF_OPT(filter) { \
+ TempBuffer buf; \
+ EnumFlagsToString(value, mnemonics, _countof(mnemonics), sep, buf);\
+ m_display->WriteFieldEnumerated( # field, offsetof(type, field), \
+ fieldsize(type, field), value,\
+ (const WCHAR*)buf ); \
+ }\
+ }while(0)
+#define DisplayWriteElementAddress(name, ptr, size, filter) \
+ do { IF_OPT(filter) m_display->WriteElementAddress( name, ptr, size ); } while(0)
+#define DisplayWriteElementAddressNamed(eltName, name, ptr, size, filter) \
+ do { IF_OPT(filter) m_display->WriteElementAddressNamed( eltName, name, ptr, size ); } while(0)
+#define DisplayWriteElementAddressNamedW(eltName, name, ptr, size, filter) \
+ do { IF_OPT(filter) m_display->WriteElementAddressNamedW( eltName, name, ptr, size ); } while(0)
+
+#define DisplayWriteFieldString(field, value, type, filter) \
+ do { IF_OPT(filter) m_display->WriteFieldString( # field, offsetof(type, field), fieldsize(type, field), value ); } while(0)
+#define DisplayWriteFieldStringW(field, value, type, filter) \
+ do { IF_OPT(filter) m_display->WriteFieldStringW( # field, offsetof(type, field), fieldsize(type, field), value ); } while(0)
+#define DisplayWriteFieldInt(field, value, type, filter) \
+ do { IF_OPT(filter) m_display->WriteFieldInt( # field, offsetof(type, field), fieldsize(type, field), value ); } while(0)
+#define DisplayWriteFieldUInt(field, value, type, filter) \
+ do { IF_OPT(filter) m_display->WriteFieldUInt( # field, offsetof(type, field), fieldsize(type, field), value ); } while(0)
+#define DisplayWriteFieldPointer(field, ptr, type, filter) \
+ do { IF_OPT(filter) m_display->WriteFieldPointer( # field, offsetof(type, field), fieldsize(type, field), ptr ); } while(0)
+#define DisplayWriteFieldPointerWithSize(field, ptr, size, type, filter) \
+ do { IF_OPT(filter) m_display->WriteFieldPointerWithSize( # field, offsetof(type, field), fieldsize(type, field), ptr, size ); } while(0)
+#define DisplayWriteFieldEmpty(field, type, filter) \
+ do { IF_OPT(filter) m_display->WriteFieldEmpty( # field, offsetof(type, field), fieldsize(type, field) ); } while(0)
+#define DisplayWriteFieldFlag(field, value, type, filter) \
+ do { IF_OPT(filter) m_display->WriteFieldFlag(# field, offsetof(type, field), fieldsize(type, field), value); } while(0)
+#define WriteFieldFieldDesc(field, ptr, type, filter) \
+ do { IF_OPT(filter) DoWriteFieldFieldDesc( # field, offsetof(type, field), fieldsize(type, field), ptr ); } while(0)
+#define WriteFieldMethodDesc(field, ptr, type, filter) \
+ do { IF_OPT(filter) DoWriteFieldMethodDesc( # field, offsetof(type, field), fieldsize(type, field), ptr ); } while(0)
+#define WriteFieldStr(field, ptr, type, filter) \
+ do { IF_OPT(filter) DoWriteFieldStr( ptr, # field, offsetof(type, field), fieldsize(type, field) ); } while(0)
+#define WriteFieldMethodTable(field, ptr, type, filter) \
+ do { IF_OPT(filter) DoWriteFieldMethodTable( # field, offsetof(type, field), fieldsize(type, field), ptr ); } while(0)
+#define WriteFieldMDToken(field, token, type, filter) \
+ do { IF_OPT(filter) DoWriteFieldMDToken( # field, offsetof(type, field), fieldsize(type, field), token ); } while(0)
+#define WriteFieldAsHex(field, ptr, type, filter) \
+ do { IF_OPT(filter) DoWriteFieldAsHex( # field, offsetof(type, field), fieldsize(type, field), ptr, fieldsize(type, field)); } while(0)
+#define WriteFieldMDTokenImport(field, token, type, filter, import) \
+ do { IF_OPT(filter) DoWriteFieldMDToken( # field, offsetof(type, field), fieldsize(type, field), token, import); } while(0)
+#define WriteFieldTypeHandle(field, ptr, type, filter) \
+ do { IF_OPT(filter) DoWriteFieldTypeHandle( # field, offsetof(type, field), fieldsize(type, field), ptr ); } while(0)
+#define WriteFieldCorElementType(field, et, type, filter) \
+ do { IF_OPT(filter) DoWriteFieldCorElementType( # field, offsetof(type, field), fieldsize(type, field), et ); } while(0)
+#define DumpFieldStub(field, ptr, type, filter) \
+ do { IF_OPT(filter) DoDumpFieldStub( ptr, offsetof(type, field), fieldsize(type, field), # field ); } while(0)
+#define DumpComPlusCallInfo(compluscall, filter) \
+ do { IF_OPT(filter) DoDumpComPlusCallInfo( compluscall ); } while(0)
+#define DisplayWriteFieldPointerAnnotated(field, ptr, annotation, type, filter) \
+ do { IF_OPT(filter) m_display->WriteFieldPointer( # field, offsetof(type, field), fieldsize(type, field), ptr ); } while(0)
+#define DisplayWriteFieldAddress(field, ptr, size, type, filter) \
+ do { IF_OPT(filter) m_display->WriteFieldAddress( # field, offsetof(type, field), fieldsize(type, field), ptr, size ); } while(0)
+#define DisplayStartTextElement(name, filter) \
+ do { IF_OPT(filter) m_display->StartTextElement( name ); } while(0)
+#define DisplayEndTextElement(filter) \
+ do { IF_OPT(filter) m_display->EndTextElement(); } while(0)
+#define DisplayWriteXmlText( args, filter ) \
+ do { IF_OPT(filter) m_display->WriteXmlText args; } while(0)
+#define DisplayWriteXmlTextBlock( args, filter ) \
+ do { IF_OPT(filter) m_display->WriteXmlTextBlock args; } while(0)
+
+#define CoverageRead( ptr, size ) \
+ do { IF_OPT(DEBUG_COVERAGE) PTR_READ(TO_TADDR(ptr), size); } while(0)
+#define CoverageReadString( taddr ) \
+ do { PTR_BYTE ptr(TO_TADDR(taddr)); while( *ptr++ ); }while(0)
+
+void AppendNilToken( mdToken token, SString& buf )
+{
+ _ASSERTE(RidFromToken(token) == mdTokenNil);
+
+ const WCHAR * id = NULL;
+ switch(token)
+ {
+#define mdNilEnt(x) case x: \
+ id = W(#x); \
+ break
+ mdNilEnt(mdModuleNil);
+ mdNilEnt(mdTypeRefNil);
+ mdNilEnt(mdTypeDefNil);
+ mdNilEnt(mdFieldDefNil);
+ mdNilEnt(mdMethodDefNil);
+ mdNilEnt(mdParamDefNil);
+ mdNilEnt(mdInterfaceImplNil);
+ mdNilEnt(mdMemberRefNil);
+ mdNilEnt(mdCustomAttributeNil);
+ mdNilEnt(mdPermissionNil);
+ mdNilEnt(mdSignatureNil);
+ mdNilEnt(mdEventNil);
+ mdNilEnt(mdPropertyNil);
+ mdNilEnt(mdModuleRefNil);
+ mdNilEnt(mdTypeSpecNil);
+ mdNilEnt(mdAssemblyNil);
+ mdNilEnt(mdAssemblyRefNil);
+ mdNilEnt(mdFileNil);
+ mdNilEnt(mdExportedTypeNil);
+ mdNilEnt(mdManifestResourceNil);
+
+ mdNilEnt(mdGenericParamNil);
+ mdNilEnt(mdGenericParamConstraintNil);
+ mdNilEnt(mdMethodSpecNil);
+
+ mdNilEnt(mdStringNil);
+#undef mdNilEnt
+ }
+ buf.Append( id );
+}
+void appendByteArray(SString& buf, const BYTE * bytes, ULONG cbBytes)
+{
+ for( COUNT_T i = 0; i < cbBytes; ++i )
+ {
+ buf.AppendPrintf(W("%02x"), bytes[i]);
+ }
+}
+/*}}}*/
+
+
+
+
+struct OptionDependencies
+{
+ OptionDependencies(CLRNativeImageDumpOptions value,
+ CLRNativeImageDumpOptions dep) : m_value(value),
+ m_dep(dep)
+ {
+
+ }
+ CLRNativeImageDumpOptions m_value;
+ CLRNativeImageDumpOptions m_dep;
+};
+
+static OptionDependencies g_dependencies[] =
+{
+#define OPT_DEP(value, dep) OptionDependencies(CLRNATIVEIMAGE_ ## value,\
+ CLRNATIVEIMAGE_ ## dep)
+ OPT_DEP(RESOURCES, COR_INFO),
+ OPT_DEP(METADATA, COR_INFO),
+ OPT_DEP(PRECODES, MODULE),
+ //Does methoddescs require ModuleTables?
+ OPT_DEP(VERBOSE_TYPES, METHODDESCS),
+ OPT_DEP(GC_INFO, METHODS),
+ OPT_DEP(FROZEN_SEGMENT, MODULE),
+ OPT_DEP(SLIM_MODULE_TBLS, MODULE),
+ OPT_DEP(MODULE_TABLES, SLIM_MODULE_TBLS),
+ OPT_DEP(DISASSEMBLE_CODE, METHODS),
+
+ OPT_DEP(FIXUP_HISTOGRAM, FIXUP_TABLES),
+ OPT_DEP(FIXUP_THUNKS, FIXUP_TABLES),
+
+#undef OPT_DEP
+};
+
+// Metadata helpers for DAC
+// This is mostly copied from mscoree.cpp which isn't available in mscordacwks.dll.
+//
+
+// This function gets the Dispenser interface given the CLSID and REFIID.
+STDAPI MetaDataGetDispenser(
+ REFCLSID rclsid, // The class to desired.
+ REFIID riid, // Interface wanted on class factory.
+ LPVOID FAR * ppv) // Return interface pointer here.
+{
+ _ASSERTE(rclsid == CLSID_CorMetaDataDispenser);
+
+ return InternalCreateMetaDataDispenser(riid, ppv);
+}
+
+
+NativeImageDumper::NativeImageDumper(PTR_VOID loadedBase,
+ const WCHAR * const name,
+ IXCLRDataDisplay * display,
+ IXCLRLibrarySupport * support,
+ IXCLRDisassemblySupport *dis)
+ :
+ m_decoder(loadedBase),
+ m_name(name),
+ m_baseAddress(loadedBase),
+ m_display(display),
+ m_librarySupport(support),
+ m_import(NULL),
+ m_assemblyImport(NULL),
+ m_manifestAssemblyImport(NULL),
+ m_dependencies(NULL),
+ m_imports(NULL),
+ m_dis(dis),
+ m_MetadataSize(0),
+ m_ILHostCopy(NULL),
+ m_isMscorlibHardBound(false),
+ m_sectionAlignment(0)
+{
+ IfFailThrow(m_display->GetDumpOptions(&m_dumpOptions));
+
+ //set up mscorwks stuff.
+ m_mscorwksBase = DacGlobalBase();
+ _ASSERTE(m_mscorwksBase);
+ PEDecoder mscorwksDecoder(dac_cast<PTR_VOID>(m_mscorwksBase));
+ m_mscorwksSize = mscorwksDecoder.GetSize();
+ m_mscorwksPreferred = TO_TADDR(mscorwksDecoder.GetPreferredBase());
+ //add implied options (i.e. if you want to dump the module, you also have
+ //to dump the native info.
+ CLRNativeImageDumpOptions current;
+ do
+ {
+ current = m_dumpOptions;
+ for( unsigned i = 0; i < _countof(g_dependencies); ++i )
+ {
+ if( m_dumpOptions & g_dependencies[i].m_value )
+ m_dumpOptions |= g_dependencies[i].m_dep;
+ }
+ }while( current != m_dumpOptions );
+ IF_OPT(DISASSEMBLE_CODE)
+ {
+ //configure the disassembler
+ m_dis->SetTranslateAddrCallback(TranslateAddressCallback);
+ m_dis->SetTranslateFixupCallback(TranslateFixupCallback);
+ m_dis->PvClientSet(this);
+ }
+}
+
+void GuidToString( GUID& guid, SString& s )
+{
+ WCHAR guidString[64];
+ GuidToLPWSTR(guid, guidString, sizeof(guidString) / sizeof(WCHAR));
+ //prune the { and }
+ _ASSERTE(guidString[0] == W('{')
+ && guidString[wcslen(guidString) - 1] == W('}'));
+ guidString[wcslen(guidString) - 1] = W('\0');
+ s.Append( guidString + 1 );
+}
+
+NativeImageDumper::~NativeImageDumper()
+{
+}
+
+inline const void * ptr_add(const void * ptr, COUNT_T size)
+{
+ return reinterpret_cast<const BYTE *>(ptr) + size;
+}
+
+//This does pointer arithmetic on a DPtr.
+template<typename T>
+inline const DPTR(T) dptr_add(T* ptr, COUNT_T offset)
+{
+ return DPTR(T)(PTR_HOST_TO_TADDR(ptr) + (offset * sizeof(T)));
+}
+
+template<typename T>
+inline const DPTR(T) dptr_sub(T* ptr, COUNT_T offset)
+{
+ return DPTR(T)(PTR_HOST_TO_TADDR(ptr) - (offset * sizeof(T)));
+}
+template<typename T>
+inline const DPTR(T) dptr_sub(DPTR(T)* ptr, COUNT_T offset)
+{
+ return DPTR(T)(PTR_HOST_TO_TADDR(ptr) - (offset * sizeof(T)));
+}
+
+struct MDTableType
+{
+ MDTableType(unsigned t, const char * n) : m_token(t), m_name(n) { }
+ unsigned m_token;
+ const char * m_name;
+};
+
+static unsigned s_tableTypes[] =
+{
+ /*
+#ifdef MiniMdTable
+#undef MiniMdTable
+#endif
+#define MiniMdTable(x) TBL_##x << 24,
+ MiniMdTables()
+#undef MiniMdTable
+ mdtName
+ */
+ mdtModule,
+ mdtTypeRef,
+ mdtTypeDef,
+ mdtFieldDef,
+ mdtMethodDef,
+ mdtParamDef,
+ mdtInterfaceImpl,
+ mdtMemberRef,
+ mdtCustomAttribute,
+ mdtPermission,
+ mdtSignature,
+ mdtEvent,
+ mdtProperty,
+ mdtModuleRef,
+ mdtTypeSpec,
+ mdtAssembly,
+ mdtAssemblyRef,
+ mdtFile,
+ mdtExportedType,
+ mdtManifestResource,
+ mdtGenericParam,
+ mdtMethodSpec,
+ mdtGenericParamConstraint,
+};
+
+const NativeImageDumper::EnumMnemonics s_CorHdrFlags[] =
+{
+#define CHF_ENTRY(f,v) NativeImageDumper::EnumMnemonics(f, v)
+ CHF_ENTRY(COMIMAGE_FLAGS_ILONLY, W("IL Only")),
+ CHF_ENTRY(COMIMAGE_FLAGS_32BITREQUIRED, W("32-bit Required")),
+ CHF_ENTRY(COMIMAGE_FLAGS_IL_LIBRARY, W("IL Library")),
+ CHF_ENTRY(COMIMAGE_FLAGS_STRONGNAMESIGNED, W("Strong Name Signed")),
+ CHF_ENTRY(COMIMAGE_FLAGS_NATIVE_ENTRYPOINT, W("Has Native Entrypoint")),
+ CHF_ENTRY(COMIMAGE_FLAGS_TRACKDEBUGDATA, W("Track Debug Data")),
+ CHF_ENTRY(COMIMAGE_FLAGS_32BITPREFERRED, W("32-bit Preferred"))
+#undef CHF_ENTRY
+};
+
+void NativeImageDumper::DumpAssemblySignature(CORCOMPILE_ASSEMBLY_SIGNATURE & assemblySignature)
+{
+ {
+ TempBuffer buf;
+ GuidToString(assemblySignature.mvid, buf);
+ DisplayWriteFieldStringW( mvid, (const WCHAR*)buf,
+ CORCOMPILE_ASSEMBLY_SIGNATURE,
+ COR_INFO );
+ }
+ DisplayWriteFieldInt( timeStamp, assemblySignature.timeStamp,
+ CORCOMPILE_ASSEMBLY_SIGNATURE, COR_INFO );
+ DisplayWriteFieldInt( ilImageSize,
+ assemblySignature.ilImageSize,
+ CORCOMPILE_ASSEMBLY_SIGNATURE, COR_INFO );
+}
+
+#ifndef FEATURE_CORECLR
+
+const NativeImageDumper::EnumMnemonics s_CorCompileDependencyInfoFlags[] =
+{
+#define CMDI_ENTRY(f) NativeImageDumper::EnumMnemonics(CORCOMPILE_DEPENDENCY_ ## f, W(#f))
+
+#ifdef FEATURE_APTCA
+ CMDI_ENTRY(IS_APTCA),
+ CMDI_ENTRY(IS_CAPTCA),
+#endif //FEATURE_APTCA
+#undef CMDI_ENTRY
+};
+
+#endif //!FEATURE_CORECLR
+
+//error code return?
+void
+NativeImageDumper::DumpNativeImage()
+{
+ COUNT_T size;
+ const void *data;
+
+ m_display->StartDocument();
+
+ DisplayStartCategory( "File", PE_INFO );
+ DisplayWriteElementStringW( "path", m_name, PE_INFO );
+
+ DisplayWriteElementInt( "diskSize", m_decoder.GetSize(), PE_INFO );
+ _ASSERTE(sizeof(IMAGE_DOS_HEADER) < m_decoder.GetSize());
+
+ PTR_IMAGE_DOS_HEADER dosHeader =
+ PTR_IMAGE_DOS_HEADER(dac_cast<TADDR>(m_baseAddress));
+ DisplayWriteElementAddress( "IMAGE_DOS_HEADER",
+ DPtrToPreferredAddr(dosHeader),
+ sizeof(*dosHeader), PE_INFO );
+
+ // NT headers
+
+ if (!m_decoder.HasNTHeaders())
+ {
+ IF_OPT(PE_INFO)
+ {
+ DisplayWriteElementString("isPEFile", "false", PE_INFO);
+ DisplayEndCategory(PE_INFO);
+ }
+ else
+ m_display->ErrorPrintF("Non-PE file");
+
+ m_display->EndDocument();
+ return;
+ }
+
+ CONSISTENCY_CHECK(m_decoder.CheckNTHeaders());
+ if (!m_decoder.CheckNTHeaders())
+ {
+ m_display->ErrorPrintF("*** NT headers are not valid ***");
+ return;
+ }
+
+ DisplayWriteElementString("imageType", m_decoder.Has32BitNTHeaders()
+ ? "32 bit image" : "64 bit image", PE_INFO);
+ DisplayWriteElementAddress("address", (SIZE_T)m_decoder.GetNativePreferredBase(),
+ m_decoder.GetVirtualSize(), PE_INFO);
+ DisplayWriteElementInt( "TimeDateStamp", m_decoder.GetTimeDateStamp(),
+ PE_INFO );
+
+ if( m_decoder.Has32BitNTHeaders() )
+ {
+ PTR_IMAGE_NT_HEADERS32 ntHeaders(m_decoder.GetNTHeaders32());
+ //base, size, sectionAlign
+ _ASSERTE(ntHeaders->OptionalHeader.SectionAlignment >=
+ ntHeaders->OptionalHeader.FileAlignment);
+ m_imageSize = ntHeaders->OptionalHeader.SizeOfImage;
+ m_display->NativeImageDimensions(PTR_TO_TADDR(m_decoder.GetBase()),
+ ntHeaders->OptionalHeader.SizeOfImage,
+ ntHeaders->OptionalHeader.SectionAlignment);
+ /* REVISIT_TODO Mon 11/21/2005
+ * I don't understand this. Sections start on a two page boundary, but
+ * data ends on a one page boundary. What's up with that?
+ */
+ m_sectionAlignment = PAGE_SIZE; //ntHeaders->OptionalHeader.SectionAlignment;
+ unsigned ntHeaderSize = sizeof(*ntHeaders)
+ - sizeof(ntHeaders->OptionalHeader)
+ + ntHeaders->FileHeader.SizeOfOptionalHeader;
+ DisplayWriteElementAddress( "IMAGE_NT_HEADERS32",
+ DPtrToPreferredAddr(ntHeaders),
+ ntHeaderSize, PE_INFO );
+
+ }
+ else
+ {
+ PTR_IMAGE_NT_HEADERS64 ntHeaders(m_decoder.GetNTHeaders64());
+ //base, size, sectionAlign
+ _ASSERTE(ntHeaders->OptionalHeader.SectionAlignment >=
+ ntHeaders->OptionalHeader.FileAlignment);
+ m_imageSize = ntHeaders->OptionalHeader.SizeOfImage;
+ m_display->NativeImageDimensions((SIZE_T)ntHeaders->OptionalHeader.ImageBase,
+ ntHeaders->OptionalHeader.SizeOfImage,
+ ntHeaders->OptionalHeader.SectionAlignment);
+ m_sectionAlignment = ntHeaders->OptionalHeader.SectionAlignment;
+ unsigned ntHeaderSize = sizeof(*ntHeaders)
+ - sizeof(ntHeaders->OptionalHeader)
+ + ntHeaders->FileHeader.SizeOfOptionalHeader;
+ DisplayWriteElementAddress( "IMAGE_NT_HEADERS64",
+ DPtrToPreferredAddr(ntHeaders),
+ ntHeaderSize, PE_INFO );
+ }
+ DisplayEndCategory(PE_INFO);
+
+ // PE Section info
+
+ DisplayStartArray("Sections", W("%-8s%s\t(disk %s) %s"), PE_INFO);
+
+ for (COUNT_T i = 0; i < m_decoder.GetNumberOfSections(); i++)
+ {
+ PTR_IMAGE_SECTION_HEADER section = dptr_add(m_decoder.FindFirstSection(), i);
+ m_display->Section(reinterpret_cast<char *>(section->Name),
+ section->VirtualAddress,
+ section->SizeOfRawData);
+ DisplayStartStructure( "Section", DPtrToPreferredAddr(section),
+ sizeof(*section), PE_INFO );
+ DisplayWriteElementString("name", (const char *)section->Name, PE_INFO);
+ DisplayWriteElementAddress( "address", RvaToDisplay(section->VirtualAddress),
+ section->Misc.VirtualSize, PE_INFO );
+ DisplayWriteElementAddress( "disk", section->PointerToRawData,
+ section->SizeOfRawData, PE_INFO );
+
+ DisplayWriteElementEnumerated( "access", section->Characteristics,
+ s_ImageSections, W(", "), PE_INFO );
+ DisplayEndStructure( PE_INFO ); //Section
+ }
+ DisplayEndArray("Total Sections", PE_INFO);
+
+ // Image directory info
+
+ DisplayStartArray( "Directories", W("%-40s%s"), PE_INFO );
+
+ for ( COUNT_T i = 0; i < IMAGE_NUMBEROF_DIRECTORY_ENTRIES; i++)
+ {
+ static const char *directoryNames[] =
+ {
+ /* 0*/"IMAGE_DIRECTORY_ENTRY_EXPORT",
+ /* 1*/"IMAGE_DIRECTORY_ENTRY_IMPORT",
+ /* 2*/"IMAGE_DIRECTORY_ENTRY_RESOURCE",
+ /* 3*/"IMAGE_DIRECTORY_ENTRY_EXCEPTION",
+ /* 4*/"IMAGE_DIRECTORY_ENTRY_SECURITY",
+ /* 5*/"IMAGE_DIRECTORY_ENTRY_BASERELOC",
+ /* 6*/"IMAGE_DIRECTORY_ENTRY_DEBUG",
+ /* 7*/"IMAGE_DIRECTORY_ENTRY_ARCHITECTURE",
+ /* 8*/"IMAGE_DIRECTORY_ENTRY_GLOBALPTR",
+ /* 9*/"IMAGE_DIRECTORY_ENTRY_TLS",
+ /*10*/"IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG",
+ /*11*/"IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT",
+ /*12*/"IMAGE_DIRECTORY_ENTRY_IAT",
+ /*13*/"IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT",
+ /*14*/"IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR",
+ /* 2*/"",
+ };
+
+ IMAGE_DATA_DIRECTORY *entry = m_decoder.GetDirectoryEntry(i);
+
+ if (entry->VirtualAddress != 0)
+ {
+ DisplayStartElement("Directory", PE_INFO);
+ DisplayWriteElementString("name", directoryNames[i], PE_INFO);
+ DisplayWriteElementAddress("address",
+ RvaToDisplay(entry->VirtualAddress),
+ entry->Size, PE_INFO);
+
+ DisplayEndElement( PE_INFO ); //Directory
+ }
+ }
+ DisplayEndArray("Total Directories", PE_INFO); //Directories
+
+ // COM+ info
+
+ if (!m_decoder.HasCorHeader())
+ {
+ IF_OPT(COR_INFO)
+ DisplayWriteElementString("CLRInfo", "<none>", COR_INFO);
+ else
+ m_display->ErrorPrintF("Non-CLR image\n");
+
+ m_display->EndDocument();
+ return;
+ }
+
+ CONSISTENCY_CHECK(m_decoder.CheckCorHeader());
+ if (!m_decoder.CheckCorHeader())
+ {
+ m_display->ErrorPrintF("*** INVALID CLR Header ***");
+ m_display->EndDocument();
+ return;
+ }
+
+ DisplayStartCategory("CLRInfo", COR_INFO);
+ PTR_IMAGE_COR20_HEADER pCor(m_decoder.GetCorHeader());
+ {
+#define WRITE_COR20_FIELD( name ) m_display->WriteFieldAddress( \
+ # name, offsetof(IMAGE_COR20_HEADER, name), \
+ fieldsize(IMAGE_COR20_HEADER, name), \
+ RvaToDisplay( pCor-> name . VirtualAddress ), \
+ pCor-> name . Size )
+
+ m_display->StartStructure( "IMAGE_COR20_HEADER",
+ DPtrToPreferredAddr(pCor),
+ sizeof(*pCor) );
+
+ DisplayWriteFieldUInt( MajorRuntimeVersion, pCor->MajorRuntimeVersion, IMAGE_COR20_HEADER, COR_INFO );
+ DisplayWriteFieldUInt( MinorRuntimeVersion, pCor->MinorRuntimeVersion, IMAGE_COR20_HEADER, COR_INFO );
+
+ // Symbol table and startup information
+ WRITE_COR20_FIELD(MetaData);
+ DisplayWriteFieldEnumerated( Flags, pCor->Flags, IMAGE_COR20_HEADER, s_CorHdrFlags, W(", "), COR_INFO );
+ DisplayWriteFieldUInt( EntryPointToken, pCor->EntryPointToken, IMAGE_COR20_HEADER, COR_INFO );
+
+ // Binding information
+ WRITE_COR20_FIELD(Resources);
+ WRITE_COR20_FIELD(StrongNameSignature);
+
+ // Regular fixup and binding information
+ WRITE_COR20_FIELD(CodeManagerTable);
+ WRITE_COR20_FIELD(VTableFixups);
+ WRITE_COR20_FIELD(ExportAddressTableJumps);
+
+ // Precompiled image info
+ WRITE_COR20_FIELD(ManagedNativeHeader);
+
+ m_display->EndStructure(); //IMAGE_COR20_HEADER
+#undef WRITE_COR20_FIELD
+ }
+
+ //make sure to touch the strong name signature even if we won't print it.
+ if (m_decoder.HasStrongNameSignature())
+ {
+ if (m_decoder.IsStrongNameSigned())
+ {
+ DACCOP_IGNORE(CastBetweenAddressSpaces,"nidump is in-proc and doesn't maintain a clean separation of address spaces (target and host are the same.");
+ data = reinterpret_cast<void*>(dac_cast<TADDR>(m_decoder.GetStrongNameSignature(&size)));
+
+ IF_OPT(COR_INFO)
+ {
+ TempBuffer sig;
+
+ appendByteArray(sig, (BYTE*)data, size);
+
+ DisplayWriteElementStringW( "StrongName", (const WCHAR *)sig,
+ COR_INFO );
+ }
+ }
+ else
+ {
+ DisplayWriteEmptyElement("DelaySigned", COR_INFO);
+ }
+ }
+
+#ifdef FEATURE_READYTORUN
+ if (m_decoder.HasReadyToRunHeader())
+ DisplayWriteElementString( "imageType", "ReadyToRun image", COR_INFO);
+ else
+#endif
+ if (m_decoder.IsILOnly())
+ DisplayWriteElementString( "imageType", "IL only image", COR_INFO);
+ else
+ if (m_decoder.HasNativeHeader())
+ DisplayWriteElementString( "imageType", "Native image", COR_INFO);
+ else
+ DisplayWriteElementString( "imageType", "Mixed image", COR_INFO);
+
+ DACCOP_IGNORE(CastBetweenAddressSpaces,"nidump is in-proc and doesn't maintain a clean separation of address spaces (target and host are the same.");
+ data = reinterpret_cast<void*>(dac_cast<TADDR>(m_decoder.GetMetadata(&size)));
+ OpenMetadata();
+ IF_OPT(METADATA)
+ {
+ DWORD dwAssemblyFlags = 0;
+ IfFailThrow(m_manifestAssemblyImport->GetAssemblyProps(TokenFromRid(1, mdtAssembly), NULL, NULL,
+ NULL, NULL,
+ NULL, NULL,
+ NULL, &dwAssemblyFlags));
+ if ((afContentType_WindowsRuntime & dwAssemblyFlags) == afContentType_WindowsRuntime)
+ {
+ // The WinMD adapter doesn't implement the IID_IMetaDataTables interface so we can't dump
+ // the raw metadata.
+ DisplayWriteElementString ("Metadata", "Not supported by WinRT", COR_INFO);
+ }
+ else
+ {
+ WriteElementsMetadata( "Metadata", TO_TADDR(data), size );
+ }
+ }
+
+ CoverageRead(TO_TADDR(data), size);
+
+ if (m_decoder.HasNativeHeader())
+ {
+ DACCOP_IGNORE(CastBetweenAddressSpaces,"nidump is in-proc and doesn't maintain a clean separation of address spaces (target and host are the same.");
+ data = reinterpret_cast<void*>(dac_cast<TADDR>(m_decoder.GetNativeManifestMetadata(&size)));
+
+ IF_OPT(METADATA)
+ {
+ WriteElementsMetadata( "NativeManifestMetadata", TO_TADDR(data), size );
+ }
+ else
+ {
+ DisplayWriteElementAddress( "NativeManifestMetadata",
+ DataPtrToDisplay((TADDR)data), size,
+ COR_INFO );
+ }
+
+
+ /* REVISIT_TODO Tue 09/20/2005
+ * Anything to display in the native metadata?
+ */
+ CoverageRead(TO_TADDR(data), size);
+
+ /* REVISIT_TODO Tue 09/20/2005
+ * Dump the debug map? Indexed by method RID... probably a good idea
+ */
+ data = reinterpret_cast<void*>(m_decoder.GetNativeDebugMap(&size));
+
+ DisplayWriteElementAddress( "debugMap", DataPtrToDisplay((TADDR)data), size,
+ COR_INFO);
+ CoverageRead(TO_TADDR(data), size);
+
+ //also read the entire debug map section
+ IMAGE_SECTION_HEADER * dbgmap = FindSection( ".dbgmap" );
+ if (dbgmap != NULL)
+ {
+ CoverageRead(TO_TADDR(dbgmap->VirtualAddress)
+ + PTR_TO_TADDR(m_decoder.GetBase()),
+ (ULONG32)ALIGN_UP(dbgmap->Misc.VirtualSize, GetSectionAlignment()));
+ }
+
+ //read the .il and .rsrc sections in their entirety
+ IF_OPT(DEBUG_COVERAGE)
+ {
+ IMAGE_SECTION_HEADER *hdr;
+ hdr = FindSection( ".rsrc" );
+ if( hdr != NULL )
+ {
+ CoverageRead( m_decoder.GetRvaData(hdr->VirtualAddress),
+ (ULONG32)hdr->Misc.VirtualSize );
+ }
+ }
+ IF_OPT_OR(DEBUG_COVERAGE, IL)
+ {
+ IMAGE_SECTION_HEADER *hdr = FindSection( ".text" );
+
+ if( hdr != NULL )
+ {
+ m_ILSectionStart = hdr->VirtualAddress;
+ m_ILHostCopy = (BYTE*)PTR_READ(m_decoder.GetRvaData(hdr->VirtualAddress), hdr->Misc.VirtualSize);
+#ifdef _DEBUG
+ m_ILSectionSize = hdr->Misc.VirtualSize;
+#endif
+ }
+ else
+ {
+ m_ILSectionStart = 0;
+ m_ILHostCopy = NULL;
+#ifdef _DEBUG
+ m_ILSectionSize = 0;
+#endif
+ }
+ _ASSERTE( (((TADDR)m_ILHostCopy) & 3) == 0 );
+ _ASSERTE((m_ILSectionStart & 3) == 0);
+
+ }
+ }
+
+ data = m_decoder.GetResources(&size);
+ IF_OPT(RESOURCES)
+ {
+ DisplayStartStructure( "resource", DataPtrToDisplay((TADDR)data), size,
+ COR_INFO );
+ DisplayStartArray( "Resources", NULL, COR_INFO );
+ HCORENUM hEnum = NULL;
+ for(;;)
+ {
+ mdManifestResource resTokens[1];
+ ULONG numTokens = 0;
+ IfFailThrow(m_assemblyImport->EnumManifestResources(&hEnum,
+ resTokens,
+ 1,
+ &numTokens));
+ if( numTokens == 0 )
+ break;
+
+ WCHAR resourceName[256];
+ ULONG nameLen;
+ mdToken impl;
+ DWORD offset, flags;
+ IfFailThrow(m_assemblyImport->GetManifestResourceProps(resTokens[0],
+ resourceName,
+ _countof(resourceName),
+ &nameLen,
+ &impl,
+ &offset,
+ &flags));
+ if( RidFromToken(impl) != 0 )
+ continue; //skip all non-zero providers
+ resourceName[nameLen] = W('\0');
+ DPTR(DWORD UNALIGNED) res(TO_TADDR(data) + offset);
+ DWORD resSize = *res;
+ DisplayWriteElementAddressNamedW( "Resource", resourceName,
+ DPtrToPreferredAddr(res),
+ resSize + sizeof(DWORD),
+ RESOURCES );
+ }
+ DisplayEndArray( "Total Resources", COR_INFO ); //Resources
+ DisplayEndStructure( COR_INFO ); //resource
+ }
+ else
+ {
+ DisplayWriteElementAddress( "resource", DataPtrToDisplay((TADDR)data), size,
+ COR_INFO );
+ }
+
+ ULONG resultSize;
+ GUID mvid;
+ m_manifestImport->GetScopeProps(bigBuffer, bigBufferSize, &resultSize, &mvid);
+ /* REVISIT_TODO Wed 09/07/2005
+ * The name is the .module entry. Why isn't it present in the ngen image?
+ */
+ TempBuffer guidString;
+ GuidToString( mvid, guidString );
+ if( wcslen(bigBuffer) )
+ DisplayWriteElementStringW( "scopeName", bigBuffer, COR_INFO );
+ DisplayWriteElementStringW( "mvid", (const WCHAR *)guidString, COR_INFO );
+
+ if (m_decoder.HasManagedEntryPoint())
+ {
+ DisplayStartVStructure( "ManagedEntryPoint", COR_INFO );
+ unsigned token = m_decoder.GetEntryPointToken();
+ DisplayWriteElementUInt( "Token", token, COR_INFO );
+ TempBuffer buf;
+ AppendTokenName( token, buf );
+ DisplayWriteElementStringW( "TokenName", (const WCHAR *)buf, COR_INFO );
+ DisplayEndVStructure( COR_INFO );
+ }
+ else if (m_decoder.HasNativeEntryPoint())
+ {
+ DisplayWriteElementPointer( "NativeEntryPoint", (SIZE_T)m_decoder.GetNativeEntryPoint(),
+ COR_INFO );
+ }
+
+ /* REVISIT_TODO Mon 11/21/2005
+ * Dump the version info completely
+ */
+ if( m_decoder.HasNativeHeader() )
+ {
+ PTR_CORCOMPILE_VERSION_INFO versionInfo( m_decoder.GetNativeVersionInfo() );
+
+ DisplayStartStructure("CORCOMPILE_VERSION_INFO",
+ DPtrToPreferredAddr(versionInfo),
+ sizeof(*versionInfo), COR_INFO);
+
+ DisplayStartStructureWithOffset( sourceAssembly,
+ DPtrToPreferredAddr(versionInfo) + offsetof(CORCOMPILE_VERSION_INFO, sourceAssembly),
+ sizeof(versionInfo->sourceAssembly),
+ CORCOMPILE_VERSION_INFO, COR_INFO );
+ DumpAssemblySignature(versionInfo->sourceAssembly);
+ DisplayEndStructure(COR_INFO); //sourceAssembly
+
+ COUNT_T numDeps;
+ PTR_CORCOMPILE_DEPENDENCY deps(TO_TADDR(m_decoder.GetNativeDependencies(&numDeps)));
+
+ DisplayStartArray( "Dependencies", NULL, COR_INFO );
+
+ for( COUNT_T i = 0; i < numDeps; ++i )
+ {
+ DisplayStartStructure("CORCOMPILE_DEPENDENCY", DPtrToPreferredAddr(deps + i),
+ sizeof(deps[i]), COR_INFO );
+ WriteFieldMDTokenImport( dwAssemblyRef, deps[i].dwAssemblyRef,
+ CORCOMPILE_DEPENDENCY, COR_INFO,
+ m_manifestImport );
+ WriteFieldMDTokenImport( dwAssemblyDef, deps[i].dwAssemblyDef,
+ CORCOMPILE_DEPENDENCY, COR_INFO,
+ m_manifestImport );
+#ifndef FEATURE_CORECLR
+ DisplayWriteFieldEnumerated( dependencyInfo, deps[i].dependencyInfo,
+ CORCOMPILE_DEPENDENCY,
+ s_CorCompileDependencyInfoFlags, W(", "),
+ COR_INFO );
+#endif // !FEATURE_CORECLR
+ DisplayStartStructureWithOffset( signAssemblyDef,
+ DPtrToPreferredAddr(deps + i) + offsetof(CORCOMPILE_DEPENDENCY, signAssemblyDef),
+ sizeof(deps[i]).signAssemblyDef,
+ CORCOMPILE_DEPENDENCY, COR_INFO );
+ DumpAssemblySignature(deps[i].signAssemblyDef);
+ DisplayEndStructure(COR_INFO); //signAssemblyDef
+
+ {
+ TempBuffer buf;
+ if( deps[i].signNativeImage == INVALID_NGEN_SIGNATURE )
+ {
+ buf.Append( W("INVALID_NGEN_SIGNATURE") );
+ }
+ else
+ {
+ GuidToString(deps[i].signNativeImage, buf);
+ }
+ DisplayWriteFieldStringW( signNativeImage, (const WCHAR*)buf,
+ CORCOMPILE_DEPENDENCY, COR_INFO );
+#if 0
+ if( m_librarySupport
+ && deps[i].signNativeImage != INVALID_NGEN_SIGNATURE )
+ {
+ buf.Clear();
+ AppendTokenName(deps[i].dwAssemblyRef, buf, m_import );
+ IfFailThrow(m_librarySupport->LoadDependency( (const WCHAR*)buf,
+ deps[i].signNativeImage ));
+ }
+#endif
+
+ }
+
+
+ DisplayEndStructure(COR_INFO); //CORCOMPILE_DEPENDENCY
+ }
+ DisplayEndArray( "Total Dependencies", COR_INFO );
+ DisplayEndStructure(COR_INFO); //CORCOMPILE_VERSION_INFO
+
+ //Now load all dependencies and imports. There may be more
+ //dependencies than imports, so make sure to get them all.
+ for( COUNT_T i = 0; i < m_decoder.GetNativeImportTableCount(); ++i )
+ {
+ NativeImageDumper::Import * import = OpenImport(i);
+ TraceDumpImport( i, import );
+ }
+ NativeImageDumper::Dependency * traceDependency = OpenDependency(0);
+ TraceDumpDependency( 0, traceDependency );
+
+ for( COUNT_T i = 0; i < numDeps; ++i )
+ {
+ traceDependency = OpenDependency( i + 1 );
+ TraceDumpDependency( i + 1, traceDependency );
+ }
+ _ASSERTE(m_dependencies[0].pModule != NULL);
+
+ /* XXX Wed 12/14/2005
+ * Now for the real insanity. I need to initialize static classes in
+ * the DAC. First I need to find mscorlib's dependency entry. Search
+ * through all of the dependencies to find the one marked as
+ * fIsMscorlib. If I don't find anything marked that way, then "self"
+ * is mscorlib.
+ */
+ Dependency * mscorlib = NULL;
+ for( COUNT_T i = 0; i < m_numDependencies; ++i )
+ {
+ if( m_dependencies[i].fIsMscorlib )
+ {
+ mscorlib = &m_dependencies[i];
+ break;
+ }
+ }
+
+ //If we're actually dumping mscorlib, remap the mscorlib dependency to our own native image.
+ if( (mscorlib == NULL) || !wcscmp(m_name, CoreLibName_W))
+ {
+ mscorlib = GetDependency(0);
+ mscorlib->fIsMscorlib = TRUE;
+ _ASSERTE(mscorlib->fIsHardbound);
+ }
+ if( mscorlib->fIsHardbound )
+ {
+ m_isMscorlibHardBound = true;
+ }
+
+ _ASSERTE(mscorlib != NULL);
+ if( m_isMscorlibHardBound )
+ {
+ //go through the module to the binder.
+ PTR_Module mscorlibModule = mscorlib->pModule;
+
+ PTR_MscorlibBinder binder = mscorlibModule->m_pBinder;
+ g_Mscorlib = *binder;
+
+ PTR_MethodTable mt = MscorlibBinder::GetExistingClass(CLASS__OBJECT);
+ g_pObjectClass = mt;
+ }
+
+
+ if (g_pObjectClass == NULL)
+ {
+ //if mscorlib is not hard bound, then warn the user (many features of nidump are shut off)
+ m_display->ErrorPrintF( "Assembly %S is soft bound to mscorlib. nidump cannot dump MethodTables completely.\n", m_name );
+ // TritonTODO: reason?
+ // reset "hard bound state"
+ m_isMscorlibHardBound = false;
+
+ }
+ }
+
+
+ // @todo: VTable Fixups
+
+ // @todo: EAT Jumps
+
+ DisplayEndCategory(COR_INFO); //CLRInfo
+
+#ifdef FEATURE_READYTORUN
+ if (m_decoder.HasReadyToRunHeader())
+ {
+ DumpReadyToRun();
+ }
+ else
+#endif
+ if (m_decoder.HasNativeHeader())
+ {
+ DumpNative();
+ }
+
+ m_display->EndDocument();
+}
+
+void NativeImageDumper::DumpNative()
+{
+ DisplayStartCategory("NativeInfo", NATIVE_INFO);
+
+ CONSISTENCY_CHECK(m_decoder.CheckNativeHeader());
+ if (!m_decoder.CheckNativeHeader())
+ {
+ m_display->ErrorPrintF("*** INVALID NATIVE HEADER ***\n");
+ return;
+ }
+
+ IF_OPT(NATIVE_INFO)
+ DumpNativeHeader();
+
+ //host pointer
+ CORCOMPILE_EE_INFO_TABLE * infoTable = m_decoder.GetNativeEEInfoTable();
+
+ DisplayStartStructure( "CORCOMPILE_EE_INFO_TABLE",
+ DataPtrToDisplay(PTR_HOST_TO_TADDR(infoTable)),
+ sizeof(*infoTable), NATIVE_INFO );
+
+ /* REVISIT_TODO Mon 09/26/2005
+ * Move this further down to include the dumping of the module, and
+ * other things.
+ */
+ DisplayEndStructure(NATIVE_INFO); //NativeInfoTable
+ DisplayEndCategory(NATIVE_INFO); //NativeInfo
+#if LATER
+ //come back here and dump all the fields of the CORCOMPILE_EE_INFO_TABLE
+#endif
+
+ IF_OPT(RELOCATIONS)
+ DumpBaseRelocs();
+
+ IF_OPT(NATIVE_TABLES)
+ DumpHelperTable();
+
+ PTR_Module module = (TADDR)m_decoder.GetPersistedModuleImage();
+
+ //this needs to run for precodes to load the tables that identify precode ranges
+ IF_OPT_OR5(MODULE, METHODTABLES, EECLASSES, TYPEDESCS, PRECODES)
+ DumpModule(module);
+
+ IF_OPT_OR3(FIXUP_TABLES, FIXUP_HISTOGRAM, FIXUP_THUNKS)
+ DumpFixupTables( module );
+ IF_OPT_OR3(METHODS, GC_INFO, DISASSEMBLE_CODE )
+ DumpMethods( module );
+ IF_OPT_OR3(METHODTABLES, EECLASSES, TYPEDESCS)
+ DumpTypes( module );
+}
+
+void NativeImageDumper::TraceDumpImport(int idx, NativeImageDumper::Import * import)
+{
+ IF_OPT(DEBUG_TRACE)
+ {
+ m_display->ErrorPrintF("Import: %d\n", idx);
+ m_display->ErrorPrintF("\tDependency: %p\n", import->dependency);
+ m_display->ErrorPrintF("\twAssemblyRid: %d\n", import->entry->wAssemblyRid);
+ m_display->ErrorPrintF("\twModuleRid %d\n", import->entry->wModuleRid);
+ }
+}
+void NativeImageDumper::TraceDumpDependency(int idx, NativeImageDumper::Dependency * dependency)
+{
+ IF_OPT(DEBUG_TRACE)
+ {
+ m_display->ErrorPrintF("Dependency: %d (%p)\n", idx, dependency);
+ m_display->ErrorPrintF("\tPreferred: %p\n", dependency->pPreferredBase);
+ m_display->ErrorPrintF("\tLoaded: %p\n", dependency->pLoadedAddress);
+ m_display->ErrorPrintF("\tSize: %x (%d)\n", dependency->size, dependency->size);
+ m_display->ErrorPrintF("\tModule: P=%p, L=%p\n", DataPtrToDisplay(dac_cast<TADDR>(dependency->pModule)),
+ PTR_TO_TADDR(dependency->pModule));
+ m_display->ErrorPrintF("Mscorlib=%s, Hardbound=%s\n",
+ (dependency->fIsMscorlib ? "true" : "false"),
+ (dependency->fIsHardbound ? "true" : "false"));
+ m_display->ErrorPrintF("Name: %S\n", dependency->name);
+ }
+}
+
+void NativeImageDumper::WriteElementsMetadata( const char * elementName,
+ TADDR data, SIZE_T size )
+{
+ DisplayStartStructure( elementName,
+ DataPtrToDisplay(data), size, ALWAYS );
+
+ /* XXX Mon 03/13/2006
+ * Create new metatadata dispenser. When I define the Emit for defining
+ * assemblyrefs for dependencies, I copy the memory and totally hork any
+ * mapping back to base addresses.
+ */
+ ReleaseHolder<IMetaDataTables> tables;
+ ReleaseHolder<IMetaDataDispenserEx> pDispenser;
+ IfFailThrow(MetaDataGetDispenser(CLSID_CorMetaDataDispenser,
+ IID_IMetaDataDispenserEx, (void **) &pDispenser));
+
+ VARIANT opt;
+
+ TADDR hostCopyStart = TO_TADDR(PTR_READ(data, (ULONG32)size));
+ TADDR rebasedPointer;
+
+ IfFailThrow(pDispenser->GetOption(MetaDataCheckDuplicatesFor, &opt));
+ V_UI4(&opt) |= MDDupAssemblyRef | MDDupFile;
+ IfFailThrow(pDispenser->SetOption(MetaDataCheckDuplicatesFor, &opt));
+
+ IfFailThrow(pDispenser->OpenScopeOnMemory((const void *)hostCopyStart, (DWORD)size,
+ ofRead, IID_IMetaDataTables,
+ (IUnknown **) &tables));
+ DisplayStartArray( "Tables", W("%s"), ALWAYS );
+
+ for( unsigned i = 0; i < _countof(s_tableTypes); ++i )
+ {
+ HRESULT hr = S_OK;
+ ULONG idx = 0;
+ hr = tables->GetTableIndex(s_tableTypes[i], &idx);
+ _ASSERTE(SUCCEEDED(hr));
+ ULONG cbRow = 0, cRows = 0, cCols = 0, iKey = 0;
+ const char * name = NULL;
+ BYTE * ptr = NULL;
+ hr = tables->GetTableInfo(idx, &cbRow, &cRows, &cCols,
+ &iKey, &name);
+ _ASSERTE(SUCCEEDED(hr) || hr == E_INVALIDARG);
+ if( hr == E_INVALIDARG || cRows == 0 )
+ {
+ continue; //no such table.
+ }
+
+ hr = tables->GetRow(idx, 1, (void**)&ptr);
+ IfFailThrow(hr);
+ _ASSERTE(SUCCEEDED(hr));
+ //compute address
+ rebasedPointer = data + (TO_TADDR(ptr) - hostCopyStart);
+ _ASSERTE( rebasedPointer >= data && rebasedPointer < (data + size) );
+ DisplayWriteElementAddressNamed( "table", name,
+ DataPtrToDisplay(rebasedPointer),
+ cbRow * cRows , ALWAYS );
+#if 0
+ DisplayStartElement( "table", ALWAYS );
+ DisplayWriteElementString( "name", name, ALWAYS );
+ //compute address
+ rebasedPointer = data + (TO_TADDR(ptr) - hostCopyStart);
+ _ASSERTE( rebasedPointer >= data && rebasedPointer < (data + size) );
+ DisplayWriteElementAddress( "address", DataPtrToDisplay(rebasedPointer),
+ cbRow * cRows, ALWAYS );
+ DisplayEndElement( ALWAYS ); //Table
+#endif
+ }
+ DisplayEndArray( "Total Tables", ALWAYS );
+
+ PTR_STORAGESIGNATURE root(data);
+ _ASSERTE(root->lSignature == STORAGE_MAGIC_SIG);
+ //the root is followed by the version string who's length is
+ //root->iVersionString. After that is a storage header that counts the
+ //number of streams.
+ PTR_STORAGEHEADER sHdr(data + sizeof(*root) + root->iVersionString);
+ DisplayStartArray( "Pools", NULL, ALWAYS );
+
+ //now check the pools
+
+ //start of stream headers
+ PTR_STORAGESTREAM streamHeader( PTR_TO_TADDR(sHdr) + sizeof(*sHdr) );
+ for( unsigned i = 0; i < sHdr->iStreams; ++i )
+ {
+ if( streamHeader->iSize > 0 )
+ {
+ DisplayWriteElementAddressNamed( "heap", streamHeader->rcName,
+ DataPtrToDisplay( data + streamHeader->iOffset ),
+ streamHeader->iSize, ALWAYS );
+ }
+ //Stream headers aren't fixed size. the size is aligned up based on a
+ //variable length string at the end.
+ streamHeader = PTR_STORAGESTREAM(PTR_TO_TADDR(streamHeader)
+ + ALIGN_UP(offsetof(STORAGESTREAM, rcName) + strlen(streamHeader->rcName) + 1, 4));
+ }
+
+ DisplayEndArray( "Total Pools", ALWAYS ); //Pools
+ DisplayEndStructure( ALWAYS ); //nativeMetadata
+}
+void NativeImageDumper::OpenMetadata()
+{
+ COUNT_T size;
+
+ DACCOP_IGNORE(CastBetweenAddressSpaces,"nidump is in-proc and doesn't maintain a clean separation of address spaces (target and host are the same.");
+ const void *data = reinterpret_cast<void*>(dac_cast<TADDR>(m_decoder.GetMetadata(&size)));
+
+ ReleaseHolder<IMetaDataDispenserEx> pDispenser;
+ IfFailThrow(MetaDataGetDispenser(CLSID_CorMetaDataDispenser,
+ IID_IMetaDataDispenserEx, (void **) &pDispenser));
+
+ VARIANT opt;
+ IfFailThrow(pDispenser->GetOption(MetaDataCheckDuplicatesFor, &opt));
+ V_UI4(&opt) |= MDDupAssemblyRef | MDDupFile;
+ IfFailThrow(pDispenser->SetOption(MetaDataCheckDuplicatesFor, &opt));
+
+ data = PTR_READ(TO_TADDR(data), size);
+ IfFailThrow(pDispenser->OpenScopeOnMemory(data, size, ofRead,
+ IID_IMetaDataImport2, (IUnknown **) &m_import));
+
+ IfFailThrow(m_import->QueryInterface(IID_IMetaDataAssemblyImport,
+ (void **)&m_assemblyImport));
+
+ m_MetadataStartTarget = TO_TADDR(data);
+ m_MetadataSize = size;
+ data = PTR_READ(TO_TADDR(data), size);
+ m_MetadataStartHost = TO_TADDR(data);
+
+ if (m_decoder.HasNativeHeader())
+ {
+ DACCOP_IGNORE(CastBetweenAddressSpaces,"nidump is in-proc and doesn't maintain a clean separation of address spaces (target and host are the same.");
+ data = reinterpret_cast<void*>(dac_cast<TADDR>(m_decoder.GetNativeManifestMetadata(&size)));
+
+ IfFailThrow(pDispenser->OpenScopeOnMemory(data, size, ofRead,
+ IID_IMetaDataImport2, (IUnknown **) &m_manifestImport));
+
+ IfFailThrow(m_manifestImport->QueryInterface(IID_IMetaDataAssemblyImport,
+ (void **)&m_manifestAssemblyImport));
+ }
+ else
+ {
+ m_manifestImport = m_import;
+ m_manifestImport->AddRef();
+
+ m_manifestAssemblyImport = m_assemblyImport;
+ m_manifestAssemblyImport->AddRef();
+ }
+}
+void
+NativeImageDumper::AppendTokenName(mdToken token, SString& buf)
+{
+ AppendTokenName(token, buf, NULL);
+}
+void
+NativeImageDumper::AppendTokenName(mdToken token, SString& buf,
+ IMetaDataImport2 *pImport,
+ bool force)
+{
+ mdToken parent;
+ ULONG size;
+ DWORD attr;
+ PCCOR_SIGNATURE pSig;
+ PTR_CCOR_SIGNATURE dacSig;
+ ULONG cSig;
+ DWORD flags;
+ ULONG rva;
+ CQuickBytes bytes;
+
+ if( CHECK_OPT(DISABLE_NAMES) && !force )
+ {
+ buf.Append( W("Disabled") );
+ return;
+ }
+
+ if (pImport == NULL)
+ pImport = m_import;
+ if( RidFromToken(token) == mdTokenNil )
+ {
+ AppendNilToken( token, buf );
+ }
+ else
+ {
+ switch (TypeFromToken(token))
+ {
+ case mdtTypeDef:
+ IfFailThrow(pImport->GetTypeDefProps(token, bigBuffer, bigBufferSize, &size, &flags, &parent));
+ buf.Append(bigBuffer);
+ break;
+
+ case mdtTypeRef:
+ // TritonTODO: consolidate with desktop
+ // IfFailThrow(pImport->GetTypeRefProps(token, &parent, bigBuffer, bigBufferSize, &size));
+ if (FAILED(pImport->GetTypeRefProps(token, &parent, bigBuffer, bigBufferSize, &size)))
+ buf.Append(W("ADDED TYPEREF (?)"));
+ else
+ buf.Append(bigBuffer);
+ break;
+
+ case mdtTypeSpec:
+ IfFailThrow(pImport->GetTypeSpecFromToken(token, &pSig, &cSig));
+ dacSig = metadataToHostDAC(pSig, pImport);
+ TypeToString(dacSig, buf, pImport);
+ break;
+
+ case mdtFieldDef:
+ IfFailThrow(pImport->GetFieldProps(token, &parent, bigBuffer, bigBufferSize, &size, &attr,
+ &pSig, &cSig, &flags, NULL, NULL));
+ AppendTokenName(parent, buf, pImport);
+ IfFailThrow(pImport->GetFieldProps(token, &parent, bigBuffer, bigBufferSize, &size, &attr,
+ &pSig, &cSig, &flags, NULL, NULL));
+ buf.AppendPrintf( W("::%s"), bigBuffer );
+ break;
+
+ case mdtMethodDef:
+ IfFailThrow(pImport->GetMethodProps(token, &parent, bigBuffer, bigBufferSize, &size, &attr,
+ &pSig, &cSig, &rva, &flags));
+ AppendTokenName(parent, buf, pImport);
+ IfFailThrow(pImport->GetMethodProps(token, &parent, bigBuffer, bigBufferSize, &size, &attr,
+ &pSig, &cSig, &rva, &flags));
+ buf.AppendPrintf( W("::%s"), bigBuffer );
+ break;
+
+ case mdtMemberRef:
+ IfFailThrow(pImport->GetMemberRefProps(token, &parent, bigBuffer, bigBufferSize, &size,
+ &pSig, &cSig));
+ AppendTokenName(parent, buf, pImport);
+ IfFailThrow(pImport->GetMemberRefProps(token, &parent, bigBuffer, bigBufferSize, &size,
+ &pSig, &cSig));
+ buf.AppendPrintf( W("::%s"), bigBuffer );
+ break;
+
+ case mdtSignature:
+ IfFailThrow(pImport->GetSigFromToken(token, &pSig, &cSig));
+#if LATER
+ PrettyPrintSig(pSig, cSig, W(""), &bytes, pImport);
+ m_display->ErrorPrintF("%S", bytes.Ptr());
+#else
+ _ASSERTE(!"Unimplemented");
+ m_display->ErrorPrintF( "unimplemented" );
+#endif
+ break;
+
+ case mdtString:
+ IfFailThrow(pImport->GetUserString(token, bigBuffer, bigBufferSize, &size));
+ bigBuffer[min(size, bigBufferSize-1)] = 0;
+ buf.Append( bigBuffer );
+ break;
+
+ case mdtAssembly:
+ case mdtAssemblyRef:
+ case mdtFile:
+ case mdtExportedType:
+ {
+ ReleaseHolder<IMetaDataAssemblyImport> pAssemblyImport;
+ IfFailThrow(pImport->QueryInterface(IID_IMetaDataAssemblyImport,
+ (void **)&pAssemblyImport));
+ PrintManifestTokenName(token, buf, pAssemblyImport, force);
+ }
+ break;
+
+ case mdtGenericParam:
+ {
+ ULONG nameLen;
+ IfFailThrow(pImport->GetGenericParamProps(token, NULL, NULL, NULL, NULL, bigBuffer,
+ _countof(bigBuffer), &nameLen));
+ bigBuffer[min(nameLen, _countof(bigBuffer) - 1)] = 0;
+ buf.Append( bigBuffer );
+ }
+ break;
+
+ default:
+ _ASSERTE( !"Unknown token type in AppendToken" );
+ buf.AppendPrintf( W("token 0x%x"), token );
+ }
+ }
+}
+void NativeImageDumper::PrintManifestTokenName(mdToken token, SString& str)
+{
+ PrintManifestTokenName(token, str, NULL);
+}
+void
+NativeImageDumper::PrintManifestTokenName(mdToken token,
+ SString& buf,
+ IMetaDataAssemblyImport *pAssemblyImport,
+ bool force)
+{
+ ULONG size;
+ const void *pSig;
+ ULONG cSig;
+ DWORD flags;
+ CQuickBytes bytes;
+ ULONG hash;
+
+ if( CHECK_OPT(DISABLE_NAMES) && !force )
+ {
+ buf.Append( W("Disabled") );
+ return;
+ }
+
+ if (pAssemblyImport == NULL)
+ pAssemblyImport = m_manifestAssemblyImport;
+
+ if( RidFromToken(token) == mdTokenNil )
+ {
+ AppendNilToken( token, buf );
+ }
+ else
+ {
+ switch (TypeFromToken(token))
+ {
+ case mdtAssembly:
+ IfFailThrow(pAssemblyImport->GetAssemblyProps(token, &pSig, &cSig,
+ &hash, bigBuffer,
+ bigBufferSize, &size,
+ NULL, &flags));
+
+ buf.Append(bigBuffer);
+ break;
+
+ case mdtAssemblyRef:
+ IfFailThrow(pAssemblyImport->GetAssemblyRefProps(token, &pSig,
+ &cSig, bigBuffer,
+ bigBufferSize,
+ &size, NULL, NULL,
+ NULL, &flags));
+ buf.Append(bigBuffer);
+ break;
+
+ case mdtFile:
+ IfFailThrow(pAssemblyImport->GetFileProps(token, bigBuffer,
+ bigBufferSize, &size,
+ NULL, NULL, &flags));
+
+ buf.Append(bigBuffer);
+ break;
+
+ case mdtExportedType:
+ IfFailThrow(pAssemblyImport->GetExportedTypeProps(token, bigBuffer,
+ bigBufferSize, &size,
+ NULL, NULL, &flags));
+
+ buf.Append(bigBuffer);
+ break;
+
+ default:
+ buf.AppendPrintf(W("token %x"), token);
+ }
+ }
+}
+
+BOOL NativeImageDumper::HandleFixupForHistogram(PTR_CORCOMPILE_IMPORT_SECTION pSection,
+ SIZE_T fixupIndex,
+ SIZE_T *fixupCell)
+{
+ COUNT_T nImportSections;
+ PTR_CORCOMPILE_IMPORT_SECTION pImportSections = m_decoder.GetNativeImportSections(&nImportSections);
+
+ COUNT_T tableSize;
+ TADDR tableBase = m_decoder.GetDirectoryData(&pSection->Section, &tableSize);
+
+ COUNT_T table = (COUNT_T)(pSection - pImportSections);
+ _ASSERTE(table < nImportSections);
+
+ SIZE_T offset = dac_cast<TADDR>(fixupCell) - tableBase;
+ _ASSERTE( offset < tableSize );
+
+ COUNT_T entry = (COUNT_T)(offset / sizeof(TADDR));
+ m_fixupHistogram[table][entry]++;
+
+ return TRUE;
+}
+
+void NativeImageDumper::ComputeMethodFixupHistogram( PTR_Module module )
+{
+ COUNT_T nImportSections;
+ PTR_CORCOMPILE_IMPORT_SECTION pImportSections = m_decoder.GetNativeImportSections(&nImportSections);
+
+ m_fixupHistogram = new COUNT_T * [nImportSections];
+
+ for (COUNT_T i=0; i < nImportSections; i++)
+ {
+ PTR_CORCOMPILE_IMPORT_SECTION pSection = m_decoder.GetNativeImportSectionFromIndex(i);
+
+ COUNT_T count = pSection->Section.Size / sizeof(TADDR);
+
+ m_fixupHistogram[i] = new COUNT_T [count];
+ ZeroMemory(m_fixupHistogram[i], count * sizeof(COUNT_T));
+ }
+
+ ZeroMemory(&m_fixupCountHistogram, sizeof(m_fixupCountHistogram));
+ // profiled hot code
+
+ MethodIterator mi(module, &m_decoder, MethodIterator::Hot);
+ while (mi.Next())
+ {
+ m_fixupCount = 0;
+
+ TADDR pFixupList = mi.GetMethodDesc()->GetFixupList();
+
+ if (pFixupList != NULL)
+ {
+ COUNT_T nImportSections;
+ PTR_CORCOMPILE_IMPORT_SECTION pImportSections = m_decoder.GetNativeImportSections(&nImportSections);
+
+ module->FixupDelayListAux(pFixupList, this,
+ &NativeImageDumper::HandleFixupForHistogram,
+ pImportSections, nImportSections,
+ &m_decoder);
+ }
+
+ if (m_fixupCount < COUNT_HISTOGRAM_SIZE)
+ m_fixupCountHistogram[m_fixupCount]++;
+ else
+ m_fixupCountHistogram[COUNT_HISTOGRAM_SIZE-1]++;
+ }
+
+ // unprofiled code
+ MethodIterator miUnprofiled(module, &m_decoder, MethodIterator::Unprofiled);
+
+ while(miUnprofiled.Next())
+ {
+ m_fixupCount = 0;
+
+ TADDR pFixupList = miUnprofiled.GetMethodDesc()->GetFixupList();
+
+ if (pFixupList != NULL)
+ {
+ COUNT_T nImportSections;
+ PTR_CORCOMPILE_IMPORT_SECTION pImportSections = m_decoder.GetNativeImportSections(&nImportSections);
+
+ module->FixupDelayListAux(pFixupList, this,
+ &NativeImageDumper::HandleFixupForHistogram,
+ pImportSections, nImportSections,
+ &m_decoder);
+ }
+
+ if (m_fixupCount < COUNT_HISTOGRAM_SIZE)
+ m_fixupCountHistogram[m_fixupCount]++;
+ else
+ m_fixupCountHistogram[COUNT_HISTOGRAM_SIZE-1]++;
+ }
+}
+
+void NativeImageDumper::DumpFixupTables( PTR_Module module )
+{
+ IF_OPT(FIXUP_HISTOGRAM)
+ ComputeMethodFixupHistogram( module );
+
+ DisplayStartCategory( "Imports", FIXUP_TABLES );
+
+ COUNT_T nImportSections;
+ PTR_CORCOMPILE_IMPORT_SECTION pImportSections = m_decoder.GetNativeImportSections(&nImportSections);
+
+ for (COUNT_T iImportSections = 0; iImportSections < nImportSections; iImportSections++)
+ {
+ PTR_CORCOMPILE_IMPORT_SECTION pImportSection = pImportSections + iImportSections;
+
+ COUNT_T size;
+ TADDR pTable(m_decoder.GetDirectoryData(&pImportSection->Section, &size));
+ TADDR pTableEnd = pTable + size;
+
+ TADDR pDataTable(NULL);
+
+ if (pImportSection->Signatures != 0)
+ pDataTable = m_decoder.GetRvaData(pImportSection->Signatures);
+
+ switch (pImportSection->Type)
+ {
+ case CORCOMPILE_IMPORT_TYPE_VIRTUAL_METHOD:
+ {
+ COUNT_T entrySize = pImportSection->EntrySize;
+ COUNT_T count = size / entrySize;
+ _ASSERTE(entrySize == sizeof(CORCOMPILE_VIRTUAL_IMPORT_THUNK));
+
+ for (TADDR pEntry = pTable; pEntry < pTableEnd; pEntry += entrySize)
+ {
+ PTR_CORCOMPILE_VIRTUAL_IMPORT_THUNK pThunk = pEntry;
+
+ DisplayStartStructure("VirtualImportThunk", DPtrToPreferredAddr(pThunk),
+ entrySize, FIXUP_THUNKS );
+
+ DisplayWriteElementInt( "Slot", pThunk->slotNum, FIXUP_THUNKS);
+
+ DisplayEndStructure( FIXUP_THUNKS );
+ }
+ }
+ break;
+
+ case CORCOMPILE_IMPORT_TYPE_EXTERNAL_METHOD:
+ {
+ COUNT_T entrySize = pImportSection->EntrySize;
+ COUNT_T count = size / entrySize;
+ _ASSERTE(entrySize == sizeof(CORCOMPILE_EXTERNAL_METHOD_THUNK));
+
+ for (TADDR pEntry = pTable; pEntry < pTableEnd; pEntry += entrySize)
+ {
+ PTR_CORCOMPILE_EXTERNAL_METHOD_THUNK pThunk = pEntry;
+
+ DisplayStartStructure("ExternalImportThunk", DPtrToPreferredAddr(pThunk),
+ entrySize, FIXUP_THUNKS );
+
+ TADDR pDataAddr = pDataTable + ((pEntry - pTable) / entrySize) * sizeof(DWORD);
+ PTR_DWORD pData = pDataAddr;
+
+ DisplayWriteElementPointer( "DataAddress ", pDataAddr, FIXUP_THUNKS );
+
+ TADDR blobSigAddr = RvaToDisplay(*pData);
+ DisplayWriteElementPointer( "TargetSigAddress", blobSigAddr, FIXUP_THUNKS );
+ TempBuffer buf;
+ FixupBlobToString(*pData, buf);
+ DisplayWriteElementStringW( "TargetName", (const WCHAR*)buf, FIXUP_THUNKS );
+
+ DisplayEndStructure( FIXUP_THUNKS );
+ }
+ }
+ break;
+
+ default:
+ {
+ COUNT_T count = size / sizeof(TADDR);
+
+ for (COUNT_T j = 0; j < count; j++)
+ {
+ if (dac_cast<PTR_TADDR>(pTable)[j] == 0)
+ continue;
+
+ SIZE_T nNextEntry = j + 1;
+ while (nNextEntry < count && dac_cast<PTR_TADDR>(pTable)[nNextEntry] == 0)
+ nNextEntry++;
+
+ DisplayStartStructure("ImportEntry", DPtrToPreferredAddr(dac_cast<PTR_TADDR>(pTable) + j),
+ (nNextEntry - j) * sizeof(TADDR), FIXUP_TABLES );
+
+ if (pDataTable != NULL)
+ {
+ DWORD rva = dac_cast<PTR_DWORD>(pDataTable)[j];
+ WriteElementsFixupTargetAndName(rva);
+ }
+ else
+ {
+ SIZE_T token = dac_cast<PTR_TADDR>(pTable)[j];
+ DisplayWriteElementPointer( "TaggedValue", token, FIXUP_TABLES );
+ WriteElementsFixupBlob(pImportSection, token);
+ }
+
+ DisplayWriteElementInt( "index", j, FIXUP_HISTOGRAM);
+ DisplayWriteElementInt( "ReferenceCount", m_fixupHistogram[iImportSections][j], FIXUP_HISTOGRAM );
+
+ DisplayEndStructure( FIXUP_TABLES );
+ }
+ }
+ }
+ }
+ DisplayEndCategory( FIXUP_TABLES );
+}
+
+void NativeImageDumper::FixupThunkToString(PTR_CORCOMPILE_IMPORT_SECTION pImportSection, TADDR addr, SString& buf)
+{
+ switch (pImportSection->Type)
+ {
+ case CORCOMPILE_IMPORT_TYPE_VIRTUAL_METHOD:
+ {
+ PTR_CORCOMPILE_VIRTUAL_IMPORT_THUNK pThunk = addr;
+ buf.AppendPrintf( W("slot %d"), pThunk->slotNum );
+ }
+ break;
+
+ case CORCOMPILE_IMPORT_TYPE_EXTERNAL_METHOD:
+ case CORCOMPILE_IMPORT_TYPE_STUB_DISPATCH:
+ {
+ TADDR pTable(m_decoder.GetDirectoryData(&pImportSection->Section));
+ COUNT_T index = (COUNT_T)(addr - pTable) / pImportSection->EntrySize;
+ TADDR pDataTable(m_decoder.GetRvaData(pImportSection->Signatures));
+ TADDR pDataAddr = pDataTable + (index * sizeof(DWORD));
+ PTR_DWORD pData = pDataAddr;
+ FixupBlobToString(*pData, buf);
+ }
+ break;
+
+ default:
+ _ASSERTE(!"Unknown import type");
+ }
+}
+
+void NativeImageDumper::WriteElementsFixupBlob(PTR_CORCOMPILE_IMPORT_SECTION pSection, SIZE_T fixup)
+{
+ if (pSection != NULL && !CORCOMPILE_IS_FIXUP_TAGGED(fixup, pSection))
+ {
+ TempBuffer buf;
+ if (pSection->Type == CORCOMPILE_IMPORT_TYPE_TYPE_HANDLE)
+ {
+ TypeHandleToString(TypeHandle::FromTAddr((TADDR)fixup), buf);
+ }
+ else
+ if (pSection->Type == CORCOMPILE_IMPORT_TYPE_METHOD_HANDLE)
+ {
+ MethodDescToString(PTR_MethodDesc((TADDR)fixup), buf);
+ }
+ else
+ {
+ _ASSERTE(!"Unknown Type");
+ IfFailThrow(E_FAIL);
+ }
+ m_display->WriteElementStringW( "FixupTargetName", (const WCHAR*)buf );
+ return;
+ }
+
+ RVA rva = CORCOMPILE_UNTAG_TOKEN(fixup);
+
+ WriteElementsFixupTargetAndName(rva);
+}
+
+const NativeImageDumper::EnumMnemonics s_EncodeMethodSigFlags[] =
+{
+#define EMS_ENTRY(f) NativeImageDumper::EnumMnemonics(ENCODE_METHOD_SIG_ ## f, W(#f))
+ EMS_ENTRY(UnboxingStub),
+ EMS_ENTRY(InstantiatingStub),
+ EMS_ENTRY(MethodInstantiation),
+ EMS_ENTRY(SlotInsteadOfToken),
+ EMS_ENTRY(MemberRefToken),
+ EMS_ENTRY(Constrained),
+ EMS_ENTRY(OwnerType),
+#undef EMS_ENTRY
+};
+
+void NativeImageDumper::FixupBlobToString(RVA rva, SString& buf)
+{
+ PTR_CCOR_SIGNATURE sig = (TADDR) m_decoder.GetRvaData(rva);
+ BYTE kind = *sig++;
+
+ CorTokenType tkType = (CorTokenType)0;
+
+ IMetaDataImport2 * pImport = m_import;
+
+ if (kind & ENCODE_MODULE_OVERRIDE)
+ {
+ Import *import = OpenImport(DacSigUncompressData(sig));
+ kind &= ~ENCODE_MODULE_OVERRIDE;
+
+ Dependency *pDep = import->dependency;
+ if (pDep == NULL)
+ {
+ return;
+ }
+
+ pImport = pDep->pImport;
+
+ _ASSERTE(pImport != NULL);
+
+ // print assembly/module info
+
+ PTR_CORCOMPILE_IMPORT_TABLE_ENTRY entry = import->entry;
+ if (entry->wAssemblyRid != 0)
+ {
+ mdToken realRef =
+ MapAssemblyRefToManifest(TokenFromRid(entry->wAssemblyRid,
+ mdtAssemblyRef),
+ m_assemblyImport);
+ AppendToken(realRef, buf, m_manifestImport);
+ buf.Append( W(" ") );
+ }
+ if (entry->wModuleRid != 0)
+ {
+ AppendToken(TokenFromRid(entry->wModuleRid, mdtFile), buf, pImport);
+ buf.Append( W(" ") );
+ }
+ }
+
+ // print further info
+
+ mdToken token;
+
+ switch (kind)
+ {
+ case ENCODE_MODULE_HANDLE:
+ // No further info
+ break;
+
+ case ENCODE_TYPE_HANDLE:
+ EncodeType:
+ if (pImport != NULL)
+ TypeToString(sig, buf, pImport);
+ else
+ buf.Append( W("<unresolved type> ") );
+
+ break;
+
+ case ENCODE_METHOD_HANDLE:
+ EncodeMethod:
+ {
+ //Flags are first
+ DWORD methodFlags = DacSigUncompressData(sig);
+
+ // If the type portion for this generic method signature
+ // is from a different module then both the generic type and the
+ // generic method tokens are interpreted in the context of that module,
+ // and not the current import. This is returned by TypeToString.
+ //
+ IMetaDataImport2 * pMethodImport = pImport;
+ if (pImport != NULL)
+ {
+ if (methodFlags & ENCODE_METHOD_SIG_OwnerType)
+ {
+ pMethodImport = TypeToString(sig, buf, pImport);
+ }
+ }
+ else
+ {
+ buf.Append( W("<unresolved method signature>") );
+ break;
+ }
+
+ //If we have SlotInsteadOfToken set then this is a slot number (i.e. for an array)
+ if( methodFlags & ENCODE_METHOD_SIG_SlotInsteadOfToken )
+ {
+ buf.AppendPrintf( W(" method slot %d"), DacSigUncompressData(sig) );
+ }
+ else
+ {
+ // decode the methodToken (a rid is encoded)
+ RID rid = DacSigUncompressData(sig);
+
+ mdMethodDef methodToken = ((methodFlags & ENCODE_METHOD_SIG_MemberRefToken) ? mdtMemberRef : mdtMethodDef) | rid;
+
+ buf.Append( W(" ") );
+
+ // Get the full signature of method from external module
+ // Need temporary buffer because method name will be inserted
+ // in between the signature
+
+ TempBuffer tempName;
+
+ AppendTokenName( methodToken, tempName, pMethodImport );
+
+ if( methodFlags & ENCODE_METHOD_SIG_MethodInstantiation )
+ {
+ //for each generic arg, there is a type handle.
+ ULONG numParams = DacSigUncompressData(sig);
+
+ tempName.Append( W("<") );
+ for( unsigned i = 0;i < numParams; ++i )
+ {
+ if( i != 0 )
+ tempName.Append( W(", ") );
+
+ // switch back to using pImport to resolve tokens
+ TypeToString(sig, tempName, pImport);
+ }
+ tempName.Append( W(">") );
+ }
+
+ PCCOR_SIGNATURE pvSigBlob;
+ ULONG cbSigBlob;
+
+ if (methodFlags & ENCODE_METHOD_SIG_MemberRefToken)
+ {
+ IfFailThrow(pMethodImport->GetMemberRefProps(methodToken,
+ NULL,
+ NULL,
+ 0,
+ NULL,
+ &pvSigBlob,
+ &cbSigBlob));
+ }
+ else
+ {
+ IfFailThrow(pMethodImport->GetMethodProps(methodToken,
+ NULL,
+ NULL,
+ 0,
+ NULL,
+ NULL,
+ &pvSigBlob,
+ &cbSigBlob,
+ NULL,
+ NULL));
+ }
+
+ CQuickBytes prettySig;
+ ReleaseHolder<IMDInternalImport> pInternal;
+ IfFailThrow(GetMDInternalInterfaceFromPublic(pMethodImport, IID_IMDInternalImport,
+ (void**)&pInternal));
+ StackScratchBuffer buffer;
+ const ANSI * ansi = tempName.GetANSI(buffer);
+ ansi = PrettyPrintSig(pvSigBlob, cbSigBlob, ansi, &prettySig, pInternal, NULL);
+ tempName.SetANSI( ansi );
+ buf.Append(tempName);
+ }
+
+ buf.Append( W(" flags=(") );
+ EnumFlagsToString( methodFlags, s_EncodeMethodSigFlags, _countof(s_EncodeMethodSigFlags),
+ W(", "), buf );
+ buf.Append( W(")") );
+ }
+ break;
+
+ case ENCODE_FIELD_HANDLE:
+ EncodeField:
+ {
+ //Flags are first
+ DWORD fieldFlags = DacSigUncompressData(sig);
+
+ IMetaDataImport2 * pFieldImport = pImport;
+ if (pImport != NULL)
+ {
+ if (fieldFlags & ENCODE_FIELD_SIG_OwnerType)
+ {
+ pFieldImport = TypeToString(sig, buf, pImport);
+ }
+ }
+ else
+ buf.Append( W("<unresolved type>") );
+
+ if (fieldFlags & ENCODE_FIELD_SIG_IndexInsteadOfToken)
+ {
+ buf.AppendPrintf( W(" field index %d"), DacSigUncompressData(sig) );
+ }
+ else
+ {
+ // decode the methodToken (a rid is encoded)
+ RID rid = DacSigUncompressData(sig);
+
+ mdMethodDef fieldToken = ((fieldFlags & ENCODE_FIELD_SIG_MemberRefToken) ? mdtMemberRef : mdtFieldDef) | rid;
+
+ buf.Append( W(" ") );
+
+ AppendTokenName( fieldToken, buf, pFieldImport );
+ }
+ }
+ break;
+
+ case ENCODE_STRING_HANDLE:
+ token = TokenFromRid(DacSigUncompressData(sig), mdtString);
+ if (pImport != NULL)
+ AppendToken(token, buf, pImport);
+ else
+ buf.AppendPrintf( W("<unresolved token %d>"), token );
+ break;
+
+ case ENCODE_VARARGS_SIG:
+ tkType = mdtFieldDef;
+ goto DataToTokenCore;
+ case ENCODE_VARARGS_METHODREF:
+ tkType = mdtMemberRef;
+ goto DataToTokenCore;
+ case ENCODE_VARARGS_METHODDEF:
+ tkType = mdtMemberRef;
+ goto DataToTokenCore;
+DataToTokenCore:
+ token = TokenFromRid(DacSigUncompressData(sig), tkType);
+ if (pImport != NULL)
+ AppendToken(token, buf, pImport);
+ else
+ buf.AppendPrintf( "<unresolved token %d>", token );
+ break;
+
+ case ENCODE_METHOD_ENTRY:
+ buf.Append( W("Entrypoint for ") );
+ goto EncodeMethod;
+
+ case ENCODE_METHOD_ENTRY_DEF_TOKEN:
+ {
+ buf.Append( W("Entrypoint for ") );
+ token = TokenFromRid(DacSigUncompressData(sig), mdtMethodDef);
+ AppendTokenName(token, buf, pImport);
+ }
+ break;
+
+ case ENCODE_METHOD_ENTRY_REF_TOKEN:
+ {
+ buf.Append( W("Entrypoint for ref ") );
+ token = TokenFromRid(DacSigUncompressData(sig), mdtMemberRef);
+ AppendTokenName(token, buf, pImport);
+ }
+ break;
+
+ case ENCODE_VIRTUAL_ENTRY:
+ buf.Append( W("Entrypoint for ") );
+ goto EncodeMethod;
+
+ case ENCODE_VIRTUAL_ENTRY_DEF_TOKEN:
+ {
+ buf.Append( W("Virtual call for ") );
+ token = TokenFromRid(DacSigUncompressData(sig), mdtMethodDef);
+ AppendTokenName(token, buf, pImport);
+ }
+ break;
+
+ case ENCODE_VIRTUAL_ENTRY_REF_TOKEN:
+ {
+ buf.Append( W("Virtual call for ref ") );
+ token = TokenFromRid(DacSigUncompressData(sig), mdtMemberRef);
+ AppendTokenName(token, buf, pImport);
+ }
+ break;
+
+ case ENCODE_VIRTUAL_ENTRY_SLOT:
+ {
+ buf.Append( W("Virtual call for ") );
+ int slot = DacSigUncompressData(sig);
+ buf.AppendPrintf( W("slot %d "), slot );
+ goto EncodeType;
+ }
+
+ case ENCODE_MODULE_ID_FOR_STATICS:
+ buf.Append( W("Module For Statics") );
+ // No further info
+ break;
+
+ case ENCODE_MODULE_ID_FOR_GENERIC_STATICS:
+ buf.Append( W("Module For Statics for ") );
+ goto EncodeType;
+
+ case ENCODE_CLASS_ID_FOR_STATICS:
+ buf.Append( W("Statics ID for ") );
+ goto EncodeType;
+
+ case ENCODE_STATIC_FIELD_ADDRESS:
+ buf.Append( W("Static field address for ") );
+ goto EncodeField;
+
+ case ENCODE_SYNC_LOCK:
+ buf.Append( W("Synchronization handle for ") );
+ break;
+
+ case ENCODE_INDIRECT_PINVOKE_TARGET:
+ buf.Append( W("Indirect P/Invoke target for ") );
+ break;
+
+ case ENCODE_PROFILING_HANDLE:
+ buf.Append( W("Profiling handle for ") );
+ goto EncodeMethod;
+
+ case ENCODE_ACTIVE_DEPENDENCY:
+ {
+ buf.Append( W("Active dependency for ") );
+
+ int targetModuleIndex = DacSigUncompressData(sig);
+ Import *targetImport = OpenImport(targetModuleIndex);
+
+ CORCOMPILE_IMPORT_TABLE_ENTRY *entry = targetImport->entry;
+ if (entry->wAssemblyRid != 0)
+ {
+ mdToken realRef =
+ MapAssemblyRefToManifest(TokenFromRid(entry->wAssemblyRid,
+ mdtAssemblyRef),
+ m_assemblyImport);
+ AppendToken(realRef, buf, m_manifestImport);
+ buf.Append( W(" ") );
+ }
+ if (entry->wModuleRid != 0)
+ {
+ AppendToken(TokenFromRid(entry->wModuleRid, mdtFile), buf,
+ targetImport->dependency->pImport);
+ }
+ }
+ break;
+
+ default:
+ buf.Append( W("Unknown fixup kind") );
+ _ASSERTE(!"Unknown fixup kind");
+ }
+}
+
+void NativeImageDumper::WriteElementsFixupTargetAndName(RVA rva)
+{
+ if( rva == NULL )
+ {
+ /* XXX Tue 04/11/2006
+ * This should only happen for static fields. If the field is
+ * unaligned, we need an extra cell for an indirection.
+ */
+ m_display->WriteElementPointer( "FixupTargetValue", NULL );
+ m_display->WriteElementStringW( "FixupTargetName", W("NULL") );
+ return;
+ }
+
+ m_display->WriteElementPointer( "FixupTargetValue", RvaToDisplay(rva) );
+
+ TempBuffer buf;
+ FixupBlobToString(rva, buf);
+
+ m_display->WriteElementStringW( "FixupTargetName", (const WCHAR*)buf );
+}
+
+NativeImageDumper::Dependency * NativeImageDumper::GetDependency(mdAssemblyRef token, IMetaDataAssemblyImport *pImport)
+{
+ if (RidFromToken(token) == 0)
+ return OpenDependency(0);
+
+ if (pImport == NULL)
+ pImport = m_assemblyImport;
+
+ // Need to map from IL token to manifest token
+ mdAssemblyRef manifestToken = MapAssemblyRefToManifest(token, pImport);
+
+ if( manifestToken == mdAssemblyNil )
+ {
+ //this is "self"
+ return OpenDependency(0);
+ }
+
+ COUNT_T count;
+ PTR_CORCOMPILE_DEPENDENCY deps(TO_TADDR(m_decoder.GetNativeDependencies(&count)));
+
+ for (COUNT_T i = 0; i < count; i++)
+ {
+ if (deps[i].dwAssemblyRef == manifestToken)
+ return OpenDependency(i+1);
+ }
+
+ TempBuffer buf;
+ AppendTokenName(manifestToken, buf, m_manifestImport);
+ m_display->ErrorPrintF("Error: unlisted assembly dependency %S\n", (const WCHAR*)buf);
+
+ return NULL;
+}
+
+mdAssemblyRef NativeImageDumper::MapAssemblyRefToManifest(mdAssemblyRef token, IMetaDataAssemblyImport *pAssemblyImport)
+{
+ // Reference may be to self
+ if (TypeFromToken(token) == mdtAssembly)
+ return token;
+
+ // Additional tokens not originally present overflow to manifest automatically during emit
+ /* REVISIT_TODO Tue 01/31/2006
+ * Factor this code out so that it is shared with the module index code in the CLR that looks
+ * exactly thes same
+ */
+ //count the assembly refs.
+ ULONG count = 0;
+
+ HCORENUM iter = NULL;
+ for (;;)
+ {
+ ULONG tokens = 0;
+ mdAssemblyRef tmp;
+ IfFailThrow(pAssemblyImport->EnumAssemblyRefs(&iter, &tmp, 1,
+ &tokens));
+ if (tokens == 0)
+ break;
+ count ++;
+ }
+ pAssemblyImport->CloseEnum(iter);
+
+ if( RidFromToken(token) > count )
+ {
+ //out of range import. This means that it has spilled over. Subtract
+ //off the max number of assembly refs and return it as a manifest
+ //token.
+ return token - (count + 1);
+ }
+
+ ULONG cchName;
+ ASSEMBLYMETADATA metadata;
+
+ ZeroMemory(&metadata, sizeof(metadata));
+
+ IfFailThrow(pAssemblyImport->GetAssemblyRefProps(token, NULL, NULL,
+ NULL, 0, &cchName,
+ &metadata, NULL, NULL,
+ NULL));
+
+ LPWSTR szAssemblyName = NULL;
+
+ if (cchName > 0)
+ szAssemblyName = (LPWSTR) _alloca(cchName * sizeof(WCHAR));
+
+ if (metadata.cbLocale > 0)
+ metadata.szLocale = (LPWSTR) _alloca(metadata.cbLocale * sizeof(WCHAR));
+ if (metadata.ulProcessor > 0)
+ metadata.rProcessor = (DWORD*) _alloca(metadata.ulProcessor * sizeof(DWORD));
+ if (metadata.ulOS > 0)
+ metadata.rOS = (OSINFO*) _alloca(metadata.ulOS * sizeof(OSINFO));
+
+ const void *pbPublicKey;
+ ULONG cbPublicKey;
+ DWORD flags;
+ const void *pbHashValue;
+ ULONG cbHashValue;
+
+
+ IfFailThrow(pAssemblyImport->GetAssemblyRefProps(token, &pbPublicKey, &cbPublicKey,
+ szAssemblyName, cchName, NULL,
+ &metadata, &pbHashValue, &cbHashValue,
+ &flags));
+
+ //Notice that we're searching for the provided metadata for the dependency info and then looking in the
+ //image we're dumping for the dependency.
+ //
+ //Also, sometimes we find "self" in these searches. If so, return mdAssemblyDefNil as a canary value.
+
+ if( !wcscmp(szAssemblyName, m_name) )
+ {
+ //we need "self".
+ return mdAssemblyNil;
+ }
+
+ mdAssemblyRef ret = mdAssemblyRefNil;
+ /*HCORENUM*/ iter = NULL;
+ for(;;)
+ {
+ //Walk through all the assemblyRefs and search for a match. I would use DefineAssemblyRef here, but
+ //if I do it will create an assemblyRef is one is not found. Then I fail in a bad place. This
+ //way I can fail in a less bad place.
+ mdAssemblyRef currentRef;
+ //ULONG count;
+ IfFailThrow(m_manifestAssemblyImport->EnumAssemblyRefs(&iter, &currentRef, 1, &count));
+ if( 0 == count )
+ break;
+
+ //get the information about the assembly ref and compare.
+ const void * publicKeyToken;
+ ULONG pktSize = 0;
+ WCHAR name[128];
+ /*ULONG*/ cchName = _countof(name);
+ ASSEMBLYMETADATA curMD = {0};
+
+ IfFailThrow(m_manifestAssemblyImport->GetAssemblyRefProps(currentRef, &publicKeyToken, &pktSize, name,
+ cchName, &cchName, &curMD,
+ NULL /*ppbHashValue*/, NULL/*pcbHashValue*/,
+ NULL/*pdwAssemblyRefFlags*/));
+ if( !wcscmp(name, szAssemblyName) )
+ {
+ if( cbPublicKey == pktSize && !memcmp(pbPublicKey, publicKeyToken, pktSize)
+ && curMD.usMajorVersion == metadata.usMajorVersion
+ && curMD.usMinorVersion == metadata.usMinorVersion)
+ {
+ ret = currentRef;
+ break;
+ }
+ else if (wcscmp(szAssemblyName, CoreLibName_W) == 0)
+ {
+ // Mscorlib is special - version number and public key token are ignored.
+ ret = currentRef;
+ break;
+ }
+ else if (metadata.usMajorVersion == 255 &&
+ metadata.usMinorVersion == 255 &&
+ metadata.usBuildNumber == 255 &&
+ metadata.usRevisionNumber == 255)
+ {
+ // WinMDs encode all assemblyrefs with version 255.255.255.255 including CLR assembly dependencies (mscorlib, System).
+ ret = currentRef;
+ }
+ else
+ {
+ //there was an assembly with the correct name, but with the wrong version number. Let the
+ //user know.
+ m_display->ErrorPrintF("MapAssemblyRefToManifest: found %S with version %d.%d in manifest. Wanted version %d.%d.\n", szAssemblyName, curMD.usMajorVersion, curMD.usMinorVersion, metadata.usMajorVersion, metadata.usMinorVersion);
+ // TritonTODO: why?
+ ret = currentRef;
+ break;
+ }
+
+ }
+ }
+ pAssemblyImport->CloseEnum(iter);
+ if( ret == mdAssemblyRefNil )
+ {
+ TempBuffer pkt;
+ appendByteArray(pkt, (const BYTE*)pbPublicKey, cbPublicKey);
+ m_display->ErrorPrintF("MapAssemblyRefToManifest could not find token for %S, Version=%d.%d, PublicKeyToken=%S\n", szAssemblyName, metadata.usMajorVersion, metadata.usMinorVersion, (const WCHAR *)pkt);
+ _ASSERTE(!"MapAssemblyRefToManifest failed to find a match");
+ }
+
+ return ret;
+}
+
+NativeImageDumper::Import * NativeImageDumper::OpenImport(int i)
+{
+ if (m_imports == NULL)
+ {
+ COUNT_T count = m_decoder.GetNativeImportTableCount();
+ m_numImports = count;
+ m_imports = new Import [count];
+ ZeroMemory(m_imports, count * sizeof(m_imports[0]));
+ }
+
+ if (m_imports[i].entry == NULL)
+ {
+ //GetNativeImportFromIndex returns a host pointer.
+ CORCOMPILE_IMPORT_TABLE_ENTRY * entry = m_decoder.GetNativeImportFromIndex(i);
+ m_imports[i].entry = (PTR_CORCOMPILE_IMPORT_TABLE_ENTRY)(TADDR)entry;
+
+ /*
+ mdToken tok = TokenFromRid(entry->wAssemblyRid, mdtAssemblyRef);
+ Dependency * dependency = GetDependency( MapAssemblyRefToManifest(tok,
+ */
+ Dependency *dependency = GetDependency(TokenFromRid(entry->wAssemblyRid, mdtAssemblyRef));
+ m_imports[i].dependency = dependency;
+ _ASSERTE(dependency); //Why can this be null?
+
+ }
+
+ return &m_imports[i];
+}
+
+
+const NativeImageDumper::Dependency *NativeImageDumper::GetDependencyForFixup(RVA rva)
+{
+ PTR_CCOR_SIGNATURE sig = (TADDR) m_decoder.GetRvaData(rva);
+ if (*sig++ & ENCODE_MODULE_OVERRIDE)
+ {
+ unsigned idx = DacSigUncompressData(sig);
+
+ _ASSERTE(idx >= 0 && idx < (int)m_decoder.GetNativeImportTableCount());
+ return OpenImport(idx)->dependency;
+ }
+
+ return &m_dependencies[0];
+}
+
+
+void NativeImageDumper::AppendToken(mdToken token, SString& buf)
+{
+ return NativeImageDumper::AppendToken(token, buf, NULL);
+}
+void NativeImageDumper::AppendToken(mdToken token, SString& buf,
+ IMetaDataImport2 *pImport)
+{
+ IF_OPT(DISABLE_NAMES)
+ {
+ buf.Append( W("Disabled") );
+ return;
+ }
+ switch (TypeFromToken(token))
+ {
+ case mdtTypeDef:
+ buf.Append( W("TypeDef ") );
+ break;
+
+ case mdtTypeRef:
+ buf.Append( W("TypeRef ") );
+ break;
+
+ case mdtTypeSpec:
+ buf.Append( W("TypeRef ") );
+ break;
+
+ case mdtFieldDef:
+ buf.Append( W("FieldDef "));
+ break;
+
+ case mdtMethodDef:
+ buf.Append( W("MethodDef ") );
+ break;
+
+ case mdtMemberRef:
+ buf.Append( W("MemberRef ") );
+ break;
+
+ case mdtAssemblyRef:
+ buf.Append( W("AssemblyRef ") );
+ break;
+
+ case mdtFile:
+ buf.Append( W("File ") );
+ break;
+
+ case mdtString:
+ buf.Append( W("String ") );
+ break;
+
+ case mdtSignature:
+ buf.Append( W("Signature ") );
+ break;
+
+ }
+ if( RidFromToken(token) == mdTokenNil )
+ buf.Append( W("Nil") );
+ else
+ AppendTokenName(token, buf, pImport);
+}
+
+NativeImageDumper::Dependency *NativeImageDumper::OpenDependency(int index)
+{
+ CORCOMPILE_VERSION_INFO *info = m_decoder.GetNativeVersionInfo();
+
+ if (m_dependencies == NULL)
+ {
+ COUNT_T count;
+ m_decoder.GetNativeDependencies(&count);
+
+ // Add one for self
+ count++;
+
+ m_numDependencies = count;
+ m_dependencies = new Dependency [count];
+ ZeroMemory(m_dependencies, count * sizeof (Dependency));
+ }
+
+ if (m_dependencies[index].entry == NULL)
+ {
+ CORCOMPILE_DEPENDENCY *entry;
+
+ if (index == 0)
+ {
+ // Make dummy entry for self
+ entry = &m_self;
+ m_self.dwAssemblyRef = TokenFromRid(1, mdtAssembly);
+ m_self.dwAssemblyDef = TokenFromRid(1, mdtAssembly);
+ m_self.signAssemblyDef = info->sourceAssembly;
+ m_manifestImport->GetScopeProps(NULL, NULL, 0, &m_self.signNativeImage);
+ m_dependencies[index].pLoadedAddress = dac_cast<TADDR>(m_baseAddress);
+ m_dependencies[index].pPreferredBase =
+ TO_TADDR(m_decoder.GetNativePreferredBase());
+ m_dependencies[index].size = m_imageSize;
+ m_dependencies[index].pImport = m_import;
+ m_dependencies[index].pMetadataStartTarget =
+ m_MetadataStartTarget;
+ m_dependencies[index].pMetadataStartHost =
+ m_MetadataStartHost;
+ m_dependencies[index].MetadataSize = m_MetadataSize;
+ m_dependencies[index].pModule =
+ (TADDR)m_decoder.GetPersistedModuleImage();
+ m_dependencies[index].fIsHardbound = TRUE;
+ _ASSERTE( (m_dependencies[index].pModule
+ > m_dependencies[index].pLoadedAddress)
+ && (m_dependencies[index].pModule
+ < m_dependencies[index].pLoadedAddress
+ + m_dependencies[index].size) );
+ // patch the Module vtable so that the DAC is able to instantiate it
+ TADDR vtbl = DacGetTargetVtForHostVt(Module::VPtrHostVTable(), true);
+ DacWriteAll( m_dependencies[index].pModule.GetAddr(), &vtbl, sizeof(vtbl), false );
+ }
+ else
+ {
+ COUNT_T numDeps;
+ PTR_CORCOMPILE_DEPENDENCY deps(TO_TADDR(m_decoder.GetNativeDependencies(&numDeps)));
+
+ entry = deps + (index-1);
+
+ //load the dependency, get the pointer, and use the PEDecoder
+ //to open the metadata.
+
+ TempBuffer buf;
+ TADDR loadedBase;
+ /* REVISIT_TODO Tue 11/22/2005
+ * Is this the right name?
+ */
+ Dependency& dependency = m_dependencies[index];
+ AppendTokenName(entry->dwAssemblyRef, buf, m_manifestImport, true);
+ bool isHardBound = !!(entry->signNativeImage != INVALID_NGEN_SIGNATURE);
+ SString mscorlibStr(SString::Literal, CoreLibName_W);
+ bool isMscorlib = (0 == buf.Compare( mscorlibStr ));
+ dependency.fIsHardbound = isHardBound;
+ wcscpy_s(dependency.name, _countof(dependency.name),
+ (const WCHAR*)buf);
+ if( isHardBound )
+ {
+ IfFailThrow(m_librarySupport->LoadHardboundDependency((const WCHAR*)buf,
+ entry->signNativeImage, &loadedBase));
+
+ dependency.pLoadedAddress = loadedBase;
+ }
+ else
+ {
+ ASSEMBLYMETADATA asmData = {0};
+ const void * hashValue;
+ ULONG hashLength, size, flags;
+ IfFailThrow(m_manifestAssemblyImport->GetAssemblyRefProps(entry->dwAssemblyRef, &hashValue, &hashLength, bigBuffer, bigBufferSize, &size, &asmData, NULL, NULL, &flags));
+
+
+ HRESULT hr =
+ m_librarySupport->LoadSoftboundDependency((const WCHAR*)buf,
+ (const BYTE*)&asmData, (const BYTE*)hashValue, hashLength,
+ &loadedBase);
+ if( FAILED(hr) )
+ {
+ TempBuffer pkt;
+ if( hashLength > 0 )
+ {
+ appendByteArray(pkt, (BYTE*)hashValue, hashLength);
+ }
+ else
+ {
+ pkt.Set( W("<No Hash>") );
+ }
+ //try to continue without loading this softbound
+ //dependency.
+ m_display->ErrorPrintF( "WARNING Failed to load softbound dependency:\n\t%S,Version=%d.%d.0.0,PublicKeyToken=%S.\n\tAttempting to continue. May crash later in due to missing metadata\n",
+ (const WCHAR *)buf, asmData.usMajorVersion,
+ asmData.usMinorVersion, (const WCHAR *)pkt );
+ m_dependencies[index].entry = entry;
+ return &m_dependencies[index];
+
+ }
+ //save this off to the side so OpenImport can find the metadata.
+ m_dependencies[index].pLoadedAddress = loadedBase;
+ }
+ /* REVISIT_TODO Wed 11/23/2005
+ * Refactor this with OpenMetadata from above.
+ */
+ //now load the metadata from the new image.
+ PEDecoder decoder(dac_cast<PTR_VOID>(loadedBase));
+ if( isHardBound )
+ {
+ dependency.pPreferredBase =
+ TO_TADDR(decoder.GetNativePreferredBase());
+ dependency.size = decoder.Has32BitNTHeaders() ?
+ decoder.GetNTHeaders32()->OptionalHeader.SizeOfImage :
+ decoder.GetNTHeaders64()->OptionalHeader.SizeOfImage;
+ }
+ ReleaseHolder<IMetaDataDispenserEx> pDispenser;
+ IfFailThrow(MetaDataGetDispenser(CLSID_CorMetaDataDispenser,
+ IID_IMetaDataDispenserEx,
+ (void **) &pDispenser));
+
+ VARIANT opt;
+ IfFailThrow(pDispenser->GetOption(MetaDataCheckDuplicatesFor,
+ &opt));
+ V_UI4(&opt) |= MDDupAssemblyRef | MDDupFile;
+ IfFailThrow(pDispenser->SetOption(MetaDataCheckDuplicatesFor,
+ &opt));
+ if( decoder.HasNativeHeader() )
+ {
+ dependency.pModule =
+ TO_TADDR(decoder.GetPersistedModuleImage());
+ _ASSERTE( (PTR_TO_TADDR(dependency.pModule) > loadedBase)
+ && (PTR_TO_TADDR(dependency.pModule) < loadedBase +
+ decoder.GetSize()) );
+ // patch the Module vtable so that the DAC is able to instantiate it
+ TADDR vtbl = DacGetTargetVtForHostVt(Module::VPtrHostVTable(), true);
+ DacWriteAll( m_dependencies[index].pModule.GetAddr(), &vtbl, sizeof(vtbl), false );
+ }
+ else
+ {
+ dependency.pModule = NULL;
+ }
+
+ const void * data;
+ COUNT_T size;
+
+ DACCOP_IGNORE(CastBetweenAddressSpaces,"nidump is in-proc and doesn't maintain a clean separation of address spaces (target and host are the same.");
+ data = reinterpret_cast<void*>(dac_cast<TADDR>(decoder.GetMetadata(&size)));
+
+ dependency.pMetadataStartTarget = TO_TADDR(data);
+ dependency.MetadataSize = size;
+ data = PTR_READ(TO_TADDR(data), size);
+ dependency.pMetadataStartHost = TO_TADDR(data);
+ IfFailThrow(pDispenser->OpenScopeOnMemory(data, size,
+ ofRead,
+ IID_IMetaDataImport2,
+ (IUnknown **) &dependency.pImport));
+ dependency.fIsMscorlib = isMscorlib;
+ }
+
+ m_dependencies[index].entry = entry;
+
+ }
+
+ return &m_dependencies[index];
+}
+
+IMetaDataImport2* NativeImageDumper::TypeToString(PTR_CCOR_SIGNATURE &sig, SString& buf)
+{
+ return TypeToString(sig, buf, NULL);
+}
+#if 0
+void NativeImageDumper::TypeToString(PTR_CCOR_SIGNATURE &sig,
+ IMetaDataImport2 *pImport)
+{
+ CQuickBytes tmp;
+
+ if (pImport == NULL)
+ pImport = m_import;
+
+ LPCWSTR type = PrettyPrintSig( sig, INT_MAX, W(""), &tmp, pImport );
+ _ASSERTE(type);
+ m_display->ErrorPrintF( "%S", type );
+}
+#endif
+
+IMetaDataImport2 * NativeImageDumper::TypeToString(PTR_CCOR_SIGNATURE &sig,
+ SString& buf,
+ IMetaDataImport2 *pImport,
+ IMetaDataImport2 *pOrigImport /* =NULL */)
+
+{
+ IF_OPT(DISABLE_NAMES)
+ {
+ buf.Append( W("Disabled") );
+ return pImport;
+ }
+
+ if (pImport == NULL)
+ pImport = m_import;
+ if (pOrigImport == NULL)
+ pOrigImport = pImport;
+
+ IMetaDataImport2 * pRet = pImport;
+#define TYPEINFO(enumName, classSpace, className, size, gcType, isArray, isPrim, isFloat, isModifier, isGenVar) \
+ className,
+ static const char *elementNames[] = {
+#include "cortypeinfo.h"
+ };
+#undef TYPEINFO
+
+ CorElementType type = DacSigUncompressElementType(sig);
+
+ if (type == (CorElementType) ELEMENT_TYPE_MODULE_ZAPSIG)
+ {
+ unsigned idx = DacSigUncompressData(sig);
+ buf.AppendPrintf( W("module %d "), idx );
+ //switch module
+ const Import * import = OpenImport(idx);
+ pImport = import->dependency->pImport;
+
+ //if there was a module switch, return the import for the new module.
+ //This is useful for singatures, where the module index applies to
+ //subsequent tokens.
+ pRet = pImport;
+
+ type = DacSigUncompressElementType(sig);
+ }
+ if (type >= 0 && (size_t)type < _countof(elementNames)
+ && elementNames[type] != NULL)
+ {
+ buf.AppendPrintf( "%s", elementNames[type] );
+ }
+ else switch ((DWORD)type)
+ {
+ case ELEMENT_TYPE_CANON_ZAPSIG:
+ buf.Append( W("System.__Canon") );
+ break;
+
+ case ELEMENT_TYPE_NATIVE_ARRAY_TEMPLATE_ZAPSIG:
+ case ELEMENT_TYPE_NATIVE_VALUETYPE_ZAPSIG:
+ {
+ buf.Append( W("native ") );
+ TypeToString(sig, buf, pImport);
+ }
+ break;
+
+ case ELEMENT_TYPE_VALUETYPE:
+ case ELEMENT_TYPE_CLASS:
+ {
+ if (type == ELEMENT_TYPE_VALUETYPE)
+ buf.Append( W("struct ") );
+
+ mdToken token = DacSigUncompressToken(sig);
+ AppendTokenName(token, buf, pImport);
+ }
+ break;
+
+ case ELEMENT_TYPE_SZARRAY:
+ TypeToString(sig, buf, pImport);
+ buf.Append( W("[]") );
+ break;
+
+ case ELEMENT_TYPE_ARRAY:
+ {
+ TypeToString(sig, buf, pImport, pOrigImport);
+ unsigned rank = DacSigUncompressData(sig);
+ if (rank == 0)
+ buf.Append( W("[??]") );
+ else
+ {
+ size_t cbLowerBounds;
+ if (!ClrSafeInt<size_t>::multiply(rank, 2*sizeof(int), cbLowerBounds/* passed by ref */))
+ ThrowHR(COR_E_OVERFLOW);
+ int* lowerBounds = (int*) _alloca(cbLowerBounds);
+ int* sizes = &lowerBounds[rank];
+ memset(lowerBounds, 0, sizeof(int)*2*rank);
+
+ unsigned numSizes = DacSigUncompressData(sig);
+ _ASSERTE(numSizes <= rank);
+ unsigned int i;
+ for(i =0; i < numSizes; i++)
+ sizes[i] = DacSigUncompressData(sig);
+
+ unsigned numLowBounds = DacSigUncompressData(sig);
+ _ASSERTE(numLowBounds <= rank);
+ for(i = 0; i < numLowBounds; i++)
+ lowerBounds[i] = DacSigUncompressData(sig);
+
+ buf.Append(W("["));
+ for(i = 0; i < rank; i++)
+ {
+ if (sizes[i] != 0 && lowerBounds[i] != 0)
+ {
+ if (lowerBounds[i] == 0)
+ buf.AppendPrintf( W("%s"), sizes[i] );
+ else
+ {
+ buf.AppendPrintf( W("%d ..."), lowerBounds[i] );
+ if (sizes[i] != 0)
+ buf.AppendPrintf( W("%d"),
+ lowerBounds[i] + sizes[i]
+ + 1 );
+ }
+ }
+ if (i < rank-1)
+ buf.Append( W(",") );
+ }
+ buf.Append( W("]") );
+ }
+ }
+ break;
+
+ case ELEMENT_TYPE_MVAR:
+ buf.Append( W("!") );
+ // fall through
+ case ELEMENT_TYPE_VAR:
+ buf.AppendPrintf( W("!%d"), DacSigUncompressData(sig));
+ break;
+
+ case ELEMENT_TYPE_VAR_ZAPSIG:
+ {
+ buf.Append( W("var ") );
+
+ mdToken token = TokenFromRid(DacSigUncompressData(sig), mdtGenericParam);
+ AppendTokenName(token, buf, pImport);
+ }
+ break;
+
+ case ELEMENT_TYPE_GENERICINST:
+ {
+ TypeToString(sig, buf, pImport, pOrigImport);
+ unsigned ntypars = DacSigUncompressData(sig);
+ buf.Append( W("<") );
+ for (unsigned i = 0; i < ntypars; i++)
+ {
+ if (i > 0)
+ buf.Append( W(",") );
+ // switch pImport back to our original Metadata importer
+ TypeToString(sig, buf, pOrigImport, pOrigImport);
+ }
+ buf.Append( W(">") );
+ }
+ break;
+
+ case ELEMENT_TYPE_FNPTR:
+ buf.Append( W("(fnptr)") );
+ break;
+
+ // Modifiers or depedant types
+ case ELEMENT_TYPE_PINNED:
+ TypeToString(sig, buf, pImport, pOrigImport);
+ buf.Append( W(" pinned") );
+ break;
+
+ case ELEMENT_TYPE_PTR:
+ TypeToString(sig, buf, pImport, pOrigImport);
+ buf.Append( W("*") );
+ break;
+
+ case ELEMENT_TYPE_BYREF:
+ TypeToString(sig, buf, pImport, pOrigImport);
+ buf.Append( W("&") );
+ break;
+
+ case ELEMENT_TYPE_SENTINEL:
+ case ELEMENT_TYPE_END:
+ default:
+ _ASSERTE(!"Unknown Type");
+ IfFailThrow(E_FAIL);
+ break;
+ }
+ return pRet;
+}
+
+void NativeImageDumper::DumpMethods(PTR_Module module)
+{
+ COUNT_T hotCodeSize;
+ PCODE hotCode = m_decoder.GetNativeHotCode(&hotCodeSize);
+
+
+ COUNT_T codeSize;
+ PCODE code = m_decoder.GetNativeCode(&codeSize);
+
+ COUNT_T coldCodeSize;
+ PCODE coldCode = m_decoder.GetNativeColdCode(&coldCodeSize);
+
+ DisplayStartCategory( "Code", METHODS );
+ DisplayWriteElementAddress( "HotCode", DataPtrToDisplay(hotCode),
+ hotCodeSize, METHODS );
+
+ DisplayWriteElementAddress( "UnprofiledCode",
+ DataPtrToDisplay(code),
+ codeSize, METHODS );
+ DisplayWriteElementAddress( "ColdCode",
+ DataPtrToDisplay(coldCode),
+ coldCodeSize, METHODS );
+
+ PTR_CORCOMPILE_CODE_MANAGER_ENTRY codeEntry(m_decoder.GetNativeCodeManagerTable());
+
+ DisplayWriteElementAddress( "ROData",
+ RvaToDisplay(codeEntry->ROData.VirtualAddress),
+ codeEntry->ROData.Size, METHODS );
+
+ DisplayWriteElementAddress( "HotCommonCode",
+ DataPtrToDisplay(hotCode),
+ codeEntry->HotIBCMethodOffset, METHODS );
+
+ DisplayWriteElementAddress( "HotIBCMethodCode",
+ DataPtrToDisplay(hotCode
+ + codeEntry->HotIBCMethodOffset),
+ codeEntry->HotGenericsMethodOffset
+ - codeEntry->HotIBCMethodOffset,
+ METHODS );
+
+ DisplayWriteElementAddress( "HotGenericsMethodCode",
+ DataPtrToDisplay(hotCode
+ + codeEntry->HotGenericsMethodOffset),
+ hotCodeSize - codeEntry->HotGenericsMethodOffset,
+ METHODS );
+
+ DisplayWriteElementAddress( "ColdIBCMethodCode",
+ DataPtrToDisplay(coldCode),
+ codeEntry->ColdUntrainedMethodOffset,
+ METHODS );
+
+ MethodIterator mi(module, &m_decoder);
+
+ DisplayStartArray( "Methods", NULL, METHODS );
+
+ while( mi.Next() )
+ {
+ DumpCompleteMethod( module, mi );
+ }
+
+ DisplayEndArray( "Total Methods", METHODS ); //Methods
+
+ /* REVISIT_TODO Wed 12/14/2005
+ * I have this coverage read in here because there is some other data between the
+ * methods in debug builds. For now just whack the whole text section. Go
+ * back later and check out that I really got everything.
+ */
+ CoverageRead( hotCode, hotCodeSize );
+ CoverageRead( coldCode, coldCodeSize );
+#ifdef USE_CORCOMPILE_HEADER
+ CoverageRead( hotCodeTable, hotCodeTableSize );
+ CoverageRead( coldCodeTable, coldCodeTableSize );
+#endif
+
+ DisplayEndCategory( METHODS ); //Code
+
+ //m_display->StartCategory( "Methods" );
+}
+
+static SString g_holdStringOutData;
+
+static void stringOut( const char* fmt, ... )
+{
+ va_list args;
+ va_start(args, fmt);
+ g_holdStringOutData.AppendVPrintf(fmt, args);
+ va_end(args);
+}
+
+static void nullStringOut( const char * fmt, ... ) { }
+
+const NativeImageDumper::EnumMnemonics s_CorExceptionFlags[] =
+{
+#define CEF_ENTRY(f,v) NativeImageDumper::EnumMnemonics(f, v)
+ CEF_ENTRY(COR_ILEXCEPTION_CLAUSE_NONE, W("none")),
+ CEF_ENTRY(COR_ILEXCEPTION_CLAUSE_FILTER, W("filter")),
+ CEF_ENTRY(COR_ILEXCEPTION_CLAUSE_FINALLY, W("finally")),
+ CEF_ENTRY(COR_ILEXCEPTION_CLAUSE_FAULT, W("fault")),
+ CEF_ENTRY(COR_ILEXCEPTION_CLAUSE_DUPLICATED, W("duplicated")),
+#undef CEF_ENTRY
+};
+
+void NativeImageDumper::DumpCompleteMethod(PTR_Module module, MethodIterator& mi)
+{
+ PTR_MethodDesc md = mi.GetMethodDesc();
+
+#ifdef WIN64EXCEPTIONS
+ PTR_RUNTIME_FUNCTION pRuntimeFunction = mi.GetRuntimeFunction();
+#endif
+
+ //Read the GCInfo to get the total method size.
+ unsigned methodSize = 0;
+ unsigned gcInfoSize = UINT_MAX;
+
+ //parse GCInfo for size information.
+ GCInfoToken gcInfoToken = mi.GetGCInfoToken();
+ PTR_CBYTE gcInfo = dac_cast<PTR_CBYTE>(gcInfoToken.Info);
+
+ void (* stringOutFn)(const char *, ...);
+ IF_OPT(GC_INFO)
+ {
+ stringOutFn = stringOut;
+ }
+ else
+ {
+ stringOutFn = nullStringOut;
+ }
+ if (gcInfo != NULL)
+ {
+ PTR_CBYTE curGCInfoPtr = gcInfo;
+ g_holdStringOutData.Clear();
+ GCDump gcDump(gcInfoToken.Version);
+ gcDump.gcPrintf = stringOutFn;
+#if !defined(_TARGET_X86_) && defined(USE_GC_INFO_DECODER)
+ GcInfoDecoder gcInfoDecoder(gcInfoToken, DECODE_CODE_LENGTH);
+ methodSize = gcInfoDecoder.GetCodeLength();
+#endif
+
+ //dump the data to a string first so we can get the gcinfo size.
+#ifdef _TARGET_X86_
+ InfoHdr hdr;
+ stringOutFn( "method info Block:\n" );
+ curGCInfoPtr += gcDump.DumpInfoHdr(PTR_CBYTE(gcInfoToken.Info), &hdr, &methodSize, 0);
+ stringOutFn( "\n" );
+#endif
+
+ IF_OPT(METHODS)
+ {
+#ifdef _TARGET_X86_
+ stringOutFn( "PointerTable:\n" );
+ curGCInfoPtr += gcDump.DumpGCTable( curGCInfoPtr,
+ hdr,
+ methodSize, 0);
+ gcInfoSize = curGCInfoPtr - gcInfo;
+#elif defined(USE_GC_INFO_DECODER)
+ stringOutFn( "PointerTable:\n" );
+ curGCInfoPtr += gcDump.DumpGCTable( curGCInfoPtr,
+ methodSize, 0);
+ gcInfoSize = (unsigned)(curGCInfoPtr - gcInfo);
+#endif
+ }
+
+ //data is output below.
+ }
+
+ TADDR hotCodePtr = mi.GetMethodStartAddress();
+ TADDR coldCodePtr = mi.GetMethodColdStartAddress();
+
+ size_t hotCodeSize = methodSize;
+ size_t coldCodeSize = 0;
+
+ if (coldCodePtr != NULL)
+ {
+ hotCodeSize = mi.GetHotCodeSize();
+ coldCodeSize = methodSize - hotCodeSize;
+ }
+
+ _ASSERTE(!CORCOMPILE_IS_POINTER_TAGGED(PTR_TO_TADDR(md)));
+ const Dependency* mdDep = GetDependencyFromMD(md);
+ TempBuffer buffer;
+ _ASSERTE(mdDep->pImport);
+ MethodDescToString(md, buffer);
+
+ DisplayStartElement( "Method", METHODS );
+ DisplayWriteElementStringW( "Name", (const WCHAR *)buffer, METHODS );
+
+ /* REVISIT_TODO Mon 10/24/2005
+ * Do I have to annotate this?
+ */
+ DisplayWriteElementPointer("m_methodDesc",
+ DPtrToPreferredAddr(md),
+ METHODS);
+
+ DisplayStartStructure( "m_gcInfo",
+ DPtrToPreferredAddr(gcInfo),
+ gcInfoSize,
+ METHODS );
+
+ DisplayStartTextElement( "Contents", GC_INFO );
+ DisplayWriteXmlTextBlock( ("%S", (const WCHAR *)g_holdStringOutData), GC_INFO );
+ DisplayEndTextElement( GC_INFO ); //Contents
+
+ DisplayEndStructure( METHODS ); //GCInfo
+
+ PTR_CORCOMPILE_EXCEPTION_LOOKUP_TABLE pExceptionInfoTable (PTR_TO_TADDR(module->GetNGenLayoutInfo()->m_ExceptionInfoLookupTable.StartAddress()));
+ if (pExceptionInfoTable)
+ {
+ COUNT_T numLookupEntries = (COUNT_T) (module->GetNGenLayoutInfo()->m_ExceptionInfoLookupTable.Size() / sizeof(CORCOMPILE_EXCEPTION_LOOKUP_TABLE_ENTRY));
+ DWORD methodStartRVA = m_decoder.GetDataRva(TO_TADDR(hotCodePtr));
+
+ COUNT_T ehInfoSize = 0;
+ DWORD exceptionInfoRVA = NativeExceptionInfoLookupTable::LookupExceptionInfoRVAForMethod(pExceptionInfoTable,
+ numLookupEntries,
+ methodStartRVA,
+ &ehInfoSize);
+
+ if( exceptionInfoRVA != 0 )
+ {
+ PTR_CORCOMPILE_EXCEPTION_CLAUSE pExceptionInfoArray = dac_cast<PTR_CORCOMPILE_EXCEPTION_CLAUSE>(PTR_TO_TADDR(m_decoder.GetBase()) + exceptionInfoRVA);
+ COUNT_T ehCount = ehInfoSize / sizeof(CORCOMPILE_EXCEPTION_CLAUSE);
+ _ASSERTE(ehCount > 0);
+ DisplayStartArray("EHClauses", NULL, METHODS );
+ for( unsigned i = 0; i < ehCount; ++i )
+ {
+ PTR_CORCOMPILE_EXCEPTION_CLAUSE host = pExceptionInfoArray + i;
+
+ DisplayStartStructure( "Clause", DPtrToPreferredAddr(host), sizeof(PTR_CORCOMPILE_EXCEPTION_CLAUSE), METHODS);
+ DisplayWriteFieldEnumerated( Flags, host->Flags,
+ EE_ILEXCEPTION_CLAUSE,
+ s_CorExceptionFlags, W(", "),
+ METHODS );
+ DisplayWriteFieldUInt( TryStartPC, host->TryStartPC,
+ EE_ILEXCEPTION_CLAUSE, METHODS );
+ DisplayWriteFieldUInt( TryEndPC, host->TryEndPC,
+ EE_ILEXCEPTION_CLAUSE, METHODS );
+ DisplayWriteFieldUInt( HandlerStartPC,
+ host->HandlerStartPC,
+ EE_ILEXCEPTION_CLAUSE, METHODS );
+ DisplayWriteFieldUInt( HandlerEndPC,
+ host->HandlerEndPC,
+ EE_ILEXCEPTION_CLAUSE, METHODS );
+ if( host->Flags & COR_ILEXCEPTION_CLAUSE_FILTER )
+ {
+ DisplayWriteFieldUInt( FilterOffset, host->FilterOffset,
+ EE_ILEXCEPTION_CLAUSE, METHODS );
+ }
+ else if( !(host->Flags & (COR_ILEXCEPTION_CLAUSE_FAULT | COR_ILEXCEPTION_CLAUSE_FINALLY)) )
+ {
+ WriteFieldMDTokenImport( ClassToken, host->ClassToken,
+ EE_ILEXCEPTION_CLAUSE, METHODS,
+ mdDep->pImport );
+ }
+ DisplayEndStructure( METHODS ); //Clause
+ }
+ DisplayEndArray("Total EHClauses", METHODS ); // Clauses
+ }
+ }
+
+ TADDR fixupList = md->GetFixupList();
+ if (fixupList != NULL)
+ {
+ DisplayStartArray( "Fixups", NULL, METHODS );
+ DumpMethodFixups(module, fixupList);
+ DisplayEndArray(NULL, METHODS); //Fixups
+ }
+
+ DisplayStartStructure( "Code", DataPtrToDisplay(hotCodePtr), hotCodeSize,
+ METHODS );
+
+ IF_OPT(DISASSEMBLE_CODE)
+ {
+ // Disassemble hot code. Read the code into the host process.
+ /* REVISIT_TODO Mon 10/24/2005
+ * Is this align up right?
+ */
+ BYTE * codeStartHost =
+ reinterpret_cast<BYTE*>(PTR_READ(hotCodePtr,
+ (ULONG32)ALIGN_UP(hotCodeSize,
+ CODE_SIZE_ALIGN)));
+ DisassembleMethod( codeStartHost, hotCodeSize );
+ }
+ else
+ {
+ CoverageRead(hotCodePtr,
+ (ULONG32)ALIGN_UP(hotCodeSize, CODE_SIZE_ALIGN));
+ }
+
+ DisplayEndStructure(METHODS); //HotCode
+
+ if( coldCodePtr != NULL )
+ {
+ DisplayStartStructure( "ColdCode", DataPtrToDisplay(coldCodePtr),
+ coldCodeSize, METHODS );
+ IF_OPT(DISASSEMBLE_CODE)
+ {
+ // Disassemble cold code. Read the code into the host process.
+ BYTE * codeStartHost =
+ reinterpret_cast<BYTE*>(PTR_READ(coldCodePtr,
+ (ULONG32)ALIGN_UP(coldCodeSize,
+ CODE_SIZE_ALIGN)));
+ DisassembleMethod( codeStartHost, coldCodeSize );
+ }
+ else
+ {
+ CoverageRead(coldCodePtr,
+ (ULONG32)ALIGN_UP(coldCodeSize, CODE_SIZE_ALIGN));
+
+ }
+ DisplayEndStructure( METHODS ); //ColdCode
+ }
+ DisplayEndElement( METHODS ); //Method
+}
+#undef IDC_SWITCH
+
+
+
+void NativeImageDumper::DisassembleMethod(BYTE *code, SIZE_T size)
+{
+ _ASSERTE(CHECK_OPT(DISASSEMBLE_CODE));
+
+ m_display->StartTextElement( "NativeCode" );
+
+#ifdef FEATURE_MSDIS
+
+ BYTE *codeStart = code;
+
+ /* XXX Wed 8/22/2007
+ * The way I compute code size includes the switch tables at the end of the hot and/or cold section.
+ * When the disassembler gets there, it has a tendency to crash as it runs off the end of mapped
+ * memory. In order to properly compute this I need to look at the UnwindData (which is a
+ * kernel32!RUNTIME_FUNCTION structure that gives the address range for the code. However, I also need
+ * to chase through the list of funclets to make sure I disassemble everything. Instead of doing that,
+ * I'll just trap the AV.
+ */
+ EX_TRY
+ {
+ while (code < (codeStart + size))
+ {
+ const size_t count = m_dis->CbDisassemble(0, code, size);
+
+ if (count == 0)
+ {
+ m_display->WriteXmlText( "%04x\tUnknown instruction (%02x)\n", code-codeStart, *code);
+ code++;
+ continue;
+ }
+
+ /* XXX Fri 09/16/2005
+ * PTR_HOST_TO_TADDR doesn't work on interior pointers.
+ */
+ m_currentAddress = m_decoder.GetDataRva(PTR_HOST_TO_TADDR(codeStart)
+ + (code - codeStart))
+ + PTR_TO_TADDR(m_decoder.GetBase());
+
+ const size_t cinstr = m_dis->Cinstruction();
+ size_t inum = 0;
+ while (true)
+ {
+ WCHAR szOpcode[4096];
+ size_t len = m_dis->CchFormatInstr(szOpcode, _countof(szOpcode));
+ _ASSERTE(szOpcode[len-1] == 0);
+ m_display->WriteXmlText( "%04x\t%S\n", (code-codeStart) + (inum * 4), szOpcode );
+
+NEXT_INSTR:
+ if (++inum >= cinstr)
+ break;
+
+ _ASSERTE((inum * 4) < count); // IA64 has 3 instructions per bundle commonly
+ // referenced as offset 0, 4, and 8
+ if (!m_dis->FSelectInstruction(inum))
+ {
+ m_display->WriteXmlText( "%04x\tUnknown instruction within bundle\n", (code-codeStart) + (inum * 4));
+ goto NEXT_INSTR;
+ }
+ }
+
+ code += count;
+ }
+ }
+ EX_CATCH
+ {
+
+ }
+ EX_END_CATCH(SwallowAllExceptions);
+
+#else // FEATURE_MSDIS
+
+ m_display->WriteXmlText( "Disassembly not supported\n" );
+
+#endif // FEATURE_MSDIS
+
+ m_display->EndTextElement(); //NativeCode
+}
+
+SIZE_T NativeImageDumper::TranslateAddressCallback(IXCLRDisassemblySupport *dis,
+ CLRDATA_ADDRESS addr,
+ __out_ecount(nameSize) WCHAR *name, SIZE_T nameSize,
+ DWORDLONG *offset)
+{
+ NativeImageDumper *pThis = (NativeImageDumper *) dis->PvClient();
+
+ SIZE_T ret = pThis->TranslateSymbol(dis,
+ addr+(SIZE_T)pThis->m_currentAddress,
+ name, nameSize, offset);
+#ifdef _DEBUG
+ if( ret == 0 )
+ {
+ _snwprintf_s(name, nameSize, _TRUNCATE, W("@TRANSLATED ADDRESS@ %p"),
+ (TADDR)(addr + (SIZE_T)pThis->m_currentAddress) );
+ ret = wcslen(name);
+ *offset = -1;
+ }
+#endif
+ return ret;
+}
+SIZE_T NativeImageDumper::TranslateFixupCallback(IXCLRDisassemblySupport *dis,
+ CLRDATA_ADDRESS addr,
+ SIZE_T size, __out_ecount(nameSize) WCHAR *name,
+ SIZE_T nameSize,
+ DWORDLONG *offset)
+{
+ NativeImageDumper *pThis = (NativeImageDumper *) dis->PvClient();
+ if( !dis->TargetIsAddress() )
+ return 0;
+
+ TADDR taddr = TO_TADDR(pThis->m_currentAddress) + (TADDR)addr;
+ SSIZE_T targetOffset;
+ switch (size)
+ {
+ case sizeof(void*):
+ targetOffset = *PTR_SIZE_T(taddr);
+ break;
+#ifdef _WIN64
+ case sizeof(INT32):
+ targetOffset = *PTR_INT32(taddr);
+ break;
+#endif
+ case sizeof(short):
+ targetOffset = *(short*)(WORD*)PTR_WORD(taddr);
+ break;
+ case sizeof(signed char):
+ targetOffset = *PTR_SBYTE(taddr);
+ break;
+ default:
+ return 0;
+ }
+
+ CLRDATA_ADDRESS address = targetOffset + TO_TADDR(pThis->m_currentAddress) + addr + size;
+
+ SIZE_T ret = pThis->TranslateSymbol(dis, address, name, nameSize, offset);
+ if( ret == 0 )
+ {
+ _snwprintf_s(name, nameSize, _TRUNCATE, W("@TRANSLATED FIXUP@ %p"), (TADDR)address);
+ ret = wcslen(name);
+ *offset = -1;
+ }
+ return ret;
+}
+
+size_t NativeImageDumper::TranslateSymbol(IXCLRDisassemblySupport *dis,
+ CLRDATA_ADDRESS addr, __out_ecount(nameSize) WCHAR *name,
+ SIZE_T nameSize, DWORDLONG *offset)
+{
+#ifdef FEATURE_READYTORUN
+ if (m_pReadyToRunHeader != NULL)
+ return 0;
+#endif
+
+ if (isInRange((TADDR)addr))
+ {
+ COUNT_T rva = (COUNT_T)(addr - PTR_TO_TADDR(m_decoder.GetBase()));
+
+ COUNT_T helperTableSize;
+ void *helperTable = m_decoder.GetNativeHelperTable(&helperTableSize);
+
+ if (rva >= m_decoder.GetDataRva(TO_TADDR(helperTable))
+ && rva < (m_decoder.GetDataRva(TO_TADDR(helperTable))
+ +helperTableSize))
+ {
+ int helperIndex = (USHORT)*PTR_DWORD(TO_TADDR(addr));
+// _ASSERTE(helperIndex < CORINFO_HELP_COUNT);
+ // because of literal blocks we might have bogus values
+ if (helperIndex < CORINFO_HELP_COUNT)
+ _snwprintf_s(name, nameSize, _TRUNCATE, W("<%S>"), g_helperNames[helperIndex]);
+ else
+ _snwprintf_s(name, nameSize, _TRUNCATE, W("Illegal HelperIndex<%04X>"), helperIndex);
+ *offset = 0;
+ return wcslen(name);
+ }
+
+ PTR_Module module = (TADDR)m_decoder.GetPersistedModuleImage();
+ PTR_NGenLayoutInfo pNgenLayout = module->GetNGenLayoutInfo();
+
+ for (int iRange = 0; iRange < 2; iRange++)
+ {
+ if (pNgenLayout->m_CodeSections[iRange].IsInRange((TADDR)addr))
+ {
+ int MethodIndex = NativeUnwindInfoLookupTable::LookupUnwindInfoForMethod(rva, pNgenLayout->m_pRuntimeFunctions[iRange], 0, pNgenLayout->m_nRuntimeFunctions[iRange] - 1);
+ if (MethodIndex >= 0)
+ {
+#ifdef WIN64EXCEPTIONS
+ while (pNgenLayout->m_MethodDescs[iRange][MethodIndex] == 0)
+ MethodIndex--;
+#endif
+
+ PTR_RUNTIME_FUNCTION pRuntimeFunction = pNgenLayout->m_pRuntimeFunctions[iRange] + MethodIndex;
+
+ PTR_MethodDesc pMD = NativeUnwindInfoLookupTable::GetMethodDesc(pNgenLayout, pRuntimeFunction, PTR_TO_TADDR(m_decoder.GetBase()));
+ TempBuffer buf;
+ MethodDescToString( pMD, buf );
+ _snwprintf_s(name, nameSize, _TRUNCATE, W("%s "), (const WCHAR *)buf );
+ *offset = rva - RUNTIME_FUNCTION__BeginAddress(pRuntimeFunction);
+ return wcslen(name);
+ }
+ }
+ }
+
+ if (pNgenLayout->m_CodeSections[2].IsInRange((TADDR)addr))
+ {
+ int ColdMethodIndex = NativeUnwindInfoLookupTable::LookupUnwindInfoForMethod(rva, pNgenLayout->m_pRuntimeFunctions[2], 0, pNgenLayout->m_nRuntimeFunctions[2] - 1);
+ if (ColdMethodIndex >= 0)
+ {
+ PTR_RUNTIME_FUNCTION pRuntimeFunction;
+
+ PTR_CORCOMPILE_COLD_METHOD_ENTRY pColdCodeMap = pNgenLayout->m_ColdCodeMap;
+
+#ifdef WIN64EXCEPTIONS
+ while (pColdCodeMap[ColdMethodIndex].mainFunctionEntryRVA == 0)
+ ColdMethodIndex--;
+
+ pRuntimeFunction = dac_cast<PTR_RUNTIME_FUNCTION>(PTR_TO_TADDR(m_decoder.GetBase()) + pColdCodeMap[ColdMethodIndex].mainFunctionEntryRVA);
+#else
+ DWORD ColdUnwindData = pNgenLayout->m_pRuntimeFunctions[2][ColdMethodIndex].UnwindData;
+ _ASSERTE((ColdUnwindData & RUNTIME_FUNCTION_INDIRECT) != 0);
+ pRuntimeFunction = dac_cast<PTR_RUNTIME_FUNCTION>(PTR_TO_TADDR(m_decoder.GetBase()) + (ColdUnwindData & ~RUNTIME_FUNCTION_INDIRECT));
+#endif
+
+ PTR_MethodDesc pMD = NativeUnwindInfoLookupTable::GetMethodDesc(pNgenLayout, pRuntimeFunction, PTR_TO_TADDR(m_decoder.GetBase()));
+ TempBuffer buf;
+ MethodDescToString( pMD, buf );
+ _snwprintf_s(name, nameSize, _TRUNCATE, W("%s (cold region)"), (const WCHAR *)buf );
+ *offset = rva - RUNTIME_FUNCTION__BeginAddress(&pNgenLayout->m_pRuntimeFunctions[2][ColdMethodIndex]);
+ return wcslen(name);
+ }
+ }
+
+ //Dumping precodes by name requires some information from the module (the precode ranges).
+ IF_OPT_OR(PRECODES, MODULE)
+ {
+ TempBuffer precodeBuf;
+ //maybe it is a precode
+ PTR_Precode maybePrecode((TADDR)addr);
+ const char * precodeName = NULL;
+ if (isPrecode((TADDR)addr))
+ {
+ switch(maybePrecode->GetType())
+ {
+ case PRECODE_INVALID:
+ precodeName = "InvalidPrecode"; break;
+ case PRECODE_STUB:
+ precodeName = "StubPrecode"; break;
+#ifdef HAS_NDIRECT_IMPORT_PRECODE
+ case PRECODE_NDIRECT_IMPORT:
+ precodeName = "NDirectImportPrecode"; break;
+#endif // HAS_NDIRECT_IMPORT_PRECODE
+#ifdef HAS_REMOTING_PRECODE
+ case PRECODE_REMOTING:
+ precodeName = "RemotingPrecode"; break;
+#endif // HAS_REMOTING_PRECODE
+#ifdef HAS_FIXUP_PRECODE
+ case PRECODE_FIXUP:
+ precodeName = "FixupPrecode"; break;
+#endif // HAS_FIXUP_PRECODE
+#ifdef HAS_THISPTR_RETBUF_PRECODE
+ case PRECODE_THISPTR_RETBUF:
+ precodeName = "ThisPtrRetBufPrecode"; break;
+#endif // HAS_THISPTR_RETBUF_PRECODE
+ }
+
+ if( precodeName )
+ {
+ //hot or cold?
+ precodeBuf.AppendPrintf( W("%S (0x%p)"), precodeName, addr );
+ }
+ //get MethodDesc from precode and dump the target
+ PTR_MethodDesc precodeMD(maybePrecode->GetMethodDesc());
+ precodeBuf.Append( W(" for ") );
+ MethodDescToString(precodeMD, precodeBuf);
+
+ _snwprintf_s(name, nameSize, _TRUNCATE, W("%s"), (const WCHAR *)precodeBuf);
+
+ *offset = 0;
+ return wcslen(name);
+ }
+ }
+
+ PTR_CORCOMPILE_IMPORT_SECTION pImportSection = m_decoder.GetNativeImportSectionForRVA(rva);
+ if (pImportSection != NULL)
+ {
+ const char * wbRangeName = NULL;
+ switch (pImportSection->Type)
+ {
+ case CORCOMPILE_IMPORT_TYPE_EXTERNAL_METHOD:
+ wbRangeName = "ExternalMethod";
+ break;
+
+#if 0
+ case CORCOMPILE_IMPORT_TYPE_VIRTUAL_METHOD:
+ wbRangeName = "VirtualMethod";
+ break;
+
+ case CORCOMPILE_IMPORT_TYPE_STUB_DISPATCH:
+ wbRangeName = "StubDispatch";
+ break;
+#endif
+
+ // This method is only ever called for targets of direct calls right now and so the only
+ // import that can meaninfully show up here is external method thunk.
+ default:
+ return 0;
+ }
+
+ TempBuffer fixupThunkBuf;
+ fixupThunkBuf.AppendPrintf( W("%S (0x%p) for "), wbRangeName, addr );
+ FixupThunkToString(pImportSection, (TADDR)addr, fixupThunkBuf);
+
+ _snwprintf_s(name, nameSize, _TRUNCATE, W("%s"), (const WCHAR *)fixupThunkBuf);
+
+ *offset = 0;
+ return wcslen(name);
+ }
+ }
+ else if( g_dacImpl->GetJitHelperFunctionName(addr,
+ _countof(bigByteBuffer),
+ (char*)bigByteBuffer,
+ NULL ) == S_OK )
+ {
+ *offset = 0;
+ _snwprintf_s( name, nameSize, _TRUNCATE, W("%S"), bigByteBuffer );
+ return wcslen(name);
+ }
+ else
+ {
+ //check mscorwks
+ if( m_mscorwksBase <= addr &&
+ addr < (m_mscorwksBase + m_mscorwksSize) )
+ {
+ *offset = addr - m_mscorwksBase;
+ _snwprintf_s( name, nameSize, _TRUNCATE, W("clr") );
+ return wcslen(name);
+ }
+ for( COUNT_T i = 0; i < m_numDependencies; ++i )
+ {
+ const Dependency& dep = m_dependencies[i];
+ if( dep.pLoadedAddress <= addr &&
+ addr < (dep.pLoadedAddress + dep.size) )
+ {
+ *offset = addr - dep.pLoadedAddress;
+ _snwprintf_s( name, nameSize, _TRUNCATE, W("%s.ni"), dep.name );
+ return wcslen(name);
+ }
+ }
+ }
+
+ return 0;
+}
+
+BOOL NativeImageDumper::HandleFixupForMethodDump(PTR_CORCOMPILE_IMPORT_SECTION pSection, SIZE_T fixupIndex, SIZE_T *fixupCell)
+{
+ PTR_SIZE_T fixupPtr(TO_TADDR(fixupCell));
+ m_display->StartElement( "Fixup" );
+ m_display->WriteElementPointer( "Address",
+ DataPtrToDisplay( TO_TADDR(fixupCell) ) );
+ m_display->WriteElementUInt( "TaggedValue", (DWORD)*fixupPtr );
+ WriteElementsFixupBlob(pSection, *fixupPtr);
+ m_display->EndElement();
+
+ return TRUE;
+}
+
+void NativeImageDumper::DumpMethodFixups(PTR_Module module,
+ TADDR fixupList)
+{
+ _ASSERTE( CHECK_OPT(METHODS) );
+
+ COUNT_T nImportSections;
+ PTR_CORCOMPILE_IMPORT_SECTION pImportSections = m_decoder.GetNativeImportSections(&nImportSections);
+
+ //create the first element outside of the callback. The callback creates
+ //subsequent elements.
+ module->FixupDelayListAux( fixupList, this,
+ &NativeImageDumper::HandleFixupForMethodDump,
+ pImportSections, nImportSections,
+ &m_decoder );
+}
+
+IMAGE_SECTION_HEADER * NativeImageDumper::FindSection( char const * name )
+{
+ COUNT_T numberOfSections = m_decoder.GetNumberOfSections();
+ PTR_IMAGE_SECTION_HEADER curSection( m_decoder.FindFirstSection() );
+
+ for ( ; numberOfSections > 0; --numberOfSections, ++curSection )
+ {
+ if ( ! strncmp( reinterpret_cast< char * >( curSection->Name ), name, 8 ) )
+ break;
+ }
+
+ if ( ! numberOfSections )
+ return NULL;
+
+ return curSection;
+}
+
+NativeImageDumper::EnumMnemonics NativeImageDumper::s_ModulePersistedFlags[] =
+{
+#define MPF_ENTRY(f) NativeImageDumper::EnumMnemonics(Module::f, W(#f))
+ MPF_ENTRY(COMPUTED_GLOBAL_CLASS),
+
+ MPF_ENTRY(COMPUTED_STRING_INTERNING),
+ MPF_ENTRY(NO_STRING_INTERNING),
+
+ MPF_ENTRY(COMPUTED_WRAP_EXCEPTIONS),
+ MPF_ENTRY(WRAP_EXCEPTIONS),
+
+ MPF_ENTRY(COMPUTED_RELIABILITY_CONTRACT),
+
+ MPF_ENTRY(COLLECTIBLE_MODULE),
+ MPF_ENTRY(COMPUTED_IS_PRE_V4_ASSEMBLY),
+ MPF_ENTRY(IS_PRE_V4_ASSEMBLY),
+ MPF_ENTRY(DEFAULT_DLL_IMPORT_SEARCH_PATHS_IS_CACHED),
+ MPF_ENTRY(DEFAULT_DLL_IMPORT_SEARCH_PATHS_STATUS),
+
+ MPF_ENTRY(NEUTRAL_RESOURCES_LANGUAGE_IS_CACHED),
+ MPF_ENTRY(COMPUTED_METHODDEF_TO_PROPERTYINFO_MAP),
+ MPF_ENTRY(LOW_LEVEL_SYSTEM_ASSEMBLY_BY_NAME),
+#undef MPF_ENTRY
+};
+
+//VirtualSectionTypes.
+#define TEXTIFY(x) W(#x)
+static const NativeImageDumper::EnumMnemonics s_virtualSectionFlags [] =
+{
+
+#define CORCOMPILE_SECTION_IBCTYPE(ibcType, _value) NativeImageDumper::EnumMnemonics(_value, TEXTIFY(ibcType)),
+ CORCOMPILE_SECTION_IBCTYPES()
+#undef CORCOMPILE_SECTION_IBCTYPE
+
+#define CORCOMPILE_SECTION_RANGE_TYPE(rangeType, _value) NativeImageDumper::EnumMnemonics(_value, TEXTIFY(rangeType) W("Range")),
+ CORCOMPILE_SECTION_RANGE_TYPES()
+#undef CORCOMPILE_SECTION_RANGE_TYPE
+};
+const WCHAR * g_sectionNames[] =
+{
+ W("SECTION_DUMMY"), // the first section start at 0x1. Make the array 1 based.
+#define CORCOMPILE_SECTION_TYPE(section) W("SECTION_") TEXTIFY(section),
+ CORCOMPILE_SECTION_TYPES()
+#undef CORCOMPILE_SECTION
+
+};
+#undef TEXTIFY
+
+
+#ifdef _PREFAST_
+#pragma warning(push)
+#pragma warning(disable:21000) // Suppress PREFast warning about overly large function
+#endif
+
+const NativeImageDumper::EnumMnemonics s_MSDFlags[] =
+{
+#define MSD_ENTRY(f) NativeImageDumper::EnumMnemonics(ModuleSecurityDescriptorFlags_ ## f, W(#f))
+ MSD_ENTRY(IsComputed),
+#ifdef FEATURE_APTCA
+ MSD_ENTRY(IsAPTCA),
+#endif // FEATURE_APTCA
+ MSD_ENTRY(IsAllCritical),
+ MSD_ENTRY(IsAllTransparent),
+ MSD_ENTRY(IsTreatAsSafe),
+ MSD_ENTRY(IsOpportunisticallyCritical),
+ MSD_ENTRY(SkipFullTrustVerification)
+#undef MSD_ENTRY
+};
+
+void NativeImageDumper::DumpModule( PTR_Module module )
+{
+
+ //the module is the fisrt thing in the .data section. We use this fact for
+ //the sectionBases down below.
+// _ASSERTE(m_decoder.GetDataRva(PTR_TO_TADDR(module))
+// == FindSection(".data")->VirtualAddress );
+
+ DisplayStartStructure( "module", DPtrToPreferredAddr(module),
+ sizeof(*module), MODULE );
+ PTR_PEFile file = module->m_file;
+ _ASSERTE(file == NULL);
+ DisplayWriteFieldPointer( m_file, DPtrToPreferredAddr(file), Module,
+ MODULE );
+
+ PTR_MethodDesc dllMain( TO_TADDR(module->m_pDllMain) );
+ WriteFieldMethodDesc( m_pDllMain, dllMain, Module,
+ MODULE );
+
+ _ASSERTE(module->m_dwTransientFlags == 0U);
+ DisplayWriteFieldUInt(m_dwTransientFlags, module->m_dwTransientFlags,
+ Module, MODULE );
+
+
+
+ DisplayWriteFieldEnumerated( m_dwPersistedFlags, module->m_dwPersistedFlags,
+ Module, s_ModulePersistedFlags, W("|"), MODULE );
+
+ DisplayWriteFieldPointer( m_pAssembly,
+ DPtrToPreferredAddr(module->m_pAssembly),
+ Module, MODULE );
+ _ASSERTE(module->m_pAssembly == NULL); //never appears in the image
+
+ DisplayWriteFieldUInt( m_moduleRef, module->m_moduleRef, Module, MODULE );
+ DisplayWriteFieldInt( m_dwDebuggerJMCProbeCount,
+ module->m_dwDebuggerJMCProbeCount, Module, MODULE );
+ /* REVISIT_TODO Fri 10/14/2005
+ * Dump the binder
+ */
+ PTR_MscorlibBinder binder = module->m_pBinder;
+ if( NULL != binder )
+ {
+ DisplayStartStructureWithOffset( m_pBinder, DPtrToPreferredAddr(binder),
+ sizeof(*binder), Module,
+ MODULE );
+
+ //these four fields don't have anything useful in ngen images.
+ DisplayWriteFieldPointer( m_classDescriptions,
+ DPtrToPreferredAddr(binder->m_classDescriptions),
+ MscorlibBinder, MODULE );
+ DisplayWriteFieldPointer( m_methodDescriptions,
+ DPtrToPreferredAddr(binder->m_methodDescriptions),
+ MscorlibBinder, MODULE );
+ DisplayWriteFieldPointer( m_fieldDescriptions,
+ DPtrToPreferredAddr(binder->m_fieldDescriptions),
+ MscorlibBinder, MODULE );
+ DisplayWriteFieldPointer( m_pModule,
+ DPtrToPreferredAddr(binder->m_pModule),
+ MscorlibBinder, MODULE );
+
+ DisplayWriteFieldInt( m_cClasses, binder->m_cClasses, MscorlibBinder,
+ MODULE );
+ DisplayWriteFieldAddress( m_pClasses,
+ DPtrToPreferredAddr(binder->m_pClasses),
+ sizeof(*binder->m_pClasses)
+ * binder->m_cClasses,
+ MscorlibBinder, MODULE );
+ DisplayWriteFieldInt( m_cFields, binder->m_cFields, MscorlibBinder,
+ MODULE );
+ DisplayWriteFieldAddress( m_pFields,
+ DPtrToPreferredAddr(binder->m_pFields),
+ sizeof(*binder->m_pFields)
+ * binder->m_cFields,
+ MscorlibBinder, MODULE );
+ DisplayWriteFieldInt( m_cMethods, binder->m_cMethods, MscorlibBinder,
+ MODULE );
+ DisplayWriteFieldAddress( m_pMethods,
+ DPtrToPreferredAddr(binder->m_pMethods),
+ sizeof(*binder->m_pMethods)
+ * binder->m_cMethods,
+ MscorlibBinder, MODULE );
+
+ DisplayEndStructure( MODULE ); //m_pBinder
+ }
+ else
+ {
+ DisplayWriteFieldPointer( m_pBinder, NULL, Module, MODULE );
+ }
+ _ASSERTE(module->m_activeDependencies.GetCount() == 0);
+
+
+ /* REVISIT_TODO Tue 10/25/2005
+ * unconditional dependencies, activations, class dependencies, thunktable
+ */
+
+
+ //round trip the LookupMap back through the DAC so that we don't have an
+ //interior host pointer.
+ PTR_LookupMapBase lookupMap( PTR_TO_TADDR(module)
+ + offsetof(Module, m_TypeDefToMethodTableMap) );
+ TraverseMap( lookupMap, "m_TypeDefToMethodTableMap",
+ offsetof(Module, m_TypeDefToMethodTableMap),
+ fieldsize(Module, m_TypeDefToMethodTableMap),
+ &NativeImageDumper::IterateTypeDefToMTCallback );
+
+ lookupMap = PTR_LookupMapBase( PTR_TO_TADDR(module)
+ + offsetof(Module, m_TypeRefToMethodTableMap) );
+
+ TraverseMap( lookupMap, "m_TypeRefToMethodTableMap",
+ offsetof(Module, m_TypeRefToMethodTableMap),
+ fieldsize(Module, m_TypeRefToMethodTableMap),
+ &NativeImageDumper::IterateTypeRefToMTCallback );
+
+ lookupMap = PTR_LookupMapBase( PTR_TO_TADDR(module)
+ + offsetof(Module, m_MethodDefToDescMap) );
+ TraverseMap( lookupMap, "m_MethodDefToDescMap",
+ offsetof(Module, m_MethodDefToDescMap),
+ fieldsize(Module, m_MethodDefToDescMap),
+ &NativeImageDumper::IterateMethodDefToMDCallback);
+
+ lookupMap = PTR_LookupMapBase( PTR_TO_TADDR(module)
+ + offsetof(Module, m_FieldDefToDescMap) );
+ TraverseMap( lookupMap, "m_FieldDefToDescMap",
+ offsetof(Module, m_FieldDefToDescMap),
+ fieldsize(Module, m_FieldDefToDescMap),
+ &NativeImageDumper::IterateFieldDefToFDCallback);
+
+ TraverseMemberRefToDescHash(module->m_pMemberRefToDescHashTable, "m_pMemberRefToDescHashTable",
+ offsetof(Module, m_pMemberRefToDescHashTable),
+ fieldsize(Module, m_pMemberRefToDescHashTable),
+ FALSE);
+
+ lookupMap = PTR_LookupMapBase( PTR_TO_TADDR(module)
+ + offsetof(Module, m_GenericParamToDescMap) );
+
+ TraverseMap( lookupMap, "m_GenericParamToDescMap",
+ offsetof(Module, m_GenericParamToDescMap),
+ fieldsize(Module, m_GenericParamToDescMap),
+ &NativeImageDumper::IterateGenericParamToDescCallback);
+
+ lookupMap = PTR_LookupMapBase( PTR_TO_TADDR(module)
+ + offsetof(Module, m_GenericTypeDefToCanonMethodTableMap) );
+
+ TraverseMap( lookupMap, "m_GenericTypeDefToCanonMethodTableMap",
+ offsetof(Module, m_GenericTypeDefToCanonMethodTableMap),
+ fieldsize(Module, m_GenericTypeDefToCanonMethodTableMap),
+ &NativeImageDumper::IterateTypeDefToMTCallback );
+
+ lookupMap = PTR_LookupMapBase( PTR_TO_TADDR(module)
+ + offsetof(Module, m_FileReferencesMap) );
+ TraverseMap( lookupMap, "m_FileReferencesMap",
+ offsetof(Module, m_FileReferencesMap),
+ fieldsize(Module, m_FileReferencesMap),
+ &NativeImageDumper::IterateMemberRefToDescCallback);
+
+ lookupMap = PTR_LookupMapBase(PTR_TO_TADDR(module)
+ + offsetof(Module,m_ManifestModuleReferencesMap));
+
+ TraverseMap( lookupMap, "m_ManifestModuleReferencesMap",
+ offsetof(Module, m_ManifestModuleReferencesMap),
+ fieldsize(Module, m_ManifestModuleReferencesMap),
+ &NativeImageDumper::IterateManifestModules);
+
+ TraverseClassHash( module->m_pAvailableClasses, "m_pAvailableClasses",
+ offsetof(Module, m_pAvailableClasses),
+ fieldsize(Module, m_pAvailableClasses), true );
+
+ TraverseTypeHash( module->m_pAvailableParamTypes, "m_pAvailableParamTypes",
+ offsetof(Module, m_pAvailableParamTypes),
+ fieldsize(Module, m_pAvailableParamTypes) );
+ TraverseInstMethodHash( module->m_pInstMethodHashTable,
+ "m_pInstMethodHashTable",
+ offsetof(Module, m_pInstMethodHashTable),
+ fieldsize(Module, m_pInstMethodHashTable),
+ module );
+ TraverseStubMethodHash( module->m_pStubMethodHashTable,
+ "m_pStubMethodHashTable",
+ offsetof(Module, m_pStubMethodHashTable),
+ fieldsize(Module, m_pStubMethodHashTable),
+ module );
+
+ IF_OPT(MODULE)
+ {
+ TraverseClassHash( module->m_pAvailableClassesCaseIns,
+ "m_pAvailableClassesCaseIns",
+ offsetof(Module, m_pAvailableClassesCaseIns),
+ fieldsize(Module, m_pAvailableClassesCaseIns),
+ false );
+ }
+
+#ifdef FEATURE_COMINTEROP
+ TraverseGuidToMethodTableHash( module->m_pGuidToTypeHash,
+ "m_pGuidToTypeHash",
+ offsetof(Module, m_pGuidToTypeHash),
+ fieldsize(Module, m_pGuidToTypeHash),
+ true);
+
+#endif // FEATURE_COMINTEROP
+
+ _ASSERTE(module->m_pProfilingBlobTable == NULL);
+
+ DisplayWriteFieldFlag( m_nativeImageProfiling,
+ module->m_nativeImageProfiling, Module, MODULE );
+
+ DisplayWriteFieldPointer( m_methodProfileList,
+ DataPtrToDisplay((TADDR)module->m_methodProfileList),
+ Module, MODULE );
+ _ASSERTE(module->m_methodProfileList == NULL);
+
+ /* REVISIT_TODO Tue 10/04/2005
+ * Dump module->m_moduleCtorInfo
+ */
+ PTR_ModuleCtorInfo ctorInfo( PTR_HOST_MEMBER_TADDR(Module, module,
+ m_ModuleCtorInfo) );
+
+ DisplayStartStructureWithOffset( m_ModuleCtorInfo,
+ DPtrToPreferredAddr(ctorInfo),
+ sizeof(*ctorInfo),
+ Module, SLIM_MODULE_TBLS );
+ DisplayWriteFieldInt( numElements, ctorInfo->numElements, ModuleCtorInfo,
+ SLIM_MODULE_TBLS );
+ DisplayWriteFieldInt( numLastAllocated, ctorInfo->numLastAllocated,
+ ModuleCtorInfo, SLIM_MODULE_TBLS );
+ DisplayWriteFieldInt( numElementsHot, ctorInfo->numElementsHot,
+ ModuleCtorInfo, SLIM_MODULE_TBLS );
+ DisplayWriteFieldAddress( ppMT, DPtrToPreferredAddr(ctorInfo->ppMT),
+ ctorInfo->numElements * sizeof(MethodTable*),
+ ModuleCtorInfo, SLIM_MODULE_TBLS );
+ /* REVISIT_TODO Tue 03/21/2006
+ * is cctorInfoHot and cctorInfoCold actually have anything interesting
+ * inside of them?
+ */
+ DisplayWriteFieldAddress( cctorInfoHot,
+ DPtrToPreferredAddr(ctorInfo->cctorInfoHot),
+ sizeof(*ctorInfo->cctorInfoHot)
+ * ctorInfo->numElementsHot,
+ ModuleCtorInfo, SLIM_MODULE_TBLS );
+ DisplayWriteFieldAddress( cctorInfoCold,
+ DPtrToPreferredAddr(ctorInfo->cctorInfoCold),
+ sizeof(*ctorInfo->cctorInfoCold)
+ * (ctorInfo->numElements
+ - ctorInfo->numElementsHot),
+ ModuleCtorInfo, SLIM_MODULE_TBLS );
+ /* XXX Thu 03/23/2006
+ * See ModuleCtorInfo::Save for why these are +1.
+ */
+ DisplayWriteFieldAddress( hotHashOffsets,
+ DPtrToPreferredAddr(ctorInfo->hotHashOffsets),
+ (ctorInfo->numHotHashes + 1)
+ * sizeof(*ctorInfo->hotHashOffsets),
+ ModuleCtorInfo, SLIM_MODULE_TBLS );
+ DisplayWriteFieldAddress( coldHashOffsets,
+ DPtrToPreferredAddr(ctorInfo->coldHashOffsets),
+ (ctorInfo->numColdHashes + 1)
+ * sizeof(*ctorInfo->coldHashOffsets),
+ ModuleCtorInfo, SLIM_MODULE_TBLS );
+
+ DisplayWriteFieldInt( numHotHashes, ctorInfo->numHotHashes, ModuleCtorInfo,
+ SLIM_MODULE_TBLS );
+ DisplayWriteFieldInt( numColdHashes, ctorInfo->numColdHashes,
+ ModuleCtorInfo, SLIM_MODULE_TBLS );
+
+ DisplayWriteFieldAddress( ppHotGCStaticsMTs,
+ DPtrToPreferredAddr(ctorInfo->ppHotGCStaticsMTs),
+ ctorInfo->numHotGCStaticsMTs
+ * sizeof(*ctorInfo->ppHotGCStaticsMTs),
+ ModuleCtorInfo, SLIM_MODULE_TBLS );
+ DisplayWriteFieldAddress( ppColdGCStaticsMTs,
+ DPtrToPreferredAddr(ctorInfo->ppColdGCStaticsMTs),
+ ctorInfo->numColdGCStaticsMTs
+ * sizeof(*ctorInfo->ppColdGCStaticsMTs),
+ ModuleCtorInfo, SLIM_MODULE_TBLS );
+ DisplayWriteFieldInt( numHotGCStaticsMTs, ctorInfo->numHotGCStaticsMTs,
+ ModuleCtorInfo, SLIM_MODULE_TBLS );
+ DisplayWriteFieldInt( numColdGCStaticsMTs, ctorInfo->numColdGCStaticsMTs,
+ ModuleCtorInfo, SLIM_MODULE_TBLS );
+
+ DisplayEndStructure( SLIM_MODULE_TBLS ); //m_ModuleCtorInfo
+
+ _ASSERTE(module->m_pNgenStats == NULL);
+
+ DisplayWriteFieldPointer( m_pNgenStats,
+ DataPtrToDisplay((TADDR)module->m_pNgenStats),
+ Module, MODULE );
+#if defined(FEATURE_MIXEDMODE)
+ DisplayWriteFieldPointer( m_pThunkHeap,
+ DataPtrToDisplay(dac_cast<TADDR>(module->m_pThunkHeap)),
+ Module, MODULE );
+ _ASSERTE(module->m_pThunkHeap == NULL);
+#endif
+
+ DisplayWriteFieldAddress(m_propertyNameSet,
+ DPtrToPreferredAddr(module->m_propertyNameSet),
+ sizeof(module->m_propertyNameSet[0]) *
+ module->m_nPropertyNameSet,
+ Module, MODULE);
+
+ DisplayWriteFieldPointer( m_ModuleID,
+ DataPtrToDisplay(dac_cast<TADDR>(module->m_ModuleID)),
+ Module, MODULE );
+ _ASSERTE(module->m_ModuleID == NULL);
+
+ /* XXX Tue 04/11/2006
+ * Value is either -1 or 0, so no need to rebase.
+ */
+ DisplayWriteFieldPointer( m_pRegularStaticOffsets,
+ PTR_TO_TADDR(module->m_pRegularStaticOffsets),
+ Module, MODULE );
+ _ASSERTE(module->m_pRegularStaticOffsets == (void*)-1
+ || module->m_pRegularStaticOffsets == 0 );
+
+ DisplayWriteFieldInt( m_dwMaxGCRegularStaticHandles,
+ module->m_dwMaxGCRegularStaticHandles, Module, MODULE );
+ DisplayWriteFieldInt( m_dwRegularStaticsBlockSize, module->m_dwRegularStaticsBlockSize,
+ Module, MODULE );
+ DisplayWriteFieldAddress( m_pDynamicStaticsInfo,
+ DataPtrToDisplay((TADDR)module->m_pDynamicStaticsInfo),
+ module->m_maxDynamicEntries
+ * sizeof(*(module->m_pDynamicStaticsInfo)),
+ Module, MODULE );
+
+ DisplayWriteFieldInt( m_cDynamicEntries,
+ (int)module->m_cDynamicEntries, Module, MODULE );
+
+ CoverageRead(TO_TADDR(module->m_pDynamicStaticsInfo),
+ (int)(module->m_maxDynamicEntries
+ * sizeof(*(module->m_pDynamicStaticsInfo))));
+
+ DisplayWriteFieldInt( m_dwReliabilityContract,
+ module->m_dwReliabilityContract, Module, MODULE );
+
+ DisplayWriteFieldPointer( m_pCerPrepInfo,
+ DataPtrToDisplay((TADDR)module->m_pCerPrepInfo),
+ Module, MODULE );
+ DisplayWriteFieldPointer( m_pCerCrst, DataPtrToDisplay((TADDR)module->m_pCerCrst),
+ Module, MODULE );
+ _ASSERTE(module->m_pCerPrepInfo == NULL && module->m_pCerCrst == NULL);
+
+ IF_OPT_OR(MODULE, SLIM_MODULE_TBLS)
+ {
+ PTR_CerNgenRootTable table( TO_TADDR(module->m_pCerNgenRootTable) );
+ DumpNgenRootTable( table, "m_pCerNgenRootTable",
+ offsetof(Module, m_pCerNgenRootTable),
+ fieldsize(Module, m_pCerNgenRootTable) );
+ }
+
+
+ _ASSERTE(module->m_debuggerSpecificData.m_pDynamicILCrst == NULL);
+ DisplayWriteFieldPointer( m_debuggerSpecificData.m_pDynamicILCrst,
+ DataPtrToDisplay(dac_cast<TADDR>(module->m_debuggerSpecificData.m_pDynamicILCrst)),
+ Module, MODULE );
+
+
+ _ASSERTE(module->m_pModuleSecurityDescriptor);
+ PTR_ModuleSecurityDescriptor msd(TO_TADDR(module->m_pModuleSecurityDescriptor));
+ DisplayStartStructureWithOffset( m_pModuleSecurityDescriptor,
+ DPtrToPreferredAddr(msd), sizeof(*msd),
+ Module, MODULE );
+ DisplayWriteElementEnumerated("Flags", msd->GetRawFlags(), s_MSDFlags, W(", "), MODULE );
+
+ _ASSERTE(msd->GetModule() == module);
+ DisplayEndStructure(MODULE); //ModuleSecurityDescriptor
+
+ /* REVISIT_TODO Wed 09/21/2005
+ * Get me in the debugger and look at the activations and module/class
+ * dependencies.
+ * As well as the thunks.
+ */
+
+ /* REVISIT_TODO Wed 09/21/2005
+ * Dump the following
+ */
+ //file
+ //assembly
+
+ DisplayWriteFieldInt( m_DefaultDllImportSearchPathsAttributeValue,
+ module->m_DefaultDllImportSearchPathsAttributeValue, Module, MODULE );
+
+
+ DisplayEndStructure(MODULE); //Module
+}
+#ifdef _PREFAST_
+#pragma warning(pop)
+#endif
+
+bool NativeImageDumper::isPrecode(TADDR maybePrecode)
+{
+ PTR_Module module = (TADDR)m_decoder.GetPersistedModuleImage();
+
+ return !!module->IsZappedPrecode(maybePrecode);
+}
+
+void NativeImageDumper::DumpNgenRootTable( PTR_CerNgenRootTable table,
+ const char * name, unsigned offset,
+ unsigned fieldSize )
+{
+ if( table == NULL )
+ {
+ IF_OPT(MODULE)
+ {
+ m_display->WriteFieldPointer( name, (unsigned)DPtrToPreferredAddr(table),
+ offset, fieldSize );
+ }
+ return;
+ }
+ IF_OPT(MODULE)
+ {
+ m_display->StartStructureWithOffset( name, offset, fieldSize,
+ DPtrToPreferredAddr(table),
+ sizeof(*table) );
+ }
+
+ DisplayWriteFieldPointer( m_cRoots, table->GetRootCount(),
+ CerNgenRootTable, MODULE );
+ DisplayWriteFieldAddress( m_pRestoreBitmap,
+ DataPtrToDisplay((TADDR)table->GetRestoreBitmap()),
+ table->SizeOfRestoreBitmap(),
+ CerNgenRootTable, MODULE );
+ DisplayWriteFieldInt( m_cSlots, table->m_cSlots, CerNgenRootTable,
+ MODULE );
+ DisplayStartArray( "Roots", NULL, SLIM_MODULE_TBLS );
+
+ PTR_CerRoot roots(TO_TADDR(table->GetRoots()));
+ for( unsigned i = 0; i < table->GetRootCount(); ++i )
+ {
+ PTR_CerRoot root = roots + i;
+ DisplayStartStructure( "CerRoot", DPtrToPreferredAddr(root),
+ sizeof(*root), SLIM_MODULE_TBLS );
+ WriteFieldMethodDesc( m_pRootMD,
+ PTR_MethodDesc(TO_TADDR(root->m_pRootMD)),
+ CerRoot, SLIM_MODULE_TBLS );
+
+ DisplayStartArray( "MethodContexts", NULL, SLIM_MODULE_TBLS );
+
+ PTR_MethodContextElement ctx(TO_TADDR(root->m_pList));
+ bool dumpedSentinel = false;
+ while( !dumpedSentinel )
+ {
+ DisplayStartStructure( "MethodContext",
+ DPtrToPreferredAddr(ctx),
+ sizeof(*ctx), SLIM_MODULE_TBLS );
+ if( ctx->m_pMethodDesc.IsNull() )
+ dumpedSentinel = true;
+ WriteFieldMethodDesc( m_pMethodDesc,
+ ctx->m_pMethodDesc.GetValue(),
+ MethodContextElement, SLIM_MODULE_TBLS );
+
+ if (!ctx->m_pExactMT.IsNull())
+ {
+ WriteFieldMethodTable( m_pExactMT,
+ ctx->m_pExactMT.GetValue(),
+ MethodContextElement, SLIM_MODULE_TBLS );
+ }
+
+ DisplayEndStructure( SLIM_MODULE_TBLS ); //MethodContext
+ ++ctx;
+ }
+
+ DisplayEndArray( "Total Contexts", SLIM_MODULE_TBLS); //MethodContexts
+ DisplayEndStructure(SLIM_MODULE_TBLS); //CerRoot
+ }
+
+ DisplayEndArray( "Total Roots", SLIM_MODULE_TBLS ); //Roots
+
+ /* REVISIT_TODO Wed 10/05/2005
+ * m_cSlots seems to be set to something, but the number seems
+ * completely useless. What is up with that?
+ */
+
+ DisplayEndStructure( MODULE ); //CERNgenRootTable
+}
+void NativeImageDumper::IterateTypeDefToMTCallback( TADDR mtTarget,
+ TADDR flags,
+ PTR_LookupMapBase map,
+ DWORD rid )
+{
+ DisplayStartElement( "Entry", MODULE_TABLES );
+
+ PTR_MethodTable mt(mtTarget);
+
+ DisplayWriteElementUInt( "Token", rid | mdtTypeDef, MODULE_TABLES );
+ /* REVISIT_TODO Fri 10/21/2005
+ * Can I use WriteElementMethodTable here?
+ */
+ DisplayWriteElementPointer( "MethodTable", DPtrToPreferredAddr(mt),
+ MODULE_TABLES );
+ DisplayWriteElementFlag( "fake", false, MODULE_TABLES );
+ /* REVISIT_TODO Fri 09/30/2005
+ * This handles the extra entries in the type table that shouldn't be there.
+ */
+ if( rid == 0 || ((rid != 1) && (mtTarget == NULL)) )
+ {
+ DisplayWriteElementString( "Name", "mdTypeDefNil", MODULE_TABLES );
+ }
+ else
+ {
+ TempBuffer buf;
+ MethodTableToString( mt, buf );
+ DisplayWriteElementStringW( "Name", (const WCHAR*)buf, MODULE_TABLES );
+ }
+ DisplayWriteElementFlag( "hot", !!map->FindHotItemValuePtr(rid),
+ MODULE_TABLES );
+ DisplayEndElement( MODULE_TABLES );
+
+ if( isInRange(PTR_TO_TADDR(mt)) )
+ {
+ m_discoveredMTs.AppendEx(mt);
+ PTR_EEClass clazz = GetClassFromMT(mt);
+ if( isInRange(PTR_TO_TADDR(clazz)) )
+ m_discoveredClasses.AppendEx(mt);
+ }
+}
+
+void NativeImageDumper::IterateTypeRefToMTCallback( TADDR mtTarget,
+ TADDR flags,
+ PTR_LookupMapBase map,
+ DWORD rid )
+{
+ DisplayStartElement( "Entry", MODULE_TABLES );
+
+ mtTarget = ((FixupPointer<TADDR>&)mtTarget).GetValue();
+
+ PTR_MethodTable mt(mtTarget);
+
+#if 0
+ RecordTypeRef(rid | mdtTypeRef, mt);
+#endif
+
+ DisplayWriteElementUInt( "Token", rid | mdtTypeRef, MODULE_TABLES );
+
+ DisplayWriteElementPointer( "MethodTable", DPtrToPreferredAddr(mt),
+ MODULE_TABLES );
+
+ if( rid == 0 )
+ {
+ DisplayWriteElementFlag( "fake", false, MODULE_TABLES );
+ DisplayWriteElementString( "Name", "mdtTypeRefNil", MODULE_TABLES );
+ }
+ else if( mt == NULL )
+ {
+ DisplayWriteElementFlag( "fake", false, MODULE_TABLES );
+ IF_OPT(MODULE_TABLES)
+ WriteElementMDToken( "Name", mdtTypeRef | rid );
+ }
+ else if( CORCOMPILE_IS_POINTER_TAGGED(PTR_TO_TADDR(mt)) )
+ {
+ RVA rva = CORCOMPILE_UNTAG_TOKEN(PTR_TO_TADDR(mt));
+ //
+ // This guy writes two things FixupTargetValue and FixupTargetName
+ //
+ WriteElementsFixupBlob( NULL,PTR_TO_TADDR(mt));
+ }
+ else
+ {
+ TempBuffer buf;
+ MethodTableToString( mt, buf );
+ DisplayWriteElementFlag( "fake", false, MODULE_TABLES );
+ DisplayWriteElementStringW( "Name", (const WCHAR*)buf, MODULE_TABLES );
+ }
+ DisplayWriteElementFlag( "hot", !!map->FindHotItemValuePtr(rid),
+ MODULE_TABLES );
+ DisplayEndElement( MODULE_TABLES );
+ if( isInRange(mtTarget) )
+ {
+ m_discoveredMTs.AppendEx(mt);
+ PTR_EEClass clazz = GetClassFromMT(mt);
+ if( isInRange(PTR_TO_TADDR(clazz)) )
+ m_discoveredClasses.AppendEx(mt);
+ }
+}
+
+void NativeImageDumper::IterateMethodDefToMDCallback( TADDR mdTarget,
+ TADDR flags,
+ PTR_LookupMapBase map,
+ DWORD rid )
+{
+ DisplayStartElement( "Entry", MODULE_TABLES );
+
+ PTR_MethodDesc md(mdTarget);
+
+ DisplayWriteElementUInt( "Token", rid | mdtMethodDef, MODULE_TABLES );
+
+ DisplayWriteElementPointer( "MethodDesc", DPtrToPreferredAddr(md),
+ MODULE_TABLES );
+
+ DisplayWriteElementFlag( "fake", false, MODULE_TABLES );
+ if( rid == 0 )
+ {
+ DisplayWriteElementString( "Name", "mdtMethodDefNil", MODULE_TABLES );
+ }
+ else
+ {
+ TempBuffer buf;
+ MethodDescToString( md, buf );
+ DisplayWriteElementStringW( "Name", (const WCHAR*)buf, MODULE_TABLES );
+ }
+ DisplayWriteElementFlag( "hot", !!map->FindHotItemValuePtr(rid),
+ MODULE_TABLES );
+ DisplayEndElement( MODULE_TABLES );
+ //m_discoveredMDs.AppendEx(md);
+}
+
+void NativeImageDumper::IterateFieldDefToFDCallback( TADDR fdTarget,
+ TADDR flags,
+ PTR_LookupMapBase map,
+ DWORD rid )
+{
+ PTR_FieldDesc fd(fdTarget);
+ DisplayStartElement( "Entry", MODULE_TABLES );
+
+
+ DisplayWriteElementUInt( "Token", rid | mdtFieldDef, MODULE_TABLES );
+
+ DisplayWriteElementPointer( "FieldDef", DPtrToPreferredAddr(fd),
+ MODULE_TABLES );
+
+ DisplayWriteElementFlag( "fake", false, MODULE_TABLES );
+ if( rid == 0 )
+ {
+ DisplayWriteElementString( "Name", "mdtFieldDefNil", MODULE_TABLES );
+ }
+ else
+ {
+ TempBuffer buf;
+ FieldDescToString( fd, mdtFieldDef | rid, buf );
+ DisplayWriteElementStringW( "Name", (const WCHAR*)buf, MODULE_TABLES );
+ }
+ DisplayWriteElementFlag( "hot", !!map->FindHotItemValuePtr(rid),
+ MODULE_TABLES );
+ DisplayEndElement( MODULE_TABLES );
+ /* XXX Mon 10/17/2005
+ * All FieldDescs are reachable from the EEClasses
+ */
+ //m_discoveredFDs.AppendEx(PTR_FieldDesc(fdTarget));
+}
+
+void NativeImageDumper::IterateMemberRefToDescCallback( TADDR mrTarget,
+ TADDR flags,
+ PTR_LookupMapBase map,
+ DWORD rid )
+{
+ DisplayStartElement( "Entry", MODULE_TABLES );
+
+
+ bool isFieldRef = (flags & IS_FIELD_MEMBER_REF) != 0;
+ mdToken targetToken = mdtMemberRef | rid;
+ mrTarget = ((FixupPointer<TADDR>&)mrTarget).GetValue();
+ DisplayWriteElementUInt( "Token", targetToken, MODULE_TABLES );
+ DisplayWriteElementPointer( isFieldRef ? "FieldDesc" : "MethodDesc",
+ DataPtrToDisplay(mrTarget), MODULE_TABLES );
+
+ TempBuffer buf;
+ if( rid == 0 )
+ {
+ buf.Append( W("mdtMemberDefNil") );
+ }
+ else if( CORCOMPILE_IS_POINTER_TAGGED(mrTarget) )
+ {
+ WriteElementsFixupBlob( NULL, mrTarget );
+ }
+ else if( isFieldRef )
+ {
+ FieldDescToString( PTR_FieldDesc(mrTarget), buf );
+ }
+ else
+ {
+ MethodDescToString( PTR_MethodDesc(mrTarget), buf );
+ }
+ DisplayWriteElementFlag( "fake", false, MODULE_TABLES );
+ DisplayWriteElementStringW( "Name", (const WCHAR*)buf, MODULE_TABLES );
+
+ DisplayWriteElementFlag( "hot", !!map->FindHotItemValuePtr(rid),
+ MODULE_TABLES );
+ DisplayEndElement(MODULE_TABLES);
+ //m_discoveredMTs.AppendEx(mt);
+}
+
+void NativeImageDumper::IterateGenericParamToDescCallback( TADDR tdTarget,
+ TADDR flags,
+ PTR_LookupMapBase map,
+ DWORD rid )
+{
+ PTR_TypeDesc td(tdTarget);
+ DisplayStartElement( "Entry", MODULE_TABLES );
+
+
+ DisplayWriteElementUInt( "Token", rid | mdtGenericParam, MODULE_TABLES );
+
+ DisplayWriteElementPointer( "GenericParam", DPtrToPreferredAddr(td),
+ MODULE_TABLES );
+
+ DisplayWriteElementFlag( "fake", false, MODULE_TABLES );
+ if( rid == 0 || td == NULL )
+ {
+ DisplayWriteElementString( "Name", "mdtGenericParamNil", MODULE_TABLES );
+ }
+ else
+ {
+ TempBuffer buf;
+ TypeDescToString( td, buf );
+ DisplayWriteElementStringW( "Name", (const WCHAR*)buf, MODULE_TABLES );
+ }
+ DisplayWriteElementFlag( "hot", !!map->FindHotItemValuePtr(rid),
+ MODULE_TABLES );
+ DisplayEndElement( MODULE_TABLES );
+}
+
+#if 0
+void NativeImageDumper::IterateFileReferencesCallback(TADDR moduleTarget,
+ TADDR flags,
+ PTR_LookupMapBase map,
+ DWORD rid)
+{
+ DisplayStartElement( "Entry", MODULE_TABLES );
+
+ PTR_Module module(moduleTarget);
+
+ DisplayWriteElementUInt( "Token", rid | mdtFile, MODULE_TABLES );
+
+ DisplayWriteElementPointer( "Module", DPtrToPreferredAddr(module),
+ MODULE_TABLES );
+
+ DisplayWriteElementFlag( "fake", false, MODULE_TABLES );
+ if( rid == 0 || (module == NULL) )
+ {
+ DisplayWriteElementString( "Name", "mdtFileNil", MODULE_TABLES );
+ }
+ else
+ {
+ TempBuffer buf;
+ AppendTokenName(mdtFile | rid, buf);
+ DisplayWriteElementStringW( "Name", (const WCHAR*)buf, MODULE_TABLES );
+ }
+ DisplayWriteElementFlag( "hot", !!map->FindHotItemValuePtr(rid),
+ MODULE_TABLES );
+ DisplayEndElement( MODULE_TABLES );
+ //m_discoveredFDs.AppendEx(mt);
+}
+#endif
+
+void NativeImageDumper::IterateManifestModules( TADDR moduleTarget,
+ TADDR flags,
+ PTR_LookupMapBase map,
+ DWORD rid )
+{
+ DisplayStartElement( "Entry", MODULE_TABLES );
+
+ moduleTarget = ((FixupPointer<TADDR>&)moduleTarget).GetValue();
+
+ PTR_Module module(moduleTarget);
+
+ DisplayWriteElementUInt( "Token", rid | mdtAssemblyRef, MODULE_TABLES );
+
+ DisplayWriteElementPointer( "Module", DPtrToPreferredAddr(module),
+ MODULE_TABLES );
+ DisplayWriteElementFlag( "fake", false, MODULE_TABLES );
+ if( rid == 0 || (module == NULL) )
+ {
+ DisplayWriteElementString( "Name", "mdtAssemblyRefNil", MODULE_TABLES );
+ }
+ else
+ {
+ TempBuffer buf;
+ AppendTokenName(mdtAssemblyRef | rid, buf, m_import);
+ DisplayWriteElementStringW( "Name", (const WCHAR*)buf, MODULE_TABLES );
+ }
+ DisplayWriteElementFlag( "hot", !!map->FindHotItemValuePtr(rid),
+ MODULE_TABLES );
+ DisplayEndElement( MODULE_TABLES );
+ //m_discoveredFDs.AppendEx(mt);
+}
+
+void NativeImageDumper::TraverseMap(PTR_LookupMapBase map, const char * name,
+ unsigned offset, unsigned fieldSize,
+ void(NativeImageDumper::*cb)(TADDR,
+ TADDR,
+ PTR_LookupMapBase,
+ DWORD))
+{
+ if( map == NULL )
+ {
+ IF_OPT(MODULE)
+ m_display->WriteFieldPointer( name, offset, fieldSize, NULL );
+ return;
+ }
+ DisplayStartVStructure(name, MODULE);
+
+ DisplayStartArray( "Tables", W("%s"), MODULE );
+ PTR_LookupMapBase current = map;
+ do
+ {
+ DWORD cbTable = map->MapIsCompressed() ? map->cbTable : map->dwCount * sizeof(*map->pTable);
+
+ IF_OPT(MODULE)
+ {
+ DisplayWriteElementAddress( "Table",
+ DPtrToPreferredAddr(map->pTable),
+ cbTable,
+ MODULE);
+ }
+
+ CoverageRead( PTR_TO_TADDR(map->pTable), cbTable );
+ _ASSERTE(current == map || current->hotItemList == NULL);
+ current = current->pNext;
+ }while( current != NULL );
+
+ DisplayEndArray( "Total Tables", MODULE ); //Tables
+
+ DisplayWriteFieldAddress( hotItemList,
+ DPtrToPreferredAddr(map->hotItemList),
+ map->dwNumHotItems * sizeof(*map->hotItemList),
+ LookupMapBase, MODULE );
+
+ DisplayStartArray( "Map", W("[%s]: %s %s%s %s %s %s"), MODULE_TABLES );
+
+ IF_OPT_OR3(MODULE_TABLES, EECLASSES, METHODTABLES)
+ {
+ LookupMap<TADDR>::Iterator iter(dac_cast<DPTR(LookupMap<TADDR>)>(map));
+ DWORD rid = 0;
+ while(iter.Next())
+ {
+ TADDR flags = 0;
+ TADDR element = iter.GetElementAndFlags(&flags);
+ (this->*cb)( element, flags, map, rid );
+ rid++;
+ }
+
+ }
+ CoverageRead( PTR_TO_TADDR(map->hotItemList),
+ map->dwNumHotItems * sizeof(*map->hotItemList) );
+ DisplayEndArray( "Total" , MODULE_TABLES );//Map
+
+ DisplayEndVStructure(MODULE); //name
+}
+
+// Templated method containing the core code necessary to traverse hash tables based on NgenHash (see
+// vm\NgenHash.h).
+template<typename HASH_CLASS, typename HASH_ENTRY_CLASS>
+void NativeImageDumper::TraverseNgenHash(DPTR(HASH_CLASS) pTable,
+ const char * name,
+ unsigned offset,
+ unsigned fieldSize,
+ bool saveClasses,
+ void (NativeImageDumper::*DisplayEntryFunction)(void *, DPTR(HASH_ENTRY_CLASS), bool),
+ void *pContext)
+{
+ if (pTable == NULL)
+ {
+ IF_OPT(MODULE)
+ m_display->WriteFieldPointer(name, offset, fieldSize, NULL);
+ return;
+ }
+ IF_OPT(MODULE)
+ {
+ m_display->StartStructureWithOffset(name, offset, fieldSize,
+ DPtrToPreferredAddr(pTable),
+ sizeof(HASH_CLASS));
+ }
+
+ DisplayWriteFieldPointer(m_pModule,
+ DPtrToPreferredAddr(pTable->m_pModule),
+ HASH_CLASS, MODULE);
+
+ // Dump warm (volatile) entries.
+ DisplayWriteFieldUInt(m_cWarmEntries, pTable->m_cWarmEntries, HASH_CLASS, MODULE);
+ DisplayWriteFieldUInt(m_cWarmBuckets, pTable->m_cWarmBuckets, HASH_CLASS, MODULE);
+ DisplayWriteFieldAddress(m_pWarmBuckets,
+ DPtrToPreferredAddr(pTable->m_pWarmBuckets),
+ sizeof(HASH_ENTRY_CLASS*) * pTable->m_cWarmBuckets,
+ HASH_CLASS, MODULE);
+
+ // Dump hot (persisted) entries.
+ DPTR(typename HASH_CLASS::PersistedEntries) pHotEntries(PTR_HOST_MEMBER_TADDR(HASH_CLASS, pTable, m_sHotEntries));
+ DisplayStartStructureWithOffset(m_sHotEntries, DPtrToPreferredAddr(pHotEntries),
+ sizeof(typename HASH_CLASS::PersistedEntries),
+ HASH_CLASS, MODULE);
+ TraverseNgenPersistedEntries<HASH_CLASS, HASH_ENTRY_CLASS>(pTable, pHotEntries, saveClasses, DisplayEntryFunction, pContext);
+ DisplayEndStructure(MODULE); // Hot entries
+
+ // Dump cold (persisted) entries.
+ DPTR(typename HASH_CLASS::PersistedEntries) pColdEntries(PTR_HOST_MEMBER_TADDR(HASH_CLASS, pTable, m_sColdEntries));
+ DisplayStartStructureWithOffset(m_sColdEntries, DPtrToPreferredAddr(pColdEntries),
+ sizeof(typename HASH_CLASS::PersistedEntries),
+ HASH_CLASS, MODULE);
+ TraverseNgenPersistedEntries<HASH_CLASS, HASH_ENTRY_CLASS>(pTable, pColdEntries, saveClasses, DisplayEntryFunction, pContext);
+ DisplayEndStructure(MODULE); // Cold entries
+
+ DisplayEndStructure(MODULE); // pTable
+}
+
+// Helper used by TraverseNgenHash above to traverse an ngen persisted section of a table (separated out here
+// because NgenHash-based tables can have two such sections, one for hot and one for cold entries).
+template<typename HASH_CLASS, typename HASH_ENTRY_CLASS>
+void NativeImageDumper::TraverseNgenPersistedEntries(DPTR(HASH_CLASS) pTable,
+ DPTR(typename HASH_CLASS::PersistedEntries) pEntries,
+ bool saveClasses,
+ void (NativeImageDumper::*DisplayEntryFunction)(void *, DPTR(HASH_ENTRY_CLASS), bool),
+ void *pContext)
+{
+ // Display top-level fields.
+ DisplayWriteFieldUInt(m_cEntries, pEntries->m_cEntries, typename HASH_CLASS::PersistedEntries, MODULE);
+ DisplayWriteFieldUInt(m_cBuckets, pEntries->m_cBuckets, typename HASH_CLASS::PersistedEntries, MODULE);
+ DisplayWriteFieldAddress(m_pBuckets,
+ DPtrToPreferredAddr(pEntries->m_pBuckets),
+ pEntries->m_cBuckets ? pEntries->m_pBuckets->GetSize(pEntries->m_cBuckets) : 0,
+ typename HASH_CLASS::PersistedEntries, MODULE);
+ DisplayWriteFieldAddress(m_pEntries,
+ DPtrToPreferredAddr(pEntries->m_pEntries),
+ sizeof(typename HASH_CLASS::PersistedEntry) * pEntries->m_cEntries,
+ typename HASH_CLASS::PersistedEntries, MODULE);
+
+ // Display entries (or maybe just the classes referenced by those entries).
+ DisplayStartArray("Entries", NULL, SLIM_MODULE_TBLS);
+
+ // Enumerate bucket list.
+ for (DWORD i = 0; i < pEntries->m_cBuckets; ++i)
+ {
+ // Get index of the first entry and the count of entries in the bucket.
+ DWORD dwEntryId, cEntries;
+ pEntries->m_pBuckets->GetBucket(i, &dwEntryId, &cEntries);
+
+ // Loop over entries.
+ while (cEntries && (CHECK_OPT(SLIM_MODULE_TBLS)
+ || CHECK_OPT(EECLASSES)
+ || CHECK_OPT(METHODTABLES)))
+ {
+ // Lookup entry in the array via the index we have.
+ typename HASH_CLASS::PTR_PersistedEntry pEntry(PTR_TO_TADDR(pEntries->m_pEntries) +
+ (dwEntryId * sizeof(typename HASH_CLASS::PersistedEntry)));
+
+ IF_OPT(SLIM_MODULE_TBLS)
+ {
+ DisplayStartStructure("PersistedEntry",
+ DPtrToPreferredAddr(pEntry),
+ sizeof(typename HASH_CLASS::PersistedEntry), SLIM_MODULE_TBLS);
+ }
+
+ // Display entry via a member function specific to the type of hash table we're traversing. Each
+ // sub-class of NgenHash hash its own entry structure that is embedded NgenHash's entry. The
+ // helper function expects a pointer to this inner entry.
+ DPTR(HASH_ENTRY_CLASS) pInnerEntry(PTR_TO_MEMBER_TADDR(typename HASH_CLASS::PersistedEntry, pEntry, m_sValue));
+ (this->*DisplayEntryFunction)(pContext, pInnerEntry, saveClasses);
+
+ IF_OPT(SLIM_MODULE_TBLS)
+ {
+ DisplayWriteFieldUInt(m_iHashValue, pEntry->m_iHashValue,
+ typename HASH_CLASS::PersistedEntry, SLIM_MODULE_TBLS);
+
+ DisplayEndStructure(SLIM_MODULE_TBLS); // Entry
+ }
+
+ dwEntryId++;
+ cEntries--;
+ }
+ }
+
+ DisplayEndArray("Total Entries", SLIM_MODULE_TBLS); // Entry array
+}
+
+void NativeImageDumper::TraverseClassHashEntry(void *pContext, PTR_EEClassHashEntry pEntry, bool saveClasses)
+{
+ IF_OPT(SLIM_MODULE_TBLS)
+ {
+ DisplayStartStructure("EEClassHashEntry",
+ DPtrToPreferredAddr(pEntry),
+ sizeof(EEClassHashEntry), SLIM_MODULE_TBLS);
+ }
+
+ size_t datum = size_t(PTR_TO_TADDR(pEntry->GetData()));
+
+ if (datum & EECLASSHASH_TYPEHANDLE_DISCR)
+ {
+ IF_OPT(SLIM_MODULE_TBLS)
+ {
+ /* REVISIT_TODO Tue 10/25/2005
+ * Raw data with annotation?
+ */
+ mdTypeDef tk;
+ tk = EEClassHashTable::UncompressModuleAndClassDef(pEntry->GetData());
+ DoWriteFieldMDToken("Token",
+ offsetof(EEClassHashEntry, m_Data),
+ fieldsize(EEClassHashEntry, m_Data),
+ tk);
+ }
+ }
+ else
+ {
+ PTR_MethodTable pMT(TO_TADDR(datum));
+ IF_OPT(SLIM_MODULE_TBLS)
+ {
+ DoWriteFieldMethodTable("MethodTable",
+ offsetof(EEClassHashEntry, m_Data),
+ fieldsize(EEClassHashEntry, m_Data),
+ pMT);
+ }
+
+ if (saveClasses)
+ {
+ // These are MethodTables. Get back to the EEClass from there.
+ if (isInRange(PTR_TO_TADDR(pMT)))
+ m_discoveredMTs.AppendEx(pMT);
+ if (pMT != NULL)
+ {
+ PTR_EEClass pClass = GetClassFromMT(pMT);
+ if (isInRange(PTR_TO_TADDR(pClass)))
+ m_discoveredClasses.AppendEx(pMT);
+ }
+ }
+ }
+
+ IF_OPT(SLIM_MODULE_TBLS)
+ {
+ DisplayWriteFieldPointer(m_pEncloser,
+ DPtrToPreferredAddr(pEntry->GetEncloser()),
+ EEClassHashEntry, SLIM_MODULE_TBLS);
+ DisplayEndStructure(SLIM_MODULE_TBLS);
+ }
+}
+
+void NativeImageDumper::TraverseClassHash(PTR_EEClassHashTable pTable,
+ const char * name,
+ unsigned offset,
+ unsigned fieldSize,
+ bool saveClasses)
+{
+ TraverseNgenHash<EEClassHashTable, EEClassHashEntry>(pTable,
+ name,
+ offset,
+ fieldSize,
+ saveClasses,
+ &NativeImageDumper::TraverseClassHashEntry,
+ NULL);
+}
+
+#ifdef FEATURE_COMINTEROP
+
+void NativeImageDumper::TraverseGuidToMethodTableEntry(void *pContext, PTR_GuidToMethodTableEntry pEntry, bool saveClasses)
+{
+ IF_OPT(SLIM_MODULE_TBLS)
+ {
+ DisplayStartStructure("GuidToMethodTableEntry",
+ DPtrToPreferredAddr(pEntry),
+ sizeof(GuidToMethodTableEntry), SLIM_MODULE_TBLS);
+ }
+
+ WriteFieldMethodTable(m_pMT, pEntry->m_pMT, GuidToMethodTableEntry, ALWAYS);
+
+ TempBuffer buf;
+ GuidToString( *(pEntry->m_Guid), buf );
+ DisplayWriteFieldStringW( m_Guid, (const WCHAR *)buf, GuidToMethodTableEntry, ALWAYS );
+
+ DisplayEndStructure( SLIM_MODULE_TBLS );
+}
+
+void NativeImageDumper::TraverseGuidToMethodTableHash(PTR_GuidToMethodTableHashTable pTable,
+ const char * name,
+ unsigned offset,
+ unsigned fieldSize,
+ bool saveClasses)
+{
+ TraverseNgenHash<GuidToMethodTableHashTable, GuidToMethodTableEntry>(pTable,
+ name,
+ offset,
+ fieldSize,
+ saveClasses,
+ &NativeImageDumper::TraverseGuidToMethodTableEntry,
+ NULL);
+}
+
+#endif // FEATURE_COMINTEROP
+
+void NativeImageDumper::TraverseMemberRefToDescHashEntry(void *pContext, PTR_MemberRefToDescHashEntry pEntry, bool saveClasses)
+{
+ IF_OPT(SLIM_MODULE_TBLS)
+ {
+ DisplayStartStructure("MemberRefToDescHashEntry",
+ DPtrToPreferredAddr(pEntry),
+ sizeof(MemberRefToDescHashEntry), SLIM_MODULE_TBLS);
+ }
+
+ if(pEntry->m_value & IS_FIELD_MEMBER_REF)
+ WriteFieldFieldDesc(m_value, dac_cast<PTR_FieldDesc>(pEntry->m_value & (~MEMBER_REF_MAP_ALL_FLAGS)), MemberRefToDescHashEntry, MODULE_TABLES);
+ else
+ WriteFieldMethodDesc(m_value, dac_cast<PTR_MethodDesc>(pEntry->m_value), MemberRefToDescHashEntry, MODULE_TABLES);
+
+ DisplayEndStructure( SLIM_MODULE_TBLS );
+}
+
+void NativeImageDumper::TraverseMemberRefToDescHash(PTR_MemberRefToDescHashTable pTable,
+ const char * name,
+ unsigned offset,
+ unsigned fieldSize,
+ bool saveClasses)
+{
+ TraverseNgenHash<MemberRefToDescHashTable, MemberRefToDescHashEntry>(pTable,
+ name,
+ offset,
+ fieldSize,
+ saveClasses,
+ &NativeImageDumper::TraverseMemberRefToDescHashEntry,
+ NULL);
+}
+
+
+void NativeImageDumper::TraverseTypeHashEntry(void *pContext, PTR_EETypeHashEntry pEntry, bool saveClasses)
+{
+ TypeHandle th = pEntry->GetTypeHandle();
+ IF_OPT(SLIM_MODULE_TBLS)
+ {
+ DisplayStartStructure("EETypeHashEntry",
+ DPtrToPreferredAddr(pEntry),
+ sizeof(EETypeHashEntry), SLIM_MODULE_TBLS);
+
+ DoWriteFieldTypeHandle("TypeHandle",
+ offsetof(EETypeHashEntry, m_data),
+ fieldsize(EETypeHashEntry, m_data),
+ th);
+ }
+
+ if (!CORCOMPILE_IS_POINTER_TAGGED(th.AsTAddr()) && th.IsTypeDesc())
+ {
+ PTR_TypeDesc td(th.AsTypeDesc());
+ if (isInRange(PTR_TO_TADDR(td)))
+ m_discoveredTypeDescs.AppendEx(td);
+ if (td->HasTypeParam())
+ {
+ PTR_ParamTypeDesc ptd(td);
+
+ /* REVISIT_TODO Thu 12/15/2005
+ * Check OwnsTemplateMethodTable. However, this asserts in
+ * this special completely unrestored and messed up state
+ * (also, it chases through MT->GetClass()). There isn't
+ * all that much harm here (bloats m_discoveredMTs though,
+ * but not by a huge amount.
+ */
+ PTR_MethodTable mt(ptd->m_TemplateMT.GetValue());
+ if (isInRange(PTR_TO_TADDR(mt)))
+ {
+ m_discoveredMTs.AppendEx(mt);
+ if (mt->IsClassPointerValid())
+ {
+ PTR_EEClass pClass = mt->GetClass();
+ if (isInRange(PTR_TO_TADDR(pClass)))
+ m_discoveredClasses.AppendEx(mt);
+ }
+ }
+ }
+ }
+ else
+ {
+ PTR_MethodTable mt(th.AsTAddr());
+
+ if (isInRange( PTR_TO_TADDR(mt)))
+ m_discoveredMTs.AppendEx(mt);
+ //don't use GetClassFromMT here. mt->m_pEEClass might be a
+ //fixup. In that case, just skip it.
+ if (mt->IsClassPointerValid())
+ {
+ PTR_EEClass pClass = mt->GetClass();
+ if (isInRange(PTR_TO_TADDR(pClass)))
+ m_discoveredClasses.AppendEx(mt);
+ }
+ }
+
+ IF_OPT(SLIM_MODULE_TBLS)
+ {
+ DisplayEndStructure(SLIM_MODULE_TBLS);
+ }
+}
+
+void NativeImageDumper::TraverseTypeHash(PTR_EETypeHashTable pTable,
+ const char * name,
+ unsigned offset,
+ unsigned fieldSize)
+{
+ TraverseNgenHash<EETypeHashTable, EETypeHashEntry>(pTable,
+ name,
+ offset,
+ fieldSize,
+ true,
+ &NativeImageDumper::TraverseTypeHashEntry,
+ NULL);
+}
+
+void NativeImageDumper::TraverseInstMethodHashEntry(void *pContext, PTR_InstMethodHashEntry pEntry, bool saveClasses)
+{
+ PTR_Module pModule((TADDR)pContext);
+
+ IF_OPT(SLIM_MODULE_TBLS)
+ {
+ DisplayStartStructure("InstMethodHashEntry",
+ DPtrToPreferredAddr(pEntry),
+ sizeof(InstMethodHashEntry), SLIM_MODULE_TBLS);
+ }
+
+ IF_OPT_OR(SLIM_MODULE_TBLS, METHODDESCS)
+ {
+ IF_OPT(METHODDESCS)
+ {
+ PTR_MethodDesc md = pEntry->GetMethod();
+ _ASSERTE(md != NULL);
+
+ //if we want methoddescs, write the data field as a
+ //structure with the whole contents of the method desc.
+ m_display->StartVStructureWithOffset("data", offsetof(InstMethodHashEntry, data),
+ sizeof(pEntry->data));
+ DumpMethodDesc(md, pModule);
+ DisplayEndVStructure(ALWAYS); //data
+ }
+ else
+ {
+ PTR_MethodDesc md = pEntry->GetMethod();
+ WriteFieldMethodDesc(data, md,
+ InstMethodHashEntry, ALWAYS);
+ }
+ }
+ else
+ CoverageRead(PTR_TO_TADDR(pEntry), sizeof(*pEntry));
+
+ IF_OPT(SLIM_MODULE_TBLS)
+ {
+ DisplayEndStructure(SLIM_MODULE_TBLS);
+ }
+}
+
+void NativeImageDumper::TraverseStubMethodHashEntry(void *pContext, PTR_StubMethodHashEntry pEntry, bool saveClasses)
+{
+ PTR_Module pModule((TADDR)pContext);
+
+ IF_OPT(SLIM_MODULE_TBLS)
+ {
+ DisplayStartStructure("StubMethodHashEntry",
+ DPtrToPreferredAddr(pEntry),
+ sizeof(StubMethodHashEntry), SLIM_MODULE_TBLS);
+ }
+
+ IF_OPT_OR(SLIM_MODULE_TBLS, METHODDESCS)
+ {
+ PTR_MethodDesc md = pEntry->GetMethod();
+ _ASSERTE(md != NULL);
+
+ PTR_MethodDesc stub = pEntry->GetStubMethod();
+ _ASSERTE(stub != NULL);
+
+ IF_OPT(METHODDESCS)
+ {
+ //if we want methoddescs, write the data fields as a
+ //structure with the whole contents of the method desc.
+ m_display->StartVStructureWithOffset("pMD", offsetof(StubMethodHashEntry, pMD),
+ sizeof(pEntry->pMD));
+ DumpMethodDesc(md, pModule);
+ DisplayEndVStructure(ALWAYS); //pMD
+
+ m_display->StartVStructureWithOffset("pStubMD", offsetof(StubMethodHashEntry, pStubMD),
+ sizeof(pEntry->pStubMD));
+ DumpMethodDesc(stub, pModule);
+ DisplayEndVStructure(ALWAYS); //pStubMD
+ }
+ else
+ {
+ WriteFieldMethodDesc(pMD, md,
+ StubMethodHashEntry, ALWAYS);
+ WriteFieldMethodDesc(pStubMD, stub,
+ StubMethodHashEntry, ALWAYS);
+ }
+ }
+ else
+ CoverageRead(PTR_TO_TADDR(pEntry), sizeof(*pEntry));
+
+ IF_OPT(SLIM_MODULE_TBLS)
+ {
+ DisplayEndStructure(SLIM_MODULE_TBLS);
+ }
+}
+
+void NativeImageDumper::TraverseInstMethodHash(PTR_InstMethodHashTable pTable,
+ const char * name,
+ unsigned fieldOffset,
+ unsigned fieldSize,
+ PTR_Module module)
+{
+ TraverseNgenHash<InstMethodHashTable, InstMethodHashEntry>(pTable,
+ name,
+ fieldOffset,
+ fieldSize,
+ true,
+ &NativeImageDumper::TraverseInstMethodHashEntry,
+ (void*)dac_cast<TADDR>(module));
+}
+
+void NativeImageDumper::TraverseStubMethodHash(PTR_StubMethodHashTable pTable,
+ const char * name,
+ unsigned fieldOffset,
+ unsigned fieldSize,
+ PTR_Module module)
+{
+ TraverseNgenHash<StubMethodHashTable, StubMethodHashEntry>(pTable,
+ name,
+ fieldOffset,
+ fieldSize,
+ true,
+ &NativeImageDumper::TraverseStubMethodHashEntry,
+ (void*)dac_cast<TADDR>(module));
+}
+
+const NativeImageDumper::Dependency *
+NativeImageDumper::GetDependencyForModule( PTR_Module module )
+{
+ for( COUNT_T i = 0; i < m_numDependencies; ++i )
+ {
+ if( m_dependencies[i].pModule == module )
+ return &m_dependencies[i];
+ }
+ return NULL;
+}
+
+#if 0
+const NativeImageDumper::Import *
+NativeImageDumper::GetImportForPointer( TADDR ptr )
+{
+ for( int i = 0; i < m_numImports; ++i )
+ {
+ const Import * import = &m_imports[i];
+ if( import->dependency->pPreferredBase == NULL )
+ continue;
+ if( import->dependency->pPreferredBase <= ptr
+ && ((import->dependency->pPreferredBase
+ + import->dependency->size) > ptr) )
+ {
+ //found the right target
+ return import;
+ }
+ }
+ return NULL;
+}
+#endif
+const NativeImageDumper::Dependency *
+NativeImageDumper::GetDependencyForPointer( TADDR ptr )
+{
+ for( COUNT_T i = 0; i < m_numDependencies; ++i )
+ {
+ const Dependency * dependency = &m_dependencies[i];
+ if( dependency->pLoadedAddress == NULL )
+ continue;
+ if( dependency->pLoadedAddress <= ptr
+ && ((dependency->pLoadedAddress + dependency->size) > ptr) )
+ {
+ //found the right target
+ return dependency;
+ }
+ }
+ return NULL;
+}
+
+void NativeImageDumper::DictionaryToArgString( PTR_Dictionary dictionary, unsigned numArgs, SString& buf )
+{
+ //this can be called with numArgs == 0 for value type instantiations.
+ buf.Append( W("<") );
+
+ for( unsigned i = 0; i < numArgs; ++i )
+ {
+ if( i > 0 )
+ buf.Append( W(",") );
+
+ TypeHandle th = dictionary->GetInstantiation()[i].GetValue();
+ if( CORCOMPILE_IS_POINTER_TAGGED(th.AsTAddr()) )
+ {
+ if (!isSelf(GetDependencyForPointer(PTR_TO_TADDR(dictionary))))
+ {
+ //this is an RVA from another hardbound dependency. We cannot decode it
+ buf.Append(W("OUT_OF_MODULE_FIXUP"));
+ }
+ else
+ {
+ RVA rva = CORCOMPILE_UNTAG_TOKEN(th.AsTAddr());
+ FixupBlobToString(rva, buf);
+ }
+ }
+ else
+ {
+ TypeHandleToString( th, buf );
+ }
+ }
+ buf.Append( W(">") );
+}
+
+void NativeImageDumper::MethodTableToString( PTR_MethodTable mt, SString& buf )
+{
+ bool hasCompleteExtents = true;
+ IF_OPT(DISABLE_NAMES)
+ {
+ buf.Append( W("Disabled") );
+ return;
+ }
+ mdToken token = mdTokenNil;
+ if( mt == NULL )
+ buf.Append( W("mdTypeDefNil") );
+ else
+ {
+ _ASSERTE(!CORCOMPILE_IS_POINTER_TAGGED(PTR_TO_TADDR(mt)));
+ const Dependency * dependency;
+ if( !mt->IsClassPointerValid() )
+ {
+ if( isSelf(GetDependencyForPointer(PTR_TO_TADDR(mt))) )
+ {
+
+ hasCompleteExtents = false;
+ RVA rva = CORCOMPILE_UNTAG_TOKEN(mt->GetCanonicalMethodTableFixup());
+ PTR_CCOR_SIGNATURE sig = (TADDR) m_decoder.GetRvaData(rva);
+
+ BYTE kind = *sig++;
+
+ if (kind & ENCODE_MODULE_OVERRIDE)
+ {
+ /* int moduleIndex = */ DacSigUncompressData(sig);
+ kind &= ~ENCODE_MODULE_OVERRIDE;
+ }
+
+ _ASSERTE(kind == ENCODE_TYPE_HANDLE);
+ CorElementType et = DacSigUncompressElementType(sig);
+ if( et == ELEMENT_TYPE_GENERICINST )
+ {
+ //generic instances have another element type
+ et = DacSigUncompressElementType(sig);
+ }
+ if (et == ELEMENT_TYPE_VALUETYPE || et == ELEMENT_TYPE_CLASS)
+ {
+ token = DacSigUncompressToken(sig);
+ }
+ else
+ {
+ // Arrays, etc.
+ token = mdtTypeDef;
+ }
+ dependency = GetDependencyForFixup(rva);
+ }
+ else
+ {
+ //this is an RVA from another hardbound dependency. We cannot decode it
+ buf.Append(W("OUT_OF_MODULE_FIXUP"));
+ return;
+ }
+ }
+ else
+ {
+ token = mt->GetCl();
+ dependency = GetDependencyFromMT(mt);
+ }
+
+ if( !isSelf(dependency) )
+ {
+ AppendTokenName( dependency->entry->dwAssemblyRef, buf,
+ m_manifestImport );
+ buf.Append(W("!"));
+ }
+
+ _ASSERTE(dependency->pImport);
+ if( token == mdtTypeDef )
+ buf.Append( W("No Token") );
+ else
+ AppendTokenName( token, buf, dependency->pImport );
+
+ if( mt->HasPerInstInfo() )
+ {
+ unsigned numDicts;
+ if( hasCompleteExtents )
+ {
+ numDicts = mt->GetNumDicts();
+ _ASSERTE(numDicts == CountDictionariesInClass(token, dependency->pImport));
+ }
+ else
+ {
+ numDicts = (DWORD)CountDictionariesInClass(token, dependency->pImport);
+ }
+ PTR_Dictionary dictionary( mt->GetPerInstInfo()[numDicts-1] );
+ unsigned numArgs = mt->GetNumGenericArgs();
+
+ DictionaryToArgString( dictionary, numArgs, buf );
+ }
+ }
+}
+
+mdToken NativeImageDumper::ConvertToTypeDef( mdToken typeToken, IMetaDataImport2* (&pImport) )
+{
+ _ASSERTE( (TypeFromToken(typeToken) == mdtTypeDef) || (TypeFromToken(typeToken) == mdtTypeRef)
+ || (TypeFromToken(typeToken) == mdtTypeSpec) );
+ if( mdtTypeDef == TypeFromToken(typeToken) )
+ return typeToken;
+ if( mdtTypeRef == TypeFromToken(typeToken) )
+ {
+ //convert the ref to a def.
+ mdToken scope;
+ WCHAR trName[MAX_CLASS_NAME];
+ ULONG trNameLen;
+ IfFailThrow(pImport->GetTypeRefProps(typeToken, &scope, trName, _countof(trName), &trNameLen));
+ _ASSERTE(trName[trNameLen-1] == 0);
+
+ //scope is now a moduleRef or assemblyRef. Find the IMetaData import for that Ref
+ /* REVISIT_TODO Fri 10/6/2006
+ * How do I handle moduleRefs?
+ */
+ _ASSERTE(TypeFromToken(scope) == mdtAssemblyRef);
+ ReleaseHolder<IMetaDataAssemblyImport> pAssemblyImport;
+ IfFailThrow(pImport->QueryInterface(IID_IMetaDataAssemblyImport,
+ (void **)&pAssemblyImport));
+ NativeImageDumper::Dependency * dep = GetDependency(scope, pAssemblyImport);
+
+ pImport = dep->pImport;
+
+ /* REVISIT_TODO Fri 10/6/2006
+ * Does this work for inner types?
+ */
+ //now I have the correct MetaData. Find the typeDef
+ HRESULT hr = pImport->FindTypeDefByName(trName, mdTypeDefNil, &typeToken);
+ while (hr == CLDB_E_RECORD_NOTFOUND)
+ {
+ // No matching TypeDef, try ExportedType
+ pAssemblyImport = NULL;
+ IfFailThrow(pImport->QueryInterface(IID_IMetaDataAssemblyImport,
+ (void **)&pAssemblyImport));
+ mdExportedType tkExportedType = mdExportedTypeNil;
+ IfFailThrow(pAssemblyImport->FindExportedTypeByName(trName, mdExportedTypeNil, &tkExportedType));
+ mdToken tkImplementation;
+ IfFailThrow(pAssemblyImport->GetExportedTypeProps(tkExportedType, NULL, 0, NULL, &tkImplementation, NULL, NULL));
+ dep = GetDependency(tkImplementation, pAssemblyImport);
+
+ pImport = dep->pImport;
+ hr = pImport->FindTypeDefByName(trName, mdTypeDefNil, &typeToken);
+ }
+ IfFailThrow(hr);
+ }
+ else
+ {
+ PCCOR_SIGNATURE pSig;
+ ULONG cbSig;
+ IfFailThrow(pImport->GetTypeSpecFromToken(typeToken, &pSig, &cbSig));
+ //GENERICINST (CLASS|VALUETYPE) typeDefOrRef
+ CorElementType et = CorSigUncompressElementType(pSig);
+ _ASSERTE(et == ELEMENT_TYPE_GENERICINST);
+ et = CorSigUncompressElementType(pSig);
+ _ASSERTE((et == ELEMENT_TYPE_CLASS) || (et == ELEMENT_TYPE_VALUETYPE));
+ typeToken = CorSigUncompressToken(pSig);
+ }
+
+ //we just removed one level of indirection. We still might have a ref or spec.
+ typeToken = ConvertToTypeDef(typeToken, pImport);
+ _ASSERTE(TypeFromToken(typeToken) == mdtTypeDef);
+ return typeToken;
+}
+
+SIZE_T NativeImageDumper::CountDictionariesInClass( mdToken typeToken, IMetaDataImport2 * pImport )
+{
+ SIZE_T myDicts; //either 0 or 1
+
+ _ASSERTE((TypeFromToken(typeToken) == mdtTypeDef) || (TypeFromToken(typeToken) == mdtTypeRef)
+ || (TypeFromToken(typeToken) == mdtTypeSpec));
+
+
+ //for refs and specs, convert to a def. This is a nop for defs.
+ typeToken = ConvertToTypeDef(typeToken, pImport);
+
+ _ASSERTE(TypeFromToken(typeToken) == mdtTypeDef);
+
+
+ //count the number of generic arguments. If there are any, then we have a dictionary.
+ HCORENUM hEnum = NULL;
+ mdGenericParam params[2];
+ ULONG numParams = 0;
+ IfFailThrow(pImport->EnumGenericParams(&hEnum, typeToken, params, _countof(params), &numParams));
+ myDicts = (numParams > 0) ? 1 : 0;
+
+ pImport->CloseEnum(hEnum);
+
+ //get my parent for the recursive call.
+ mdToken parent;
+ IfFailThrow(pImport->GetTypeDefProps(typeToken, NULL, 0, NULL, NULL, &parent));
+ return myDicts + (IsNilToken(parent) ? 0 : CountDictionariesInClass(parent, pImport));
+}
+
+const NativeImageDumper::EnumMnemonics s_Subsystems[] =
+{
+#define S_ENTRY(f,v) NativeImageDumper::EnumMnemonics(f, 0, v)
+ S_ENTRY(IMAGE_SUBSYSTEM_UNKNOWN, W("Unknown")),
+ S_ENTRY(IMAGE_SUBSYSTEM_NATIVE, W("Native")),
+ S_ENTRY(IMAGE_SUBSYSTEM_WINDOWS_CUI, W("Windows CUI")),
+ S_ENTRY(IMAGE_SUBSYSTEM_WINDOWS_GUI, W("Windows GUI")),
+ S_ENTRY(IMAGE_SUBSYSTEM_OS2_CUI, W("OS/2 CUI")),
+ S_ENTRY(IMAGE_SUBSYSTEM_POSIX_CUI, W("POSIX CUI")),
+ S_ENTRY(IMAGE_SUBSYSTEM_WINDOWS_CE_GUI, W("WinCE GUI")),
+ S_ENTRY(IMAGE_SUBSYSTEM_XBOX, W("XBox"))
+#undef S_ENTRY
+};
+
+const NativeImageDumper::EnumMnemonics s_CorCompileHdrFlags[] =
+{
+#define CCHF_ENTRY(f) NativeImageDumper::EnumMnemonics(f, W(#f))
+ CCHF_ENTRY(CORCOMPILE_HEADER_HAS_SECURITY_DIRECTORY),
+ CCHF_ENTRY(CORCOMPILE_HEADER_IS_IBC_OPTIMIZED),
+ CCHF_ENTRY(CORCOMPILE_HEADER_IS_READY_TO_RUN),
+#undef CCHF_ENTRY
+};
+
+const NativeImageDumper::EnumMnemonics s_CorPEKind[] =
+{
+#define CPEK_ENTRY(f) NativeImageDumper::EnumMnemonics(f, W(#f))
+ CPEK_ENTRY(peNot),
+ CPEK_ENTRY(peILonly),
+ CPEK_ENTRY(pe32BitRequired),
+ CPEK_ENTRY(pe32Plus),
+ CPEK_ENTRY(pe32Unmanaged),
+ CPEK_ENTRY(pe32BitPreferred)
+#undef CPEK_ENTRY
+};
+const NativeImageDumper::EnumMnemonics s_IFH_Machine[] =
+{
+#define IFH_ENTRY(f) NativeImageDumper::EnumMnemonics(f, 0, W(#f))
+ IFH_ENTRY(IMAGE_FILE_MACHINE_UNKNOWN),
+ IFH_ENTRY(IMAGE_FILE_MACHINE_I386),
+ IFH_ENTRY(IMAGE_FILE_MACHINE_AMD64),
+ IFH_ENTRY(IMAGE_FILE_MACHINE_ARMNT),
+#undef IFH_ENTRY
+};
+
+const NativeImageDumper::EnumMnemonics s_IFH_Characteristics[] =
+{
+#define IFH_ENTRY(f) NativeImageDumper::EnumMnemonics(f, W(#f))
+ IFH_ENTRY(IMAGE_FILE_RELOCS_STRIPPED),
+ IFH_ENTRY(IMAGE_FILE_EXECUTABLE_IMAGE),
+ IFH_ENTRY(IMAGE_FILE_LINE_NUMS_STRIPPED),
+ IFH_ENTRY(IMAGE_FILE_LOCAL_SYMS_STRIPPED),
+ IFH_ENTRY(IMAGE_FILE_AGGRESIVE_WS_TRIM),
+ IFH_ENTRY(IMAGE_FILE_LARGE_ADDRESS_AWARE),
+ IFH_ENTRY(IMAGE_FILE_BYTES_REVERSED_LO),
+ IFH_ENTRY(IMAGE_FILE_32BIT_MACHINE),
+ IFH_ENTRY(IMAGE_FILE_DEBUG_STRIPPED),
+ IFH_ENTRY(IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP),
+ IFH_ENTRY(IMAGE_FILE_NET_RUN_FROM_SWAP),
+ IFH_ENTRY(IMAGE_FILE_SYSTEM),
+ IFH_ENTRY(IMAGE_FILE_DLL),
+ IFH_ENTRY(IMAGE_FILE_UP_SYSTEM_ONLY),
+ IFH_ENTRY(IMAGE_FILE_BYTES_REVERSED_HI),
+#undef IFH_ENTRY
+};
+
+const NativeImageDumper::EnumMnemonics s_ImportSectionType[] =
+{
+#define IST_ENTRY(f) NativeImageDumper::EnumMnemonics(f, 0, W(#f))
+ IST_ENTRY(CORCOMPILE_IMPORT_TYPE_UNKNOWN),
+ IST_ENTRY(CORCOMPILE_IMPORT_TYPE_EXTERNAL_METHOD),
+ IST_ENTRY(CORCOMPILE_IMPORT_TYPE_STUB_DISPATCH),
+ IST_ENTRY(CORCOMPILE_IMPORT_TYPE_STRING_HANDLE),
+ IST_ENTRY(CORCOMPILE_IMPORT_TYPE_TYPE_HANDLE),
+ IST_ENTRY(CORCOMPILE_IMPORT_TYPE_METHOD_HANDLE),
+ IST_ENTRY(CORCOMPILE_IMPORT_TYPE_VIRTUAL_METHOD),
+#undef IST_ENTRY
+};
+
+const NativeImageDumper::EnumMnemonics s_ImportSectionFlags[] =
+{
+#define IST_FLAGS(f) NativeImageDumper::EnumMnemonics(f, W(#f))
+ IST_FLAGS(CORCOMPILE_IMPORT_FLAGS_EAGER),
+ IST_FLAGS(CORCOMPILE_IMPORT_FLAGS_CODE),
+ IST_FLAGS(CORCOMPILE_IMPORT_FLAGS_PCODE),
+#undef IST_FLAGS
+};
+
+void NativeImageDumper::DumpNativeHeader()
+{
+ PTR_CORCOMPILE_HEADER nativeHeader(m_decoder.GetNativeHeader());
+
+ IF_OPT(NATIVE_INFO)
+ {
+
+#define WRITE_NATIVE_FIELD( name ) m_display->WriteFieldAddress(\
+ # name, offsetof(CORCOMPILE_HEADER, name), \
+ fieldsize(CORCOMPILE_HEADER, name), \
+ RvaToDisplay( nativeHeader-> name . VirtualAddress ), \
+ nativeHeader-> name . Size )
+
+ m_display->StartStructure( "CORCOMPILE_HEADER",
+ DPtrToPreferredAddr(nativeHeader),
+ sizeof(*nativeHeader) );
+
+ DisplayWriteFieldUInt( Signature, nativeHeader->Signature, CORCOMPILE_HEADER, ALWAYS );
+ DisplayWriteFieldUInt( MajorVersion, nativeHeader->MajorVersion, CORCOMPILE_HEADER, ALWAYS );
+ DisplayWriteFieldUInt( MinorVersion, nativeHeader->MinorVersion, CORCOMPILE_HEADER, ALWAYS );
+
+ WRITE_NATIVE_FIELD(HelperTable);
+
+ WRITE_NATIVE_FIELD(ImportSections);
+ PTR_CORCOMPILE_IMPORT_SECTION pImportSections =
+ nativeHeader->ImportSections.VirtualAddress
+ + PTR_TO_TADDR(m_decoder.GetBase());
+ DisplayStartArray( "ImportSections", NULL, ALWAYS );
+ for( COUNT_T i = 0; i < nativeHeader->ImportSections.Size
+ / sizeof(*pImportSections); ++i )
+ {
+ DisplayStartStructure( "CORCOMPILE_IMPORT_SECTION",
+ DPtrToPreferredAddr(pImportSections + i),
+ sizeof(pImportSections[i]), ALWAYS );
+ DisplayWriteElementAddress( "Section",
+ RvaToDisplay(pImportSections[i].Section.VirtualAddress),
+ pImportSections[i].Section.Size, ALWAYS );
+
+ DisplayWriteFieldEnumerated( Flags, pImportSections[i].Flags,
+ CORCOMPILE_IMPORT_SECTION, s_ImportSectionFlags, W(", "), ALWAYS );
+ DisplayWriteFieldEnumerated( Type, pImportSections[i].Type,
+ CORCOMPILE_IMPORT_SECTION, s_ImportSectionType, W(""), ALWAYS );
+
+ DisplayWriteFieldUInt( EntrySize, pImportSections[i].EntrySize,
+ CORCOMPILE_IMPORT_SECTION, ALWAYS );
+ DisplayWriteFieldUInt( Signatures, pImportSections[i].Signatures,
+ CORCOMPILE_IMPORT_SECTION, ALWAYS );
+ DisplayWriteFieldUInt( AuxiliaryData, pImportSections[i].AuxiliaryData,
+ CORCOMPILE_IMPORT_SECTION, ALWAYS );
+ DisplayEndStructure( ALWAYS ); //PTR_CORCOMPILE_IMPORT_SECTION
+
+ }
+ DisplayEndArray( NULL, ALWAYS ); //delayLoads
+
+ WRITE_NATIVE_FIELD(ImportTable);
+ DisplayStartArray( "imports", NULL, ALWAYS );
+ PTR_CORCOMPILE_IMPORT_TABLE_ENTRY ent( nativeHeader->ImportTable.VirtualAddress + PTR_TO_TADDR(m_decoder.GetBase()) );
+ for( COUNT_T i = 0; i < nativeHeader->ImportTable.Size / sizeof(*ent); ++i )
+ {
+ DisplayStartStructure( "CORCOMPILE_IMPORT_TABLE_ENTRY",
+ DPtrToPreferredAddr(ent + i),
+ sizeof(ent[i]), ALWAYS );
+ DisplayWriteFieldUInt( wAssemblyRid, ent[i].wAssemblyRid,
+ CORCOMPILE_IMPORT_TABLE_ENTRY, ALWAYS );
+ DisplayWriteFieldUInt( wModuleRid, ent[i].wModuleRid,
+ CORCOMPILE_IMPORT_TABLE_ENTRY, ALWAYS );
+ DisplayEndStructure( ALWAYS ); //CORCOMPILE_IMPORT_TABLE_ENTRY
+ }
+ DisplayEndArray( NULL, ALWAYS ); //imports
+
+ WRITE_NATIVE_FIELD(VersionInfo);
+ WRITE_NATIVE_FIELD(DebugMap);
+ WRITE_NATIVE_FIELD(ModuleImage);
+ WRITE_NATIVE_FIELD(CodeManagerTable);
+ WRITE_NATIVE_FIELD(ProfileDataList);
+ WRITE_NATIVE_FIELD(ManifestMetaData);
+
+ WRITE_NATIVE_FIELD(VirtualSectionsTable);
+ DisplayStartArray( "VirtualSections", W("%-48s%s"), SLIM_MODULE_TBLS );
+ PTR_CORCOMPILE_VIRTUAL_SECTION_INFO sects( nativeHeader->VirtualSectionsTable.VirtualAddress + PTR_TO_TADDR(m_decoder.GetBase()) );
+ COUNT_T numVirtualSections = nativeHeader->VirtualSectionsTable.Size / sizeof (CORCOMPILE_VIRTUAL_SECTION_INFO);
+
+ for( COUNT_T i = 0; i < numVirtualSections; ++i )
+ {
+ TempBuffer sectionNameBuf;
+ TempBuffer sectionFlags;
+ StackScratchBuffer scratch;
+
+ sectionNameBuf.Append(g_sectionNames[VirtualSectionData::VirtualSectionType(sects[i].SectionType)]);
+
+ EnumFlagsToString( sects[i].SectionType, s_virtualSectionFlags, dim(s_virtualSectionFlags),
+ W(" | "), sectionFlags);
+
+ sectionNameBuf.Append(W(" ["));
+ sectionNameBuf.Append(sectionFlags);
+ sectionNameBuf.Append(W("]"));
+
+ DisplayStartElement( "Section", SLIM_MODULE_TBLS );
+ DisplayWriteElementString("Name", sectionNameBuf.GetANSI(scratch), SLIM_MODULE_TBLS);
+
+ DisplayWriteElementAddress( "Address",
+ RvaToDisplay(sects[i].VirtualAddress),
+ sects[i].Size,
+ SLIM_MODULE_TBLS );
+ DisplayEndElement( SLIM_MODULE_TBLS ); //Section
+ }
+ DisplayEndArray( "Total VirtualSections", SLIM_MODULE_TBLS );
+
+ WRITE_NATIVE_FIELD(EEInfoTable);
+
+#undef WRITE_NATIVE_FIELD
+ DisplayWriteFieldEnumerated( Flags, nativeHeader->Flags,
+ CORCOMPILE_HEADER, s_CorCompileHdrFlags, W(", "),
+ NATIVE_INFO );
+
+ DisplayWriteFieldEnumerated( PEKind, nativeHeader->PEKind,
+ CORCOMPILE_HEADER, s_CorPEKind, W(", "),
+ NATIVE_INFO );
+
+ DisplayWriteFieldEnumerated( COR20Flags, nativeHeader->COR20Flags,
+ CORCOMPILE_HEADER, s_CorHdrFlags, W(", "),
+ NATIVE_INFO );
+
+ DisplayWriteFieldEnumerated( Machine, nativeHeader->Machine,
+ CORCOMPILE_HEADER, s_IFH_Machine,
+ W(""), NATIVE_INFO );
+ DisplayWriteFieldEnumerated( Characteristics,
+ nativeHeader->Characteristics,
+ CORCOMPILE_HEADER, s_IFH_Characteristics,
+ W(", "), NATIVE_INFO );
+
+ m_display->EndStructure(); //CORCOMPILE_HEADER
+ }
+}
+
+const NativeImageDumper::EnumMnemonics s_RelocType[] =
+{
+#define REL_ENTRY(x) NativeImageDumper::EnumMnemonics( x, 0, W(#x))
+ REL_ENTRY(IMAGE_REL_BASED_ABSOLUTE),
+ REL_ENTRY(IMAGE_REL_BASED_HIGHLOW),
+ REL_ENTRY(IMAGE_REL_BASED_DIR64),
+ REL_ENTRY(IMAGE_REL_BASED_THUMB_MOV32),
+#undef REL_ENTRY
+};
+
+void NativeImageDumper::DumpBaseRelocs()
+{
+ COUNT_T size;
+ TADDR data;
+
+ data = m_decoder.GetDirectoryEntryData(IMAGE_DIRECTORY_ENTRY_BASERELOC, &size);
+
+ if (size != 0)
+ {
+ DisplayStartStructure( "Relocations", DataPtrToDisplay(data), size,
+ ALWAYS );
+
+ while (size != 0)
+ {
+ IMAGE_BASE_RELOCATION * pBaseRelocation = dac_cast<DPTR(IMAGE_BASE_RELOCATION)>(data);
+ _ASSERTE(size >= pBaseRelocation->SizeOfBlock);
+
+ SIZE_T rel = sizeof(IMAGE_BASE_RELOCATION);
+ while (rel < pBaseRelocation->SizeOfBlock)
+ {
+ USHORT typeOffset = *PTR_USHORT(data + rel);
+
+ DisplayStartElement( "Entry", ALWAYS );
+
+ DisplayWriteElementPointer( "Address", RvaToDisplay(pBaseRelocation->VirtualAddress + (typeOffset & 0xFFF)), ALWAYS );
+
+ DisplayWriteElementEnumerated( "Type", (typeOffset >> 12),
+ s_RelocType, W(", "), ALWAYS );
+
+ DisplayEndElement( ALWAYS ); //Entry
+
+ rel += sizeof(USHORT);
+ }
+
+ data += pBaseRelocation->SizeOfBlock;
+ size -= pBaseRelocation->SizeOfBlock;
+ }
+
+ DisplayEndStructure( ALWAYS ); //Relocations
+ }
+}
+
+void NativeImageDumper::DumpHelperTable()
+{
+ COUNT_T size;
+ TADDR data;
+
+ data = TO_TADDR(m_decoder.GetNativeHelperTable(&size));
+ if( size != 0 )
+ {
+ DisplayStartStructure( "HelperTable", DataPtrToDisplay(data), size,
+ ALWAYS );
+
+ TADDR curEntry = data;
+ TADDR tableEnd = data + size;
+
+ while (curEntry < tableEnd)
+ {
+ DWORD dwHelper = *PTR_DWORD(curEntry);
+
+ int iHelper = (USHORT)dwHelper;
+ _ASSERTE(iHelper < CORINFO_HELP_COUNT);
+
+ DisplayStartStructure( "Helper",
+ DataPtrToDisplay(curEntry), (dwHelper & CORCOMPILE_HELPER_PTR) ? sizeof(TADDR) : HELPER_TABLE_ENTRY_LEN,
+ ALWAYS );
+
+ DisplayWriteElementUInt( "dwHelper", dwHelper, ALWAYS );
+ DisplayWriteElementString( "Name", g_helperNames[iHelper], ALWAYS );
+
+ DisplayEndStructure( ALWAYS ); //Helper
+
+ curEntry += (dwHelper & CORCOMPILE_HELPER_PTR) ? sizeof(TADDR) : HELPER_TABLE_ENTRY_LEN;
+ }
+
+ DisplayEndStructure( ALWAYS ); //HelperTable
+ }
+}
+
+// TODO: fix these to work with the updated flags in MethodTable, AND to understand
+// the new overloading of component size...
+
+NativeImageDumper::EnumMnemonics s_MTFlagsLow[] =
+{
+#define MTFLAG_ENTRY(x) \
+ NativeImageDumper::EnumMnemonics(MethodTable::enum_flag_ ## x, W(#x))
+
+ MTFLAG_ENTRY(UNUSED_ComponentSize_1),
+ MTFLAG_ENTRY(StaticsMask),
+ MTFLAG_ENTRY(StaticsMask_NonDynamic),
+ MTFLAG_ENTRY(StaticsMask_Dynamic),
+ MTFLAG_ENTRY(StaticsMask_Generics),
+ MTFLAG_ENTRY(StaticsMask_CrossModuleGenerics),
+ MTFLAG_ENTRY(NotInPZM),
+ MTFLAG_ENTRY(GenericsMask),
+ MTFLAG_ENTRY(GenericsMask_NonGeneric),
+ MTFLAG_ENTRY(GenericsMask_GenericInst),
+ MTFLAG_ENTRY(GenericsMask_SharedInst),
+ MTFLAG_ENTRY(GenericsMask_TypicalInst),
+#if defined(FEATURE_REMOTING)
+ MTFLAG_ENTRY(ContextStatic),
+#endif
+ MTFLAG_ENTRY(HasRemotingVtsInfo),
+ MTFLAG_ENTRY(HasVariance),
+ MTFLAG_ENTRY(HasDefaultCtor),
+ MTFLAG_ENTRY(HasPreciseInitCctors),
+#if defined(FEATURE_HFA)
+ MTFLAG_ENTRY(IsHFA),
+#endif // FEATURE_HFA
+#if defined(FEATURE_UNIX_AMD64_STRUCT_PASSING_ITF)
+ MTFLAG_ENTRY(IsRegStructPassed),
+#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING_ITF
+ MTFLAG_ENTRY(IsByRefLike),
+ MTFLAG_ENTRY(UNUSED_ComponentSize_5),
+ MTFLAG_ENTRY(UNUSED_ComponentSize_6),
+ MTFLAG_ENTRY(UNUSED_ComponentSize_7),
+#undef MTFLAG_ENTRY
+};
+
+NativeImageDumper::EnumMnemonics s_MTFlagsHigh[] =
+{
+#define MTFLAG_ENTRY(x) \
+ NativeImageDumper::EnumMnemonics(MethodTable::enum_flag_ ## x, W(#x))
+
+#define MTFLAG_CATEGORY_ENTRY(x) \
+ NativeImageDumper::EnumMnemonics(MethodTable::enum_flag_Category_ ## x, MethodTable::enum_flag_Category_Mask, W("Category_") W(#x))
+
+#define MTFLAG_CATEGORY_ENTRY_WITH_MASK(x, m) \
+ NativeImageDumper::EnumMnemonics(MethodTable::enum_flag_Category_ ## x, MethodTable::enum_flag_Category_ ## m, W("Category_") W(#x))
+
+ MTFLAG_CATEGORY_ENTRY(Class),
+ MTFLAG_CATEGORY_ENTRY(Unused_1),
+ MTFLAG_CATEGORY_ENTRY(MarshalByRef),
+ MTFLAG_CATEGORY_ENTRY(Contextful),
+ MTFLAG_CATEGORY_ENTRY(ValueType),
+ MTFLAG_CATEGORY_ENTRY(Nullable),
+ MTFLAG_CATEGORY_ENTRY(PrimitiveValueType),
+ MTFLAG_CATEGORY_ENTRY(TruePrimitive),
+
+ MTFLAG_CATEGORY_ENTRY(Interface),
+ MTFLAG_CATEGORY_ENTRY(Unused_2),
+ MTFLAG_CATEGORY_ENTRY(TransparentProxy),
+ MTFLAG_CATEGORY_ENTRY(AsyncPin),
+
+ MTFLAG_CATEGORY_ENTRY_WITH_MASK(Array, Array_Mask),
+ MTFLAG_CATEGORY_ENTRY_WITH_MASK(IfArrayThenSzArray, IfArrayThenSzArray),
+
+#undef MTFLAG_CATEGORY_ENTRY_WITH_MASK
+#undef MTFLAG_CATEGORY_ENTRY
+
+ MTFLAG_ENTRY(HasFinalizer),
+ MTFLAG_ENTRY(IfNotInterfaceThenMarshalable),
+#if defined(FEATURE_COMINTEROP)
+ MTFLAG_ENTRY(IfInterfaceThenHasGuidInfo),
+#endif
+#if defined(FEATURE_ICASTABLE)
+ MTFLAG_ENTRY(ICastable),
+#endif
+ MTFLAG_ENTRY(HasIndirectParent),
+ MTFLAG_ENTRY(ContainsPointers),
+ MTFLAG_ENTRY(HasTypeEquivalence),
+#if defined(FEATURE_COMINTEROP)
+ MTFLAG_ENTRY(HasRCWPerTypeData),
+#endif
+ MTFLAG_ENTRY(HasCriticalFinalizer),
+ MTFLAG_ENTRY(Collectible),
+ MTFLAG_ENTRY(ContainsGenericVariables),
+#if defined(FEATURE_COMINTEROP)
+ MTFLAG_ENTRY(ComObject),
+#endif
+ MTFLAG_ENTRY(HasComponentSize),
+#undef MTFLAG_ENTRY
+};
+
+
+NativeImageDumper::EnumMnemonics s_MTFlags2[] =
+{
+#define MTFLAG2_ENTRY(x) \
+ NativeImageDumper::EnumMnemonics(MethodTable::enum_flag_ ## x, W(#x))
+ MTFLAG2_ENTRY(HasPerInstInfo),
+ MTFLAG2_ENTRY(HasInterfaceMap),
+ MTFLAG2_ENTRY(HasDispatchMapSlot),
+ MTFLAG2_ENTRY(HasNonVirtualSlots),
+ MTFLAG2_ENTRY(HasModuleOverride),
+ MTFLAG2_ENTRY(IsZapped),
+ MTFLAG2_ENTRY(IsPreRestored),
+ MTFLAG2_ENTRY(HasModuleDependencies),
+ MTFLAG2_ENTRY(NoSecurityProperties),
+ MTFLAG2_ENTRY(RequiresDispatchTokenFat),
+ MTFLAG2_ENTRY(HasCctor),
+ MTFLAG2_ENTRY(HasCCWTemplate),
+#ifdef FEATURE_64BIT_ALIGNMENT
+ MTFLAG2_ENTRY(RequiresAlign8),
+#endif
+ MTFLAG2_ENTRY(HasBoxedRegularStatics),
+ MTFLAG2_ENTRY(HasSingleNonVirtualSlot),
+ MTFLAG2_ENTRY(DependsOnEquivalentOrForwardedStructs),
+#undef MTFLAG2_ENTRY
+};
+
+NativeImageDumper::EnumMnemonics s_WriteableMTFlags[] =
+{
+#define WMTFLAG_ENTRY(x) \
+ NativeImageDumper::EnumMnemonics(MethodTableWriteableData::enum_flag_ ## x,\
+ W(#x))
+
+ WMTFLAG_ENTRY(RemotingConfigChecked),
+ WMTFLAG_ENTRY(RequiresManagedActivation),
+ WMTFLAG_ENTRY(Unrestored),
+ WMTFLAG_ENTRY(CriticalTypePrepared),
+ WMTFLAG_ENTRY(HasApproxParent),
+ WMTFLAG_ENTRY(UnrestoredTypeKey),
+ WMTFLAG_ENTRY(IsNotFullyLoaded),
+ WMTFLAG_ENTRY(DependenciesLoaded),
+
+#ifdef _DEBUG
+ WMTFLAG_ENTRY(ParentMethodTablePointerValid),
+#endif
+
+ WMTFLAG_ENTRY(NGEN_IsFixedUp),
+ WMTFLAG_ENTRY(NGEN_IsNeedsRestoreCached),
+ WMTFLAG_ENTRY(NGEN_CachedNeedsRestore),
+#undef WMTFLAG_ENTRY
+};
+
+static NativeImageDumper::EnumMnemonics s_CorElementType[] =
+{
+#define CET_ENTRY(x) NativeImageDumper::EnumMnemonics(ELEMENT_TYPE_ ## x, 0, W("ELEMENT_TYPE_") W(#x))
+ CET_ENTRY(END),
+ CET_ENTRY(VOID),
+ CET_ENTRY(BOOLEAN),
+ CET_ENTRY(CHAR),
+ CET_ENTRY(I1),
+ CET_ENTRY(U1),
+ CET_ENTRY(I2),
+ CET_ENTRY(U2),
+ CET_ENTRY(I4),
+ CET_ENTRY(U4),
+ CET_ENTRY(I8),
+ CET_ENTRY(U8),
+ CET_ENTRY(R4),
+ CET_ENTRY(R8),
+ CET_ENTRY(STRING),
+ CET_ENTRY(PTR),
+ CET_ENTRY(BYREF),
+ CET_ENTRY(VALUETYPE),
+ CET_ENTRY(CLASS),
+ CET_ENTRY(VAR),
+ CET_ENTRY(ARRAY),
+ CET_ENTRY(GENERICINST),
+ CET_ENTRY(TYPEDBYREF),
+ CET_ENTRY(VALUEARRAY_UNSUPPORTED),
+ CET_ENTRY(I),
+ CET_ENTRY(U),
+ CET_ENTRY(R_UNSUPPORTED),
+ CET_ENTRY(FNPTR),
+ CET_ENTRY(OBJECT),
+ CET_ENTRY(SZARRAY),
+ CET_ENTRY(MVAR),
+ CET_ENTRY(CMOD_REQD),
+ CET_ENTRY(CMOD_OPT),
+ CET_ENTRY(INTERNAL),
+
+ CET_ENTRY(SENTINEL),
+ CET_ENTRY(PINNED),
+#undef CET_ENTRY
+};
+
+void NativeImageDumper::DoWriteFieldCorElementType( const char * name,
+ unsigned offset,
+ unsigned fieldSize,
+ CorElementType type )
+{
+ TempBuffer buf;
+ EnumFlagsToString( (int)type, s_CorElementType, dim(s_CorElementType),
+ W(""), buf );
+ m_display->WriteFieldEnumerated( name, offset, fieldSize, (unsigned)type,
+ (const WCHAR *) buf );
+
+}
+
+static NativeImageDumper::EnumMnemonics s_CorTypeAttr[] =
+{
+#define CTA_ENTRY(x) NativeImageDumper::EnumMnemonics( x, W(#x) )
+
+#define CTA_VISIBILITY_ENTRY(x) NativeImageDumper::EnumMnemonics( x, tdVisibilityMask, W(#x) )
+ CTA_VISIBILITY_ENTRY(tdNotPublic),
+ CTA_VISIBILITY_ENTRY(tdPublic),
+ CTA_VISIBILITY_ENTRY(tdNestedPublic),
+ CTA_VISIBILITY_ENTRY(tdNestedPrivate),
+ CTA_VISIBILITY_ENTRY(tdNestedFamily),
+ CTA_VISIBILITY_ENTRY(tdNestedAssembly),
+ CTA_VISIBILITY_ENTRY(tdNestedFamANDAssem),
+ CTA_VISIBILITY_ENTRY(tdNestedFamORAssem),
+#undef CTA_VISIBILITY_ENTRY
+
+ CTA_ENTRY(tdSequentialLayout),
+ CTA_ENTRY(tdExplicitLayout),
+
+ CTA_ENTRY(tdInterface),
+
+ CTA_ENTRY(tdAbstract),
+ CTA_ENTRY(tdSealed),
+ CTA_ENTRY(tdSpecialName),
+
+ CTA_ENTRY(tdImport),
+ CTA_ENTRY(tdSerializable),
+
+ CTA_ENTRY(tdUnicodeClass),
+ CTA_ENTRY(tdAutoClass),
+ CTA_ENTRY(tdCustomFormatClass),
+ CTA_ENTRY(tdCustomFormatMask),
+
+ CTA_ENTRY(tdBeforeFieldInit),
+ CTA_ENTRY(tdForwarder),
+
+ CTA_ENTRY(tdRTSpecialName),
+ CTA_ENTRY(tdHasSecurity)
+#undef CTA_ENTRY
+};
+static NativeImageDumper::EnumMnemonics s_VMFlags[] =
+{
+#define VMF_ENTRY_TRANSPARENCY(x) NativeImageDumper::EnumMnemonics( EEClass::VMFLAG_ ## x, EEClass::VMFLAG_TRANSPARENCY_MASK, W(#x) )
+ VMF_ENTRY_TRANSPARENCY(TRANSPARENCY_UNKNOWN),
+ VMF_ENTRY_TRANSPARENCY(TRANSPARENCY_TRANSPARENT),
+ VMF_ENTRY_TRANSPARENCY(TRANSPARENCY_ALL_TRANSPARENT),
+ VMF_ENTRY_TRANSPARENCY(TRANSPARENCY_CRITICAL),
+ VMF_ENTRY_TRANSPARENCY(TRANSPARENCY_CRITICAL_TAS),
+ VMF_ENTRY_TRANSPARENCY(TRANSPARENCY_ALLCRITICAL),
+ VMF_ENTRY_TRANSPARENCY(TRANSPARENCY_ALLCRITICAL_TAS),
+ VMF_ENTRY_TRANSPARENCY(TRANSPARENCY_TAS_NOTCRITICAL),
+#undef VMF_ENTRY_TRANSPARENCY
+
+#define VMF_ENTRY(x) NativeImageDumper::EnumMnemonics( EEClass::VMFLAG_ ## x, W(#x) )
+
+#ifdef FEATURE_READYTORUN
+ VMF_ENTRY(LAYOUT_DEPENDS_ON_OTHER_MODULES),
+#endif
+ VMF_ENTRY(DELEGATE),
+ VMF_ENTRY(FIXED_ADDRESS_VT_STATICS),
+ VMF_ENTRY(HASLAYOUT),
+ VMF_ENTRY(ISNESTED),
+#ifdef FEATURE_REMOTING
+ VMF_ENTRY(CANNOT_BE_BLITTED_BY_OBJECT_CLONER),
+#endif
+ VMF_ENTRY(IS_EQUIVALENT_TYPE),
+
+ VMF_ENTRY(HASOVERLAYEDFIELDS),
+ VMF_ENTRY(HAS_FIELDS_WHICH_MUST_BE_INITED),
+ VMF_ENTRY(UNSAFEVALUETYPE),
+
+ VMF_ENTRY(BESTFITMAPPING_INITED),
+ VMF_ENTRY(BESTFITMAPPING),
+ VMF_ENTRY(THROWONUNMAPPABLECHAR),
+
+ VMF_ENTRY(NOSUPPRESSUNMGDCODEACCESS),
+ VMF_ENTRY(NO_GUID),
+ VMF_ENTRY(HASNONPUBLICFIELDS),
+ VMF_ENTRY(REMOTING_PROXY_ATTRIBUTE),
+ VMF_ENTRY(PREFER_ALIGN8),
+ VMF_ENTRY(METHODS_REQUIRE_INHERITANCE_CHECKS),
+
+#ifdef FEATURE_COMINTEROP
+ VMF_ENTRY(SPARSE_FOR_COMINTEROP),
+ VMF_ENTRY(HASCOCLASSATTRIB),
+ VMF_ENTRY(COMEVENTITFMASK),
+ VMF_ENTRY(PROJECTED_FROM_WINRT),
+ VMF_ENTRY(EXPORTED_TO_WINRT),
+#endif // FEATURE_COMINTEROP
+
+ VMF_ENTRY(NOT_TIGHTLY_PACKED),
+ VMF_ENTRY(CONTAINS_METHODIMPLS),
+#ifdef FEATURE_COMINTEROP
+ VMF_ENTRY(MARSHALINGTYPE_MASK),
+ VMF_ENTRY(MARSHALINGTYPE_INHIBIT),
+ VMF_ENTRY(MARSHALINGTYPE_FREETHREADED),
+ VMF_ENTRY(MARSHALINGTYPE_STANDARD),
+#endif
+#undef VMF_ENTRY
+};
+static NativeImageDumper::EnumMnemonics s_SecurityProperties[] =
+{
+#define SP_ENTRY(x) NativeImageDumper::EnumMnemonics(DECLSEC_ ## x, W(#x))
+ SP_ENTRY(DEMANDS),
+ SP_ENTRY(ASSERTIONS),
+ SP_ENTRY(DENIALS),
+ SP_ENTRY(INHERIT_CHECKS),
+ SP_ENTRY(LINK_CHECKS),
+ SP_ENTRY(PERMITONLY),
+ SP_ENTRY(REQUESTS),
+ SP_ENTRY(UNMNGD_ACCESS_DEMAND),
+ SP_ENTRY(NONCAS_DEMANDS),
+ SP_ENTRY(NONCAS_LINK_DEMANDS),
+ SP_ENTRY(NONCAS_INHERITANCE),
+
+ SP_ENTRY(NULL_INHERIT_CHECKS),
+ SP_ENTRY(NULL_LINK_CHECKS),
+#undef SP_ENTRY
+};
+static NativeImageDumper::EnumMnemonics s_CorFieldAttr[] =
+{
+#define CFA_ENTRY(x) NativeImageDumper::EnumMnemonics( x, W(#x) )
+
+#define CFA_ACCESS_ENTRY(x) NativeImageDumper::EnumMnemonics( x, fdFieldAccessMask, W(#x) )
+ CFA_ENTRY(fdPrivateScope),
+ CFA_ENTRY(fdPrivate),
+ CFA_ENTRY(fdFamANDAssem),
+ CFA_ENTRY(fdAssembly),
+ CFA_ENTRY(fdFamily),
+ CFA_ENTRY(fdFamORAssem),
+ CFA_ENTRY(fdPublic),
+#undef CFA_ACCESS_ENTRY
+
+ CFA_ENTRY(fdStatic),
+ CFA_ENTRY(fdInitOnly),
+ CFA_ENTRY(fdLiteral),
+ CFA_ENTRY(fdNotSerialized),
+
+ CFA_ENTRY(fdSpecialName),
+
+ CFA_ENTRY(fdPinvokeImpl),
+
+ CFA_ENTRY(fdRTSpecialName),
+ CFA_ENTRY(fdHasFieldMarshal),
+ CFA_ENTRY(fdHasDefault),
+ CFA_ENTRY(fdHasFieldRVA),
+#undef CFA_ENTRY
+};
+
+NativeImageDumper::EnumMnemonics NativeImageDumper::s_MDFlag2[] =
+{
+#define MDF2_ENTRY(x) NativeImageDumper::EnumMnemonics( MethodDesc::enum_flag2_ ## x, W("enum_flag2_") W(#x) )
+ MDF2_ENTRY(HasStableEntryPoint),
+ MDF2_ENTRY(HasPrecode),
+ MDF2_ENTRY(IsUnboxingStub),
+ MDF2_ENTRY(HasNativeCodeSlot),
+ MDF2_ENTRY(Transparency_TreatAsSafe),
+ MDF2_ENTRY(Transparency_Transparent),
+ MDF2_ENTRY(Transparency_Critical),
+ MDF2_ENTRY(HostProtectionLinkCheckOnly),
+ MDF2_ENTRY(CASDemandsOnly),
+#undef MDF2_ENTRY
+};
+
+NativeImageDumper::EnumMnemonics NativeImageDumper::s_MDC[] =
+{
+#define MDC_ENTRY(x) NativeImageDumper::EnumMnemonics( x, W(#x) )
+
+#define MDC_ENTRY_CLASSIFICATION(x) NativeImageDumper::EnumMnemonics( x, mdcClassification, W(#x) )
+ MDC_ENTRY_CLASSIFICATION(mcIL),
+ MDC_ENTRY_CLASSIFICATION(mcFCall),
+ MDC_ENTRY_CLASSIFICATION(mcNDirect),
+ MDC_ENTRY_CLASSIFICATION(mcEEImpl),
+ MDC_ENTRY_CLASSIFICATION(mcArray),
+ MDC_ENTRY_CLASSIFICATION(mcInstantiated),
+#ifdef FEATURE_COMINTEROP
+ MDC_ENTRY_CLASSIFICATION(mcComInterop),
+#endif // FEATURE_COMINTEROP
+ MDC_ENTRY_CLASSIFICATION(mcDynamic),
+#undef MDC_ENTRY_CLASSIFICATION
+
+ MDC_ENTRY(mdcHasNonVtableSlot),
+ MDC_ENTRY(mdcMethodImpl),
+
+ // Method is static
+ MDC_ENTRY(mdcStatic),
+ MDC_ENTRY(mdcIntercepted),
+
+ MDC_ENTRY(mdcRequiresLinktimeCheck),
+
+ MDC_ENTRY(mdcRequiresInheritanceCheck),
+
+ MDC_ENTRY(mdcParentRequiresInheritanceCheck),
+
+ MDC_ENTRY(mdcDuplicate),
+ MDC_ENTRY(mdcVerifiedState),
+ MDC_ENTRY(mdcVerifiable),
+ MDC_ENTRY(mdcNotInline),
+ MDC_ENTRY(mdcSynchronized),
+ MDC_ENTRY(mdcRequiresFullSlotNumber),
+#undef MDC_ENTRY
+};
+
+
+
+void NativeImageDumper::DumpTypes(PTR_Module module)
+{
+ _ASSERTE(CHECK_OPT(EECLASSES) || CHECK_OPT(METHODTABLES)
+ || CHECK_OPT(TYPEDESCS));
+
+ IF_OPT_OR3(METHODTABLES, EECLASSES, TYPEDESCS)
+ m_display->StartCategory( "Types" );
+ IF_OPT(METHODTABLES)
+ {
+ //there may be duplicates in the list. Remove them before moving on.
+ COUNT_T mtCount = m_discoveredMTs.GetCount();
+
+#if !defined(FEATURE_CORESYSTEM) // no STL right now
+ std::sort(&*m_discoveredMTs.Begin(),
+ (&*m_discoveredMTs.Begin())
+ + (m_discoveredMTs.End() - m_discoveredMTs.Begin()));
+ PTR_MethodTable* newEnd = std::unique(&*m_discoveredMTs.Begin(),
+ (&*m_discoveredMTs.Begin())
+ + (m_discoveredMTs.End()
+ - m_discoveredMTs.Begin()));
+ mtCount = (COUNT_T)(newEnd - &*m_discoveredMTs.Begin());
+#endif
+
+ DisplayStartArray( "MethodTables", NULL, METHODTABLES );
+ for(COUNT_T i = 0; i < mtCount; ++i )
+ {
+ PTR_MethodTable mt = m_discoveredMTs[i];
+ if( mt == NULL )
+ continue;
+ DumpMethodTable( mt, "MethodTable", module );
+ }
+
+ DisplayEndArray( "Total MethodTables", METHODTABLES );
+
+ DisplayStartArray( "MethodTableSlotChunks", NULL, METHODTABLES );
+ {
+ COUNT_T slotChunkCount = m_discoveredSlotChunks.GetCount();
+#if !defined(FEATURE_CORESYSTEM) // no STL right now
+ std::sort(&*m_discoveredSlotChunks.Begin(),
+ (&*m_discoveredSlotChunks.Begin())
+ + (m_discoveredSlotChunks.End() - m_discoveredSlotChunks.Begin()));
+ SlotChunk *newEndChunks = std::unique(&*m_discoveredSlotChunks.Begin(),
+ (&*m_discoveredSlotChunks.Begin())
+ + (m_discoveredSlotChunks.End() - m_discoveredSlotChunks.Begin()));
+ slotChunkCount = (COUNT_T)(newEndChunks - &*m_discoveredSlotChunks.Begin());
+#endif
+
+ for (COUNT_T i = 0; i < slotChunkCount; ++i)
+ {
+ DumpMethodTableSlotChunk(m_discoveredSlotChunks[i].addr, m_discoveredSlotChunks[i].nSlots);
+ }
+ }
+ DisplayEndArray( "Total MethodTableSlotChunks", METHODTABLES );
+ }
+ IF_OPT(EECLASSES)
+ {
+ DisplayStartArray( "EEClasses", NULL, EECLASSES );
+
+ //there may be duplicates in the list. Remove them before moving on.
+ COUNT_T clazzCount = m_discoveredClasses.GetCount();
+#if !defined(FEATURE_CORESYSTEM) // no STL right now
+ std::sort(&*m_discoveredClasses.Begin(),
+ (&*m_discoveredClasses.Begin())
+ + (m_discoveredClasses.End() - m_discoveredClasses.Begin()));
+ PTR_MethodTable * newEndClazz = std::unique(&*m_discoveredClasses.Begin(),
+ (&*m_discoveredClasses.Begin())
+ +(m_discoveredClasses.End()
+ -m_discoveredClasses.Begin()));
+ clazzCount = (COUNT_T)(newEndClazz - &*m_discoveredClasses.Begin());
+#endif
+
+ for(COUNT_T i = 0; i < clazzCount; ++i )
+ {
+ PTR_MethodTable mt = m_discoveredClasses[i];
+ if( mt == NULL )
+ continue;
+ DumpEEClassForMethodTable( mt );
+ }
+
+ DisplayEndArray( "Total EEClasses", EECLASSES ); //EEClasses
+
+ }
+ IF_OPT(TYPEDESCS)
+ {
+ DisplayStartArray( "TypeDescs", NULL, TYPEDESCS );
+
+ //there may be duplicates in the list. Remove them before moving on.
+ COUNT_T tdCount = m_discoveredTypeDescs.GetCount();
+#if !defined(FEATURE_CORESYSTEM) // no STL right now
+ std::sort(&*m_discoveredTypeDescs.Begin(),
+ (&*m_discoveredTypeDescs.Begin())
+ + (m_discoveredTypeDescs.End()
+ - m_discoveredTypeDescs.Begin()));
+ PTR_TypeDesc* newEndTD = std::unique(&*m_discoveredTypeDescs.Begin(),
+ (&*m_discoveredTypeDescs.Begin())
+ +(m_discoveredTypeDescs.End()
+ -m_discoveredTypeDescs.Begin()));
+ tdCount = (COUNT_T)(newEndTD - &*m_discoveredTypeDescs.Begin());
+#endif
+
+ for(COUNT_T i = 0; i < tdCount; ++i )
+ {
+ PTR_TypeDesc td = m_discoveredTypeDescs[i];
+ if( td == NULL )
+ continue;
+ DumpTypeDesc( td );
+ }
+
+ DisplayEndArray( "Total TypeDescs", TYPEDESCS ); //EEClasses
+
+ }
+ IF_OPT_OR3(EECLASSES, METHODTABLES, TYPEDESCS)
+ m_display->EndCategory(); //Types
+}
+
+PTR_EEClass NativeImageDumper::GetClassFromMT( PTR_MethodTable mt )
+{
+ /* REVISIT_TODO Tue 10/11/2005
+ * Handle fixups
+ */
+ _ASSERTE( mt->IsClassPointerValid() );
+ PTR_EEClass clazz( mt->GetClass() );
+ return clazz;
+}
+PTR_MethodTable NativeImageDumper::GetParent( PTR_MethodTable mt )
+{
+ /* REVISIT_TODO Thu 12/01/2005
+ * Handle fixups
+ */
+ PTR_MethodTable parent( mt->m_pParentMethodTable );
+ _ASSERTE(!CORCOMPILE_IS_POINTER_TAGGED(PTR_TO_TADDR(parent)));
+ return parent;
+}
+
+//Counts the FieldDescs in a class. This is all of the non-static and static
+//non-literal fields.
+SIZE_T NativeImageDumper::CountFields( PTR_MethodTable mt )
+{
+ SIZE_T fieldCount = 0;
+
+ HCORENUM hEnum = NULL;
+
+ const Dependency * dep = GetDependencyFromMT(mt);
+ mdToken classToken = mt->GetCl();
+
+ _ASSERTE(dep);
+ _ASSERTE(dep->pImport);
+
+ //Arrays have no token.
+ if( RidFromToken(classToken) == 0 )
+ return 0;
+
+ for (;;)
+ {
+ mdToken fields[1];
+ ULONG numFields;
+
+ IfFailThrow(dep->pImport->EnumFields( &hEnum, classToken, fields,
+ 1, &numFields));
+
+ if (numFields == 0)
+ break;
+
+ DWORD dwAttr;
+ IfFailThrow(dep->pImport->GetFieldProps( fields[0], NULL, NULL, 0,
+ NULL, & dwAttr, NULL, NULL,
+ NULL, NULL, NULL ) );
+ if( !IsFdStatic(dwAttr) || !IsFdLiteral(dwAttr) )
+ ++fieldCount;
+ }
+ dep->pImport->CloseEnum(hEnum);
+ return fieldCount;
+}
+const NativeImageDumper::Dependency*
+NativeImageDumper::GetDependencyFromMT( PTR_MethodTable mt )
+{
+ if( !mt->IsClassPointerValid() )
+ {
+ //This code will not work for out of module dependencies.
+ _ASSERTE(isSelf(GetDependencyForPointer(PTR_TO_TADDR(mt))));
+
+ //the EEClass is a fixup. The home for that fixup tells us the
+ //home for the metadata.
+ unsigned rva = CORCOMPILE_UNTAG_TOKEN(mt->GetCanonicalMethodTableFixup());
+ return GetDependencyForFixup(rva);
+ }
+ PTR_Module module = mt->GetModule();
+ if( CORCOMPILE_IS_POINTER_TAGGED(PTR_TO_TADDR(module)) )
+ {
+ unsigned rva = CORCOMPILE_UNTAG_TOKEN(PTR_TO_TADDR(module));
+ return GetDependencyForFixup(rva);
+ }
+ return GetDependencyForModule(module);
+}
+const NativeImageDumper::Dependency*
+NativeImageDumper::GetDependencyFromFD( PTR_FieldDesc fd )
+{
+ PTR_MethodTable mt = fd->GetApproxEnclosingMethodTable();
+ if( CORCOMPILE_IS_POINTER_TAGGED(PTR_TO_TADDR(mt)) )
+ {
+ //This code will not work for out of module dependencies.
+ _ASSERTE(isSelf(GetDependencyForPointer(PTR_TO_TADDR(fd))));
+
+ //the MethodTable has a fixup. The home for that fixup tells us the
+ //home for the metadata.
+ unsigned rva = CORCOMPILE_UNTAG_TOKEN(PTR_TO_TADDR(mt));
+ return GetDependencyForFixup(rva);
+ }
+ return GetDependencyFromMT(mt);
+}
+const NativeImageDumper::Dependency*
+NativeImageDumper::GetDependencyFromMD( PTR_MethodDesc md )
+{
+ PTR_MethodDescChunk chunk( md->GetMethodDescChunk() );
+ PTR_MethodTable mt = chunk->GetMethodTable();
+ if( CORCOMPILE_IS_POINTER_TAGGED(PTR_TO_TADDR(mt)) )
+ {
+ //This code will not work for out of module dependencies.
+ _ASSERTE(isSelf(GetDependencyForPointer(PTR_TO_TADDR(md))));
+
+ //the MethodTable has a fixup. The home for that fixup tells us the
+ //home for the metadata.
+ unsigned rva = CORCOMPILE_UNTAG_TOKEN(PTR_TO_TADDR(mt));
+ return GetDependencyForFixup(rva);
+ }
+ return GetDependencyFromMT(mt);
+}
+
+BOOL NativeImageDumper::DoWriteFieldAsFixup( const char * name,
+ unsigned offset,
+ unsigned fieldSize, TADDR fixup)
+{
+ if( !CORCOMPILE_IS_POINTER_TAGGED(fixup) )
+ return FALSE;
+ if( UINT_MAX == offset )
+ m_display->StartVStructure( name );
+ else
+ m_display->StartVStructureWithOffset( name, offset, fieldSize );
+
+ WriteElementsFixupBlob( NULL, fixup );
+ m_display->EndVStructure(); //name
+ return TRUE;
+}
+
+void AppendTypeQualifier( CorElementType kind, DWORD rank, SString& buf )
+{
+ switch( kind )
+ {
+ case ELEMENT_TYPE_BYREF :
+ buf.Append( W("&") );
+ break;
+ case ELEMENT_TYPE_PTR :
+ buf.Append( W("*") );
+ break;
+ case ELEMENT_TYPE_SZARRAY :
+ buf.Append( W("[]") );
+ break;
+ case ELEMENT_TYPE_ARRAY :
+ if( rank == 1 )
+ {
+ buf.Append( W("[*]") );
+ }
+ else
+ {
+ buf.Append( W("[") );
+ for( COUNT_T i = 0; i < rank; ++i )
+ buf.Append( W(","));
+ buf.Append( W("]") );
+ }
+ break;
+ default :
+ break;
+ }
+}
+void NativeImageDumper::TypeDescToString( PTR_TypeDesc td, SString& buf )
+{
+ _ASSERTE(!(PTR_TO_TADDR(td) & 0x2));
+ if( td->IsGenericVariable() )
+ {
+ PTR_TypeVarTypeDesc tvtd( PTR_TO_TADDR(td) );
+ //From code:TypeString::AppendType
+ mdGenericParam token = tvtd->GetToken();
+ PTR_Module module(tvtd->GetModule());
+ IMetaDataImport2 * pImport;
+ if( CORCOMPILE_IS_POINTER_TAGGED(PTR_TO_TADDR(module)) )
+ {
+ if (!isSelf(GetDependencyForPointer(PTR_TO_TADDR(td))))
+ {
+ //this is an RVA from another hardbound dependency. We cannot decode it
+ buf.Append(W("OUT_OF_MODULE_FIXUP"));
+ return;
+ }
+ else
+ {
+ RVA rva = CORCOMPILE_UNTAG_TOKEN(PTR_TO_TADDR(module));
+ pImport = GetDependencyForFixup(rva)->pImport;
+ }
+ }
+ else
+ {
+ pImport = GetDependencyForModule(module)->pImport;
+ }
+ AppendTokenName(token, buf, pImport);
+ }
+ else if( ELEMENT_TYPE_FNPTR == td->GetInternalCorElementType() )
+ {
+ PTR_FnPtrTypeDesc fptd( PTR_TO_TADDR(td) );
+ buf.Append( W("(fnptr)") );
+ }
+ else if( td->HasTypeParam() || td->IsArray() )
+ {
+ //either a Parameter or an Array.
+ PTR_ParamTypeDesc ptd(PTR_TO_TADDR(td));
+ TypeHandle elemType;
+ /* REVISIT_TODO Thu 10/5/2006
+ * Do I need to find a rank somewhere in the TypeDesc?
+ */
+ unsigned rank;
+ if( td->IsArray() )
+ {
+ //td->HasTypeParam() may also be true.
+ PTR_MethodTable mt = ptd->m_TemplateMT.GetValue();
+ _ASSERTE( PTR_TO_TADDR(mt) );
+ if( CORCOMPILE_IS_POINTER_TAGGED(PTR_TO_TADDR(mt)) )
+ {
+ if (!isSelf(GetDependencyForPointer(PTR_TO_TADDR(ptd))))
+ {
+ //this is an RVA from another hardbound dependency. We cannot decode it
+ buf.Append(W("OUT_OF_MODULE_FIXUP"));
+ }
+ else
+ {
+ RVA rva = CORCOMPILE_UNTAG_TOKEN(PTR_TO_TADDR(mt));
+ FixupBlobToString(rva, buf);
+ }
+ return;
+ }
+ else
+ {
+ _ASSERTE( !CORCOMPILE_IS_POINTER_TAGGED(PTR_TO_TADDR(mt)) );
+ MethodTableToString( mt, buf );
+ rank = PTR_ArrayTypeDesc(PTR_TO_TADDR(ptd))->GetRank();
+ }
+ }
+ else
+ {
+ _ASSERTE(td->HasTypeParam());
+ TypeHandle th(ptd->GetTypeParam());
+ _ASSERTE( !CORCOMPILE_IS_POINTER_TAGGED(th.AsTAddr()) );
+ _ASSERTE( th.AsTAddr() );
+ TypeHandleToString(th, buf);
+ rank = 0;
+ }
+ AppendTypeQualifier( td->GetInternalCorElementType(), rank, buf );
+ }
+ else
+ {
+ //generic typedesc?
+ EnumFlagsToString( (int)td->GetInternalCorElementType(), s_CorElementType, dim(s_CorElementType),
+ W(""), buf );
+ }
+}
+void NativeImageDumper::TypeHandleToString( TypeHandle th, SString& buf )
+{
+ TADDR arg = th.AsTAddr();
+ /* REVISIT_TODO Thu 10/5/2006
+ * Is this constant somewhere?
+ */
+ //0x2 is the subtle hint that this is a typedesc. code:TypeHandle::AsTypeDesc
+ if( arg & 0x2 )
+ {
+ PTR_TypeDesc argTD( arg & ~0x2 );
+ TypeDescToString( argTD, buf );
+ }
+ else
+ {
+ PTR_MethodTable argMT( th.AsTAddr() );
+ MethodTableToString( argMT, buf );
+ }
+}
+
+void NativeImageDumper::DoWriteFieldTypeHandle( const char * name,
+ unsigned offset,
+ unsigned fieldSize,
+ TypeHandle th )
+{
+ TempBuffer buf;
+ TADDR ptr = th.AsTAddr();
+ if( DoWriteFieldAsFixup(name, offset, fieldSize, th.AsTAddr() ) )
+ return;
+ else
+ {
+ TypeHandleToString(th, buf);
+
+ buf.Append( W(" (from TypeHandle)") );
+ /* REVISIT_TODO Fri 10/14/2005
+ * Do a better job of this
+ */
+ if( offset == UINT_MAX )
+ {
+ m_display->WriteElementPointerAnnotated( name,
+ DataPtrToDisplay(ptr),
+ (const WCHAR*) buf );
+ }
+ else
+ {
+ m_display->WriteFieldPointerAnnotated( name, offset, fieldSize,
+ DataPtrToDisplay(ptr),
+ (const WCHAR*) buf );
+ }
+ }
+}
+void NativeImageDumper::WriteElementTypeHandle( const char * name,
+ TypeHandle th )
+{
+ DoWriteFieldTypeHandle( name, UINT_MAX, UINT_MAX, th );
+}
+
+void NativeImageDumper::DoDumpFieldStub( PTR_Stub stub, unsigned offset,
+ unsigned fieldSize, const char * name )
+{
+ _ASSERTE(CHECK_OPT(EECLASSES));
+ if( stub == NULL )
+ {
+ m_display->WriteFieldPointer( name, offset, fieldSize, NULL );
+ }
+ else
+ {
+ m_display->StartStructureWithOffset( name, offset, fieldSize,
+ DPtrToPreferredAddr(stub),
+ sizeof(*stub) );
+ /* REVISIT_TODO Fri 10/14/2005
+ * Dump stub
+ */
+ m_display->EndStructure();
+ }
+}
+
+#ifdef FEATURE_COMINTEROP
+void NativeImageDumper::DoDumpComPlusCallInfo( PTR_ComPlusCallInfo compluscall )
+{
+ m_display->StartStructure( "ComPlusCallInfo",
+ DPtrToPreferredAddr(compluscall),
+ sizeof(*compluscall) );
+
+ DisplayWriteFieldPointer( m_pILStub, compluscall->m_pILStub,
+ ComPlusCallInfo, ALWAYS);
+ /* REVISIT_TODO Fri 12/16/2005
+ * Coverage read stub?
+ */
+ WriteFieldMethodTable(m_pInterfaceMT,
+ compluscall->m_pInterfaceMT,
+ ComPlusCallInfo, ALWAYS);
+
+ PTR_MethodDesc pEventProviderMD = PTR_MethodDesc((TADDR)compluscall->m_pEventProviderMD);
+ WriteFieldMethodDesc(m_pEventProviderMD,
+ pEventProviderMD,
+ ComPlusCallInfo, ALWAYS);
+ DisplayWriteFieldInt( m_cachedComSlot, compluscall->m_cachedComSlot,
+ ComPlusCallInfo, ALWAYS );
+
+ /* REVISIT_TODO Fri 12/16/2005
+ * Dump these as mnemonics
+ */
+ DisplayWriteFieldInt( m_flags, compluscall->m_flags,
+ ComPlusCallInfo, ALWAYS );
+ WriteFieldMethodDesc( m_pStubMD,
+ compluscall->m_pStubMD.GetValueMaybeNull(PTR_HOST_MEMBER_TADDR(ComPlusCallInfo, compluscall, m_pStubMD)),
+ ComPlusCallInfo, ALWAYS );
+
+#ifdef _TARGET_X86_
+ DisplayWriteFieldInt( m_cbStackArgumentSize, compluscall->m_cbStackArgumentSize,
+ ComPlusCallInfo, ALWAYS );
+
+ DisplayWriteFieldPointer( m_pRetThunk,
+ DataPtrToDisplay((TADDR)compluscall->m_pRetThunk),
+ ComPlusCallInfo, ALWAYS );
+#endif
+ m_display->EndStructure(); //ComPlusCallInfo
+}
+#endif // FEATURE_COMINTEROP
+
+void NativeImageDumper::DoWriteFieldStr( PTR_BYTE ptr, const char * name,
+ unsigned offset, unsigned fieldSize )
+{
+ if( ptr == NULL )
+ {
+ if( UINT_MAX == offset )
+ m_display->WriteElementPointer( name, NULL );
+ else
+ m_display->WriteFieldPointer( name, offset, fieldSize, NULL );
+ }
+ else
+ {
+ /* REVISIT_TODO Wed 03/22/2006
+ * Obviously this does the wrong thing for UTF-8.
+ */
+ TempBuffer buf;
+ BYTE b;
+ TADDR taddr = DPtrToPreferredAddr(ptr);
+ PTR_BYTE current = ptr;
+ /* REVISIT_TODO Mon 03/27/2006
+ * Actually handle UTF-8 properly
+ */
+ while( (b = *current++) != 0 )
+ buf.Append( (WCHAR)b );
+ /* REVISIT_TODO Wed 03/22/2006
+ * This seems way way way more verbose than it needs to be.
+ */
+ if( UINT_MAX == offset )
+ {
+ m_display->StartStructure( name, DataPtrToDisplay(taddr),
+ current - ptr );
+ }
+ else
+ {
+ m_display->StartStructureWithOffset( name, offset, fieldSize,
+ DataPtrToDisplay(taddr),
+ current - ptr );
+ }
+ DisplayWriteElementStringW( "Value", (const WCHAR *)buf, ALWAYS );
+ m_display->EndStructure();
+ /*
+ m_display->WriteFieldPointerAnnotated( name, offset, fieldSize,
+ taddr, (const WCHAR *)buf );
+ */
+ }
+}
+void NativeImageDumper::WriteFieldDictionaryLayout(const char * name,
+ unsigned offset,
+ unsigned fieldSize,
+ PTR_DictionaryLayout layout,
+ IMetaDataImport2 * import)
+{
+ if( layout == NULL )
+ {
+ m_display->WriteFieldPointer(name, NULL, offset, fieldSize);
+ return;
+ }
+ m_display->StartVStructureWithOffset( name, offset, fieldSize );
+ DisplayStartArray( "DictionaryLayouts", NULL, ALWAYS );
+ do
+ {
+ DisplayStartStructure( "DictionaryLayout", DPtrToPreferredAddr(layout),
+ sizeof(DictionaryLayout)
+ + sizeof(DictionaryEntryLayout)
+ * (layout->m_numSlots - 1), ALWAYS );
+
+
+ DisplayWriteFieldPointer( m_pNext, DataPtrToDisplay((TADDR)layout->m_pNext),
+ DictionaryLayout, ALWAYS );
+ DisplayWriteFieldInt( m_numSlots, layout->m_numSlots,
+ DictionaryLayout, ALWAYS );
+ DisplayStartArrayWithOffset( m_slots, NULL, DictionaryLayout, ALWAYS );
+ for( unsigned i = 0; i < layout->m_numSlots; ++i )
+ {
+ PTR_DictionaryEntryLayout entry( PTR_HOST_MEMBER_TADDR(DictionaryLayout, layout, m_slots) + (i * sizeof(DictionaryEntryLayout)) );
+ DisplayStartStructure( "DictionaryEntryLayout",
+ DPtrToPreferredAddr(entry), sizeof(*entry),
+ ALWAYS );
+ const char * kind = NULL;
+ switch( entry->GetKind() )
+ {
+#define KIND_ENTRY(x) case x : kind = # x ; break
+ KIND_ENTRY(EmptySlot);
+ KIND_ENTRY(TypeHandleSlot);
+ KIND_ENTRY(MethodDescSlot);
+ KIND_ENTRY(MethodEntrySlot);
+ KIND_ENTRY(ConstrainedMethodEntrySlot);
+ KIND_ENTRY(DispatchStubAddrSlot);
+ KIND_ENTRY(FieldDescSlot);
+#undef KIND_ENTRY
+ default:
+ _ASSERTE( !"unreachable" );
+ }
+ DisplayWriteElementString( "Kind", kind, ALWAYS );
+ DisplayWriteElementPointer( "Signature", DPtrToPreferredAddr(entry->m_signature), ALWAYS );
+ DisplayEndStructure( ALWAYS ); //DictionaryEntryLayout
+ }
+ DisplayEndArray( "Total Dictionary Entries", ALWAYS ); //m_slots
+ DisplayEndStructure( ALWAYS ); //Layout
+ layout = PTR_DictionaryLayout(TO_TADDR(layout->m_pNext));
+ }while( layout != NULL );
+ DisplayEndArray( "Total Dictionary Layouts", ALWAYS ); //DictionaryLayouts
+
+
+ DisplayEndVStructure( ALWAYS ); // name
+}
+void NativeImageDumper::DoWriteFieldFieldDesc( const char * name,
+ unsigned offset,
+ unsigned fieldSize,
+ PTR_FieldDesc fd )
+{
+ if( fd == NULL )
+ {
+ m_display->WriteFieldPointer( name, offset, fieldSize, NULL );
+ }
+ else
+ {
+ TempBuffer buf;
+ FieldDescToString( fd, buf );
+ m_display->WriteFieldPointerAnnotated( name, offset, fieldSize,
+ DPtrToPreferredAddr(fd),
+ (const WCHAR*) buf );
+ }
+
+}
+void NativeImageDumper::DoWriteFieldMethodDesc( const char * name,
+ unsigned offset,
+ unsigned fieldSize,
+ PTR_MethodDesc md )
+{
+ if( md == NULL )
+ {
+ m_display->WriteFieldPointer( name, offset, fieldSize, NULL );
+ }
+ else if( DoWriteFieldAsFixup(name, offset, fieldSize, PTR_TO_TADDR(md)) )
+ {
+ return;
+ }
+ else
+ {
+ TempBuffer buf;
+ MethodDescToString( md, buf );
+ m_display->WriteFieldPointerAnnotated( name, offset, fieldSize,
+ DPtrToPreferredAddr(md),
+ (const WCHAR*) buf );
+ }
+}
+
+void NativeImageDumper::EntryPointToString( TADDR pEntryPoint,
+ SString& buf )
+{
+ const Dependency * dependency = GetDependencyForPointer(pEntryPoint);
+
+ PTR_MethodDesc md;
+ if (dependency->pModule->IsZappedPrecode(pEntryPoint))
+ {
+ md = dac_cast<PTR_MethodDesc>(Precode::GetPrecodeFromEntryPoint(pEntryPoint)->GetMethodDesc());
+ }
+ else
+ {
+ PTR_Module module = (TADDR)m_decoder.GetPersistedModuleImage();
+ PTR_NGenLayoutInfo pNgenLayout = module->GetNGenLayoutInfo();
+ DWORD rva = (DWORD)(pEntryPoint - PTR_TO_TADDR(m_decoder.GetBase()));
+
+ for (int iRange = 0; iRange < 2; iRange++)
+ {
+ if (pNgenLayout->m_CodeSections[iRange].IsInRange(pEntryPoint))
+ {
+ int MethodIndex = NativeUnwindInfoLookupTable::LookupUnwindInfoForMethod(rva, pNgenLayout->m_pRuntimeFunctions[iRange], 0, pNgenLayout->m_nRuntimeFunctions[iRange] - 1);
+ if (MethodIndex >= 0)
+ {
+#ifdef WIN64EXCEPTIONS
+ while (pNgenLayout->m_MethodDescs[iRange][MethodIndex] == 0)
+ MethodIndex--;
+#endif
+
+ PTR_RUNTIME_FUNCTION pRuntimeFunction = pNgenLayout->m_pRuntimeFunctions[iRange] + MethodIndex;
+
+ md = NativeUnwindInfoLookupTable::GetMethodDesc(pNgenLayout, pRuntimeFunction, PTR_TO_TADDR(m_decoder.GetBase()));
+ break;
+ }
+ }
+ }
+ }
+
+ MethodDescToString(md, buf);
+}
+
+void NativeImageDumper::MethodDescToString( PTR_MethodDesc md,
+ SString& buf )
+{
+ if( md == NULL )
+ buf.Append( W("mdMethodDefNil") );
+ else if( md->IsILStub() )
+ buf.AppendUTF8(md->AsDynamicMethodDesc()->GetName());
+ else
+ {
+ //write the name to a temporary location, since I'm going to insert it
+ //into the middle of a signature.
+ TempBuffer tempName;
+
+ _ASSERTE(!CORCOMPILE_IS_POINTER_TAGGED(PTR_TO_TADDR(md)));
+ //work back to the EEClass. That gives us the context for the token.
+ PTR_MethodDescChunk chunk(md->GetMethodDescChunk());
+ //chunk is implicitly remapped because it's calculated from the pointer
+ //to MD.
+ PTR_MethodTable mt = chunk->GetMethodTable();
+ const Dependency * dependency;
+ if( CORCOMPILE_IS_POINTER_TAGGED(PTR_TO_TADDR(mt)) )
+ {
+ //This code will not work for out of module dependencies.
+ _ASSERTE(isSelf(GetDependencyForPointer(PTR_TO_TADDR(md))));
+
+ RVA rva = CORCOMPILE_UNTAG_TOKEN(PTR_TO_TADDR(mt));
+ dependency = GetDependencyForFixup(rva);
+ mt = NULL; //make sure we don't use this for anything.
+ }
+ else
+ dependency = GetDependencyFromMT(mt);
+
+ _ASSERTE(dependency);
+
+
+ /* REVISIT_TODO Fri 10/13/2006
+ * Don't I need the array type name here?
+ */
+ _ASSERTE(dependency->pImport);
+ if( md->GetClassification() == mcArray )
+ {
+
+ //We don't need to append the dependency all the time.
+ //MethodTableToString() already appends it to the MethodTable.
+ //Only do it in cases where we don't call MethodTableToString.
+ if( !isSelf(dependency) )
+ {
+ AppendTokenName( dependency->entry->dwAssemblyRef, tempName,
+ m_manifestImport );
+ tempName.Append(W("!"));
+ }
+
+ _ASSERTE(PTR_TO_TADDR(mt));
+ MethodTableToString( mt, tempName );
+ tempName.Append( W("::") );
+
+ //there are four hard coded names for array method descs, use these
+ //instead of the token.
+ PTR_ArrayMethodDesc amd(PTR_TO_TADDR(md));
+ tempName.AppendUTF8( amd->GetMethodName() );
+ }
+ else
+ {
+ //if we have a MethodTable, use that and compose the name
+ //ourselves. That way we can get generic arguments.
+ if( mt )
+ {
+ ULONG size;
+ MethodTableToString( mt, tempName );
+ tempName.Append( W("::") );
+ IfFailThrow(dependency->pImport->GetMethodProps(md->GetMemberDef(), NULL, bigBuffer,
+ bigBufferSize, &size, NULL, NULL, NULL, NULL,
+ NULL));
+ tempName.Append(bigBuffer);
+ }
+ else
+ {
+ //We don't need to append the dependency all the time.
+ //MethodTableToString() already appends it to the MethodTable.
+ //Only do it in cases where we don't call MethodTableToString.
+ if( !isSelf(dependency) )
+ {
+ AppendTokenName( dependency->entry->dwAssemblyRef, tempName,
+ m_manifestImport );
+ tempName.Append(W("!"));
+ }
+ AppendTokenName( md->GetMemberDef(), tempName, dependency->pImport );
+ }
+
+ if( mcInstantiated == md->GetClassification() )
+ {
+ PTR_InstantiatedMethodDesc imd(PTR_TO_TADDR(md));
+ unsigned numArgs = imd->m_wNumGenericArgs;
+ PTR_Dictionary dict(imd->IMD_GetMethodDictionary());
+ if( dict != NULL )
+ {
+ DictionaryToArgString( dict, numArgs, tempName );
+ }
+ }
+
+ PCCOR_SIGNATURE pvSigBlob;
+ ULONG cbSigBlob;
+ IfFailThrow(dependency->pImport->GetMethodProps(md->GetMemberDef(),
+ NULL,
+ NULL,
+ 0,
+ NULL,
+ NULL,
+ &pvSigBlob,
+ &cbSigBlob,
+ NULL,
+ NULL));
+
+
+ CQuickBytes prettySig;
+ ReleaseHolder<IMDInternalImport> pInternal;
+ IfFailThrow(GetMDInternalInterfaceFromPublic(dependency->pImport, IID_IMDInternalImport,
+ (void**)&pInternal));
+ StackScratchBuffer buffer;
+ const ANSI * ansi = tempName.GetANSI(buffer);
+ ansi = PrettyPrintSig(pvSigBlob, cbSigBlob, ansi, &prettySig, pInternal, NULL);
+ tempName.SetANSI( ansi );
+ }
+ buf.Append(tempName);
+ }
+}
+void NativeImageDumper::WriteElementMethodDesc( const char * name,
+ PTR_MethodDesc md )
+{
+ if( md == NULL )
+ {
+ m_display->WriteElementPointer( name, NULL );
+ }
+ else
+ {
+ TempBuffer buf;
+ MethodDescToString( md, buf );
+ m_display->WriteElementPointerAnnotated( name, DPtrToPreferredAddr(md),
+ (const WCHAR*) buf );
+ }
+}
+void NativeImageDumper::FieldDescToString( PTR_FieldDesc fd, SString& buf )
+{
+ FieldDescToString( fd, mdFieldDefNil, buf );
+}
+void NativeImageDumper::FieldDescToString( PTR_FieldDesc fd, mdFieldDef tok,
+ SString& buf )
+{
+ IF_OPT(DISABLE_NAMES)
+ {
+ buf.Append( W("Disabled") );
+ return;
+ }
+ if( fd == NULL )
+ {
+ if( tok == mdFieldDefNil )
+ buf.Append( W("mdFieldDefNil") );
+ else
+ AppendTokenName( tok, buf );
+ }
+ else
+ {
+ _ASSERTE(!CORCOMPILE_IS_POINTER_TAGGED(PTR_TO_TADDR(fd)));
+ IMetaDataImport2 * importMD = NULL;
+ if( !isInRange(PTR_TO_TADDR(fd)) )
+ {
+ const Dependency * dependency = GetDependencyFromFD(fd);
+ _ASSERTE(dependency);
+ AppendTokenName( dependency->entry->dwAssemblyRef, buf,
+ m_manifestImport );
+ buf.Append(W("!"));
+ importMD = dependency->pImport;
+ _ASSERTE(importMD);
+
+ }
+ else
+ {
+ importMD = m_import;
+ }
+ AppendTokenName( fd->GetMemberDef(), buf, importMD );
+ }
+}
+
+void NativeImageDumper::DoWriteFieldAsHex( const char * name, unsigned offset,
+ unsigned fieldSize, PTR_BYTE ptr,
+ unsigned dataLen )
+{
+ TempBuffer buffer;
+ for( unsigned i = 0; i < dataLen; ++i )
+ {
+ unsigned char b = ptr[i];
+ buffer.AppendPrintf( W("%02x%02x"), (b & 0xf0) >> 4, b & 0xf );
+ }
+ if( offset == UINT_MAX )
+ {
+ m_display->WriteElementStringW( name, (const WCHAR *)buffer );
+ }
+ else
+ {
+ m_display->WriteFieldStringW( name, offset, fieldSize,
+ (const WCHAR *)buffer );
+ }
+}
+void NativeImageDumper::WriteElementMDToken( const char * name, mdToken token )
+{
+ DoWriteFieldMDToken( name, UINT_MAX, UINT_MAX, token );
+}
+void NativeImageDumper::DoWriteFieldMDToken( const char * name, unsigned offset,
+ unsigned fieldSize, mdToken token,
+ IMetaDataImport2 * import )
+{
+ TempBuffer buf;
+ if( RidFromToken(token) == mdTokenNil )
+ {
+ AppendNilToken( token, buf );
+ }
+ else
+ {
+ AppendToken( token, buf, import );
+ }
+ if( UINT_MAX == offset )
+ m_display->WriteElementEnumerated( name, token, (const WCHAR *)buf );
+ else
+ {
+ m_display->WriteFieldEnumerated(name, offset, fieldSize, token,
+ (const WCHAR*)buf);
+ }
+}
+
+void NativeImageDumper::WriteElementMethodTable( const char * name,
+ PTR_MethodTable mt )
+{
+ DoWriteFieldMethodTable( name, UINT_MAX, UINT_MAX, mt );
+}
+void NativeImageDumper::DoWriteFieldMethodTable( const char * name,
+ unsigned offset,
+ unsigned fieldSize,
+ PTR_MethodTable mt )
+{
+ if( mt == NULL )
+ {
+ if( UINT_MAX == offset )
+ m_display->WriteElementPointer( name, NULL );
+ else
+ m_display->WriteFieldPointer( name, offset, fieldSize, NULL );
+ }
+ else if( DoWriteFieldAsFixup( name, offset, fieldSize, PTR_TO_TADDR(mt) ) )
+ {
+ return;
+ }
+ else
+ {
+ TempBuffer buf;
+ MethodTableToString( mt, buf );
+ if( UINT_MAX == offset )
+ {
+
+ m_display->WriteElementPointerAnnotated( name,
+ DPtrToPreferredAddr(mt),
+ (const WCHAR*) buf );
+ }
+ else
+ {
+ m_display->WriteFieldPointerAnnotated( name, offset, fieldSize,
+ DPtrToPreferredAddr(mt),
+ (const WCHAR*) buf );
+ }
+ }
+}
+
+const char * s_VTSCallbackNames[] =
+{
+#define VTSCB_ENTRY(x) # x
+ VTSCB_ENTRY(VTS_CALLBACK_ON_SERIALIZING),
+ VTSCB_ENTRY(VTS_CALLBACK_ON_SERIALIZED),
+ VTSCB_ENTRY(VTS_CALLBACK_ON_DESERIALIZING),
+ VTSCB_ENTRY(VTS_CALLBACK_ON_DESERIALIZED),
+#undef VTSCB_ENTRY
+};
+void NativeImageDumper::DumpFieldDesc( PTR_FieldDesc fd, const char * name )
+{
+ DisplayStartStructure( name, DPtrToPreferredAddr(fd), sizeof(*fd),
+ ALWAYS );
+ WriteFieldMethodTable( m_pMTOfEnclosingClass,
+ fd->GetApproxEnclosingMethodTable(), FieldDesc, ALWAYS );
+ m_display->WriteFieldUInt( "m_mb", offsetof(FieldDesc, m_dword1),
+ fieldsize(FieldDesc, m_dword1),
+ fd->GetMemberDef() );
+ m_display->WriteFieldFlag( "m_isStatic",
+ offsetof(FieldDesc, m_dword1),
+ fieldsize(FieldDesc, m_dword1),
+ fd->m_isStatic );
+ m_display->WriteFieldFlag( "m_isThreadLocal",
+ offsetof(FieldDesc, m_dword1),
+ fieldsize(FieldDesc, m_dword1),
+ fd->m_isThreadLocal );
+#if defined(FEATURE_REMOTING)
+ m_display->WriteFieldFlag( "m_isContextLocal",
+ offsetof(FieldDesc, m_dword1),
+ fieldsize(FieldDesc, m_dword1),
+ fd->m_isContextLocal );
+#endif
+ m_display->WriteFieldFlag( "m_isRVA", offsetof(FieldDesc, m_dword1),
+ fieldsize(FieldDesc, m_dword1),
+ fd->m_isRVA );
+
+ {
+ TempBuffer buf;
+ EnumFlagsToString( fd->m_prot, s_CorFieldAttr,
+ _countof(s_CorFieldAttr), W(" "), buf );
+ m_display->WriteFieldEnumerated( "m_prot",
+ offsetof(FieldDesc, m_dword1),
+ fieldsize(FieldDesc, m_dword1),
+ fd->m_prot, (const WCHAR *)buf );
+ }
+ m_display->WriteFieldFlag( "m_requiresFullMbValue",
+ offsetof(FieldDesc, m_dword1),
+ fieldsize(FieldDesc, m_dword1),
+ fd->m_requiresFullMbValue );
+ m_display->WriteFieldInt( "m_dwOffset",
+ offsetof(FieldDesc, m_dword2),
+ fieldsize(FieldDesc, m_dword2),
+ fd->m_dwOffset );
+ DoWriteFieldCorElementType( "m_type",
+ offsetof(FieldDesc, m_dword2),
+ fieldsize(FieldDesc, m_dword2),
+ (CorElementType)fd->m_type );
+#ifdef _DEBUG
+ WriteFieldStr( m_debugName, PTR_BYTE(TO_TADDR(fd->m_debugName)),
+ FieldDesc, ALWAYS );
+#endif
+ DisplayEndStructure( ALWAYS ); //name
+}
+#if defined(FEATURE_REMOTING)
+NativeImageDumper::EnumMnemonics s_XADOptFlags[] =
+{
+#define XAD_ENTRY(x) NativeImageDumper::EnumMnemonics( RemotableMethodInfo:: x, W(#x) )
+ XAD_ENTRY(XAD_FLAGS_INITIALIZED),
+ XAD_ENTRY(XAD_NOT_OPTIMIZABLE),
+ XAD_ENTRY(XAD_BLITTABLE_ARGS),
+ XAD_ENTRY(XAD_BLITTABLE_RET),
+
+ //this is actually the OR of the three below this. It is a "flag" so
+ //it gets cleared out.
+ XAD_ENTRY(XAD_RET_GC_REF),
+ XAD_ENTRY(XAD_RET_FLOAT),
+ XAD_ENTRY(XAD_RET_DOUBLE),
+#ifdef FEATURE_HFA
+ XAD_ENTRY(XAD_RET_HFA_TYPE),
+#endif
+
+ XAD_ENTRY(XAD_METHOD_IS_VIRTUAL),
+ XAD_ENTRY(XAD_ARGS_HAVE_A_FLOAT),
+
+#undef XAD_ENTRY
+};
+#endif
+
+#ifdef _PREFAST_
+#pragma warning(push)
+#pragma warning(disable:21000) // Suppress PREFast warning about overly large function
+#endif
+void
+NativeImageDumper::DumpMethodTable( PTR_MethodTable mt, const char * name,
+ PTR_Module module )
+{
+ _ASSERTE(NULL != mt);
+ TADDR start, end;
+ bool haveCompleteExtents = true;
+ PTR_EEClass clazz = NULL;
+ if( !mt->IsCanonicalMethodTable() && CORCOMPILE_IS_POINTER_TAGGED(PTR_TO_TADDR(mt->GetCanonicalMethodTable())) )
+ {
+ /* REVISIT_TODO Wed 02/01/2006
+ * GetExtent requires the class in order to compute GetInstAndDictSize.
+ * If the EEClass isn't present, I cannot compute the size. If we are
+ * in this case, skip all of the generic dictionaries.
+ */
+ haveCompleteExtents = false;
+ TempBuffer buf;
+ MethodTableToString( mt, buf );
+ m_display->ErrorPrintF( "WARNING! MethodTable %S is generic but is not hard bound to its EEClass. Cannot compute generic dictionary sizes.\n", (const WCHAR *)buf );
+ }
+ else if( !m_isMscorlibHardBound )
+ {
+ /* REVISIT_TODO Mon 8/20/2007
+ * If we're not hard bound to mscorlib, most things don't work. They depend on knowing what
+ * g_pObjectClass is. Without the hard binding to mscorlib, I can't figure that out.
+ */
+ haveCompleteExtents = false;
+ }
+ if( haveCompleteExtents )
+ {
+ mt->GetSavedExtent(&start, &end);
+ clazz = mt->GetClass();
+ }
+ else
+ {
+ start = PTR_TO_TADDR(mt);
+ end = start + sizeof(*mt);
+ }
+ IF_OPT(METHODTABLES)
+ {
+ m_display->StartStructureWithNegSpace( name, DPtrToPreferredAddr(mt),
+ DataPtrToDisplay(start), end - start );
+ }
+
+ IF_OPT(METHODTABLES)
+ {
+ {
+ TempBuffer buf;
+ MethodTableToString( mt, buf );
+ DisplayWriteElementStringW( "Name", (const WCHAR *)buf, ALWAYS );
+ }
+ if( mt->ContainsPointers() )
+ {
+ PTR_CGCDesc cgc = CGCDesc::GetCGCDescFromMT(mt);
+ unsigned size = (unsigned)cgc->GetSize();
+ /* REVISIT_TODO Tue 12/13/2005
+ * Does anyone actually care about what's inside here?
+ */
+ m_display->WriteFieldEmpty( "CGCDesc", ~size + 1, size );
+ }
+ }
+
+ /* XXX Mon 10/24/2005
+ * The MT might have a component size as the low WORD of the m_dwFlags
+ * field, if it doesn't then that field instead represents a number of
+ * flags, which we know as the "low flags"
+ */
+ if (mt->HasComponentSize())
+ {
+ DisplayWriteElementInt( "ComponentSize", mt->RawGetComponentSize(),
+ METHODTABLES );
+ }
+ else
+ {
+ DisplayWriteFieldEnumerated( m_dwFlags, mt->m_dwFlags & 0xFFFF, MethodTable,
+ s_MTFlagsLow, W(", "), METHODTABLES );
+ }
+
+ /* XXX Fri 10/07/2005
+ * The low WORD of the flags is used for either a component size or flags
+ * (see above), the high WORD is always flags. If this changes then this
+ * might be busted.
+ */
+ DisplayWriteFieldEnumerated( m_dwFlags, mt->m_dwFlags & ~0xFFFF, MethodTable,
+ s_MTFlagsHigh, W(", "), METHODTABLES );
+
+ DisplayWriteFieldInt( m_BaseSize, mt->m_BaseSize, MethodTable,
+ METHODTABLES );
+
+ DisplayWriteFieldEnumerated( m_wFlags2, mt->m_wFlags2, MethodTable,
+ s_MTFlags2, W(", "), METHODTABLES );
+
+ DisplayWriteFieldInt( m_wToken, mt->m_wToken, MethodTable,
+ METHODTABLES );
+ DisplayWriteFieldInt( m_wNumVirtuals, mt->m_wNumVirtuals, MethodTable,
+ METHODTABLES );
+ DisplayWriteFieldInt( m_wNumInterfaces, mt->m_wNumInterfaces, MethodTable,
+ METHODTABLES );
+
+
+
+ PTR_MethodTable parent = mt->m_pParentMethodTable;
+ if( parent == NULL )
+ {
+ DisplayWriteFieldPointer( m_pParentMethodTable, NULL, MethodTable,
+ METHODTABLES );
+ }
+ else
+ {
+ IF_OPT(METHODTABLES)
+ {
+ DoWriteFieldMethodTable( "m_pParentMethodTable",
+ offsetof(MethodTable, m_pParentMethodTable),
+ fieldsize(MethodTable, m_pParentMethodTable),
+ mt->GetParentMethodTable() );
+ }
+ }
+ DisplayWriteFieldPointer( m_pLoaderModule,
+ DPtrToPreferredAddr(mt->GetLoaderModule()),
+ MethodTable, METHODTABLES );
+
+ PTR_MethodTableWriteableData wd = mt->m_pWriteableData;
+ _ASSERTE(wd != NULL);
+ DisplayStartStructureWithOffset( m_pWriteableData, DPtrToPreferredAddr(wd),
+ sizeof(*wd), MethodTable, METHODTABLES );
+ DisplayWriteFieldEnumerated( m_dwFlags, wd->m_dwFlags,
+ MethodTableWriteableData, s_WriteableMTFlags,
+ W(", "), METHODTABLES );
+ DisplayWriteFieldPointer( m_hExposedClassObject,
+ DataPtrToDisplay(wd->m_hExposedClassObject),
+ MethodTableWriteableData, METHODTABLES );
+ _ASSERTE(wd->m_hExposedClassObject == 0);
+ DisplayEndStructure( METHODTABLES ); //m_pWriteableData
+
+ if( !mt->IsCanonicalMethodTable() )
+ {
+ WriteFieldMethodTable( m_pCanonMT, mt->GetCanonicalMethodTable(),
+ MethodTable, METHODTABLES );
+ }
+ else
+ {
+ DisplayWriteFieldPointer( m_pEEClass, DPtrToPreferredAddr(mt->GetClass()),
+ MethodTable, METHODTABLES );
+ }
+
+ if( mt->IsArray() )
+ {
+ WriteFieldTypeHandle( m_ElementTypeHnd,
+ mt->GetApproxArrayElementTypeHandle(),
+ MethodTable, METHODTABLES );
+ }
+
+ if( mt->HasPerInstInfo() && haveCompleteExtents )
+ {
+ //print out the generics dictionary info, and then print out
+ //the contents of those dictionaries.
+ PTR_GenericsDictInfo di = mt->GetGenericsDictInfo();
+ _ASSERTE(NULL != di);
+
+ DisplayStartStructure("GenericsDictInfo", DPtrToPreferredAddr(di), sizeof(*di), METHODTABLES);
+
+ DisplayWriteFieldInt( m_wNumDicts, di->m_wNumDicts, GenericsDictInfo,
+ METHODTABLES );
+ DisplayWriteFieldInt( m_wNumTyPars, di->m_wNumTyPars,
+ GenericsDictInfo, METHODTABLES);
+ DisplayEndStructure( METHODTABLES ); //GenericsDictInfo
+
+
+ DPTR(PTR_Dictionary) perInstInfo = mt->GetPerInstInfo();
+
+ DisplayStartStructure( "PerInstInfo",
+ DPtrToPreferredAddr(perInstInfo),
+ mt->GetPerInstInfoSize(),
+ METHODTABLES );
+ /* XXX Tue 10/11/2005
+ * Only dump this type's dictionary, rather than the inherited
+ * dictionaries. (there are multiple entries in m_pPerInstInfo, but
+ * only print the last one, which is the one for this class).
+ * cloned from Genericdict.cpp
+ */
+ PTR_Dictionary currentDictionary(mt->GetDictionary());
+ if( currentDictionary != NULL )
+ {
+ PTR_DictionaryEntry entry(currentDictionary->EntryAddr(0));
+
+ PTR_DictionaryLayout layout( clazz->GetDictionaryLayout() );
+
+ DisplayStartStructure( "Dictionary",
+ DPtrToPreferredAddr(currentDictionary),
+ //if there is a layout, use it to compute
+ //the size, otherwise there is just the one
+ //entry.
+ DictionaryLayout::GetFirstDictionaryBucketSize(mt->GetNumGenericArgs(), layout),
+ METHODTABLES );
+
+ DisplayStartArrayWithOffset( m_pEntries, NULL, Dictionary,
+ METHODTABLES );
+
+ /* REVISIT_TODO Thu 12/15/2005
+ * use VERBOSE_TYPES here.
+ */
+ _ASSERTE(CHECK_OPT(METHODTABLES));
+
+ //for each generic arg, there is a type handle slot
+ for( unsigned i = 0; i < mt->GetNumGenericArgs(); ++i )
+ DumpDictionaryEntry("Entry", TypeHandleSlot, entry + i);
+
+ //now check for a layout. If it is present, then there are more
+ //entries.
+ if( layout != NULL && (layout->GetNumUsedSlots() > 0) )
+ {
+ unsigned numUsedSlots = layout->GetNumUsedSlots();
+ for( unsigned i = 0; i < numUsedSlots; ++i )
+ {
+ //DictionaryLayout::GetEntryLayout
+ PTR_DictionaryEntryLayout entryLayout(layout->GetEntryLayout(i));
+
+ //Dictionary::GetSlotAddr
+ PTR_DictionaryEntry ent(currentDictionary->EntryAddr(mt->GetNumGenericArgs() + i));
+
+ DumpDictionaryEntry( "Entry", entryLayout->GetKind(), ent );
+ }
+ }
+ if( layout != NULL )
+ {
+ /* REVISIT_TODO Thu 12/15/2005
+ * Where is this data?
+ */
+ }
+ DisplayEndArray( "Total Per instance Info",
+ METHODTABLES ); //m_pEntries
+ DisplayEndStructure( METHODTABLES ); //Dictionary
+ }
+ DisplayEndStructure( METHODTABLES ); //m_pPerInstInfo
+ }
+
+#ifdef _DEBUG
+ WriteFieldStr( debug_m_szClassName,
+ PTR_BYTE(TO_TADDR(mt->debug_m_szClassName)), MethodTable,
+ METHODTABLES );
+#if 0 //already dumping the optional member
+ PTR_InterfaceInfo imap( TO_TADDR(mt->m_pIMapDEBUG) );
+ /* REVISIT_TODO Mon 10/24/2005
+ * Dump interface map
+ */
+ DisplayStartArrayWithOffset( m_pIMapDEBUG, NULL, MethodTable,
+ METHODTABLES );
+ DisplayEndArray( "Total Interfaces", METHODTABLES );
+#endif
+#endif
+
+ if( mt->HasDispatchMapSlot() )
+ {
+ PTR_DispatchMap dispatchMap(mt->GetDispatchMap());
+
+ DisplayStartStructure( "DispatchMap",
+ DPtrToPreferredAddr(dispatchMap),
+ DispatchMap::GetObjectSize(dispatchMap->GetMapSize()),
+ METHODTABLES );
+
+ IF_OPT(VERBOSE_TYPES )
+ {
+ DispatchMap::Iterator iter(mt);
+ DisplayStartArray( "DispatchMap", NULL, VERBOSE_TYPES );
+ while( iter.Next() )
+ {
+ DispatchMapEntry * ent = iter.Entry();
+
+ DisplayStartElement( "Entry", METHODTABLES );
+ DisplayStartVStructure( "TypeID", METHODTABLES );
+ DispatchMapTypeID typeID = ent->GetTypeID();
+ if( typeID.IsThisClass() )
+ DisplayWriteElementFlag("IsThisClass", true, METHODTABLES );
+ else if( typeID.IsImplementedInterface() )
+ {
+ DisplayWriteElementFlag( "IsImplementedInterface",
+ true, METHODTABLES );
+ DisplayWriteElementInt( "GetInterfaceNum",
+ typeID.GetInterfaceNum(), METHODTABLES );
+ }
+ DisplayEndStructure( METHODTABLES ); //TypeID
+ m_display->WriteElementInt( "SlotNumber",
+ ent->GetSlotNumber() );
+ DisplayWriteElementInt( "TargetSlotNumber",
+ ent->GetSlotNumber(), METHODTABLES );
+
+ m_display->EndElement(); //Entry
+ }
+ //DispatchMap
+ DisplayEndArray("Total Dispatch Map Entries", METHODTABLES );
+ }
+ else
+ {
+ CoverageRead(PTR_TO_TADDR(dispatchMap),
+ DispatchMap::GetObjectSize(dispatchMap->GetMapSize()));
+ }
+
+ DisplayEndStructure( METHODTABLES ); //DispatchMap
+ }
+
+ IF_OPT( METHODTABLES )
+ {
+ m_display->StartStructureWithOffset("Vtable",
+ mt->GetVtableOffset(),
+ mt->GetNumVtableIndirections() * sizeof(PTR_PCODE),
+ DataPtrToDisplay(PTR_TO_TADDR(mt) + mt->GetVtableOffset()),
+ mt->GetNumVtableIndirections() * sizeof(PTR_PCODE));
+
+
+ MethodTable::VtableIndirectionSlotIterator itIndirect = mt->IterateVtableIndirectionSlots();
+ while (itIndirect.Next())
+ {
+ SlotChunk sc;
+ sc.addr = itIndirect.GetIndirectionSlot();
+ sc.nSlots = (WORD)itIndirect.GetNumSlots();
+ m_discoveredSlotChunks.AppendEx(sc);
+ }
+
+ IF_OPT(VERBOSE_TYPES)
+ {
+ DisplayStartList( W("[%-4s]: %s (%s)"), ALWAYS );
+ for( unsigned i = 0; i < mt->GetNumVtableIndirections(); ++i )
+ {
+ DisplayStartElement( "Slot", ALWAYS );
+ DisplayWriteElementInt( "Index", i, ALWAYS );
+ PTR_PCODE tgt = mt->GetVtableIndirections()[i];
+ DisplayWriteElementPointer( "Pointer",
+ DataPtrToDisplay(dac_cast<TADDR>(tgt)),
+ ALWAYS );
+ DisplayWriteElementString( "Type", "chunk indirection",
+ ALWAYS );
+ DisplayEndElement( ALWAYS ); //Slot
+ }
+
+ if (mt->HasNonVirtualSlotsArray())
+ {
+ DisplayStartElement( "Slot", ALWAYS );
+ DisplayWriteElementInt( "Index", -1, ALWAYS );
+ PTR_PCODE tgt = mt->GetNonVirtualSlotsArray();
+ DisplayWriteElementPointer( "Pointer",
+ DataPtrToDisplay(dac_cast<TADDR>(tgt)),
+ ALWAYS );
+ DisplayWriteElementString( "Type", "non-virtual chunk indirection",
+ ALWAYS );
+ DisplayEndElement( ALWAYS ); //Slot
+
+ SlotChunk sc;
+ sc.addr = tgt;
+ sc.nSlots = (mt->GetNumVtableSlots() - mt->GetNumVirtuals());
+ m_discoveredSlotChunks.AppendEx(sc);
+ }
+ else if (mt->HasSingleNonVirtualSlot())
+ {
+ DumpSlot((unsigned)-1, mt->GetSlot(mt->GetNumVirtuals()));
+ }
+
+ DisplayEndList( ALWAYS ); //vtable
+ }
+ else
+ {
+ CoverageRead( PTR_TO_TADDR(mt) + mt->GetVtableOffset(),
+ mt->GetNumVtableIndirections() * sizeof(PTR_PCODE) );
+
+ if (mt->HasNonVirtualSlotsArray())
+ {
+ CoverageRead( PTR_TO_TADDR(mt->GetNonVirtualSlotsArray()),
+ mt->GetNonVirtualSlotsArraySize() );
+ }
+
+ }
+ DisplayEndStructure(ALWAYS); //Vtable
+ }
+
+ if( mt->HasInterfaceMap() && CHECK_OPT(METHODTABLES) )
+ {
+ PTR_InterfaceInfo ifMap = mt->GetInterfaceMap();
+ m_display->StartArrayWithOffset( "InterfaceMap",
+ offsetof(MethodTable, m_pMultipurposeSlot2),
+ sizeof(void*),
+ NULL );
+ for( unsigned i = 0; i < mt->GetNumInterfaces(); ++i )
+ {
+ PTR_InterfaceInfo info = ifMap + i;
+ DisplayStartStructure( "InterfaceInfo_t", DPtrToPreferredAddr(info),
+ sizeof(*info), METHODTABLES );
+ WriteFieldMethodTable( m_pMethodTable,
+ info->GetMethodTable(),
+ InterfaceInfo_t, METHODTABLES );
+ DisplayEndStructure( METHODTABLES ); //InterfaceInfo_t
+ }
+ DisplayEndArray( "Total InterfaceInfos",
+ METHODTABLES ); //InterfaceMap
+ }
+
+ //rest of the optional members
+
+ //GenericStatics comes after the generic dictionaries. So if I
+ //don't have extents, I can't print them.
+ if( haveCompleteExtents &&
+ mt->HasGenericsStaticsInfo() &&
+ CHECK_OPT(METHODTABLES)
+ )
+ {
+ PTR_GenericsStaticsInfo genStatics = mt->GetGenericsStaticsInfo();
+ m_display->StartStructureWithOffset( "OptionalMember_"
+ "GenericsStaticsInfo",
+ mt->GetOffsetOfOptionalMember(MethodTable::OptionalMember_GenericsStaticsInfo),
+ sizeof(*genStatics),
+ DPtrToPreferredAddr(genStatics),
+ sizeof(*genStatics) );
+
+ PTR_FieldDesc fieldDescs = genStatics->m_pFieldDescs;
+ if( fieldDescs == NULL )
+ {
+ DisplayWriteFieldPointer( m_pFieldDescs, NULL, GenericsStaticsInfo,
+ ALWAYS );
+ }
+ else
+ {
+ DisplayStartArrayWithOffset( m_pFieldDescs, NULL,
+ GenericsStaticsInfo, ALWAYS );
+ _ASSERTE(clazz == GetClassFromMT(mt));
+ for( int i = 0; i < clazz->GetNumStaticFields(); ++i )
+ {
+ PTR_FieldDesc fd = fieldDescs + i;
+ DumpFieldDesc( fd, "FieldDesc" );
+ }
+ DisplayEndArray( "Total Static Fields", ALWAYS ); // m_pFieldDescs
+ }
+ DisplayWriteFieldUInt( m_DynamicTypeID, (DWORD)genStatics->m_DynamicTypeID,
+ GenericsStaticsInfo, METHODTABLES );
+
+ DisplayEndStructure( METHODTABLES );//OptionalMember_GenericsStaticsInfo
+
+ }
+
+#ifdef FEATURE_COMINTEROP
+ if (haveCompleteExtents &&
+ mt->HasGuidInfo() &&
+ CHECK_OPT(METHODTABLES)
+ )
+ {
+ PTR_GuidInfo guidInfo(*mt->GetGuidInfoPtr());
+
+ if (guidInfo != NULL)
+ {
+ m_display->StartStructureWithOffset( "OptionalMember_GuidInfo",
+ mt->GetOffsetOfOptionalMember(MethodTable::OptionalMember_GuidInfo),
+ sizeof(void*),
+ DPtrToPreferredAddr(guidInfo),
+ sizeof(GuidInfo)
+ );
+ TempBuffer buf;
+ GuidToString( guidInfo->m_Guid, buf );
+ DisplayWriteFieldStringW( m_Guid, (const WCHAR *)buf, GuidInfo,
+ ALWAYS );
+ DisplayWriteFieldFlag( m_bGeneratedFromName,
+ guidInfo->m_bGeneratedFromName,
+ GuidInfo, ALWAYS );
+ DisplayEndStructure( ALWAYS ); // OptionalMember_GuidInfo
+ }
+ }
+
+ if (haveCompleteExtents &&
+ mt->HasCCWTemplate()
+ && CHECK_OPT(METHODTABLES)
+ )
+ {
+ PTR_ComCallWrapperTemplate ccwTemplate(TO_TADDR(*mt->GetCCWTemplatePtr()));
+ m_display->WriteFieldPointer( "OptionalMember_CCWTemplate",
+ mt->GetOffsetOfOptionalMember(MethodTable::OptionalMember_CCWTemplate),
+ sizeof(void *),
+ DPtrToPreferredAddr(ccwTemplate)
+ );
+ }
+#endif // FEATURE_COMINTEROP
+
+#if defined(FEATURE_REMOTING)
+ //RemotingVtsInfo comes after the generic dictionaries. So if I
+ //don't have extents, I can't print them.
+ if(haveCompleteExtents &&
+ mt->HasRemotingVtsInfo()
+ && CHECK_OPT(METHODTABLES)
+ )
+ {
+ _ASSERTE(clazz == GetClassFromMT(mt));
+ /* REVISIT_TODO Tue 12/13/2005
+ * Is this right? is m_wNumStaticFields actually the number of static
+ * fields?
+ */
+ unsigned numFields = (unsigned)(CountFields(mt)
+ - clazz->GetNumStaticFields());
+ PTR_RemotingVtsInfo vts = *mt->GetRemotingVtsInfoPtr();
+ m_display->StartStructureWithOffset( "OptionalMember_RemotingVtsInfo",
+ mt->GetOffsetOfOptionalMember(MethodTable::OptionalMember_RemotingVtsInfo),
+ sizeof(void*),
+ DPtrToPreferredAddr(vts),
+ RemotingVtsInfo::GetSize(numFields)
+ );
+
+ DisplayStartArrayWithOffset( m_pCallbacks, W("%-30s: %s"),
+ RemotingVtsInfo, ALWAYS );
+ for( int i = 0; i < _countof(vts->m_pCallbacks); ++i )
+ {
+ PTR_MethodDesc md( vts->m_pCallbacks[i].GetValue() );
+ DisplayStartElement( "Callback", ALWAYS );
+ DisplayWriteElementString( "CallbackType", s_VTSCallbackNames[i],
+ ALWAYS );
+ if (CORCOMPILE_IS_POINTER_TAGGED(PTR_TO_TADDR(md)))
+ {
+ DoWriteFieldMethodDesc( "MethodDesc", UINT_MAX, UINT_MAX, md );
+ }
+ else
+ {
+ WriteElementMethodDesc( "MethodDesc", md );
+ }
+ DisplayEndElement( ALWAYS ); //Callback
+ }
+ DisplayEndArray( NULL, ALWAYS ); //m_pCallbacks
+#ifdef _DEBUG
+ DisplayWriteFieldInt( m_dwNumFields, vts->m_dwNumFields,
+ RemotingVtsInfo, ALWAYS );
+#endif
+ /* REVISIT_TODO Tue 12/13/2005
+ * Is there any reason to dump this map?
+ */
+ m_display->WriteFieldEmpty("m_rFieldTypes",
+ offsetof(RemotingVtsInfo, m_rFieldTypes),
+ RemotingVtsInfo::GetSize(numFields)
+ - offsetof(RemotingVtsInfo, m_rFieldTypes) );
+ DisplayEndStructure( ALWAYS ); //OptionalMember_RemotingVtsInfo
+ }
+
+ //RemotableMethodInfo comes after the generic dictionaries. So if I
+ //don't have extents, I can't print them.
+ if(haveCompleteExtents &&
+ mt->HasRemotableMethodInfo() &&
+ CHECK_OPT(METHODTABLES)
+ )
+ {
+ PTR_CrossDomainOptimizationInfo cdOpt = *mt->GetRemotableMethodInfoPtr();
+ if( cdOpt == NULL )
+ {
+ _ASSERTE(mt->GetNumVtableSlots() == 0 );
+ m_display->WriteElementPointer("OptionalMember_RemotableMethodInfo",
+ NULL);
+ }
+ else
+ {
+ _ASSERTE(mt->GetNumVtableSlots() > 0 );
+ /* REVISIT_TODO Tue 12/13/2005
+ * One liner copied from CrossDomainCalls.cpp
+ */
+ unsigned size = sizeof(RemotableMethodInfo) * mt->GetNumVtableSlots();
+ //one remotable method info for each method.
+ m_display->StartStructureWithOffset( "OptionalMember_RemotableMethodInfo",
+ mt->GetOffsetOfOptionalMember(MethodTable::OptionalMember_RemotableMethodInfo),
+ sizeof(void*),
+ DPtrToPreferredAddr(cdOpt),
+ size );
+ m_display->StartArrayWithOffset( "m_rmi",
+ offsetof(CrossDomainOptimizationInfo,
+ m_rmi),
+ mt->GetNumVtableSlots()
+ * sizeof(RemotableMethodInfo), NULL );
+ PTR_RemotableMethodInfo rmi( PTR_HOST_MEMBER_TADDR(CrossDomainOptimizationInfo, cdOpt, m_rmi) );
+ for( unsigned i = 0; i < mt->GetNumVtableSlots(); ++i )
+ {
+ PTR_RemotableMethodInfo current = rmi + i;
+ DisplayStartStructure( "RemotableMethodInfo",
+ DPtrToPreferredAddr(current),
+ sizeof(*current), ALWAYS );
+ DisplayWriteFieldEnumerated( m_OptFlags, current->m_OptFlags,
+ RemotableMethodInfo, s_XADOptFlags,
+ W(", "), ALWAYS );
+ DisplayEndStructure( ALWAYS ); //RemotableMethodInfo
+ }
+ DisplayEndArray( "Total RemotableMethodInfos", ALWAYS ); //m_rmi
+ DisplayEndStructure( ALWAYS ); // OptionalMember_RemotableMethodInfo
+ }
+ }
+
+ //ContextStatics comes after the generic dictionaries. So if I
+ //don't have extents, I can't print them.
+ if(haveCompleteExtents &&
+ mt->HasContextStatics() &&
+ CHECK_OPT(METHODTABLES)
+ )
+ {
+ PTR_ContextStaticsBucket statics =
+ *(mt->GetContextStaticsBucketPtr());
+ m_display->StartStructureWithOffset( "OptionalMember_ContextStatics",
+ mt->GetOffsetOfOptionalMember(MethodTable::OptionalMember_ContextStatics),
+ sizeof(void*),
+ DPtrToPreferredAddr(statics),
+ sizeof(*statics) );
+
+#define HANDLE_FIELD(field) \
+ DisplayWriteFieldInt( field, statics->field, \
+ ContextStaticsBucket, ALWAYS )
+ HANDLE_FIELD(m_dwContextStaticsOffset);
+ HANDLE_FIELD(m_wContextStaticsSize);
+#undef HANDLE_FIELD
+ DisplayEndStructure( ALWAYS ); //OptionalMember_ContextStatics
+ }
+#endif
+ DisplayEndStructure( METHODTABLES ); //MethodTable
+} // NativeImageDumper::DumpMethodTable
+#ifdef _PREFAST_
+#pragma warning(pop)
+#endif
+
+void
+NativeImageDumper::DumpMethodTableSlotChunk( PTR_PCODE slotChunk, COUNT_T numSlots )
+{
+ IF_OPT( METHODTABLES )
+ {
+ DisplayStartStructure( "MethodTableSlotChunk", DPtrToPreferredAddr(slotChunk), numSlots * sizeof(PCODE),
+ METHODTABLES );
+
+ IF_OPT(VERBOSE_TYPES)
+ {
+ DisplayStartList( W("[%-4s]: %s (%s)"), ALWAYS );
+ for( unsigned i = 0; i < numSlots; ++i )
+ {
+ DumpSlot(i, slotChunk[i]);
+ }
+ DisplayEndList( ALWAYS ); //Slot list
+ }
+ else
+ CoverageRead( PTR_TO_TADDR(slotChunk),
+ numSlots * sizeof(PCODE) );
+ DisplayEndStructure(ALWAYS); //Slot chunk
+ }
+}
+
+
+void
+NativeImageDumper::DumpSlot( unsigned index, PCODE tgt )
+{
+ IF_OPT(VERBOSE_TYPES)
+ {
+ DisplayStartElement( "Slot", ALWAYS );
+ DisplayWriteElementInt( "Index", index, ALWAYS );
+ DisplayWriteElementPointer( "Pointer",
+ DataPtrToDisplay(tgt),
+ ALWAYS );
+ if( !isInRange(TO_TADDR(tgt)) )
+ {
+ DisplayWriteElementString( "Type", "external",
+ ALWAYS );
+ }
+ else if( isPrecode(TO_TADDR(tgt))
+ && Precode::IsValidType(PTR_Precode(TO_TADDR(tgt))->GetType()) )
+ {
+ PTR_Precode precode(TO_TADDR(tgt));
+ DisplayWriteElementString( "Type", "precode",
+ ALWAYS );
+ //DumpPrecode( precode, module );
+ }
+ else
+ {
+ DisplayWriteElementString( "Type", "code pointer",
+ ALWAYS );
+ }
+ DisplayEndElement( ALWAYS ); //Slot
+ }
+}
+
+NativeImageDumper::EnumMnemonics NativeImageDumper::s_SSMDExtendedFlags[] =
+{
+#define SSMD_ENTRY(x) NativeImageDumper::EnumMnemonics( x, W(#x) )
+
+#define SSMD_ACCESS_ENTRY(x) NativeImageDumper::EnumMnemonics( x, mdMemberAccessMask, W(#x) )
+ SSMD_ACCESS_ENTRY(mdPrivateScope),
+ SSMD_ACCESS_ENTRY(mdPrivate),
+ SSMD_ACCESS_ENTRY(mdFamANDAssem),
+ SSMD_ACCESS_ENTRY(mdAssem),
+ SSMD_ACCESS_ENTRY(mdFamily),
+ SSMD_ACCESS_ENTRY(mdFamORAssem),
+ SSMD_ACCESS_ENTRY(mdPublic),
+#undef SSMD_ACCESS_ENTRY
+
+ SSMD_ENTRY(mdStatic),
+ SSMD_ENTRY(mdFinal),
+ SSMD_ENTRY(mdVirtual),
+ SSMD_ENTRY(mdHideBySig),
+
+ SSMD_ENTRY(mdVtableLayoutMask),
+ SSMD_ENTRY(mdNewSlot),
+
+ SSMD_ENTRY(mdCheckAccessOnOverride),
+ SSMD_ENTRY(mdAbstract),
+ SSMD_ENTRY(mdSpecialName),
+
+ SSMD_ENTRY(mdPinvokeImpl),
+ SSMD_ENTRY(mdUnmanagedExport),
+
+ SSMD_ENTRY(mdRTSpecialName),
+ SSMD_ENTRY(mdHasSecurity),
+ SSMD_ENTRY(mdRequireSecObject),
+
+ NativeImageDumper::EnumMnemonics( DynamicMethodDesc::nomdILStub,
+ W("nomdILStub") ),
+ NativeImageDumper::EnumMnemonics( DynamicMethodDesc::nomdLCGMethod,
+ W("nomdLCGMethod") ),
+#undef SSMD_ENTRY
+};
+
+//maps MethodClassification to a name for a MethodDesc
+const char * const s_MDTypeName[] =
+{
+ "MethodDesc", //mcIL
+ "FCallMethodDesc", //mcFCall
+ "NDirectMethodDesc", //mcNDirect
+ "EEImplMethodDesc", //mcEEImpl - //public StoredSigMethodDesc
+ "ArrayMethodDesc", //mcArray - //public StoredSigMethodDesc
+ "InstantiatedMethodDesc", //mcInstantiated
+#if defined(FEATURE_COMINTEROP)
+ "ComPlusCallMethodDesc", //mcComInterop
+#else
+ "",
+#endif
+ "DynamicMethodDesc", //mcDynamic -- //public StoredSigMethodDesc
+};
+
+unsigned s_MDSizes[] =
+{
+ sizeof(MethodDesc), //mcIL
+ sizeof(FCallMethodDesc), //mcFCall
+ sizeof(NDirectMethodDesc), //mcNDirect
+ sizeof(EEImplMethodDesc), //mcEEImpl
+ sizeof(ArrayMethodDesc), //mcArray
+ sizeof(InstantiatedMethodDesc), //mcInstantiated
+#if defined(FEATURE_COMINTEROP)
+ sizeof(ComPlusCallMethodDesc), //mcComInterop
+#else
+ 0,
+#endif
+ sizeof(DynamicMethodDesc), //mcDynamic
+};
+
+static NativeImageDumper::EnumMnemonics g_NDirectFlags[] =
+{
+#define NDF_ENTRY(x) NativeImageDumper::EnumMnemonics( NDirectMethodDesc:: x, W(#x) )
+ NDF_ENTRY(kEarlyBound),
+ NDF_ENTRY(kHasSuppressUnmanagedCodeAccess),
+ NDF_ENTRY(kIsMarshalingRequiredCached),
+ NDF_ENTRY(kCachedMarshalingRequired),
+ NDF_ENTRY(kNativeAnsi),
+ NDF_ENTRY(kLastError),
+ NDF_ENTRY(kNativeNoMangle),
+ NDF_ENTRY(kVarArgs),
+ NDF_ENTRY(kStdCall),
+ NDF_ENTRY(kThisCall),
+ NDF_ENTRY(kIsQCall),
+ NDF_ENTRY(kHasCopyCtorArgs),
+ NDF_ENTRY(kStdCallWithRetBuf),
+#undef NDF_ENTRY
+};
+NativeImageDumper::EnumMnemonics NativeImageDumper::s_IMDFlags[] =
+{
+#define IMD_ENTRY(x) NativeImageDumper::EnumMnemonics( InstantiatedMethodDesc:: x, W(#x) )
+
+#define IMD_KIND_ENTRY(x) NativeImageDumper::EnumMnemonics( InstantiatedMethodDesc:: x, InstantiatedMethodDesc::KindMask, W(#x) )
+ IMD_KIND_ENTRY(GenericMethodDefinition),
+ IMD_KIND_ENTRY(UnsharedMethodInstantiation),
+ IMD_KIND_ENTRY(SharedMethodInstantiation),
+ IMD_KIND_ENTRY(WrapperStubWithInstantiations),
+#undef IMD_KIND_ENTRY
+
+#ifdef EnC_SUPPORTED
+ // Method is a new virtual function added through EditAndContinue.
+ IMD_ENTRY(EnCAddedMethod),
+#endif // EnC_SUPPORTED
+
+ IMD_ENTRY(Unrestored),
+
+#ifdef FEATURE_COMINTEROP
+ IMD_ENTRY(HasComPlusCallInfo),
+#endif // FEATURE_COMINTEROP
+
+#undef IMD_ENTRY
+};
+
+void NativeImageDumper::DumpPrecode( PTR_Precode precode, PTR_Module module )
+{
+ _ASSERTE(isPrecode(PTR_TO_TADDR(precode)));
+
+ PrecodeType pType = precode->GetType();
+ switch(pType)
+ {
+#define DISPLAY_PRECODE(type) \
+ IF_OPT_AND(PRECODES, METHODDESCS) \
+ { \
+ PTR_ ## type p( precode->As ## type () ); \
+ DisplayStartStructure( # type, \
+ DPtrToPreferredAddr(p), \
+ sizeof(*p), ALWAYS ); \
+ WriteFieldMethodDesc( m_pMethodDesc, \
+ p->m_pMethodDesc, \
+ type, ALWAYS ); \
+ TADDR target = p->GetTarget(); \
+ DisplayWriteElementPointer("Target",\
+ DataPtrToDisplay(target),\
+ ALWAYS );\
+ DisplayEndStructure( ALWAYS ); \
+ }
+
+ case PRECODE_STUB:
+ DISPLAY_PRECODE(StubPrecode); break;
+#ifdef HAS_NDIRECT_IMPORT_PRECODE
+ case PRECODE_NDIRECT_IMPORT:
+ DISPLAY_PRECODE(NDirectImportPrecode); break;
+#endif
+#ifdef HAS_REMOTING_PRECODE
+ case PRECODE_REMOTING:
+ DISPLAY_PRECODE(RemotingPrecode); break;
+#endif
+#ifdef HAS_FIXUP_PRECODE
+ case PRECODE_FIXUP:
+ IF_OPT_AND(PRECODES, METHODDESCS)
+ {
+ PTR_FixupPrecode p( precode->AsFixupPrecode() );
+ DisplayStartStructure( "FixupPrecode",
+ DPtrToPreferredAddr(p),
+ sizeof(*p),
+ ALWAYS );
+ PTR_MethodDesc precodeMD(p->GetMethodDesc());
+#ifdef HAS_FIXUP_PRECODE_CHUNKS
+ {
+ DisplayWriteFieldInt( m_MethodDescChunkIndex,
+ p->m_MethodDescChunkIndex, FixupPrecode,
+ ALWAYS );
+ DisplayWriteFieldInt( m_PrecodeChunkIndex,
+ p->m_PrecodeChunkIndex, FixupPrecode,
+ ALWAYS );
+ if( p->m_PrecodeChunkIndex == 0 )
+ {
+ //dump the location of the Base
+ DisplayWriteElementAddress( "PrecodeChunkBase",
+ DataPtrToDisplay(p->GetBase()),
+ sizeof(void*), ALWAYS );
+ }
+ //Make sure I align up if there is no code slot to make
+ //sure that I get the padding
+ TADDR mdPtrStart = p->GetBase()
+ + (p->m_MethodDescChunkIndex * MethodDesc::ALIGNMENT);
+ TADDR mdPtrEnd = ALIGN_UP( mdPtrStart + sizeof(MethodDesc*),
+ 8 );
+ CoverageRead( mdPtrStart, (ULONG32)(mdPtrEnd - mdPtrStart) );
+ TADDR precodeMDSlot = p->GetBase()
+ + p->m_MethodDescChunkIndex * MethodDesc::ALIGNMENT;
+ DoWriteFieldMethodDesc( "MethodDesc",
+ (DWORD)(precodeMDSlot - PTR_TO_TADDR(p)),
+ sizeof(TADDR), precodeMD );
+ }
+#else //HAS_FIXUP_PRECODE_CHUNKS
+ WriteFieldMethodDesc( m_pMethodDesc,
+ p->m_pMethodDesc,
+ FixupPrecode, ALWAYS );
+#endif //HAS_FIXUP_PRECODE_CHUNKS
+ TADDR target = p->GetTarget();
+ DisplayWriteElementPointer("Target",
+ DataPtrToDisplay(target),
+ ALWAYS );
+ /* REVISIT_TODO Thu 01/05/2006
+ * dump slot with offset if it is here
+ */
+ DisplayEndStructure( ALWAYS ); //FixupPrecode
+ }
+ break;
+#endif
+#ifdef HAS_THISPTR_RETBUF_PRECODE
+ case PRECODE_THISPTR_RETBUF:
+ DISPLAY_PRECODE(ThisPtrRetBufPrecode); break;
+#endif
+ default:
+ _ASSERTE( !"Unsupported precode type" );
+#undef DISPLAY_PRECODE
+#undef PrecodeMDWrite
+ }
+}
+
+#ifdef _PREFAST_
+#pragma warning(push)
+#pragma warning(disable:21000) // Suppress PREFast warning about overly large function
+#endif
+void NativeImageDumper::DumpMethodDesc( PTR_MethodDesc md, PTR_Module module )
+{
+ //StoredSigMethodDesc
+
+ MethodClassification mc =
+ (MethodClassification)md->GetClassification();
+ _ASSERTE(mc >= 0 && mc < mcCount);
+ const char * mdTypeName = s_MDTypeName[mc];
+ unsigned mdSize = (unsigned)md->SizeOf();
+
+ DisplayStartStructure( mdTypeName, DPtrToPreferredAddr(md),
+ mdSize, METHODDESCS );
+ IF_OPT(METHODDESCS)
+ {
+ TempBuffer buf;
+ MethodDescToString( md, buf );
+ DisplayWriteElementStringW( "Name", (const WCHAR *)buf, METHODDESCS );
+ }
+#ifdef _DEBUG
+ IF_OPT(METHODDESCS)
+ {
+ WriteFieldStr(m_pszDebugMethodName,
+ PTR_BYTE(TO_TADDR(md->m_pszDebugMethodName)),
+ MethodDesc, METHODDESCS);
+ WriteFieldStr(m_pszDebugClassName,
+ PTR_BYTE(TO_TADDR(md->m_pszDebugClassName)),
+ MethodDesc, METHODDESCS);
+ WriteFieldStr(m_pszDebugMethodSignature,
+ PTR_BYTE(TO_TADDR(md->m_pszDebugMethodSignature)),
+ MethodDesc, METHODDESCS);
+ }
+ else
+ {
+ CoverageReadString(TO_TADDR(md->m_pszDebugMethodName));
+ CoverageReadString(TO_TADDR(md->m_pszDebugClassName));
+ CoverageReadString(TO_TADDR(md->m_pszDebugMethodSignature));
+ }
+#endif
+
+ DisplayWriteFieldInt( m_wFlags3AndTokenRemainder, md->m_wFlags3AndTokenRemainder,
+ MethodDesc, METHODDESCS );
+
+ DisplayWriteFieldInt( m_chunkIndex, md->m_chunkIndex,
+ MethodDesc, METHODDESCS );
+
+ /* XXX Fri 03/24/2006
+ * This is a workaround. The InstantiatedMethodDescs are in chunks, but there's
+ * no obvious place to display the chunk, so display the bounds here.
+ */
+ if( mc == mcInstantiated && md->m_chunkIndex == 0 )
+ {
+ PTR_MethodDescChunk chunk( md->GetMethodDescChunk() );
+ DisplayWriteElementAddress( "MethodDescChunk", DPtrToPreferredAddr(chunk),
+ chunk->SizeOf(), METHODDESCS );
+ }
+
+ DisplayWriteFieldEnumerated( m_bFlags2, md->m_bFlags2, MethodDesc,
+ s_MDFlag2, W(", "), METHODDESCS );
+
+ DisplayWriteFieldInt( m_wSlotNumber, md->GetSlot(), MethodDesc,
+ METHODDESCS );
+ DisplayWriteFieldEnumerated( m_wFlags, md->m_wFlags, MethodDesc,
+ s_MDC, W(", "), METHODDESCS );
+
+ IF_OPT(IL)
+ {
+ if( md->IsIL() )
+ {
+ PTR_MethodDescChunk chunk(md->GetMethodDescChunk());
+ //chunk is implicitly remapped because it's calculated from the pointer
+ //to MD.
+ PTR_MethodTable mt = chunk->GetMethodTable();
+ if( !CORCOMPILE_IS_POINTER_TAGGED(PTR_TO_TADDR(mt)) )
+ {
+ if ( md->IsTypicalMethodDefinition() )
+ {
+ DWORD dwRVA = 0;
+ m_import->GetMethodProps(md->GetMemberDef(), NULL, NULL, NULL, 0,
+ NULL, NULL, NULL, &dwRVA, NULL);
+
+ if (dwRVA != 0)
+ {
+ _ASSERTE(m_ILHostCopy);
+ _ASSERTE(m_ILSectionStart);
+ _ASSERTE(dwRVA >= m_ILSectionStart);
+ _ASSERTE(dwRVA < (m_ILSectionStart + m_ILSectionSize));
+ //The RVA is from the start of the file, so convert it
+ //to an RVA to the start of the .text section.
+ TADDR pILTarget = (TADDR)m_decoder.GetRvaData(dwRVA);
+ COR_ILMETHOD * pILHeader = (COR_ILMETHOD*)(m_ILHostCopy + dwRVA - m_ILSectionStart);
+
+ COR_ILMETHOD_DECODER decoder(pILHeader);
+
+ DisplayStartStructure( "IL",
+ DataPtrToDisplay(pILTarget),
+ PEDecoder::ComputeILMethodSize(pILTarget),
+ ALWAYS );
+
+ DisplayWriteElementInt( "CodeSize", decoder.GetCodeSize(), ALWAYS );
+
+ // Dump the disassembled IL code?
+
+ DisplayEndStructure( ALWAYS );
+ }
+ }
+ }
+ }
+ }
+ if( md->HasPrecode() )
+ {
+ PTR_Precode precode( md->GetPrecode() );
+
+ DumpPrecode( precode, module );
+ }
+ if ( md->HasNonVtableSlot() )
+ {
+ DisplayWriteElementInt( "Slot", (DWORD)(PTR_TO_TADDR(md->GetAddrOfSlot()) - PTR_TO_TADDR(md)), ALWAYS);
+ }
+ if (md->HasNativeCodeSlot())
+ {
+ DisplayWriteElementInt( "NativeCode", DWORD(md->GetAddrOfNativeCodeSlot() - PTR_TO_TADDR(md)), ALWAYS);
+ //m_display->WriteFieldPointer( "NativeCode",
+ // DWORD(md->GetAddrOfNativeCodeSlot() - PTR_TO_TADDR(md)),
+ // sizeof(TADDR),
+ // md->GetNativeCode() );
+ }
+ if (md->HasMethodImplSlot())
+ {
+ DisplayStartVStructure( "MethodImpl", METHODDESCS );
+ PTR_MethodImpl impl(md->GetMethodImpl());
+ PTR_DWORD slots = impl->GetSlots() - 1; // GetSlots returns the address of the first real slot (past the size)
+ unsigned numSlots = impl->GetSize();
+ _ASSERTE(!numSlots || numSlots == slots[0]);
+ _ASSERTE(slots == NULL || isInRange(PTR_TO_TADDR(slots)));
+ if ((slots != NULL) && isInRange(PTR_TO_TADDR(slots)))
+ {
+ DisplayWriteFieldAddress(pdwSlots, DataPtrToDisplay(dac_cast<TADDR>(slots)),
+ (numSlots + 1) * sizeof(*slots),
+ MethodImpl, METHODDESCS);
+ }
+ else
+ {
+ DisplayWriteFieldPointer(pdwSlots, DataPtrToDisplay(dac_cast<TADDR>(slots)),
+ MethodImpl, METHODDESCS);
+
+ }
+ _ASSERTE(impl->pImplementedMD == NULL
+ || isInRange(PTR_TO_TADDR(impl->pImplementedMD)));
+ if ((impl->pImplementedMD != NULL) &&
+ isInRange(PTR_TO_TADDR(impl->pImplementedMD)))
+ {
+ DisplayWriteFieldAddress( pImplementedMD,
+ DataPtrToDisplay(dac_cast<TADDR>(impl->pImplementedMD)),
+ numSlots * sizeof(MethodDesc*),
+ MethodImpl, METHODDESCS );
+ }
+ else
+ {
+ DisplayWriteFieldPointer( pImplementedMD,
+ DataPtrToDisplay(dac_cast<TADDR>(impl->pImplementedMD)),
+ MethodImpl, METHODDESCS );
+ }
+ DisplayEndVStructure( METHODDESCS );
+ }
+ if (md->HasStoredSig())
+ {
+ DisplayStartVStructure( "StoredSigMethodDesc", METHODDESCS );
+ PTR_StoredSigMethodDesc ssmd(md);
+ //display signature information.
+ if( isInRange(ssmd->m_pSig) )
+ {
+ DisplayWriteFieldAddress(m_pSig, DataPtrToDisplay(ssmd->m_pSig),
+ ssmd->m_cSig, StoredSigMethodDesc,
+ METHODDESCS);
+ }
+ else
+ {
+ DisplayWriteFieldPointer(m_pSig, DataPtrToDisplay(ssmd->m_pSig),
+ StoredSigMethodDesc, METHODDESCS);
+
+ }
+ CoverageRead(TO_TADDR(ssmd->m_pSig), ssmd->m_cSig);
+ DisplayWriteFieldInt( m_cSig, ssmd->m_cSig,
+ StoredSigMethodDesc, METHODDESCS );
+#ifdef _WIN64
+ DisplayWriteFieldEnumerated( m_dwExtendedFlags,
+ ssmd->m_dwExtendedFlags,
+ StoredSigMethodDesc,
+ s_SSMDExtendedFlags, W(", "),
+ METHODDESCS );
+#endif
+ DisplayEndVStructure( METHODDESCS ); //StoredSigMethodDesc
+ }
+ if( mc == mcDynamic )
+ {
+ PTR_DynamicMethodDesc dmd(md);
+ DisplayStartVStructure( "DynamicMethodDesc", METHODDESCS );
+ WriteFieldStr( m_pszMethodName, PTR_BYTE(dmd->m_pszMethodName),
+ DynamicMethodDesc, METHODDESCS );
+ if( !CHECK_OPT(METHODDESCS) )
+ CoverageReadString( PTR_TO_TADDR(dmd->m_pszMethodName) );
+ DisplayWriteFieldPointer( m_pResolver,
+ DPtrToPreferredAddr(dmd->m_pResolver),
+ DynamicMethodDesc, METHODDESCS );
+#ifndef _WIN64
+ DisplayWriteFieldEnumerated( m_dwExtendedFlags,
+ dmd->m_dwExtendedFlags,
+ DynamicMethodDesc,
+ s_SSMDExtendedFlags, W(", "),
+ METHODDESCS );
+#endif
+ DisplayEndVStructure( METHODDESCS );
+ }
+ if (mc == mcFCall )
+ {
+ PTR_FCallMethodDesc fcmd(md);
+ DisplayStartVStructure( "FCallMethodDesc", METHODDESCS );
+
+ DisplayWriteFieldInt( m_dwECallID,
+ fcmd->m_dwECallID,
+ FCallMethodDesc,
+ METHODDESCS );
+
+ DisplayEndVStructure( METHODDESCS ); //NDirectMethodDesc
+ }
+ if( mc == mcNDirect )
+ {
+ PTR_NDirectMethodDesc ndmd(md);
+ DisplayStartVStructure( "NDirectMethodDesc", METHODDESCS );
+ DPTR(NDirectMethodDesc::temp1) nd( PTR_HOST_MEMBER_TADDR(NDirectMethodDesc, ndmd, ndirect) );
+ DisplayStartStructureWithOffset( ndirect,
+ DPtrToPreferredAddr(nd),
+ sizeof(*nd), NDirectMethodDesc,
+ METHODDESCS );
+ DisplayWriteFieldPointer( m_pNativeNDirectTarget,
+ DataPtrToDisplay((TADDR)nd->m_pNativeNDirectTarget),
+ NDirectMethodDesc::temp1,
+ METHODDESCS );
+ DisplayWriteFieldEnumerated( m_wFlags, nd->m_wFlags,
+ NDirectMethodDesc::temp1,
+ g_NDirectFlags, W(", "),
+ METHODDESCS );
+
+ WriteFieldStr( m_pszEntrypointName,
+ PTR_BYTE(TO_TADDR(nd->m_pszEntrypointName)),
+ NDirectMethodDesc::temp1, METHODDESCS );
+ if( !CHECK_OPT(METHODDESCS) )
+ CoverageReadString(TO_TADDR(nd->m_pszEntrypointName));
+ if (md->IsQCall())
+ {
+ DisplayWriteFieldInt( m_dwECallID,
+ nd->m_dwECallID,
+ NDirectMethodDesc::temp1,
+ METHODDESCS );
+ }
+ else
+ {
+ WriteFieldStr( m_pszLibName,
+ PTR_BYTE(TO_TADDR(nd->m_pszLibName)),
+ NDirectMethodDesc::temp1, METHODDESCS );
+ }
+ if( !CHECK_OPT(METHODDESCS) )
+ CoverageReadString( TO_TADDR(nd->m_pszLibName) );
+
+ PTR_NDirectWriteableData wnd( nd->m_pWriteableData );
+ DisplayStartStructureWithOffset( m_pWriteableData,
+ DPtrToPreferredAddr(wnd),
+ sizeof(*wnd),
+ NDirectMethodDesc::temp1,
+ METHODDESCS );
+ DisplayWriteFieldPointer( m_pNDirectTarget,
+ DataPtrToDisplay((TADDR)wnd->m_pNDirectTarget), NDirectWriteableData, METHODDESCS );
+ if( !CHECK_OPT(METHODDESCS) )
+ CoverageRead( PTR_TO_TADDR(wnd), sizeof(*wnd) );
+ DisplayEndStructure( METHODDESCS ); //m_pWriteableData
+
+
+#ifdef HAS_NDIRECT_IMPORT_PRECODE
+ PTR_NDirectImportThunkGlue glue(nd->m_pImportThunkGlue);
+#else
+ PTR_NDirectImportThunkGlue glue(PTR_HOST_MEMBER_TADDR(NDirectMethodDesc::temp1, nd, m_ImportThunkGlue));
+#endif
+
+#ifdef HAS_NDIRECT_IMPORT_PRECODE
+ if (glue == NULL)
+ {
+ // import thunk glue is not needed for P/Invoke that is not inlinable
+ DisplayWriteFieldPointer( m_pImportThunkGlue,
+ NULL,
+ NDirectMethodDesc::temp1,
+ METHODDESCS );
+ }
+ else
+ {
+ DisplayStartStructureWithOffset( m_pImportThunkGlue,
+ DPtrToPreferredAddr(glue),
+ sizeof(*glue),
+ NDirectMethodDesc::temp1,
+ METHODDESCS);
+#else
+ DisplayStartStructureWithOffset( m_ImportThunkGlue,
+ DPtrToPreferredAddr(glue),
+ sizeof(*glue),
+ NDirectMethodDesc::temp1,
+ METHODDESCS);
+#endif
+#ifdef HAS_NDIRECT_IMPORT_PRECODE
+ /* REVISIT_TODO Thu 01/05/2006
+ * Dump this properly as a precode
+ */
+ WriteFieldMethodDesc( m_pMethodDesc, glue->m_pMethodDesc,
+ NDirectImportThunkGlue, METHODDESCS );
+ {
+ PTR_Precode p(glue);
+ DumpPrecode( p, module );
+ }
+ if( !CHECK_OPT(METHODDESCS) )
+ CoverageRead(PTR_TO_TADDR(glue), sizeof(*glue));
+ /* REVISIT_TODO Fri 12/16/2005
+ * Factor out this code into some shared precode dumping code
+ */
+#else //!HAS_NDIRECT_IMPORT_PRECODE
+ /* REVISIT_TODO Fri 10/27/2006
+ * For Whidbey AMD64 (!HAS_NDIRECT_IMPORT_PRECODE), I don't have this data structure in the output.
+ */
+#endif //HAS_NDIRECT_IMPORT_PRECODE
+
+ DisplayEndStructure( METHODDESCS ); //m_pImportThunkGlue
+#ifdef HAS_NDIRECT_IMPORT_PRECODE
+ }
+#endif
+
+#ifdef _TARGET_X86_
+ DisplayWriteFieldInt( m_cbStackArgumentSize,
+ nd->m_cbStackArgumentSize,
+ NDirectMethodDesc::temp1, METHODDESCS );
+#endif
+
+ WriteFieldMethodDesc( m_pStubMD,
+ nd->m_pStubMD.GetValueMaybeNull(PTR_HOST_MEMBER_TADDR(NDirectMethodDesc::temp1, nd, m_pStubMD)),
+ NDirectMethodDesc::temp1, METHODDESCS );
+
+ DisplayEndStructure( METHODDESCS ); //ndirect
+
+
+ DisplayEndVStructure( METHODDESCS ); //NDirectMethodDesc
+ }
+ if( mc == mcEEImpl )
+ {
+ DisplayStartVStructure( "EEImplMethodDesc", METHODDESCS );
+ DisplayEndVStructure( METHODDESCS );
+ }
+#if defined(FEATURE_COMINTEROP)
+ if( mc == mcComInterop )
+ {
+ PTR_ComPlusCallMethodDesc cpmd(md);
+ DisplayStartVStructure( "ComPlusCallMethodDesc", METHODDESCS );
+ PTR_ComPlusCallInfo compluscall((TADDR)cpmd->m_pComPlusCallInfo);
+
+ if (compluscall == NULL)
+ {
+ DisplayWriteFieldPointer( m_pComPlusCallInfo,
+ NULL,
+ ComPlusCallMethodDesc,
+ METHODDESCS );
+ }
+ else
+ {
+ DumpComPlusCallInfo( compluscall, METHODDESCS );
+ }
+
+ DisplayEndVStructure( METHODDESCS ); //ComPlusCallMethodDesc
+ }
+#endif
+ if( mc == mcInstantiated )
+ {
+ PTR_InstantiatedMethodDesc imd(md);
+ DisplayStartVStructure( "InstantiatedMethodDesc", METHODDESCS );
+ unsigned kind = imd->m_wFlags2
+ & InstantiatedMethodDesc::KindMask;
+ if( kind == InstantiatedMethodDesc::SharedMethodInstantiation )
+ {
+ PTR_DictionaryLayout layout(TO_TADDR(imd->m_pDictLayout));
+ IF_OPT(METHODDESCS)
+ {
+ WriteFieldDictionaryLayout( "m_pDictLayout",
+ offsetof(InstantiatedMethodDesc, m_pDictLayout ),
+ fieldsize(InstantiatedMethodDesc, m_pDictLayout),
+ layout,
+ GetDependencyFromMD(md)->pImport );
+ }
+ else
+ {
+ while( layout != NULL )
+ {
+ CoverageRead( PTR_TO_TADDR(layout),
+ sizeof(DictionaryLayout)
+ + sizeof(DictionaryEntryLayout)
+ * (layout->m_numSlots - 1) );
+ layout = PTR_DictionaryLayout(TO_TADDR(layout->m_pNext));
+ }
+ }
+ }
+ else if( kind ==
+ InstantiatedMethodDesc::WrapperStubWithInstantiations )
+ {
+ PTR_MethodDesc wimd(imd->m_pWrappedMethodDesc.GetValue());
+ if( wimd == NULL || !DoWriteFieldAsFixup( "m_pWrappedMethodDesc",
+ offsetof(InstantiatedMethodDesc, m_pWrappedMethodDesc),
+ fieldsize(InstantiatedMethodDesc, m_pWrappedMethodDesc),
+ PTR_TO_TADDR(wimd) ) )
+ {
+ WriteFieldMethodDesc( m_pWrappedMethodDesc, wimd,
+ InstantiatedMethodDesc, METHODDESCS );
+ }
+ }
+ else
+ {
+ _ASSERTE(imd->m_pDictLayout == NULL);
+ DisplayWriteFieldPointer( m_pDictLayout, NULL,
+ InstantiatedMethodDesc,
+ METHODDESCS );
+ }
+ //now handle the contents of the m_pMethInst/m_pPerInstInfo union.
+ unsigned numSlots = imd->m_wNumGenericArgs;
+ PTR_Dictionary inst(imd->m_pPerInstInfo);
+ unsigned dictSize;
+ if( kind == InstantiatedMethodDesc::SharedMethodInstantiation )
+ {
+ dictSize = sizeof(TypeHandle);
+ }
+ else if( kind == InstantiatedMethodDesc::WrapperStubWithInstantiations )
+ {
+ PTR_InstantiatedMethodDesc wrapped =
+ PTR_InstantiatedMethodDesc(imd->m_pWrappedMethodDesc.GetValue());
+ if( CORCOMPILE_IS_POINTER_TAGGED(PTR_TO_TADDR(wrapped)) )
+ {
+ /* XXX Mon 03/27/2006
+ * Note that 4 is the correct answer for all IMDs at this time.
+ */
+ TempBuffer buf;
+ MethodDescToString( md, buf );
+ //m_display->ErrorPrintF( "WARNING! InstantiatedMethodDesc %S wraps a MethodDesc that is a fixup. I cannot accurately determine the size of the associated generic dictionary. Assuming 4.\n", (const WCHAR *)buf );
+ dictSize = (imd->GetNumGenericMethodArgs() + 4) * sizeof(void*);
+ }
+ else
+ {
+ PTR_DictionaryLayout layout(wrapped->IsSharedByGenericMethodInstantiations()
+ ? TO_TADDR(wrapped->m_pDictLayout) : NULL );
+ dictSize = DictionaryLayout::GetFirstDictionaryBucketSize(imd->GetNumGenericMethodArgs(),
+ layout);
+ }
+ }
+ else
+ {
+ dictSize = sizeof(TypeHandle);
+ }
+ //instantiations has the number of slots of
+ //GetNumGenericMethodArgs.
+ if( inst == NULL )
+ {
+ m_display->WriteFieldPointer( "m_pPerInstInfo",
+ offsetof(InstantiatedMethodDesc, m_pPerInstInfo),
+ fieldsize(InstantiatedMethodDesc, m_pPerInstInfo),
+ NULL );
+ }
+ else
+ {
+ IF_OPT(METHODDESCS)
+ {
+
+ m_display->StartStructureWithOffset( "m_pPerInstInfo",
+ offsetof(InstantiatedMethodDesc, m_pPerInstInfo),
+ fieldsize(InstantiatedMethodDesc, m_pPerInstInfo),
+ DPtrToPreferredAddr(inst),
+ dictSize );
+ }
+ DisplayStartArray( "InstantiationInfo", W("[%-2s]: %s"),
+ METHODDESCS );
+ /* REVISIT_TODO Thu 03/23/2006
+ * This doesn't dump the contents of the dictionary which are
+ * hanging around after the real slots. Get around to doing that.
+ */
+ for( unsigned i = 0; i < numSlots
+ && CHECK_OPT(METHODDESCS); ++i )
+ {
+ DisplayStartElement( "Handle", METHODDESCS );
+ DisplayWriteElementInt( "Index", i, METHODDESCS );
+
+ TypeHandle thArg = inst->GetInstantiation()[i].GetValue();
+ IF_OPT(METHODDESCS)
+ WriteElementTypeHandle( "TypeHandle", thArg);
+
+ /* XXX Fri 03/24/2006
+ * There is no really good home for TypeDescs, so I gotta check
+ * lots of places for them.
+ */
+ if( !CORCOMPILE_IS_POINTER_TAGGED(thArg.AsTAddr()) &&
+ thArg.IsTypeDesc() )
+ {
+ PTR_TypeDesc td(thArg.AsTypeDesc());
+ if( isInRange(PTR_TO_TADDR(td)) )
+ {
+ m_discoveredTypeDescs.AppendEx(td);
+ }
+ }
+ DisplayEndElement( METHODDESCS ); //Handle
+ }
+ //Instantiation Info
+ DisplayEndArray( "Total TypeHandles", METHODDESCS );
+
+ DisplayEndVStructure(METHODDESCS); //m_pPerInstInfo;
+ if( !CHECK_OPT(METHODDESCS) )
+ CoverageRead(PTR_TO_TADDR(inst), numSlots * sizeof(*inst));
+ }
+
+ DisplayWriteFieldEnumerated( m_wFlags2, imd->m_wFlags2,
+ InstantiatedMethodDesc, s_IMDFlags,
+ W(", "), METHODDESCS );
+ DisplayWriteFieldInt( m_wNumGenericArgs, imd->m_wNumGenericArgs,
+ InstantiatedMethodDesc, METHODDESCS );
+
+#ifdef FEATURE_COMINTEROP
+ if (imd->IMD_HasComPlusCallInfo())
+ {
+ PTR_ComPlusCallInfo compluscall = imd->IMD_GetComPlusCallInfo();
+ DumpComPlusCallInfo( compluscall, METHODDESCS );
+ }
+#endif // FEATURE_COMINTEROP
+
+ DisplayEndStructure( METHODDESCS );
+ }
+
+ DisplayEndStructure( METHODDESCS ); //MethodDesc (mdTypeName)
+ if( !CHECK_OPT(METHODDESCS) )
+ CoverageRead( PTR_TO_TADDR(md), mdSize );
+
+}
+#ifdef _PREFAST_
+#pragma warning(pop)
+#endif
+
+NativeImageDumper::EnumMnemonics NativeImageDumper::s_EECLIFlags[] =
+{
+#define EECLI_FLAGS_ENTRY(x) NativeImageDumper::EnumMnemonics( EEClassLayoutInfo:: x, W(#x) )
+ EECLI_FLAGS_ENTRY(e_BLITTABLE),
+ EECLI_FLAGS_ENTRY(e_MANAGED_SEQUENTIAL),
+ EECLI_FLAGS_ENTRY(e_ZERO_SIZED),
+ EECLI_FLAGS_ENTRY(e_HAS_EXPLICIT_SIZE),
+#undef EECLI_FLAGS_ENTRY
+};
+
+
+#ifdef _PREFAST_
+#pragma warning(push)
+#pragma warning(disable:21000) // Suppress PREFast warning about overly large function
+#endif
+void
+NativeImageDumper::DumpEEClassForMethodTable( PTR_MethodTable mt )
+{
+ PTR_EEClass clazz = mt->GetClass();
+
+ _ASSERTE(CHECK_OPT(EECLASSES));
+ _ASSERTE(clazz != NULL);
+ _ASSERTE(isInRange(PTR_TO_TADDR(clazz)));
+
+ const char * eeClassType;
+
+ if( clazz->HasLayout() )
+ eeClassType = "LayoutEEClass";
+ else if( mt->IsArray() )
+ eeClassType = "ArrayClass";
+ else if( clazz->IsDelegate() )
+ eeClassType = "DelegateEEClass";
+ else
+ eeClassType = "EEClass";
+
+ DisplayStartStructure( eeClassType, DPtrToPreferredAddr(clazz), clazz->GetSize(),
+ EECLASSES );
+ {
+ TempBuffer buf;
+ MethodTableToString( mt, buf );
+ DisplayWriteElementStringW( "Name", (const WCHAR *)buf, EECLASSES );
+ }
+
+ PTR_GuidInfo guidInfo = clazz->GetGuidInfo();
+ if(guidInfo != NULL)
+ {
+ DisplayStartStructureWithOffset( m_pGuidInfo,
+ DPtrToPreferredAddr(guidInfo),
+ sizeof(*guidInfo), EEClass,
+ EECLASSES );
+ TempBuffer buf;
+ GuidToString( guidInfo->m_Guid, buf );
+ DisplayWriteFieldStringW( m_Guid, (const WCHAR *)buf, GuidInfo,
+ EECLASSES );
+ DisplayWriteFieldFlag( m_bGeneratedFromName,
+ guidInfo->m_bGeneratedFromName,
+ GuidInfo, EECLASSES );
+ DisplayEndStructure( EECLASSES ); //guidinfo
+ }
+ else
+ {
+ /* XXX Fri 10/14/2005
+ * if Clazz isn't an interface, m_pGuidInfo is undefined.
+ */
+ DisplayWriteFieldPointerAnnotated( m_pGuidInfo, PTR_TO_TADDR(guidInfo),
+ W("Invalid"), EEClass, EECLASSES );
+ }
+
+
+#ifdef _DEBUG
+ WriteFieldStr( m_szDebugClassName,
+ PTR_BYTE(TO_TADDR(clazz->m_szDebugClassName)),
+ EEClass, EECLASSES );
+ DisplayWriteFieldFlag( m_fDebuggingClass, clazz->m_fDebuggingClass,
+ EEClass, EECLASSES );
+#endif
+
+ WriteFieldMethodTable( m_pMethodTable, clazz->m_pMethodTable, EEClass,
+ EECLASSES );
+
+ WriteFieldCorElementType( m_NormType, (CorElementType)clazz->m_NormType,
+ EEClass, EECLASSES );
+
+ PTR_FieldDesc fdList = clazz->GetFieldDescList();
+
+ ULONG fieldCount = (ULONG)CountFields(mt);
+ _ASSERTE((fdList == NULL) == (fieldCount == 0));
+
+ IF_OPT(EECLASSES)
+ {
+ m_display->StartStructureWithOffset( "m_pFieldDescList",
+ offsetof(EEClass, m_pFieldDescList),
+ fieldsize(EEClass, m_pFieldDescList),
+ DPtrToPreferredAddr(fdList),
+ fdList != NULL ?
+ sizeof(*fdList) * fieldCount :
+ 0 );
+ }
+ IF_OPT(VERBOSE_TYPES)
+ {
+ if( fdList != NULL )
+ {
+ DisplayStartArray( "FieldDescs", NULL, EECLASSES );
+ for( SIZE_T i = 0; i < fieldCount; ++i )
+ {
+ PTR_FieldDesc fd = fdList + i;
+ IF_OPT(EECLASSES)
+ DumpFieldDesc( fd, "FieldDesc" );
+ }
+ DisplayEndArray( "Total FieldDescs", EECLASSES ); //FieldDescs
+ }
+ }
+ else if( (fdList != NULL) && CHECK_OPT(DEBUG_COVERAGE) )
+ {
+ for( SIZE_T i = 0; i < fieldCount; ++i )
+ {
+ PTR_FieldDesc fd = fdList + i;
+#ifdef _DEBUG
+ if( fd != NULL && fd->m_debugName != NULL )
+ CoverageReadString( fd->m_debugName );
+#endif
+ }
+ CoverageRead( PTR_TO_TADDR(fdList), sizeof(*fdList) * fieldCount );
+ }
+
+ DisplayEndStructure( EECLASSES ); //FieldDescList
+
+ DisplayWriteFieldEnumerated( m_dwAttrClass, clazz->GetAttrClass(),
+ EEClass, s_CorTypeAttr, W(" "), EECLASSES );
+ DisplayWriteFieldEnumerated( m_VMFlags, clazz->m_VMFlags, EEClass,
+ s_VMFlags, W(", "), EECLASSES );
+
+ PTR_MethodDescChunk chunk = clazz->GetChunks();
+
+ DisplayStartArrayWithOffset( m_pChunks, NULL, EEClass, EECLASSES );
+ while( chunk != NULL )
+ {
+ DisplayStartStructure( "MethodDescChunk",
+ DPtrToPreferredAddr(chunk),
+ chunk->SizeOf(), EECLASSES );
+ _ASSERTE(!CORCOMPILE_IS_POINTER_TAGGED(PTR_TO_TADDR(chunk->GetMethodTable())));
+ PTR_MethodTable chunkMT = chunk->GetMethodTable();
+ DisplayWriteFieldPointer( m_methodTable,
+ DPtrToPreferredAddr(chunkMT),
+ MethodDescChunk, EECLASSES );
+ PTR_MethodDescChunk chunkNext = chunk->GetNextChunk();
+ DisplayWriteFieldPointer( m_next,
+ DPtrToPreferredAddr(chunkNext),
+ MethodDescChunk, EECLASSES );
+ DisplayWriteFieldInt( m_size, chunk->m_size, MethodDescChunk,
+ EECLASSES );
+ DisplayWriteFieldInt( m_count, chunk->m_count, MethodDescChunk,
+ EECLASSES );
+ DisplayWriteFieldInt( m_flagsAndTokenRange, chunk->m_flagsAndTokenRange, MethodDescChunk,
+ EECLASSES );
+ /* XXX Wed 12/14/2005
+ * Don't skip walking this array. I need to make sure I touch the
+ * precodes.
+ */
+ DisplayStartArray( "MethodDescs", NULL, METHODDESCS );
+ PTR_MethodDesc md(chunk->GetFirstMethodDesc());
+ while (md != NULL)
+ {
+ IF_OPT_OR(METHODDESCS, DEBUG_COVERAGE)
+ {
+ PTR_Module module = mt->GetModule();
+ if(CORCOMPILE_IS_POINTER_TAGGED(PTR_TO_TADDR(module) ))
+ DumpMethodDesc( md, PTR_Module((TADDR)0) );
+ else
+ DumpMethodDesc( md, module );
+ }
+
+ // Check whether the next MethodDesc is within the bounds of the current chunks
+ TADDR pNext = PTR_HOST_TO_TADDR(md) + md->SizeOf();
+ TADDR pEnd = PTR_HOST_TO_TADDR(chunk) + chunk->SizeOf();
+
+ md = (pNext < pEnd) ? PTR_MethodDesc(pNext) : NULL;
+ }
+
+ DisplayEndArray( "Total MethodDescs", METHODDESCS); //MethodDescs
+
+ chunk = chunk->GetNextChunk();
+
+ DisplayEndStructure( EECLASSES ); //MethodDescChunk
+ }
+
+ DisplayEndArray( "Total MethodDescChunks", EECLASSES );
+ /* REVISIT_TODO Fri 10/14/2005
+ * Dump the class dependencies
+ */
+ //_ASSERTE(!clazz->m_classDependencies.TestAnyBit());
+
+ /* REVISIT_TODO Mon 10/24/2005
+ * Create vstructure for union?
+ */
+ //decode union here
+#ifdef FEATURE_COMINTEROP
+ if( clazz->IsBlittable() || clazz->HasLayout() )
+ {
+ DisplayWriteFieldInt(m_cbNativeSize, clazz->m_cbNativeSize, EEClass,
+ EECLASSES );
+ }
+ else if( clazz->IsInterface() )
+ {
+ DisplayWriteFieldPointer( m_ohDelegate,
+ DataPtrToDisplay(clazz->m_ohDelegate),
+ EEClass, EECLASSES );
+ }
+ else
+ {
+ static const WCHAR * ifnames[] ={W("Dual"),W("Vtable"),W("Dispatch")};
+ m_display->WriteFieldEnumerated( "ComInterfaceType",
+ offsetof(EEClass,
+ m_ComInterfaceType),
+ fieldsize(EEClass,
+ m_ComInterfaceType),
+ (int)clazz->m_ComInterfaceType,
+ ifnames[(int)clazz->m_ComInterfaceType] );
+ }
+#else
+ DisplayWriteFieldInt( m_cbNativeSize, clazz->m_cbNativeSize,
+ EEClass, EECLASSES );
+#endif
+
+#if defined(FEATURE_COMINTEROP)
+ PTR_ComCallWrapperTemplate ccwTemplate(TO_TADDR(clazz->m_pccwTemplate));
+ if( ccwTemplate != NULL )
+ {
+ DisplayWriteFieldPointer( m_pccwTemplate, NULL, EEClass,
+ EECLASSES );
+ }
+ else
+ {
+ /* REVISIT_TODO Fri 10/14/2005
+ * Dump CcwTemplate
+ */
+ DisplayWriteFieldPointer( m_pccwTemplate,
+ DPtrToPreferredAddr(ccwTemplate), EEClass,
+ EECLASSES );
+ }
+#endif // defined(FEATURE_COMINTEROP)
+
+ //fields for classes that aren't just EEClasses.
+ if( clazz->HasLayout() )
+ {
+ PTR_LayoutEEClass layoutClass(PTR_TO_TADDR(clazz));
+ DisplayStartVStructure("LayoutEEClass", EECLASSES );
+
+ PTR_EEClassLayoutInfo eecli( PTR_HOST_MEMBER_TADDR( LayoutEEClass,
+ layoutClass,
+ m_LayoutInfo ) );
+ DisplayStartStructureWithOffset( m_LayoutInfo,
+ DPtrToPreferredAddr(eecli),
+ sizeof(EEClassLayoutInfo),
+ LayoutEEClass, EECLASSES );
+ /* REVISIT_TODO Fri 10/14/2005
+ * Dump EEClassLayoutInfo
+ */
+ DisplayWriteFieldInt( m_cbNativeSize, eecli->m_cbNativeSize,
+ EEClassLayoutInfo, VERBOSE_TYPES );
+ DisplayWriteFieldInt( m_cbManagedSize, eecli->m_cbManagedSize,
+ EEClassLayoutInfo, VERBOSE_TYPES );
+ DisplayWriteFieldInt( m_LargestAlignmentRequirementOfAllMembers,
+ eecli->m_LargestAlignmentRequirementOfAllMembers,
+ EEClassLayoutInfo, VERBOSE_TYPES );
+ DisplayWriteFieldInt( m_ManagedLargestAlignmentRequirementOfAllMembers,
+ eecli->m_ManagedLargestAlignmentRequirementOfAllMembers,
+ EEClassLayoutInfo, VERBOSE_TYPES );
+ DisplayWriteFieldEnumerated( m_bFlags, eecli->m_bFlags,
+ EEClassLayoutInfo, s_EECLIFlags, W(", "),
+ VERBOSE_TYPES );
+ DisplayWriteFieldInt( m_numCTMFields, eecli->m_numCTMFields,
+ EEClassLayoutInfo, VERBOSE_TYPES );
+ PTR_FieldMarshaler fmArray( TO_TADDR(eecli->m_pFieldMarshalers) );
+ DisplayWriteFieldAddress( m_pFieldMarshalers,
+ DPtrToPreferredAddr(fmArray),
+ eecli->m_numCTMFields
+ * MAXFIELDMARSHALERSIZE,
+ EEClassLayoutInfo, VERBOSE_TYPES );
+ /* REVISIT_TODO Wed 03/22/2006
+ * Dump the various types of FieldMarshalers.
+ */
+#if 0
+ DisplayStartArrayWithOffset( m_pFieldMarshalers, NULL,
+ EEClassLayoutInfo, VERBOSE_TYPES );
+ for( unsigned i = 0; i < eecli->m_numCTMFields; ++i )
+ {
+ /* REVISIT_TODO Wed 03/22/2006
+ * Try to display the type of the field marshaler in the future.
+ */
+ PTR_FieldMarshaler current = fmArray + i;
+ DisplayStartStructure( "FieldMarshaler",
+ DPtrToPreferredAddr(current),
+ sizeof(*current), VERBOSE_TYPES );
+ WriteFieldFieldDesc( m_pFD, PTR_FieldDesc(TO_TADDR(current->m_pFD)),
+ FieldMarshaler, VERBOSE_TYPES );
+ DisplayWriteFieldInt( m_dwExternalOffset,
+ current->m_dwExternalOffset, FieldMarshaler,
+ VERBOSE_TYPES );
+ DisplayEndStructure( VERBOSE_TYPES ); //FieldMarshaler
+ }
+
+ DisplayEndArray( "Number of FieldMarshalers", VERBOSE_TYPES ); //m_pFieldMarshalers
+#endif
+
+ DisplayEndStructure( EECLASSES ); //LayoutInfo
+
+ DisplayEndVStructure( EECLASSES ); //LayoutEEClass
+ }
+ else if( mt->IsArray() )
+ {
+ PTR_ArrayClass arrayClass(PTR_TO_TADDR(clazz));
+ DisplayStartVStructure( "ArrayClass", EECLASSES);
+ IF_OPT(EECLASSES)
+ {
+ m_display->WriteFieldInt( "m_rank", offsetof(ArrayClass, m_rank),
+ fieldsize(ArrayClass, m_rank),
+ arrayClass->GetRank() );
+ }
+ DoWriteFieldCorElementType( "m_ElementType",
+ offsetof(ArrayClass, m_ElementType),
+ fieldsize(ArrayClass, m_ElementType),
+ arrayClass->GetArrayElementType() );
+
+ DisplayEndVStructure( EECLASSES ); //ArrayClass
+ }
+ else if( clazz->IsDelegate() )
+ {
+ PTR_DelegateEEClass delegateClass(PTR_TO_TADDR(clazz));
+ DisplayStartVStructure( "DelegateEEClass", EECLASSES );
+
+ DumpFieldStub( m_pStaticCallStub, delegateClass->m_pStaticCallStub,
+ DelegateEEClass, EECLASSES );
+ DumpFieldStub( m_pInstRetBuffCallStub,
+ delegateClass->m_pInstRetBuffCallStub,
+ DelegateEEClass, EECLASSES );
+
+ WriteFieldMethodDesc( m_pInvokeMethod,
+ delegateClass->m_pInvokeMethod,
+ DelegateEEClass, EECLASSES );
+ DumpFieldStub( m_pMultiCastInvokeStub,
+ delegateClass->m_pMultiCastInvokeStub,
+ DelegateEEClass, EECLASSES );
+
+ DPTR(UMThunkMarshInfo)
+ umInfo(TO_TADDR(delegateClass->m_pUMThunkMarshInfo));
+
+ if( umInfo == NULL )
+ {
+ DisplayWriteFieldPointer( m_pUMThunkMarshInfo, NULL,
+ DelegateEEClass, EECLASSES );
+ }
+ else
+ {
+ DisplayStartStructureWithOffset( m_pUMThunkMarshInfo,
+ DPtrToPreferredAddr(umInfo),
+ sizeof(*umInfo),
+ DelegateEEClass, EECLASSES );
+ /* REVISIT_TODO Fri 10/14/2005
+ * DumpUMThunkMarshInfo
+ */
+ DisplayEndStructure( EECLASSES ); //UMThunkMarshInfo
+ }
+
+ WriteFieldMethodDesc( m_pBeginInvokeMethod,
+ delegateClass->m_pBeginInvokeMethod,
+ DelegateEEClass, EECLASSES );
+ WriteFieldMethodDesc( m_pEndInvokeMethod,
+ delegateClass->m_pEndInvokeMethod,
+ DelegateEEClass, EECLASSES );
+ DisplayWriteFieldPointer( m_pMarshalStub, delegateClass->m_pMarshalStub,
+ DelegateEEClass, EECLASSES );
+
+ WriteFieldMethodDesc( m_pForwardStubMD,
+ PTR_MethodDesc(TO_TADDR(delegateClass->m_pForwardStubMD)),
+ DelegateEEClass, EECLASSES );
+ WriteFieldMethodDesc( m_pReverseStubMD,
+ PTR_MethodDesc(TO_TADDR(delegateClass->m_pReverseStubMD)),
+ DelegateEEClass, EECLASSES );
+
+#ifdef FEATURE_COMINTEROP
+ DPTR(ComPlusCallInfo) compluscall((TADDR)delegateClass->m_pComPlusCallInfo);
+ if (compluscall == NULL)
+ {
+ DisplayWriteFieldPointer( m_pComPlusCallInfo,
+ NULL,
+ DelegateEEClass,
+ EECLASSES );
+ }
+ else
+ {
+ DumpComPlusCallInfo( compluscall, EECLASSES );
+ }
+#endif // FEATURE_COMINTEROP
+
+ DisplayEndVStructure( EECLASSES ); //DelegateEEClass
+ }
+
+ DisplayEndStructure( EECLASSES ); //eeClassType
+
+ PTR_EEClassOptionalFields pClassOptional = clazz->GetOptionalFields();
+ if (pClassOptional)
+ {
+ DisplayStartStructure( "EEClassOptionalFields", DPtrToPreferredAddr(pClassOptional), sizeof(EEClassOptionalFields),
+ EECLASSES );
+
+#ifdef FEATURE_COMINTEROP
+ PTR_SparseVTableMap sparseVTMap(TO_TADDR(pClassOptional->m_pSparseVTableMap));
+ if( sparseVTMap == NULL )
+ {
+ DisplayWriteFieldPointer( m_pSparseVTableMap, NULL, EEClassOptionalFields,
+ EECLASSES );
+ }
+ else
+ {
+ _ASSERTE( !"Untested code" );
+ IF_OPT(EECLASSES)
+ {
+ m_display->StartStructure( "m_SparseVTableMap",
+ DPtrToPreferredAddr(sparseVTMap),
+ sizeof(*sparseVTMap) );
+ }
+ _ASSERTE(sparseVTMap->m_MapList != NULL);
+ PTR_SparseVTableMap_Entry mapList(TO_TADDR(sparseVTMap->m_MapList));
+ DisplayStartArray( "m_MapList", NULL, EECLASSES );
+ for( WORD i = 0; i < sparseVTMap->m_MapEntries; ++i )
+ {
+ DisplayWriteFieldInt( m_Start, mapList[i].m_Start,
+ SparseVTableMap::Entry, EECLASSES );
+ DisplayWriteFieldInt( m_Span, mapList[i].m_Span,
+ SparseVTableMap::Entry, EECLASSES );
+ DisplayWriteFieldInt( m_Span, mapList[i].m_MapTo,
+ SparseVTableMap::Entry, EECLASSES );
+ }
+
+ DisplayEndArray( "Total Entries", EECLASSES ); //m_MapList
+
+ DisplayWriteFieldInt( m_MapEntries, sparseVTMap->m_MapEntries,
+ SparseVTableMap, EECLASSES );
+ DisplayWriteFieldInt( m_Allocated, sparseVTMap->m_Allocated,
+ SparseVTableMap, EECLASSES );
+ DisplayWriteFieldInt( m_LastUsed, sparseVTMap->m_LastUsed,
+ SparseVTableMap, EECLASSES );
+ DisplayWriteFieldInt( m_VTSlot, sparseVTMap->m_VTSlot,
+ SparseVTableMap, EECLASSES );
+ DisplayWriteFieldInt( m_MTSlot, sparseVTMap->m_MTSlot,
+ SparseVTableMap, EECLASSES );
+
+ DisplayEndStructure( EECLASSES ); //SparseVTableMap
+ }
+
+ WriteFieldTypeHandle( m_pCoClassForIntf, pClassOptional->m_pCoClassForIntf,
+ EEClassOptionalFields, EECLASSES );
+
+ PTR_ClassFactoryBase classFactory(TO_TADDR(pClassOptional->m_pClassFactory));
+ if( classFactory != NULL )
+ {
+ DisplayWriteFieldPointer( m_pClassFactory, NULL, EEClassOptionalFields,
+ EECLASSES );
+ }
+ else
+ {
+ /* REVISIT_TODO Fri 10/14/2005
+ * Dump ComClassFactory
+ */
+ DisplayWriteFieldPointer( m_pClassFactory,
+ DPtrToPreferredAddr(classFactory),
+ EEClassOptionalFields, EECLASSES );
+ }
+#endif // FEATURE_COMINTEROP
+
+ PTR_DictionaryLayout layout = pClassOptional->m_pDictLayout;
+ if( layout == NULL )
+ {
+ DisplayWriteFieldPointer( m_pDictLayout, NULL, EEClassOptionalFields, EECLASSES );
+ }
+ else
+ {
+ IF_OPT(VERBOSE_TYPES)
+ {
+ WriteFieldDictionaryLayout( "m_pDictLayout",
+ offsetof(EEClassOptionalFields, m_pDictLayout),
+ fieldsize(EEClassOptionalFields, m_pDictLayout),
+ layout, GetDependencyFromMT(mt)->pImport );
+ }
+ else
+ {
+ while( layout != NULL )
+ {
+ CoverageRead( PTR_TO_TADDR(layout),
+ sizeof(DictionaryLayout)
+ + sizeof(DictionaryEntryLayout)
+ * (layout->m_numSlots - 1) );
+ layout = PTR_DictionaryLayout(TO_TADDR(layout->m_pNext));
+ }
+ }
+ }
+ PTR_BYTE varianceInfo = TO_TADDR(pClassOptional->m_pVarianceInfo);
+ if( varianceInfo == NULL )
+ {
+ DisplayWriteFieldPointer( m_pVarianceInfo, NULL,
+ EEClassOptionalFields, EECLASSES );
+ }
+ else
+ {
+ /* REVISIT_TODO Fri 10/14/2005
+ * Dump variance info
+ */
+ DisplayWriteFieldPointer( m_pVarianceInfo,
+ DPtrToPreferredAddr(varianceInfo), EEClassOptionalFields,
+ EECLASSES );
+ }
+
+ DisplayWriteFieldInt( m_cbModuleDynamicID, pClassOptional->m_cbModuleDynamicID,
+ EEClassOptionalFields, EECLASSES );
+
+ /* REVISIT_TODO Fri 10/14/2005
+ * Use the macros from ConstrainedExecutionRegion.cpp on this?
+ */
+ DisplayWriteFieldUInt( m_dwReliabilityContract,
+ clazz->GetReliabilityContract(),
+ EEClassOptionalFields, EECLASSES );
+
+ DisplayWriteFieldEnumerated( m_SecProps, clazz->GetSecurityProperties()->dwFlags,
+ EEClassOptionalFields, s_SecurityProperties, W("|"),
+ EECLASSES );
+
+ DisplayEndStructure( EECLASSES ); // EEClassOptionalFields
+ }
+} // NativeImageDumper::DumpEEClassForMethodTable
+#ifdef _PREFAST_
+#pragma warning(pop)
+#endif
+
+enum TypeDescType
+{
+ TDT_IsTypeDesc,
+ TDT_IsParamTypeDesc,
+ TDT_IsArrayTypeDesc,
+ TDT_IsTypeVarTypeDesc,
+ TDT_IsFnPtrTypeDesc
+};
+const char * const g_typeDescTypeNames[] =
+{
+ "TypeDesc",
+ "ParamTypeDesc",
+ "ArrayTypeDesc",
+ "TypeVarTypeDesc",
+ "FnPtrTypeDesc"
+};
+int g_typeDescSizes[] =
+{
+ sizeof(TypeDesc),
+ sizeof(ParamTypeDesc),
+ sizeof(ArrayTypeDesc),
+ sizeof(TypeVarTypeDesc),
+ -1//sizeof(FnPtrTypeDesc) -- variable size
+};
+TypeDescType getTypeDescType( PTR_TypeDesc td )
+{
+ _ASSERTE(td != NULL);
+ if( td->IsArray() )
+ return TDT_IsArrayTypeDesc;
+ if( td->HasTypeParam() )
+ return TDT_IsParamTypeDesc;
+ if( td->IsGenericVariable() )
+ return TDT_IsTypeVarTypeDesc;
+ if( td->GetInternalCorElementType() == ELEMENT_TYPE_FNPTR )
+ return TDT_IsFnPtrTypeDesc;
+ return TDT_IsTypeDesc;
+}
+NativeImageDumper::EnumMnemonics NativeImageDumper::s_TDFlags[] =
+{
+
+#define TDF_ENTRY(x) NativeImageDumper::EnumMnemonics(TypeDesc:: x, W(#x) )
+ TDF_ENTRY(enum_flag_NeedsRestore),
+ TDF_ENTRY(enum_flag_PreRestored),
+ TDF_ENTRY(enum_flag_Unrestored),
+ TDF_ENTRY(enum_flag_UnrestoredTypeKey),
+ TDF_ENTRY(enum_flag_IsNotFullyLoaded),
+ TDF_ENTRY(enum_flag_DependenciesLoaded),
+#undef TDF_ENTRY
+};
+
+NativeImageDumper::EnumMnemonics s_CConv[] =
+{
+#define CC_ENTRY(x) NativeImageDumper::EnumMnemonics( x, W(#x) )
+
+#define CC_CALLCONV_ENTRY(x) NativeImageDumper::EnumMnemonics( x, IMAGE_CEE_CS_CALLCONV_MASK, W(#x) )
+ CC_CALLCONV_ENTRY(IMAGE_CEE_CS_CALLCONV_VARARG),
+ CC_CALLCONV_ENTRY(IMAGE_CEE_CS_CALLCONV_FIELD),
+ CC_CALLCONV_ENTRY(IMAGE_CEE_CS_CALLCONV_LOCAL_SIG),
+ CC_CALLCONV_ENTRY(IMAGE_CEE_CS_CALLCONV_PROPERTY),
+ CC_CALLCONV_ENTRY(IMAGE_CEE_CS_CALLCONV_UNMGD),
+ CC_CALLCONV_ENTRY(IMAGE_CEE_CS_CALLCONV_GENERICINST),
+ CC_CALLCONV_ENTRY(IMAGE_CEE_CS_CALLCONV_NATIVEVARARG),
+#undef CC_CALLCONV_ENTRY
+
+ CC_ENTRY(IMAGE_CEE_CS_CALLCONV_HASTHIS),
+ CC_ENTRY(IMAGE_CEE_CS_CALLCONV_EXPLICITTHIS),
+ CC_ENTRY(IMAGE_CEE_CS_CALLCONV_GENERIC)
+};
+
+
+void NativeImageDumper::DumpTypeDesc( PTR_TypeDesc td )
+{
+ _ASSERTE(CHECK_OPT(TYPEDESCS));
+ TypeDescType tdt = getTypeDescType(td);
+ int size = g_typeDescSizes[(int)tdt];
+ if( size == -1 )
+ {
+ _ASSERTE(tdt == TDT_IsFnPtrTypeDesc);
+ size = FnPtrTypeDesc::DacSize(PTR_TO_TADDR(td));
+ }
+ DisplayStartStructure( g_typeDescTypeNames[(int)tdt],
+ DPtrToPreferredAddr(td), size, TYPEDESCS );
+
+ //first handle the fields of typedesc
+ WriteFieldCorElementType( m_typeAndFlags, td->GetInternalCorElementType(),
+ TypeDesc, TYPEDESCS );
+ DisplayWriteFieldEnumerated( m_typeAndFlags, td->m_typeAndFlags, TypeDesc,
+ s_TDFlags, W(", "), TYPEDESCS );
+ if( tdt == TDT_IsParamTypeDesc || tdt == TDT_IsArrayTypeDesc )
+ {
+ PTR_ParamTypeDesc ptd(td);
+ DisplayStartVStructure( "ParamTypeDesc", TYPEDESCS );
+ WriteFieldMethodTable( m_TemplateMT, ptd->m_TemplateMT.GetValue(),
+ ParamTypeDesc, TYPEDESCS );
+ WriteFieldTypeHandle( m_Arg, ptd->m_Arg,
+ ParamTypeDesc, TYPEDESCS );
+ DisplayWriteFieldPointer( m_hExposedClassObject,
+ DataPtrToDisplay(ptd->m_hExposedClassObject),
+ ParamTypeDesc, TYPEDESCS );
+
+ DisplayEndVStructure( TYPEDESCS ); //ParamTypeDesc
+ }
+ else if( tdt == TDT_IsFnPtrTypeDesc )
+ {
+ PTR_FnPtrTypeDesc ftd(td);
+ DisplayStartVStructure( "FnPtrTypeDesc", TYPEDESCS );
+ DisplayWriteFieldInt( m_NumArgs, ftd->m_NumArgs, FnPtrTypeDesc,
+ TYPEDESCS );
+ DisplayWriteFieldEnumerated( m_CallConv, ftd->m_CallConv,
+ FnPtrTypeDesc, s_CConv, W(", "),
+ TYPEDESCS );
+ DisplayStartArrayWithOffset( m_RetAndArgTypes, W("[%-4s]: %s"),
+ FnPtrTypeDesc, TYPEDESCS );
+ PTR_TypeHandle args( PTR_HOST_MEMBER_TADDR(FnPtrTypeDesc, ftd,
+ m_RetAndArgTypes) );
+ for( unsigned i = 0; i < ftd->m_NumArgs; ++i )
+ {
+ DisplayStartElement( "Argument", TYPEDESCS );
+ DisplayWriteElementInt( "Index", i, TYPEDESCS );
+ IF_OPT( TYPEDESCS )
+ WriteElementTypeHandle( "TypeHandle", args[i] );
+ DisplayEndElement( TYPEDESCS );
+ }
+ DisplayEndArray( "Total Arguments", TYPEDESCS );
+ DisplayEndVStructure( TYPEDESCS );
+ }
+ else if( tdt == TDT_IsTypeVarTypeDesc )
+ {
+ PTR_TypeVarTypeDesc tvtd(td);
+ DisplayStartVStructure( "TypeVarTypeDesc", TYPEDESCS );
+ DisplayWriteFieldPointer( m_pModule,
+ DPtrToPreferredAddr(tvtd->m_pModule),
+ TypeVarTypeDesc, TYPEDESCS );
+ DisplayWriteFieldUInt( m_typeOrMethodDef,
+ tvtd->m_typeOrMethodDef,
+ TypeVarTypeDesc, TYPEDESCS );
+ DisplayWriteFieldInt( m_numConstraints, tvtd->m_numConstraints,
+ TypeVarTypeDesc, TYPEDESCS );
+ if( tvtd->m_constraints == NULL )
+ {
+ DisplayWriteFieldPointer( m_constraints, NULL, TypeVarTypeDesc,
+ TYPEDESCS );
+ }
+ else
+ {
+ DisplayStartStructureWithOffset( m_constraints,
+ DPtrToPreferredAddr(tvtd->m_constraints),
+ sizeof(*tvtd->m_constraints) *
+ tvtd->m_numConstraints,
+ TypeVarTypeDesc, TYPEDESCS );
+ DisplayStartArray( "Constraints", NULL, TYPEDESCS );
+ for( unsigned i = 0; i < tvtd->m_numConstraints; ++i )
+ {
+ WriteElementTypeHandle( "TypeHandle", tvtd->m_constraints[i] );
+ }
+ DisplayEndArray( "Total Constraints", TYPEDESCS ); //Constraints
+ DisplayEndStructure( TYPEDESCS ); //m_constraints
+ }
+ DisplayWriteFieldPointer( m_hExposedClassObject,
+ DataPtrToDisplay(tvtd->m_hExposedClassObject),
+ TypeVarTypeDesc, TYPEDESCS );
+ DisplayWriteFieldUInt( m_token, tvtd->m_token, TypeVarTypeDesc,
+ TYPEDESCS );
+ DisplayWriteFieldInt( m_index, tvtd->m_index, TypeVarTypeDesc,
+ TYPEDESCS );
+
+ DisplayEndVStructure( TYPEDESCS ); //TypeVarTypeDesc
+ }
+
+
+ DisplayEndStructure( TYPEDESCS ); // g_typeDescTypeNames
+
+}
+
+void NativeImageDumper::DumpDictionaryEntry( const char * elementName,
+ DictionaryEntryKind kind,
+ PTR_DictionaryEntry entry )
+{
+ m_display->StartElement( elementName );
+ const char * name = NULL;
+ switch(kind)
+ {
+ case EmptySlot:
+ m_display->WriteEmptyElement("EmptySlot");
+ break;
+ case TypeHandleSlot:
+ {
+ TypeHandle th = dac_cast<DPTR(FixupPointer<TypeHandle>)>(entry)->GetValue();
+ WriteElementTypeHandle( "TypeHandle", th );
+ /* XXX Fri 03/24/2006
+ * There is no straightforward home for these, so make sure to
+ * record them
+ */
+ if( !CORCOMPILE_IS_POINTER_TAGGED(th.AsTAddr()) && th.IsTypeDesc() )
+ {
+ PTR_TypeDesc td(th.AsTypeDesc());
+ if( isInRange(PTR_TO_TADDR(td)) )
+ {
+ m_discoveredTypeDescs.AppendEx(td);
+ }
+ }
+ }
+ break;
+ case MethodDescSlot:
+ {
+ TempBuffer buf;
+ PTR_MethodDesc md(TO_TADDR(*entry));
+ WriteElementMethodDesc( "MethodDesc", md );
+ }
+ break;
+ case MethodEntrySlot:
+ name = "MethodEntry";
+ goto StandardEntryDisplay;
+ case ConstrainedMethodEntrySlot:
+ name = "ConstrainedMethodEntry";
+ goto StandardEntryDisplay;
+ case DispatchStubAddrSlot:
+ name = "DispatchStubAddr";
+ goto StandardEntryDisplay;
+ /* REVISIT_TODO Tue 10/11/2005
+ * Print out name information here
+ */
+ case FieldDescSlot:
+ name = "FieldDescSlot";
+StandardEntryDisplay:
+ m_display->WriteElementPointer(name, DataPtrToDisplay((TADDR)*entry));
+ break;
+ default:
+ _ASSERTE( !"unreachable" );
+ }
+ m_display->EndElement(); //elementName
+}
+
+#ifdef FEATURE_READYTORUN
+IMAGE_DATA_DIRECTORY * NativeImageDumper::FindReadyToRunSection(DWORD type)
+{
+ PTR_READYTORUN_SECTION pSections = dac_cast<PTR_READYTORUN_SECTION>(dac_cast<TADDR>(m_pReadyToRunHeader) + sizeof(READYTORUN_HEADER));
+ for (DWORD i = 0; i < m_pReadyToRunHeader->NumberOfSections; i++)
+ {
+ // Verify that section types are sorted
+ _ASSERTE(i == 0 || (pSections[i - 1].Type < pSections[i].Type));
+
+ READYTORUN_SECTION * pSection = pSections + i;
+ if (pSection->Type == type)
+ return &pSection->Section;
+ }
+ return NULL;
+}
+
+//
+// Ready to Run specific dumping methods
+//
+void NativeImageDumper::DumpReadyToRun()
+{
+ m_pReadyToRunHeader = m_decoder.GetReadyToRunHeader();
+
+ m_nativeReader = NativeFormat::NativeReader(dac_cast<PTR_BYTE>(m_decoder.GetBase()), m_decoder.GetVirtualSize());
+
+ IMAGE_DATA_DIRECTORY * pRuntimeFunctionsDir = FindReadyToRunSection(READYTORUN_SECTION_RUNTIME_FUNCTIONS);
+ if (pRuntimeFunctionsDir != NULL)
+ {
+ m_pRuntimeFunctions = dac_cast<PTR_RUNTIME_FUNCTION>(m_decoder.GetDirectoryData(pRuntimeFunctionsDir));
+ m_nRuntimeFunctions = pRuntimeFunctionsDir->Size / sizeof(T_RUNTIME_FUNCTION);
+ }
+ else
+ {
+ m_nRuntimeFunctions = 0;
+ }
+
+ IMAGE_DATA_DIRECTORY * pEntryPointsDir = FindReadyToRunSection(READYTORUN_SECTION_METHODDEF_ENTRYPOINTS);
+ if (pEntryPointsDir != NULL)
+ m_methodDefEntryPoints = NativeFormat::NativeArray((TADDR)&m_nativeReader, pEntryPointsDir->VirtualAddress);
+
+ DisplayStartCategory("NativeInfo", NATIVE_INFO);
+
+ IF_OPT(NATIVE_INFO)
+ DumpReadyToRunHeader();
+
+ DisplayEndCategory(NATIVE_INFO); //NativeInfo
+
+ IF_OPT_OR3(METHODS, GC_INFO, DISASSEMBLE_CODE)
+ DumpReadyToRunMethods();
+
+ IF_OPT(RELOCATIONS)
+ DumpBaseRelocs();
+}
+
+const NativeImageDumper::EnumMnemonics s_ReadyToRunFlags[] =
+{
+#define RTR_FLAGS(f) NativeImageDumper::EnumMnemonics(f, W(#f))
+ RTR_FLAGS(READYTORUN_FLAG_PLATFORM_NEUTRAL_SOURCE),
+#undef RTR_FLAGS
+};
+
+void NativeImageDumper::DumpReadyToRunHeader()
+{
+ IF_OPT(NATIVE_INFO)
+ {
+ m_display->StartStructure( "READYTORUN_HEADER",
+ DPtrToPreferredAddr(dac_cast<PTR_READYTORUN_HEADER>(m_pReadyToRunHeader)),
+ sizeof(*m_pReadyToRunHeader) );
+
+ DisplayWriteFieldUInt( Signature, m_pReadyToRunHeader->Signature, READYTORUN_HEADER, ALWAYS );
+ DisplayWriteFieldUInt( MajorVersion, m_pReadyToRunHeader->MajorVersion, READYTORUN_HEADER, ALWAYS );
+ DisplayWriteFieldUInt( MinorVersion, m_pReadyToRunHeader->MinorVersion, READYTORUN_HEADER, ALWAYS );
+
+ DisplayWriteFieldEnumerated( Flags, m_pReadyToRunHeader->Flags,
+ READYTORUN_HEADER, s_ReadyToRunFlags, W(", "),
+ NATIVE_INFO );
+
+ m_display->EndStructure(); //READYTORUN_HEADER
+ }
+}
+
+void NativeImageDumper::DumpReadyToRunMethods()
+{
+ DisplayStartArray("Methods", NULL, METHODS);
+
+ for (uint rid = 1; rid <= m_methodDefEntryPoints.GetCount(); rid++)
+ {
+ uint offset;
+ if (!m_methodDefEntryPoints.TryGetAt(rid - 1, &offset))
+ continue;
+
+ uint id;
+ offset = m_nativeReader.DecodeUnsigned(offset, &id);
+
+ if (id & 1)
+ {
+ if (id & 2)
+ {
+ uint val;
+ m_nativeReader.DecodeUnsigned(offset, &val);
+ offset -= val;
+ }
+
+ // TODO: Dump fixups from dac_cast<TADDR>(m_pLayout->GetBase()) + offset
+
+ id >>= 2;
+ }
+ else
+ {
+ id >>= 1;
+ }
+
+ _ASSERTE(id < m_nRuntimeFunctions);
+ PTR_RUNTIME_FUNCTION pRuntimeFunction = m_pRuntimeFunctions + id;
+ PCODE pEntryPoint = dac_cast<TADDR>(m_decoder.GetBase()) + pRuntimeFunction->BeginAddress;
+
+ SString buf;
+ AppendTokenName(TokenFromRid(rid, mdtMethodDef), buf, m_import);
+
+ DumpReadyToRunMethod(pEntryPoint, pRuntimeFunction, buf);
+ }
+
+ DisplayEndArray("Total Methods", METHODS); //Methods
+}
+
+extern PTR_VOID GetUnwindDataBlob(TADDR moduleBase, PTR_RUNTIME_FUNCTION pRuntimeFunction, /* out */ SIZE_T * pSize);
+
+void NativeImageDumper::DumpReadyToRunMethod(PCODE pEntryPoint, PTR_RUNTIME_FUNCTION pRuntimeFunction, SString& name)
+{
+ //Read the GCInfo to get the total method size.
+ unsigned methodSize = 0;
+ unsigned gcInfoSize = UINT_MAX;
+
+ SIZE_T nUnwindDataSize;
+ PTR_VOID pUnwindData = GetUnwindDataBlob(dac_cast<TADDR>(m_decoder.GetBase()), pRuntimeFunction, &nUnwindDataSize);
+
+ // GCInfo immediatelly follows unwind data
+ PTR_CBYTE gcInfo = dac_cast<PTR_CBYTE>(pUnwindData) + nUnwindDataSize;
+
+ void(*stringOutFn)(const char *, ...);
+ IF_OPT(GC_INFO)
+ {
+ stringOutFn = stringOut;
+ }
+ else
+ {
+ stringOutFn = nullStringOut;
+ }
+ if (gcInfo != NULL)
+ {
+ PTR_CBYTE curGCInfoPtr = gcInfo;
+ g_holdStringOutData.Clear();
+ GCDump gcDump(GCINFO_VERSION);
+ gcDump.gcPrintf = stringOutFn;
+#if !defined(_TARGET_X86_) && defined(USE_GC_INFO_DECODER)
+ UINT32 r2rversion = m_pReadyToRunHeader->MajorVersion;
+ UINT32 gcInfoVersion = GCInfoToken::ReadyToRunVersionToGcInfoVersion(r2rversion);
+ GcInfoDecoder gcInfoDecoder({ curGCInfoPtr, gcInfoVersion }, DECODE_CODE_LENGTH);
+ methodSize = gcInfoDecoder.GetCodeLength();
+#endif
+
+ //dump the data to a string first so we can get the gcinfo size.
+#ifdef _TARGET_X86_
+ InfoHdr hdr;
+ stringOutFn("method info Block:\n");
+ curGCInfoPtr += gcDump.DumpInfoHdr(curGCInfoPtr, &hdr, &methodSize, 0);
+ stringOutFn("\n");
+#endif
+
+ IF_OPT(METHODS)
+ {
+#ifdef _TARGET_X86_
+ stringOutFn("PointerTable:\n");
+ curGCInfoPtr += gcDump.DumpGCTable(curGCInfoPtr,
+ hdr,
+ methodSize, 0);
+ gcInfoSize = curGCInfoPtr - gcInfo;
+#elif defined(USE_GC_INFO_DECODER)
+ stringOutFn("PointerTable:\n");
+ curGCInfoPtr += gcDump.DumpGCTable(curGCInfoPtr,
+ methodSize, 0);
+ gcInfoSize = (unsigned)(curGCInfoPtr - gcInfo);
+#endif
+ }
+
+ //data is output below.
+ }
+
+ DisplayStartElement("Method", METHODS);
+ DisplayWriteElementStringW("Name", (const WCHAR *)name, METHODS);
+
+ DisplayStartStructure("GCInfo",
+ DPtrToPreferredAddr(gcInfo),
+ gcInfoSize,
+ METHODS);
+
+ DisplayStartTextElement("Contents", GC_INFO);
+ DisplayWriteXmlTextBlock(("%S", (const WCHAR *)g_holdStringOutData), GC_INFO);
+ DisplayEndTextElement(GC_INFO); //Contents
+
+ DisplayEndStructure(METHODS); //GCInfo
+
+ DisplayStartStructure("Code", DataPtrToDisplay(pEntryPoint), methodSize,
+ METHODS);
+
+ IF_OPT(DISASSEMBLE_CODE)
+ {
+ // Disassemble hot code. Read the code into the host process.
+ /* REVISIT_TODO Mon 10/24/2005
+ * Is this align up right?
+ */
+ BYTE * codeStartHost =
+ reinterpret_cast<BYTE*>(PTR_READ(pEntryPoint,
+ (ULONG32)ALIGN_UP(methodSize,
+ CODE_SIZE_ALIGN)));
+ DisassembleMethod(codeStartHost, methodSize);
+ }
+
+ DisplayEndStructure(METHODS); //Code
+
+ DisplayEndElement(METHODS); //Method
+}
+#endif // FEATURE_READYTORUN
+
+#if 0
+void NativeImageDumper::RecordTypeRef( mdTypeRef token, PTR_MethodTable mt )
+{
+ if( mt != NULL )
+ m_mtToTypeRefMap.Add( mt, token );
+}
+mdTypeRef NativeImageDumper::FindTypeRefForMT( PTR_MethodTable mt )
+{
+ return m_mtToTypeRefMap.Find(mt);
+}
+#endif
+
+
+/* REVISIT_TODO Mon 10/10/2005
+ * Here is where it gets bad. There is no DAC build of gcdump, so instead
+ * build it directly into the the dac. That's what all these ugly defines
+ * are all about.
+ */
+#ifdef __MSC_VER
+#pragma warning(disable:4244) // conversion from 'unsigned int' to 'unsigned short', possible loss of data
+#pragma warning(disable:4189) // local variable is initialized but not referenced
+#endif // __MSC_VER
+
+#undef assert
+#define assert(a)
+#define NOTHROW
+#define GC_NOTRIGGER
+#include <gcdecoder.cpp>
+#undef NOTHROW
+#undef GC_NOTRIGGER
+
+#if defined _DEBUG && defined _TARGET_X86_
+// disable FPO for checked build
+#pragma optimize("y", off)
+#endif
+
+#undef _ASSERTE
+#define _ASSERTE(a) do {} while (0)
+#ifdef _TARGET_X86_
+#include <gcdump.cpp>
+#endif
+
+#undef LIMITED_METHOD_CONTRACT
+#undef WRAPPER_NO_CONTRACT
+#ifdef _TARGET_X86_
+#include <i386/gcdumpx86.cpp>
+#else // !_TARGET_X86_
+#undef PREGDISPLAY
+#include <gcdumpnonx86.cpp>
+#endif // !_TARGET_X86_
+
+#ifdef __MSC_VER
+#pragma warning(default:4244)
+#pragma warning(default:4189)
+#endif // __MSC_VER
+
+
+#else //!FEATURE_PREJIT
+//dummy implementation for dac
+HRESULT ClrDataAccess::DumpNativeImage(CLRDATA_ADDRESS loadedBase,
+ LPCWSTR name,
+ IXCLRDataDisplay * display,
+ IXCLRLibrarySupport * support,
+ IXCLRDisassemblySupport * dis)
+{
+ return E_FAIL;
+}
+#endif //FEATURE_PREJIT
diff --git a/src/debug/daccess/nidump.h b/src/debug/daccess/nidump.h
new file mode 100644
index 0000000000..d14eb89f24
--- /dev/null
+++ b/src/debug/daccess/nidump.h
@@ -0,0 +1,624 @@
+// 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 _NIDUMP_H_
+#define _NIDUMP_H_
+
+
+#ifdef FEATURE_PREJIT
+#include <daccess.h>
+
+//some DPTR definitions that aren't elsewhere in the source
+typedef DPTR(const COR_SIGNATURE) PTR_CCOR_SIGNATURE;
+typedef DPTR(IMAGE_SECTION_HEADER) PTR_IMAGE_SECTION_HEADER;
+typedef DPTR(CerNgenRootTable) PTR_CerNgenRootTable;
+typedef DPTR(struct CerRoot) PTR_CerRoot;
+typedef DPTR(MethodContextElement) PTR_MethodContextElement;
+typedef DPTR(ModuleSecurityDescriptor) PTR_ModuleSecurityDescriptor;
+typedef DPTR(DictionaryEntry) PTR_DictionaryEntry;
+typedef DPTR(GuidInfo) PTR_GuidInfo;
+#if defined(FEATURE_COMINTEROP)
+typedef DPTR(SparseVTableMap) PTR_SparseVTableMap;
+#endif
+#if defined(FEATURE_COMINTEROP)
+typedef DPTR(ClassFactoryBase) PTR_ClassFactoryBase;
+#endif
+typedef DPTR(LayoutEEClass) PTR_LayoutEEClass;
+typedef DPTR(ArrayClass) PTR_ArrayClass;
+typedef DPTR(DelegateEEClass) PTR_DelegateEEClass;
+typedef DPTR(UMThunkMarshInfo) PTR_UMThunkMarshInfo;
+typedef DPTR(CORCOMPILE_DEPENDENCY) PTR_CORCOMPILE_DEPENDENCY;
+typedef DPTR(struct RemotableMethodInfo) PTR_RemotableMethodInfo;
+typedef DPTR(struct ModuleCtorInfo) PTR_ModuleCtorInfo;
+typedef DPTR(class EEImplMethodDesc) PTR_EEImplMethodDesc;
+typedef DPTR(class EEClassLayoutInfo) PTR_EEClassLayoutInfo;
+typedef DPTR(class FieldMarshaler) PTR_FieldMarshaler;
+typedef DPTR(LPCUTF8) PTR_LPCUTF8;
+typedef DPTR(struct STORAGESIGNATURE UNALIGNED) PTR_STORAGESIGNATURE;
+typedef DPTR(struct STORAGEHEADER UNALIGNED) PTR_STORAGEHEADER;
+typedef DPTR(struct STORAGESTREAM UNALIGNED) PTR_STORAGESTREAM;
+typedef DPTR(ArrayMethodDesc) PTR_ArrayMethodDesc;
+
+
+#if 0
+template<typename PtrType>
+class TokenHashMap : CClosedHash< Pair<DPTR(PtrType), mdToken> >
+{
+public:
+ typedef DPTR(PtrType) Key;
+ typedef mdTypeRef Data;
+ typedef Pair<Key, Data> Entry;
+ typedef CClosedHash< Entry > Parent;
+ TokenHashMap(int buckets = 23) : Parent(buckets)
+ {
+
+ }
+ ~TokenHashMap() { }
+
+ void Add(const Key key, const Data data)
+ {
+ Entry * newEntry = Parent::Add((void*)PTR_HOST_TO_TADDR(key));
+ newEntry->First() = key;
+ newEntry->Second() = data;
+ }
+
+ Data Find(const Key key)
+ {
+ Entry * found = Parent::Find((void*)PTR_HOST_TO_TADDR(key));
+ if( !found )
+ return mdTokenNil;
+ else
+ return found->Second();
+ }
+ inline Key GetKey(Entry * entry) { return entry->First(); }
+ Parent::ELEMENTSTATUS Status(Entry * entry)
+ {
+ if( entry->First() == 0xffffffff && entry->Second() == 0xffffffff )
+ return Parent::DELETED;
+ else if( entry->First() == 0x00000000 && entry->Second() == 0x00000000 )
+ return Parent::FREE;
+ else
+ return Parent::USED;
+ }
+ void SetStatus(Entry * entry, Parent::ELEMENTSTATUS status)
+ {
+ switch(status)
+ {
+ case Parent::FREE:
+ entry->First() = Key((TADDR)0x00000000);
+ entry->Second() = 0x00000000;
+ break;
+ case Parent::DELETED:
+ entry->First() = Key((TADDR)0xffffffff);
+ entry->Second() = 0xffffffff;
+ break;
+ }
+ }
+
+ unsigned int Compare(const Entry * lhs, Entry * rhs)
+ {
+ return lhs->First() == rhs->First() && lhs->Second() == rhs->Second();
+ }
+
+ //parent methods
+ unsigned int Hash(const void *pData)
+ {
+ return (int)(INT_PTR)pData;
+ }
+ unsigned int Compare(const void * p1, BYTE * p2)
+ {
+ return Compare((const Entry *) p1, (Entry*) p2);
+ }
+ Parent::ELEMENTSTATUS Status(BYTE * p){
+ return Status((Entry*)p);
+ }
+ void SetStatus(BYTE * p, Parent::ELEMENTSTATUS status) {
+ SetStatus((Entry*)p, status);
+ }
+ void * GetKey(BYTE *p) { return (void*)GetKey((Entry*)p); }
+};
+typedef TokenHashMap<EEClass> EEClassToTypeRefMap;
+typedef TokenHashMap<MethodTable> MTToTypeRefMap;
+#endif
+
+class NativeImageDumper
+{
+public:
+ //DPTR to private field needs to be a member of NativeImageDumper
+#if defined(FEATURE_COMINTEROP)
+ typedef DPTR(SparseVTableMap::Entry) PTR_SparseVTableMap_Entry;
+#endif
+
+ NativeImageDumper(PTR_VOID loadedBase, const WCHAR * const name,
+ IXCLRDataDisplay * display, IXCLRLibrarySupport *support,
+ IXCLRDisassemblySupport * dis);
+ ~NativeImageDumper();
+
+ //type dumping methods
+ void DumpNativeImage();
+
+ void ComputeMethodFixupHistogram( PTR_Module module );
+ void DumpFixupTables( PTR_Module module);
+
+ void WriteElementTypeHandle( const char * name, TypeHandle th );
+ void DoWriteFieldFieldDesc( const char * name, unsigned offset,
+ unsigned fieldSize, PTR_FieldDesc fd );
+ void DoWriteFieldMethodDesc( const char * name, unsigned offset,
+ unsigned fieldSize, PTR_MethodDesc md );
+ void DoWriteFieldTypeHandle( const char * name, unsigned offset,
+ unsigned fieldSize, TypeHandle th );
+ void DoWriteFieldMDToken( const char * name, unsigned offset,
+ unsigned fieldsize, mdToken token,
+ IMetaDataImport2 *pAssemblyImport = NULL);
+ void DoWriteFieldMethodTable( const char * name, unsigned offset,
+ unsigned fieldSize, PTR_MethodTable mt );
+ //if fixup is a fixup, it writes the field as if it were a fixup (including
+ //subelements) and returns true. Otherwise, it returns false.
+ BOOL DoWriteFieldAsFixup( const char * name, unsigned offset,
+ unsigned fieldSize, TADDR fixup );
+
+ void WriteElementMethodTable( const char * name, PTR_MethodTable mt );
+ void WriteElementMethodDesc( const char * name, PTR_MethodDesc md );
+
+ void DoWriteFieldCorElementType( const char * name, unsigned offset,
+ unsigned fieldSize, CorElementType type );
+ void WriteElementMDToken( const char * name, mdToken token );
+
+ void DoWriteFieldAsHex( const char * name, unsigned offset,
+ unsigned fieldSize, PTR_BYTE data,
+ unsigned dataLen );
+
+ void DumpMethods(PTR_Module module);
+
+ void DumpCompleteMethod(PTR_Module module, MethodIterator& mi);
+
+ void DisassembleMethod(BYTE *method, SIZE_T size);
+
+ void DumpModule( PTR_Module module );
+ void DumpNative();
+ void DumpNativeHeader();
+
+ void DumpBaseRelocs();
+ void DumpHelperTable();
+
+ void DumpMethodFixups(PTR_Module module,
+ TADDR fixupList);
+
+ void DumpTypes( PTR_Module module );
+
+ void DumpNgenRootTable( PTR_CerNgenRootTable table, const char * name,
+ unsigned offset, unsigned fieldSize );
+
+ void DumpMethodTable( PTR_MethodTable mt, const char * name,
+ PTR_Module module );
+
+#ifndef STUB_DISPATCH_ALL
+ void DumpMethodTableSlotChunk( PTR_PCODE slotChunk, COUNT_T size );
+#endif
+
+ void DumpSlot( unsigned index, PCODE tgt );
+ void DumpFieldDesc( PTR_FieldDesc fd, const char * name );
+ void DumpEEClassForMethodTable( PTR_MethodTable mt );
+ void DumpTypeDesc( PTR_TypeDesc td );
+
+ void DumpMethodDesc( PTR_MethodDesc md, PTR_Module module );
+ void DumpPrecode( PTR_Precode precode, PTR_Module module );
+
+
+
+
+
+
+
+ //utility routines
+ void AppendTokenName(mdToken token, SString& str);
+ void AppendTokenName(mdToken token, SString& str, IMetaDataImport2 *pImport,
+ bool force = false);
+ void PrintManifestTokenName(mdToken token, SString& str);
+ void PrintManifestTokenName(mdToken token, SString& str,
+ IMetaDataAssemblyImport *pAssemblyImport,
+ bool force = false);
+ void WriteElementsFixupBlob(PTR_CORCOMPILE_IMPORT_SECTION pSection, SIZE_T fixup);
+ void WriteElementsFixupTargetAndName(RVA rva);
+ void FixupBlobToString(RVA rva, SString& buf);
+
+ void AppendToken(mdToken token, SString& buf);
+ void AppendToken(mdToken token, SString& buf, IMetaDataImport2 *pImport);
+ IMetaDataImport2* TypeToString(PTR_CCOR_SIGNATURE &sig, SString& buf); // assumes pImport is m_import
+ IMetaDataImport2* TypeToString(PTR_CCOR_SIGNATURE &sig, SString& buf,
+ IMetaDataImport2 *pImport,
+ IMetaDataImport2 *pOrigImport =NULL);
+ void MethodTableToString( PTR_MethodTable mt, SString& buf );
+ void TypeHandleToString( TypeHandle td, SString& buf );
+ void TypeDescToString( PTR_TypeDesc td, SString& buf );
+ void DictionaryToArgString( PTR_Dictionary dictionary, unsigned numArgs, SString& buf );
+
+ void EntryPointToString( PCODE pEntryPoint, SString& buf );
+ void MethodDescToString( PTR_MethodDesc md, SString& buf );
+ void FieldDescToString( PTR_FieldDesc fd, SString& buf );
+ //uses tok to generate a name if fd == NULL
+ void FieldDescToString( PTR_FieldDesc fd, mdFieldDef tok, SString& buf );
+
+#ifdef FEATURE_READYTORUN
+private:
+ READYTORUN_HEADER * m_pReadyToRunHeader;
+
+ PTR_RUNTIME_FUNCTION m_pRuntimeFunctions;
+ DWORD m_nRuntimeFunctions;
+
+ NativeFormat::NativeReader m_nativeReader;
+ NativeFormat::NativeArray m_methodDefEntryPoints;
+
+ IMAGE_DATA_DIRECTORY * FindReadyToRunSection(DWORD type);
+
+public:
+ void DumpReadyToRun();
+ void DumpReadyToRunHeader();
+ void DumpReadyToRunMethods();
+ void DumpReadyToRunMethod(PCODE pEntryPoint, PTR_RUNTIME_FUNCTION pRuntimeFunction, SString& name);
+#endif // FEATURE_READYTORUN
+
+private:
+ PEDecoder m_decoder;
+ const WCHAR * const m_name;
+ PTR_VOID m_baseAddress;
+ SIZE_T m_imageSize;
+ IXCLRDataDisplay * m_display;
+ IXCLRLibrarySupport * m_librarySupport;
+
+ bool isInRange(TADDR ptr)
+ {
+ return dac_cast<TADDR>(m_baseAddress) <= ptr
+ && ptr < (dac_cast<TADDR>(m_baseAddress) + m_imageSize);
+ }
+
+
+ COUNT_T ** m_fixupHistogram;
+
+ #define COUNT_HISTOGRAM_SIZE 16
+ COUNT_T m_fixupCountHistogram[COUNT_HISTOGRAM_SIZE];
+ COUNT_T m_fixupCount; //used to track above counts
+
+ // Primary image metadata
+ IMetaDataImport2 *m_import;
+ IMetaDataAssemblyImport *m_assemblyImport;
+
+ // Installation manifest metadata. For native images this is metadata
+ // copied from the IL image.
+ IMetaDataImport2 *m_manifestImport;
+ IMetaDataAssemblyImport *m_manifestAssemblyImport;
+
+ //helper for ComputeMethodFixupHistogram
+ BOOL HandleFixupForHistogram(PTR_CORCOMPILE_IMPORT_SECTION pSection, SIZE_T fixupIndex, SIZE_T *fixupCell);
+
+ //helper for DumpMethodFixups
+ BOOL HandleFixupForMethodDump(PTR_CORCOMPILE_IMPORT_SECTION pSection, SIZE_T fixupIndex, SIZE_T *fixupCell);
+
+ // Dependencies
+
+public:
+ struct Dependency
+ {
+ CORCOMPILE_DEPENDENCY * entry;
+ //CORINFO_ASSEMBLY_HANDLE assembly;
+
+ TADDR pPreferredBase;
+ TADDR pLoadedAddress;
+ SIZE_T size;
+
+ PTR_Module pModule;
+ IMetaDataImport2 *pImport;
+ TADDR pMetadataStartTarget;
+ TADDR pMetadataStartHost;
+ SIZE_T MetadataSize;
+ bool fIsMscorlib;
+ bool fIsHardbound;
+ WCHAR name[128];
+ };
+
+ /* REVISIT_TODO Fri 12/09/2005
+ * Perhaps the module and import should be in the dependency. In order to
+ * properly name tokens in modules w/o import entries.
+ */
+ struct Import
+ {
+ PTR_CORCOMPILE_IMPORT_TABLE_ENTRY entry;
+ Dependency *dependency;
+ };
+private:
+
+ Dependency *m_dependencies;
+ COUNT_T m_numDependencies;
+ Import *m_imports;
+ COUNT_T m_numImports;
+ CORCOMPILE_DEPENDENCY m_self;
+
+ bool inline isSelf(const Dependency* dep) {
+ return &m_dependencies[0] == dep;
+ }
+
+
+ void OpenMetadata();
+ void WriteElementsMetadata( const char * elementName,
+ TADDR data, SIZE_T size );
+ NativeImageDumper::Dependency*
+ GetDependency(mdAssemblyRef token, IMetaDataAssemblyImport *pImport = NULL);
+ NativeImageDumper::Import *OpenImport(int i);
+ NativeImageDumper::Dependency * OpenDependency(int index);
+ void TraceDumpImport(int idx, NativeImageDumper::Import * import);
+ void TraceDumpDependency(int idx, NativeImageDumper::Dependency * dependency);
+ mdAssemblyRef MapAssemblyRefToManifest(mdAssemblyRef token, IMetaDataAssemblyImport *pAssemblyImport);
+
+ const Dependency * GetDependencyForFixup(RVA rva);
+ const Dependency * GetDependencyForModule( PTR_Module module );
+#if 0
+ const Import * GetImportForPointer( TADDR ptr );
+#endif
+ const Dependency * GetDependencyForPointer( TADDR ptr );
+#ifdef MANUAL_RELOCS
+ template< typename T >
+ inline T RemapPointerForReloc( T ptr );
+
+ inline TADDR RemapTAddrForReloc( TADDR ptr );
+
+ inline TADDR RemapTAddrForReloc( const NativeImageDumper::Dependency * d,
+ TADDR ptr );
+
+ template< typename T >
+ inline T RemapPointerForReloc( const NativeImageDumper::Dependency * d,
+ T ptr );
+#endif
+
+
+ // msdis support
+
+#if 0
+ static size_t TranslateFixupCallback(const DIS *, DIS::ADDR, size_t, WCHAR *, size_t, DWORDLONG *);
+ static size_t TranslateRegrelCallback(const DIS *, DIS::REGA, DWORD, WCHAR *, size_t, DWORD *);
+ static size_t TranslateConstCallback(const DIS *, DWORD, WCHAR *, size_t);
+#endif
+ IXCLRDisassemblySupport * m_dis;
+ static SIZE_T __stdcall TranslateFixupCallback(IXCLRDisassemblySupport *dis,
+ CLRDATA_ADDRESS addr,
+ SIZE_T size, __out_ecount(nameSize) WCHAR *name,
+ SIZE_T nameSize,
+ DWORDLONG *offset);
+ static SIZE_T __stdcall TranslateAddressCallback(IXCLRDisassemblySupport *dis,
+ CLRDATA_ADDRESS addr,
+ __out_ecount(nameSize) WCHAR *name, SIZE_T nameSize,
+ DWORDLONG *offset);
+ size_t TranslateSymbol(IXCLRDisassemblySupport *dis,
+ CLRDATA_ADDRESS addr, __out_ecount(nameSize) WCHAR *name,
+ SIZE_T nameSize, DWORDLONG *offset);
+
+ CLRDATA_ADDRESS m_currentAddress;
+ bool m_currentIsAddress;
+
+ //mscorwks sizes
+ TADDR m_mscorwksBase;
+ TADDR m_mscorwksPreferred;
+ SIZE_T m_mscorwksSize;
+
+
+ //internal type dumpers
+ void DumpDictionaryEntry( const char * name, DictionaryEntryKind kind,
+ PTR_DictionaryEntry entry );
+ void WriteFieldDictionaryLayout( const char * name, unsigned offset,
+ unsigned fieldSize,
+ PTR_DictionaryLayout layout,
+ IMetaDataImport2 * import );
+
+
+ IMAGE_SECTION_HEADER * FindSection( char const * name );
+
+
+ //map traversal methods and helpers
+ void IterateTypeDefToMTCallback(TADDR taddrTarget, TADDR flags, PTR_LookupMapBase map, DWORD rid);
+ void IterateTypeRefToMTCallback(TADDR taddrTarget, TADDR flags, PTR_LookupMapBase map, DWORD rid);
+ void IterateMethodDefToMDCallback(TADDR taddrTarget, TADDR flags, PTR_LookupMapBase map, DWORD rid);
+ void IterateFieldDefToFDCallback(TADDR taddrTarget, TADDR flags, PTR_LookupMapBase map, DWORD rid);
+ void IterateMemberRefToDescCallback(TADDR taddrTarget, TADDR flags, PTR_LookupMapBase map, DWORD rid);
+ void IterateGenericParamToDescCallback(TADDR fdTarget, TADDR flags, PTR_LookupMapBase map, DWORD rid);
+ void IterateFileReferencesCallback(TADDR moduleTarget, TADDR flags, PTR_LookupMapBase map, DWORD rid);
+ void IterateManifestModules(TADDR moduleTarget, TADDR flags, PTR_LookupMapBase map, DWORD rid);
+
+ void TraverseMap(PTR_LookupMapBase map, const char * name, unsigned offset,
+ unsigned fieldSize,
+ void(NativeImageDumper::*cb)(TADDR, TADDR, PTR_LookupMapBase, DWORD));
+
+ template<typename HASH_CLASS, typename HASH_ENTRY_CLASS>
+ void TraverseNgenHash(DPTR(HASH_CLASS) pTable, const char * name,
+ unsigned offset, unsigned fieldSize,
+ bool saveClasses,
+ void (NativeImageDumper::*DisplayEntryFunction)(void *, DPTR(HASH_ENTRY_CLASS), bool),
+ void *pContext);
+ template<typename HASH_CLASS, typename HASH_ENTRY_CLASS>
+ void TraverseNgenPersistedEntries(DPTR(HASH_CLASS) pTable,
+ DPTR(typename HASH_CLASS::PersistedEntries) pEntries,
+ bool saveClasses,
+ void (NativeImageDumper::*DisplayEntryFunction)(void *, DPTR(HASH_ENTRY_CLASS), bool),
+ void *pContext);
+
+ void TraverseClassHashEntry(void *pContext, PTR_EEClassHashEntry pEntry, bool saveClasses);
+ void TraverseClassHash(PTR_EEClassHashTable pTable, const char * name,
+ unsigned offset, unsigned fieldSize,
+ bool saveClasses);
+
+#ifdef FEATURE_COMINTEROP
+ void TraverseGuidToMethodTableEntry(void *pContext, PTR_GuidToMethodTableEntry pEntry, bool saveClasses);
+ void TraverseGuidToMethodTableHash(PTR_GuidToMethodTableHashTable pTable, const char * name,
+ unsigned offset, unsigned fieldSize, bool saveClasses);
+#endif // FEATURE_COMINTEROP
+
+ void TraverseMemberRefToDescHashEntry(void *pContext, PTR_MemberRefToDescHashEntry pEntry, bool saveClasses);
+
+ void TraverseMemberRefToDescHash(PTR_MemberRefToDescHashTable pTable, const char * name,
+ unsigned offset, unsigned fieldSize, bool saveClasses);
+
+
+ void TraverseTypeHashEntry(void *pContext, PTR_EETypeHashEntry pEntry, bool saveClasses);
+ void TraverseTypeHash(PTR_EETypeHashTable pTable, const char * name,
+ unsigned offset, unsigned fieldSize );
+
+ void TraverseInstMethodHashEntry(void *pContext, PTR_InstMethodHashEntry pEntry, bool saveClasses);
+ void TraverseInstMethodHash(PTR_InstMethodHashTable pTable,
+ const char * name, unsigned offset,
+ unsigned fieldSize, PTR_Module module);
+
+ void TraverseStubMethodHashEntry(void *pContext, PTR_StubMethodHashEntry pEntry, bool saveClasses);
+ void TraverseStubMethodHash(PTR_StubMethodHashTable pTable,
+ const char * name, unsigned offset,
+ unsigned fieldSize, PTR_Module module);
+
+ void DoWriteFieldStr( PTR_BYTE ptr, const char * name, unsigned offset,
+ unsigned fieldSize );
+
+
+ template<typename T>
+ TADDR DPtrToPreferredAddr( T ptr );
+
+ void DumpAssemblySignature(CORCOMPILE_ASSEMBLY_SIGNATURE & assemblySignature);
+
+ SIZE_T CountFields( PTR_MethodTable mt );
+ mdToken ConvertToTypeDef( mdToken typeSpecOrRef, IMetaDataImport2* (&pImport) );
+ SIZE_T CountDictionariesInClass( mdToken typeDefOrRef, IMetaDataImport2 * pImport );
+ PTR_EEClass GetClassFromMT( PTR_MethodTable mt );
+ PTR_MethodTable GetParent( PTR_MethodTable mt );
+
+ const Dependency* GetDependencyFromFD( PTR_FieldDesc fd );
+ const Dependency* GetDependencyFromMD( PTR_MethodDesc md );
+ const Dependency* GetDependencyFromMT( PTR_MethodTable mt );
+
+ CLRNativeImageDumpOptions m_dumpOptions;
+ inline TADDR RvaToDisplay( SIZE_T rva );
+ inline TADDR DataPtrToDisplay(TADDR ptr);
+ inline int CheckOptions( CLRNativeImageDumpOptions opt );
+
+ //support various LookupMap Iterators
+ SArray<PTR_MethodTable> m_discoveredMTs;
+
+ struct SlotChunk
+ {
+ PTR_PCODE addr;
+ WORD nSlots;
+
+ inline bool operator==(const SlotChunk& sc) const
+ {
+ return (addr == sc.addr) && (nSlots == sc.nSlots);
+ }
+
+ inline bool operator<(const SlotChunk& sc) const
+ {
+ if (addr < sc.addr)
+ {
+ return TRUE;
+ }
+ else if (addr > sc.addr)
+ {
+ return FALSE;
+ }
+ else
+ {
+ return nSlots < sc.nSlots;
+ }
+ }
+ };
+
+ SArray<SlotChunk> m_discoveredSlotChunks;
+ //SArray<PTR_MethodDesc> m_discoveredMDs;
+ //SArray<PTR_FieldDesc> m_discoveredFDs;
+ SArray<PTR_MethodTable> m_discoveredClasses;
+ SArray<PTR_TypeDesc> m_discoveredTypeDescs;
+
+ typedef InlineSString<128> TempBuffer;
+
+ /* XXX Mon 10/03/2005
+ * When we encounter pointers from metadata they are already in the host
+ * process because we read all of metadata in as one big block (since the
+ * metadata api isn't dac-ized. Map the metadata pointers back to good DAC
+ * pointers for compatibility with certain sig parsing code.
+ */
+ TADDR m_MetadataStartHost;
+ TADDR m_MetadataStartTarget;
+ COUNT_T m_MetadataSize;
+
+ //Support dumping IL. The COR_ILMETHOD_DECODER is not DACized, so read the
+ //whole IL section in, and translate RVAs into host pointers into the IL
+ //section copy
+ RVA m_ILSectionStart;
+ BYTE * m_ILHostCopy;
+#ifdef _DEBUG
+ COUNT_T m_ILSectionSize;
+#endif
+
+ //This is true if we are hard bound to mscorlib. This enables various forms of generics dumping and MT
+ //dumping that require g_pObjectClass to be set.
+ bool m_isMscorlibHardBound;
+
+#if 0
+ PTR_CCOR_SIGNATURE metadataToHostDAC( PCCOR_SIGNATURE pSig,
+ IMetaDataImport2 * import );
+#endif
+ template<typename T>
+ DPTR(T) metadataToHostDAC( T * pSig, IMetaDataImport2 * import);
+
+ void DoDumpFieldStub( PTR_Stub stub, unsigned offset, unsigned fieldSize,
+ const char * name );
+#ifdef FEATURE_COMINTEROP
+ void DoDumpComPlusCallInfo( PTR_ComPlusCallInfo compluscall );
+#endif // FEATURE_COMINTEROP
+
+ SIZE_T m_sectionAlignment;
+ inline SIZE_T GetSectionAlignment() const;
+
+public:
+ //this is the list of valid precode addresses for the current module.
+ struct PrecodeRange
+ {
+ PrecodeRange( CorCompileSection section, TADDR start, SIZE_T size )
+ : m_sectionType(section), m_rangeStart(start),
+ m_rangeSize(size) { }
+ CorCompileSection m_sectionType;
+ TADDR m_rangeStart;
+ SIZE_T m_rangeSize;
+ };
+private:
+ bool isPrecode(TADDR maybePrecode);
+ void FixupThunkToString(PTR_CORCOMPILE_IMPORT_SECTION pImportSection, TADDR thunkAddr, SString& buf);
+
+#if 0
+ MTToTypeRefMap m_mtToTypeRefMap;
+ EEClassToTypeRefMap m_eeClassToTypeRefMap;
+ void RecordTypeRef( mdTypeRef token, PTR_MethodTable mt );
+ void RecordTypeRef( mdTypeRef token, PTR_EEClass clazz );
+ mdTypeRef FindTypeRefForMT( PTR_MethodTable mt );
+ mdTypeRef FindTypeRefForEEClass( PTR_EEClass clazz );
+#endif
+
+
+public:
+ struct EnumMnemonics
+ {
+ EnumMnemonics( DWORD val, const WCHAR * m )
+ : value(val), mask(val), mnemonic(m){ }
+ EnumMnemonics( DWORD val, DWORD msk, const WCHAR * m )
+ : value(val), mask(msk), mnemonic(m) { }
+ DWORD value;
+ DWORD mask;
+ const WCHAR * mnemonic;
+ };
+
+ static EnumMnemonics s_ModulePersistedFlags[];
+ static EnumMnemonics s_MDC[];
+ static EnumMnemonics s_MDFlag2[];
+ static EnumMnemonics s_TDFlags[];
+ static EnumMnemonics s_SSMDExtendedFlags[];
+ static EnumMnemonics s_IMDFlags[];
+ static EnumMnemonics s_EECLIFlags[];
+};
+#include "nidump.inl"
+
+#endif //FEATURE_PREJIT
+#endif
diff --git a/src/debug/daccess/nidump.inl b/src/debug/daccess/nidump.inl
new file mode 100644
index 0000000000..541f4194e9
--- /dev/null
+++ b/src/debug/daccess/nidump.inl
@@ -0,0 +1,169 @@
+// 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 _NIDUMP_INL_
+#define _NIDUMP_INL_
+template<typename T>
+TADDR NativeImageDumper::DPtrToPreferredAddr( T ptr )
+{
+ TADDR tptr = PTR_TO_TADDR(ptr);
+ return DataPtrToDisplay(tptr);
+}
+
+inline TADDR NativeImageDumper::RvaToDisplay( SIZE_T rva )
+{
+ return DataPtrToDisplay(m_decoder.GetRvaData((RVA)rva));
+}
+inline TADDR NativeImageDumper::DataPtrToDisplay(TADDR ptr)
+{
+ if( ptr == NULL || ptr == (TADDR)-1
+ || CheckOptions(CLRNATIVEIMAGE_DISABLE_REBASING) )
+ return TO_TADDR(ptr);
+
+ if( isInRange(ptr) || m_dependencies == NULL )
+ {
+ //fast path in case the dependencies aren't loaded.
+ RVA rva = m_decoder.GetDataRva(ptr);
+ if (CheckOptions(CLRNATIVEIMAGE_FILE_OFFSET))
+ return (TADDR) m_decoder.RvaToOffset(rva);
+ else
+ return rva + (INT_PTR)m_decoder.GetNativePreferredBase();
+ }
+ if( m_mscorwksBase <= ptr && ptr < (m_mscorwksBase + m_mscorwksSize) )
+ {
+ return ptr - m_mscorwksBase + m_mscorwksPreferred;
+ }
+ for( COUNT_T i = 0; i < m_numDependencies; ++i )
+ {
+ const Dependency * dependency = &m_dependencies[i];
+ if( dependency->pPreferredBase == NULL )
+ continue;
+ if( dependency->pLoadedAddress <= ptr
+ && ((dependency->pLoadedAddress + dependency->size) > ptr) )
+ {
+ //found the right target
+ return ptr - (INT_PTR)dependency->pLoadedAddress
+ + (INT_PTR)dependency->pPreferredBase;
+ }
+ }
+ return ptr;
+}
+inline int NativeImageDumper::CheckOptions( CLRNativeImageDumpOptions opt )
+{
+ //if( opt == ((CLRNativeImageDumpOptions)~0) )
+ //return 1;
+ return (m_dumpOptions & opt) != 0;
+}
+
+
+#if 0
+PTR_CCOR_SIGNATURE
+NativeImageDumper::metadataToHostDAC( PCCOR_SIGNATURE pSig,
+ IMetaDataImport2 * import)
+{
+ TADDR tsig = TO_TADDR(pSig);
+ if( m_MetadataSize == 0 ) //assume target
+ return PTR_CCOR_SIGNATURE(tsig);
+
+ //find the dependency for this import
+ const Dependency * dependency = NULL;
+ for( COUNT_T i = 0; i < m_numDependencies; ++i )
+ {
+ if( m_dependencies[i].pImport == import )
+ {
+ dependency = &m_dependencies[i];
+ break;
+ }
+ }
+ if( dependency != NULL && dependency->pMetadataStartHost <= tsig
+ && tsig < (dependency->pMetadataStartHost
+ + dependency->MetadataSize) )
+ {
+ //host metadata pointer
+ return PTR_CCOR_SIGNATURE((tsig
+ - dependency->pMetadataStartHost)
+ + dependency->pMetadataStartTarget );
+ }
+ return PTR_CCOR_SIGNATURE(tsig);
+}
+#endif
+template<typename T>
+DPTR(T)
+NativeImageDumper::metadataToHostDAC( T * pSig,
+ IMetaDataImport2 * import)
+{
+ TADDR tsig = TO_TADDR(pSig);
+ if( m_MetadataSize == 0 ) //assume target
+ return DPTR(T)(tsig);
+
+ //find the dependency for this import
+ const Dependency * dependency = NULL;
+ for( COUNT_T i = 0; i < m_numDependencies; ++i )
+ {
+ if( m_dependencies[i].pImport == import )
+ {
+ dependency = &m_dependencies[i];
+ break;
+ }
+ }
+ if( dependency != NULL && dependency->pMetadataStartHost <= tsig
+ && tsig < (dependency->pMetadataStartHost
+ + dependency->MetadataSize) )
+ {
+ //host metadata pointer
+ return DPTR(T)((tsig - dependency->pMetadataStartHost)
+ + dependency->pMetadataStartTarget );
+ }
+ return DPTR(T)(tsig);
+}
+
+inline SIZE_T NativeImageDumper::GetSectionAlignment() const {
+ _ASSERTE( m_sectionAlignment > 0 );
+ return m_sectionAlignment;
+}
+#ifdef MANUAL_RELOCS
+template< typename T >
+inline T NativeImageDumper::RemapPointerForReloc( T ptr )
+{
+ return T(RemapTAddrForReloc(PTR_TO_TADDR(ptr)));
+}
+inline TADDR NativeImageDumper::RemapTAddrForReloc( TADDR ptr )
+{
+#if 0
+ if( NULL == ptr )
+ return ptr;
+ if( isInRange(ptr) )
+ return ptr;
+ const NativeImageDumper::Dependency * dependency =
+ GetDependencyForPointer(ptr);
+ _ASSERTE(dependency);
+ return RemapTAddrForReloc( dependency, ptr );
+#else
+ return ptr;
+#endif
+
+}
+template< typename T >
+inline T
+NativeImageDumper::RemapPointerForReloc(const NativeImageDumper::Dependency* dependency,
+ T ptr )
+{
+ return T(RemapTAddrForReloc( dependency, PTR_TO_TADDR(ptr) ));
+}
+inline TADDR
+NativeImageDumper::RemapTAddrForReloc( const NativeImageDumper::Dependency * d,
+ TADDR ptr )
+{
+#if 0
+ if( d->pPreferredBase == d->pLoadedAddress )
+ return ptr;
+ else
+ return (ptr - d->pPreferredBase) + d->pLoadedAddress;
+#else
+ return ptr;
+#endif
+}
+#endif //MANUAL_RELOCS
+#endif //!_NIDUMP_INL_
diff --git a/src/debug/daccess/reimpl.cpp b/src/debug/daccess/reimpl.cpp
new file mode 100644
index 0000000000..d1198eea7b
--- /dev/null
+++ b/src/debug/daccess/reimpl.cpp
@@ -0,0 +1,115 @@
+// 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.
+//*****************************************************************************
+// File: reimpl.cpp
+//
+
+//
+// Data-access-specific reimplementations of standard code.
+//
+//*****************************************************************************
+
+#include "stdafx.h"
+
+//
+// Get the Thread instance for a specific OS thread ID
+//
+// Arguments:
+// osThread - the OS thread ID of interest.
+//
+// Return value:
+// A Thread object marshalled from the target corresponding to the specified OS thread
+// ID, or NULL if there is no such Thread.
+//
+// Notes:
+// We used to accept a thread ID of '0' to mean "use the current thread", which was based on
+// ICLRDataTarget::GetCurrentThreadID. But this is error-prone and not well-defined (many data targets
+// don't implement that API). It's better to require explicit thread IDs to be passed down when they
+// are needed.
+//
+Thread* __stdcall
+DacGetThread(ULONG32 osThread)
+{
+ _ASSERTE(osThread > 0);
+
+ if (!g_dacImpl)
+ {
+ DacError(E_UNEXPECTED);
+ UNREACHABLE();
+ }
+
+ // Note that if we had access to TLS, we could get this at index gThreadTLSIndex for the specified
+ // thread. However, this is the only place we might want to use TLS, and it's not performance critical,
+ // so we haven't added TLS support to ICorDebugDataTarget (the legacy ICLRDataTarget interface has it though)
+
+ // Scan the whole thread store to see if there's a matching thread.
+
+ if (!ThreadStore::s_pThreadStore)
+ {
+ return NULL;
+ }
+
+ Thread* thread = ThreadStore::s_pThreadStore->m_ThreadList.GetHead();
+ while (thread)
+ {
+ if (thread->GetOSThreadId() == osThread)
+ {
+ return thread;
+ }
+
+ thread = ThreadStore::s_pThreadStore->m_ThreadList.GetNext(thread);
+ }
+
+ return NULL;
+}
+
+EXTERN_C Thread* GetThread()
+{
+ // In dac mode it's unlikely that the thread calling dac
+ // is actually the same "current thread" that the runtime cares
+ // about. Fail all queries of the current thread by
+ // the runtime code to catch any inadvertent usage.
+ // Enumerating the ThreadStore is the proper way to get access
+ // to specific Thread objects.
+ DacError(E_UNEXPECTED);
+ return NULL;
+}
+
+BOOL
+DacGetThreadContext(Thread* thread, T_CONTEXT* context)
+{
+ SUPPORTS_DAC;
+
+ if (!g_dacImpl)
+ {
+ DacError(E_UNEXPECTED);
+ UNREACHABLE();
+ }
+
+ // XXX Microsoft - How do you retrieve the context for
+ // a Thread that's not running?
+ if (!thread->GetOSThreadId() ||
+ thread->GetOSThreadId() == 0xbaadf00d)
+ {
+ DacError(E_UNEXPECTED);
+ UNREACHABLE();
+ }
+
+ ULONG32 contextFlags;
+
+ contextFlags = CONTEXT_ALL;
+
+ HRESULT status =
+ g_dacImpl->m_pTarget->
+ GetThreadContext(thread->GetOSThreadId(), contextFlags,
+ sizeof(*context), (PBYTE)context);
+ if (status != S_OK)
+ {
+ DacError(status);
+ UNREACHABLE();
+ }
+
+ return TRUE;
+}
+
diff --git a/src/debug/daccess/request.cpp b/src/debug/daccess/request.cpp
new file mode 100644
index 0000000000..a30ec37eac
--- /dev/null
+++ b/src/debug/daccess/request.cpp
@@ -0,0 +1,4377 @@
+// 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.
+//*****************************************************************************
+// File: request.cpp
+//
+
+//
+// CorDataAccess::Request implementation.
+//
+//*****************************************************************************
+
+#include "stdafx.h"
+#include <win32threadpool.h>
+
+#include <gceewks.cpp>
+#include <handletablepriv.h>
+#include "typestring.h"
+#include <gccover.h>
+#include <virtualcallstub.h>
+#ifdef FEATURE_COMINTEROP
+#include <comcallablewrapper.h>
+#endif // FEATURE_COMINTEROP
+
+#ifndef FEATURE_PAL
+// It is unfortunate having to include this header just to get the definition of GenericModeBlock
+#include <msodw.h>
+#endif // FEATURE_PAL
+
+// To include definiton of IsThrowableThreadAbortException
+#include <exstatecommon.h>
+
+#include "rejit.h"
+
+
+// GC headers define these to EE-specific stuff that we don't want.
+#undef EnterCriticalSection
+#undef LeaveCriticalSection
+
+#define PTR_CDADDR(ptr) TO_CDADDR(PTR_TO_TADDR(ptr))
+#define HOST_CDADDR(host) TO_CDADDR(PTR_HOST_TO_TADDR(host))
+
+#define SOSDacEnter() \
+ DAC_ENTER(); \
+ HRESULT hr = S_OK; \
+ EX_TRY \
+ {
+
+#define SOSDacLeave() \
+ } \
+ EX_CATCH \
+ { \
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &hr)) \
+ { \
+ EX_RETHROW; \
+ } \
+ } \
+ EX_END_CATCH(SwallowAllExceptions) \
+ DAC_LEAVE();
+
+// Use this when you don't want to instantiate an Object * in the host.
+TADDR DACGetMethodTableFromObjectPointer(TADDR objAddr, ICorDebugDataTarget * target)
+{
+ ULONG32 returned = 0;
+ TADDR Value = NULL;
+
+ HRESULT hr = target->ReadVirtual(objAddr, (PBYTE)&Value, sizeof(TADDR), &returned);
+
+ if ((hr != S_OK) || (returned != sizeof(TADDR)))
+ {
+ return NULL;
+ }
+
+ Value = Value & ~3; // equivalent to Object::GetGCSafeMethodTable()
+ return Value;
+}
+
+// Use this when you don't want to instantiate an Object * in the host.
+PTR_SyncBlock DACGetSyncBlockFromObjectPointer(TADDR objAddr, ICorDebugDataTarget * target)
+{
+ ULONG32 returned = 0;
+ DWORD Value = NULL;
+
+ HRESULT hr = target->ReadVirtual(objAddr - sizeof(DWORD), (PBYTE)&Value, sizeof(DWORD), &returned);
+
+ if ((hr != S_OK) || (returned != sizeof(DWORD)))
+ {
+ return NULL;
+ }
+
+ if ((Value & (BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX | BIT_SBLK_IS_HASHCODE)) != BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX)
+ return NULL;
+ Value &= MASK_SYNCBLOCKINDEX;
+
+ PTR_SyncTableEntry ste = PTR_SyncTableEntry(dac_cast<TADDR>(g_pSyncTable) + (sizeof(SyncTableEntry) * Value));
+ return ste->m_SyncBlock;
+}
+
+BOOL DacValidateEEClass(EEClass *pEEClass)
+{
+ // Verify things are right.
+ // The EEClass method table pointer should match the method table.
+ // TODO: Microsoft, need another test for validity, this one isn't always true anymore.
+ BOOL retval = TRUE;
+ EX_TRY
+ {
+ MethodTable *pMethodTable = pEEClass->GetMethodTable();
+ if (!pMethodTable)
+ {
+ // PREfix.
+ retval = FALSE;
+ }
+ else if (pEEClass != pMethodTable->GetClass())
+ {
+ retval = FALSE;
+ }
+ }
+ EX_CATCH
+ {
+ retval = FALSE; // Something is wrong
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+ return retval;
+
+}
+
+BOOL DacValidateMethodTable(MethodTable *pMT, BOOL &bIsFree)
+{
+ // Verify things are right.
+ BOOL retval = FALSE;
+ EX_TRY
+ {
+ bIsFree = FALSE;
+ EEClass *pEEClass = pMT->GetClass();
+ if (pEEClass==NULL)
+ {
+ // Okay to have a NULL EEClass if this is a free methodtable
+ CLRDATA_ADDRESS MethTableAddr = HOST_CDADDR(pMT);
+ CLRDATA_ADDRESS FreeObjMethTableAddr = HOST_CDADDR(g_pFreeObjectMethodTable);
+ if (MethTableAddr != FreeObjMethTableAddr)
+ goto BadMethodTable;
+
+ bIsFree = TRUE;
+ }
+ else
+ {
+ // Standard fast check
+ if (!pMT->ValidateWithPossibleAV())
+ goto BadMethodTable;
+
+ // In rare cases, we've seen the standard check above pass when it shouldn't.
+ // Insert additional/ad-hoc tests below.
+
+ // Metadata token should look valid for a class
+ mdTypeDef td = pMT->GetCl();
+ if (td != mdTokenNil && TypeFromToken(td) != mdtTypeDef)
+ goto BadMethodTable;
+
+ // BaseSize should always be greater than 0 for valid objects (unless it's an interface)
+ // For strings, baseSize is not ptr-aligned
+ if (!pMT->IsInterface() && !pMT->IsString())
+ {
+ if (pMT->GetBaseSize() == 0 || !IS_ALIGNED(pMT->GetBaseSize(), sizeof(void *)))
+ goto BadMethodTable;
+ }
+ }
+
+ retval = TRUE;
+
+BadMethodTable: ;
+ }
+ EX_CATCH
+ {
+ retval = FALSE; // Something is wrong
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+ return retval;
+
+}
+
+BOOL DacValidateMD(MethodDesc * pMD)
+{
+ if (pMD == NULL)
+ {
+ return FALSE;
+ }
+
+ // Verify things are right.
+ BOOL retval = TRUE;
+ EX_TRY
+ {
+ MethodTable *pMethodTable = pMD->GetMethodTable();
+
+ // Standard fast check
+ if (!pMethodTable->ValidateWithPossibleAV())
+ {
+ retval = FALSE;
+ }
+
+ if (retval && (pMD->GetSlot() >= pMethodTable->GetNumVtableSlots() && !pMD->HasNonVtableSlot()))
+ {
+ retval = FALSE;
+ }
+
+ if (retval && pMD->HasTemporaryEntryPoint())
+ {
+ MethodDesc *pMDCheck = MethodDesc::GetMethodDescFromStubAddr(pMD->GetTemporaryEntryPoint(), TRUE);
+
+ if (PTR_HOST_TO_TADDR(pMD) != PTR_HOST_TO_TADDR(pMDCheck))
+ {
+ retval = FALSE;
+ }
+ }
+
+ if (retval && pMD->HasNativeCode())
+ {
+ PCODE jitCodeAddr = pMD->GetNativeCode();
+
+ MethodDesc *pMDCheck = ExecutionManager::GetCodeMethodDesc(jitCodeAddr);
+ if (pMDCheck)
+ {
+ // Check that the given MethodDesc matches the MethodDesc from
+ // the CodeHeader
+ if (PTR_HOST_TO_TADDR(pMD) != PTR_HOST_TO_TADDR(pMDCheck))
+ {
+ retval = FALSE;
+ }
+ }
+ else
+ {
+ retval = FALSE;
+ }
+ }
+ }
+ EX_CATCH
+ {
+ retval = FALSE; // Something is wrong
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+ return retval;
+}
+
+BOOL DacValidateMD(LPCVOID pMD)
+{
+ return DacValidateMD((MethodDesc *)pMD);
+}
+
+VOID GetJITMethodInfo (EECodeInfo * pCodeInfo, JITTypes *pJITType, CLRDATA_ADDRESS *pGCInfo)
+{
+ DWORD dwType = pCodeInfo->GetJitManager()->GetCodeType();
+ if (IsMiIL(dwType))
+ {
+ *pJITType = TYPE_JIT;
+ }
+ else if (IsMiNative(dwType))
+ {
+ *pJITType = TYPE_PJIT;
+ }
+ else
+ {
+ *pJITType = TYPE_UNKNOWN;
+ }
+
+ *pGCInfo = (CLRDATA_ADDRESS)PTR_TO_TADDR(pCodeInfo->GetGCInfo());
+}
+
+
+HRESULT
+ClrDataAccess::GetWorkRequestData(CLRDATA_ADDRESS addr, struct DacpWorkRequestData *workRequestData)
+{
+ if (addr == 0 || workRequestData == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ WorkRequest *pRequest = PTR_WorkRequest(TO_TADDR(addr));
+ workRequestData->Function = (TADDR)(pRequest->Function);
+ workRequestData->Context = (TADDR)(pRequest->Context);
+ workRequestData->NextWorkRequest = (TADDR)(pRequest->next);
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetHillClimbingLogEntry(CLRDATA_ADDRESS addr, struct DacpHillClimbingLogEntry *entry)
+{
+ if (addr == 0 || entry == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ HillClimbingLogEntry *pLogEntry = PTR_HillClimbingLogEntry(TO_TADDR(addr));
+ entry->TickCount = pLogEntry->TickCount;
+ entry->NewControlSetting = pLogEntry->NewControlSetting;
+ entry->LastHistoryCount = pLogEntry->LastHistoryCount;
+ entry->LastHistoryMean = pLogEntry->LastHistoryMean;
+ entry->Transition = pLogEntry->Transition;
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetThreadpoolData(struct DacpThreadpoolData *threadpoolData)
+{
+ if (threadpoolData == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ threadpoolData->cpuUtilization = ThreadpoolMgr::cpuUtilization;
+ threadpoolData->MinLimitTotalWorkerThreads = ThreadpoolMgr::MinLimitTotalWorkerThreads;
+ threadpoolData->MaxLimitTotalWorkerThreads = ThreadpoolMgr::MaxLimitTotalWorkerThreads;
+
+ //
+ // Read ThreadpoolMgr::WorkerCounter
+ //
+ TADDR pCounter = DacGetTargetAddrForHostAddr(&ThreadpoolMgr::WorkerCounter,true);
+ ThreadpoolMgr::ThreadCounter counter;
+ DacReadAll(pCounter,&counter,sizeof(ThreadpoolMgr::ThreadCounter),true);
+ ThreadpoolMgr::ThreadCounter::Counts counts = counter.counts;
+
+ threadpoolData->NumWorkingWorkerThreads = counts.NumWorking;
+ threadpoolData->NumIdleWorkerThreads = counts.NumActive - counts.NumWorking;
+ threadpoolData->NumRetiredWorkerThreads = counts.NumRetired;
+
+ threadpoolData->FirstUnmanagedWorkRequest = HOST_CDADDR(ThreadpoolMgr::WorkRequestHead);
+
+ threadpoolData->HillClimbingLog = dac_cast<TADDR>(&HillClimbingLog);
+ threadpoolData->HillClimbingLogFirstIndex = HillClimbingLogFirstIndex;
+ threadpoolData->HillClimbingLogSize = HillClimbingLogSize;
+
+
+ //
+ // Read ThreadpoolMgr::CPThreadCounter
+ //
+ pCounter = DacGetTargetAddrForHostAddr(&ThreadpoolMgr::CPThreadCounter,true);
+ DacReadAll(pCounter,&counter,sizeof(ThreadpoolMgr::ThreadCounter),true);
+ counts = counter.counts;
+
+ threadpoolData->NumCPThreads = (LONG)(counts.NumActive + counts.NumRetired);
+ threadpoolData->NumFreeCPThreads = (LONG)(counts.NumActive - counts.NumWorking);
+ threadpoolData->MaxFreeCPThreads = ThreadpoolMgr::MaxFreeCPThreads;
+ threadpoolData->NumRetiredCPThreads = (LONG)(counts.NumRetired);
+ threadpoolData->MaxLimitTotalCPThreads = ThreadpoolMgr::MaxLimitTotalCPThreads;
+ threadpoolData->CurrentLimitTotalCPThreads = (LONG)(counts.NumActive); //legacy: currently has no meaning
+ threadpoolData->MinLimitTotalCPThreads = ThreadpoolMgr::MinLimitTotalCPThreads;
+
+ TADDR pEntry = DacGetTargetAddrForHostAddr(&ThreadpoolMgr::TimerQueue,true);
+ ThreadpoolMgr::LIST_ENTRY entry;
+ DacReadAll(pEntry,&entry,sizeof(ThreadpoolMgr::LIST_ENTRY),true);
+ TADDR node = (TADDR) entry.Flink;
+ threadpoolData->NumTimers = 0;
+ while (node && node != pEntry)
+ {
+ threadpoolData->NumTimers++;
+ DacReadAll(node,&entry,sizeof(ThreadpoolMgr::LIST_ENTRY),true);
+ node = (TADDR) entry.Flink;
+ }
+
+ threadpoolData->AsyncTimerCallbackCompletionFPtr = (CLRDATA_ADDRESS) GFN_TADDR(ThreadpoolMgr__AsyncTimerCallbackCompletion);
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT ClrDataAccess::GetThreadStoreData(struct DacpThreadStoreData *threadStoreData)
+{
+ SOSDacEnter();
+
+ ThreadStore* threadStore = ThreadStore::s_pThreadStore;
+ if (!threadStore)
+ {
+ hr = E_UNEXPECTED;
+ }
+ else
+ {
+ // initialize the fields of our local structure
+ threadStoreData->threadCount = threadStore->m_ThreadCount;
+ threadStoreData->unstartedThreadCount = threadStore->m_UnstartedThreadCount;
+ threadStoreData->backgroundThreadCount = threadStore->m_BackgroundThreadCount;
+ threadStoreData->pendingThreadCount = threadStore->m_PendingThreadCount;
+ threadStoreData->deadThreadCount = threadStore->m_DeadThreadCount;
+ threadStoreData->fHostConfig = g_fHostConfig;
+
+ // identify the "important" threads
+ threadStoreData->firstThread = HOST_CDADDR(threadStore->m_ThreadList.GetHead());
+ threadStoreData->finalizerThread = HOST_CDADDR(g_pFinalizerThread);
+ threadStoreData->gcThread = HOST_CDADDR(g_pSuspensionThread);
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetStressLogAddress(CLRDATA_ADDRESS *stressLog)
+{
+ if (stressLog == NULL)
+ return E_INVALIDARG;
+
+#ifdef STRESS_LOG
+ SOSDacEnter();
+ if (g_pStressLog.IsValid())
+ *stressLog = HOST_CDADDR(g_pStressLog);
+ else
+ hr = E_FAIL;
+
+ SOSDacLeave();
+ return hr;
+#else
+ return E_NOTIMPL;
+#endif // STRESS_LOG
+}
+
+HRESULT
+ClrDataAccess::GetJitManagerList(unsigned int count, struct DacpJitManagerInfo managers[], unsigned int *pNeeded)
+{
+ SOSDacEnter();
+
+ if (managers)
+ {
+ if (count >= 1)
+ {
+ EEJitManager * managerPtr = ExecutionManager::GetEEJitManager();
+
+ DacpJitManagerInfo *currentPtr = &managers[0];
+ currentPtr->managerAddr = HOST_CDADDR(managerPtr);
+ currentPtr->codeType = managerPtr->GetCodeType();
+
+ EEJitManager *eeJitManager = PTR_EEJitManager(PTR_HOST_TO_TADDR(managerPtr));
+ currentPtr->ptrHeapList = HOST_CDADDR(eeJitManager->m_pCodeHeap);
+ }
+#ifdef FEATURE_PREJIT
+ if (count >= 2)
+ {
+ NativeImageJitManager * managerPtr = ExecutionManager::GetNativeImageJitManager();
+ DacpJitManagerInfo *currentPtr = &managers[1];
+ currentPtr->managerAddr = HOST_CDADDR(managerPtr);
+ currentPtr->codeType = managerPtr->GetCodeType();
+ }
+#endif
+ }
+ else if (pNeeded)
+ {
+ *pNeeded = 1;
+#ifdef FEATURE_PREJIT
+ (*pNeeded)++;
+#endif
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetMethodTableSlot(CLRDATA_ADDRESS mt, unsigned int slot, CLRDATA_ADDRESS *value)
+{
+ if (mt == 0 || value == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ MethodTable* mTable = PTR_MethodTable(TO_TADDR(mt));
+ BOOL bIsFree = FALSE;
+ if (!DacValidateMethodTable(mTable, bIsFree))
+ {
+ hr = E_INVALIDARG;
+ }
+ else if (slot < mTable->GetNumVtableSlots())
+ {
+ // Now get the slot:
+ *value = mTable->GetRestoredSlot(slot);
+ }
+ else
+ {
+ hr = E_INVALIDARG;
+ MethodTable::IntroducedMethodIterator it(mTable);
+ for (; it.IsValid() && FAILED(hr); it.Next())
+ {
+ MethodDesc * pMD = it.GetMethodDesc();
+ if (pMD->GetSlot() == slot)
+ {
+ *value = pMD->GetMethodEntryPoint();
+ hr = S_OK;
+ }
+ }
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+
+HRESULT
+ClrDataAccess::GetCodeHeapList(CLRDATA_ADDRESS jitManager, unsigned int count, struct DacpJitCodeHeapInfo codeHeaps[], unsigned int *pNeeded)
+{
+ if (jitManager == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ EEJitManager *pJitManager = PTR_EEJitManager(TO_TADDR(jitManager));
+ HeapList *heapList = pJitManager->m_pCodeHeap;
+
+ if (codeHeaps)
+ {
+ unsigned int i = 0;
+ while ((heapList != NULL) && (i < count))
+ {
+ // What type of CodeHeap pointer do we have?
+ CodeHeap *codeHeap = heapList->pHeap;
+ TADDR ourVTablePtr = VPTR_HOST_VTABLE_TO_TADDR(*(LPVOID*)codeHeap);
+ if (ourVTablePtr == LoaderCodeHeap::VPtrTargetVTable())
+ {
+ LoaderCodeHeap *loaderCodeHeap = PTR_LoaderCodeHeap(PTR_HOST_TO_TADDR(codeHeap));
+ codeHeaps[i].codeHeapType = CODEHEAP_LOADER;
+ codeHeaps[i].LoaderHeap =
+ TO_CDADDR(PTR_HOST_MEMBER_TADDR(LoaderCodeHeap, loaderCodeHeap, m_LoaderHeap));
+ }
+ else if (ourVTablePtr == HostCodeHeap::VPtrTargetVTable())
+ {
+ HostCodeHeap *hostCodeHeap = PTR_HostCodeHeap(PTR_HOST_TO_TADDR(codeHeap));
+ codeHeaps[i].codeHeapType = CODEHEAP_HOST;
+ codeHeaps[i].HostData.baseAddr = PTR_CDADDR(hostCodeHeap->m_pBaseAddr);
+ codeHeaps[i].HostData.currentAddr = PTR_CDADDR(hostCodeHeap->m_pLastAvailableCommittedAddr);
+ }
+ else
+ {
+ codeHeaps[i].codeHeapType = CODEHEAP_UNKNOWN;
+ }
+ heapList = heapList->hpNext;
+ i++;
+ }
+
+ if (pNeeded)
+ *pNeeded = i;
+ }
+ else if (pNeeded)
+ {
+ int i = 0;
+ while (heapList != NULL)
+ {
+ heapList = heapList->hpNext;
+ i++;
+ }
+
+ *pNeeded = i;
+ }
+ else
+ {
+ hr = E_INVALIDARG;
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetStackLimits(CLRDATA_ADDRESS threadPtr, CLRDATA_ADDRESS *lower,
+ CLRDATA_ADDRESS *upper, CLRDATA_ADDRESS *fp)
+{
+ if (threadPtr == 0 || (lower == NULL && upper == NULL && fp == NULL))
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ Thread * thread = PTR_Thread(TO_TADDR(threadPtr));
+
+ if (lower)
+ *lower = TO_CDADDR(thread->GetCachedStackBase().GetAddr());
+
+ if (upper)
+ *upper = TO_CDADDR(thread->GetCachedStackLimit().GetAddr());
+
+ if (fp)
+ *fp = PTR_HOST_MEMBER_TADDR(Thread, thread, m_pFrame);
+
+ SOSDacLeave();
+
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetRegisterName(int regNum, unsigned int count, __out_z __inout_ecount(count) wchar_t *buffer, unsigned int *pNeeded)
+{
+ if (!buffer && !pNeeded)
+ return E_POINTER;
+
+#ifdef _TARGET_AMD64_
+ static const wchar_t *regs[] =
+ {
+ W("rax"), W("rcx"), W("rdx"), W("rbx"), W("rsp"), W("rbp"), W("rsi"), W("rdi"),
+ W("r8"), W("r9"), W("r10"), W("r11"), W("r12"), W("r13"), W("r14"), W("r15"),
+ };
+#elif defined(_TARGET_ARM_)
+ static const wchar_t *regs[] =
+ {
+ W("r0"),
+ W("r1"),
+ W("r2"),
+ W("r3"),
+ W("r4"),
+ W("r5"),
+ W("r6"),
+ W("r7"),
+ W("r8"), W("r9"), W("r10"), W("r11"), W("r12"), W("sp"), W("lr")
+ };
+#elif defined(_TARGET_ARM64_)
+ static const wchar_t *regs[] =
+ {
+ W("X0"),
+ W("X1"),
+ W("X2"),
+ W("X3"),
+ W("X4"),
+ W("X5"),
+ W("X6"),
+ W("X7"),
+ W("X8"), W("X9"), W("X10"), W("X11"), W("X12"), W("X13"), W("X14"), W("X15"), W("X16"), W("X17"),
+ W("X18"), W("X19"), W("X20"), W("X21"), W("X22"), W("X23"), W("X24"), W("X25"), W("X26"), W("X27"),
+ W("X28"), W("Fp"), W("Sp"), W("Lr")
+ };
+#elif defined(_TARGET_X86_)
+ static const wchar_t *regs[] =
+ {
+ W("eax"), W("ecx"), W("edx"), W("ebx"), W("esp"), W("ebp"), W("esi"), W("edi"),
+ };
+#endif
+
+ // Caller frame registers are encoded as "-(reg+1)".
+ bool callerFrame = regNum < 0;
+ if (callerFrame)
+ regNum = -regNum-1;
+
+ if ((unsigned int)regNum >= _countof(regs))
+ return E_UNEXPECTED;
+
+
+ const wchar_t caller[] = W("caller.");
+ unsigned int needed = (callerFrame?(unsigned int)wcslen(caller):0) + (unsigned int)wcslen(regs[regNum]) + 1;
+ if (pNeeded)
+ *pNeeded = needed;
+
+ if (buffer)
+ {
+ _snwprintf_s(buffer, count, _TRUNCATE, W("%s%s"), callerFrame ? caller : W(""), regs[regNum]);
+ if (count < needed)
+ return S_FALSE;
+ }
+
+ return S_OK;
+}
+
+HRESULT
+ClrDataAccess::GetStackReferences(DWORD osThreadID, ISOSStackRefEnum **ppEnum)
+{
+ if (ppEnum == NULL)
+ return E_POINTER;
+
+ SOSDacEnter();
+
+ DacStackReferenceWalker *walker = new (nothrow) DacStackReferenceWalker(this, osThreadID);
+
+ if (walker == NULL)
+ {
+ hr = E_OUTOFMEMORY;
+ }
+ else
+ {
+ hr = walker->Init();
+
+ if (SUCCEEDED(hr))
+ hr = walker->QueryInterface(__uuidof(ISOSStackRefEnum), (void**)ppEnum);
+
+ if (FAILED(hr))
+ {
+ delete walker;
+ *ppEnum = NULL;
+ }
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetThreadFromThinlockID(UINT thinLockId, CLRDATA_ADDRESS *pThread)
+{
+ if (pThread == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ Thread *thread = g_pThinLockThreadIdDispenser->IdToThread(thinLockId);
+ *pThread = PTR_HOST_TO_TADDR(thread);
+
+ SOSDacLeave();
+ return hr;
+}
+
+
+HRESULT
+ClrDataAccess::GetThreadAllocData(CLRDATA_ADDRESS addr, struct DacpAllocData *data)
+{
+ if (data == NULL)
+ return E_POINTER;
+
+ SOSDacEnter();
+
+ Thread* thread = PTR_Thread(TO_TADDR(addr));
+
+ data->allocBytes = TO_CDADDR(thread->m_alloc_context.alloc_bytes);
+ data->allocBytesLoh = TO_CDADDR(thread->m_alloc_context.alloc_bytes_loh);
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetHeapAllocData(unsigned int count, struct DacpGenerationAllocData *data, unsigned int *pNeeded)
+{
+ if (data == 0 && pNeeded == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+#if defined(FEATURE_SVR_GC)
+ if (GCHeap::IsServerHeap())
+ {
+ hr = GetServerAllocData(count, data, pNeeded);
+ }
+ else
+#endif //FEATURE_SVR_GC
+ {
+ if (pNeeded)
+ *pNeeded = 1;
+
+ if (data && count >= 1)
+ {
+ for (int i=0;i<NUMBERGENERATIONS;i++)
+ {
+ data[0].allocData[i].allocBytes = (CLRDATA_ADDRESS)(ULONG_PTR) WKS::generation_table[i].allocation_context.alloc_bytes;
+ data[0].allocData[i].allocBytesLoh = (CLRDATA_ADDRESS)(ULONG_PTR) WKS::generation_table[i].allocation_context.alloc_bytes_loh;
+ }
+ }
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetThreadData(CLRDATA_ADDRESS threadAddr, struct DacpThreadData *threadData)
+{
+ SOSDacEnter();
+
+ // marshal the Thread object from the target
+ Thread* thread = PTR_Thread(TO_TADDR(threadAddr));
+
+ // initialize our local copy from the marshaled target Thread instance
+ ZeroMemory (threadData, sizeof(DacpThreadData));
+ threadData->corThreadId = thread->m_ThreadId;
+ threadData->osThreadId = thread->m_OSThreadId;
+ threadData->state = thread->m_State;
+ threadData->preemptiveGCDisabled = thread->m_fPreemptiveGCDisabled;
+ threadData->allocContextPtr = TO_CDADDR(thread->m_alloc_context.alloc_ptr);
+ threadData->allocContextLimit = TO_CDADDR(thread->m_alloc_context.alloc_limit);
+
+ // @todo Microsoft: the following assignment is pointless--we're just getting the
+ // target address of the m_pFiberData field of the Thread instance. Then we're going to
+ // compute it again as the argument to ReadVirtual. Ultimately, we want the value of
+ // that field, not its address. We already have that value as part of thread (the
+ // marshaled Thread instance).This should just go away and we should simply have:
+ // threadData->fiberData = TO_CDADDR(thread->m_pFiberData );
+ // instead of the next 11 lines.
+ threadData->fiberData = (CLRDATA_ADDRESS)PTR_HOST_MEMBER_TADDR(Thread, thread, m_pFiberData);
+
+ ULONG32 returned = 0;
+ TADDR Value = NULL;
+ HRESULT hr = m_pTarget->ReadVirtual(PTR_HOST_MEMBER_TADDR(Thread, thread, m_pFiberData),
+ (PBYTE)&Value,
+ sizeof(TADDR),
+ &returned);
+
+ if ((hr == S_OK) && (returned == sizeof(TADDR)))
+ {
+ threadData->fiberData = (CLRDATA_ADDRESS) Value;
+ }
+
+ threadData->pFrame = PTR_CDADDR(thread->m_pFrame);
+ threadData->context = PTR_CDADDR(thread->m_Context);
+ threadData->domain = PTR_CDADDR(thread->m_pDomain);
+ threadData->lockCount = thread->m_dwLockCount;
+#ifndef FEATURE_PAL
+ threadData->teb = TO_CDADDR(thread->m_pTEB);
+#else
+ threadData->teb = NULL;
+#endif
+ threadData->lastThrownObjectHandle =
+ TO_CDADDR(thread->m_LastThrownObjectHandle);
+ threadData->nextThread =
+ HOST_CDADDR(ThreadStore::s_pThreadStore->m_ThreadList.GetNext(thread));
+#ifdef WIN64EXCEPTIONS
+ if (thread->m_ExceptionState.m_pCurrentTracker)
+ {
+ threadData->firstNestedException = PTR_HOST_TO_TADDR(
+ thread->m_ExceptionState.m_pCurrentTracker->m_pPrevNestedInfo);
+ }
+#else
+ threadData->firstNestedException = PTR_HOST_TO_TADDR(
+ thread->m_ExceptionState.m_currentExInfo.m_pPrevNestedInfo);
+#endif // _WIN64
+
+ SOSDacLeave();
+ return hr;
+}
+
+#ifdef FEATURE_REJIT
+void CopyReJitInfoToReJitData(ReJitInfo * pReJitInfo, DacpReJitData * pReJitData)
+{
+ pReJitData->rejitID = pReJitInfo->m_pShared->GetId();
+ pReJitData->NativeCodeAddr = pReJitInfo->m_pCode;
+
+ switch (pReJitInfo->m_pShared->GetState())
+ {
+ default:
+ _ASSERTE(!"Unknown SharedRejitInfo state. DAC should be updated to understand this new state.");
+ pReJitData->flags = DacpReJitData::kUnknown;
+ break;
+
+ case SharedReJitInfo::kStateRequested:
+ pReJitData->flags = DacpReJitData::kRequested;
+ break;
+
+ case SharedReJitInfo::kStateActive:
+ pReJitData->flags = DacpReJitData::kActive;
+ break;
+
+ case SharedReJitInfo::kStateReverted:
+ pReJitData->flags = DacpReJitData::kReverted;
+ break;
+ }
+}
+#endif // FEATURE_REJIT
+
+
+
+//---------------------------------------------------------------------------------------
+//
+// Given a method desc addr, this loads up DacpMethodDescData and multiple DacpReJitDatas
+// with data on that method
+//
+// Arguments:
+// * methodDesc - MD to look up
+// * ip - IP address of interest (e.g., from an !ip2md call). This is used to ensure
+// the rejitted version corresponding to this IP is returned. May be NULL if you
+// don't care.
+// * methodDescData - [out] DacpMethodDescData to populate
+// * cRevertedRejitVersions - Number of entries allocated in rgRevertedRejitData
+// array
+// * rgRevertedRejitData - [out] Array of DacpReJitDatas to populate with rejitted
+// rejit version data
+// * pcNeededRevertedRejitData - [out] If cRevertedRejitVersions==0, the total
+// number of available rejit versions (including the current version) is
+// returned here. Else, the number of reverted rejit data actually fetched is
+// returned here.
+//
+// Return Value:
+// HRESULT indicating success or failure.
+//
+
+HRESULT ClrDataAccess::GetMethodDescData(
+ CLRDATA_ADDRESS methodDesc,
+ CLRDATA_ADDRESS ip,
+ struct DacpMethodDescData *methodDescData,
+ ULONG cRevertedRejitVersions,
+ DacpReJitData * rgRevertedRejitData,
+ ULONG * pcNeededRevertedRejitData)
+{
+ if (methodDesc == 0)
+ return E_INVALIDARG;
+
+ if ((cRevertedRejitVersions != 0) && (rgRevertedRejitData == NULL))
+ {
+ return E_INVALIDARG;
+ }
+
+ if ((rgRevertedRejitData != NULL) && (pcNeededRevertedRejitData == NULL))
+ {
+ // If you're asking for reverted rejit data, you'd better ask for the number of
+ // elements we return
+ return E_INVALIDARG;
+ }
+
+ SOSDacEnter();
+
+ PTR_MethodDesc pMD = PTR_MethodDesc(TO_TADDR(methodDesc));
+
+ if (!DacValidateMD(pMD))
+ {
+ hr = E_INVALIDARG;
+ }
+ else
+ {
+ ZeroMemory(methodDescData,sizeof(DacpMethodDescData));
+ if (rgRevertedRejitData != NULL)
+ ZeroMemory(rgRevertedRejitData, sizeof(*rgRevertedRejitData)*cRevertedRejitVersions);
+ if (pcNeededRevertedRejitData != NULL)
+ *pcNeededRevertedRejitData = 0;
+
+ methodDescData->requestedIP = ip;
+ methodDescData->bHasNativeCode = pMD->HasNativeCode();
+ methodDescData->bIsDynamic = (pMD->IsLCGMethod()) ? TRUE : FALSE;
+ methodDescData->wSlotNumber = pMD->GetSlot();
+ if (pMD->HasNativeCode())
+ {
+ methodDescData->NativeCodeAddr = TO_CDADDR(pMD->GetNativeCode());
+#ifdef DBG_TARGET_ARM
+ methodDescData->NativeCodeAddr &= ~THUMB_CODE;
+#endif
+ }
+ else
+ {
+ methodDescData->NativeCodeAddr = (CLRDATA_ADDRESS)-1;
+ }
+ methodDescData->AddressOfNativeCodeSlot = pMD->HasNativeCodeSlot() ?
+ TO_CDADDR(pMD->GetAddrOfNativeCodeSlot()) : NULL;
+ methodDescData->MDToken = pMD->GetMemberDef();
+ methodDescData->MethodDescPtr = methodDesc;
+ methodDescData->MethodTablePtr = HOST_CDADDR(pMD->GetMethodTable());
+ methodDescData->ModulePtr = HOST_CDADDR(pMD->GetModule());
+
+#ifdef FEATURE_REJIT
+
+ // If rejit info is appropriate, get the following:
+ // * ReJitInfo for the current, active version of the method
+ // * ReJitInfo for the requested IP (for !ip2md and !u)
+ // * ReJitInfos for all reverted versions of the method (up to
+ // cRevertedRejitVersions)
+ //
+ // Minidumps will not have all this rejit info, and failure to get rejit info
+ // should not be fatal. So enclose all rejit stuff in a try.
+
+ EX_TRY
+ {
+ ReJitManager * pReJitMgr = pMD->GetReJitManager();
+
+ // Current ReJitInfo
+ ReJitInfo * pReJitInfoCurrent = pReJitMgr->FindNonRevertedReJitInfo(pMD);
+ if (pReJitInfoCurrent != NULL)
+ {
+ CopyReJitInfoToReJitData(pReJitInfoCurrent, &methodDescData->rejitDataCurrent);
+ }
+
+ // Requested ReJitInfo
+ _ASSERTE(methodDescData->rejitDataRequested.rejitID == 0);
+ if (methodDescData->requestedIP != NULL)
+ {
+ ReJitInfo * pReJitInfoRequested = pReJitMgr->FindReJitInfo(
+ pMD,
+ CLRDATA_ADDRESS_TO_TADDR(methodDescData->requestedIP),
+ NULL /* reJitId */);
+
+ if (pReJitInfoRequested != NULL)
+ {
+ CopyReJitInfoToReJitData(pReJitInfoRequested, &methodDescData->rejitDataRequested);
+ }
+ }
+
+ // Total number of jitted rejit versions
+ ULONG cJittedRejitVersions;
+ if (SUCCEEDED(pReJitMgr->GetReJITIDs(pMD, 0 /* cReJitIds */, &cJittedRejitVersions, NULL /* reJitIds */)))
+ {
+ methodDescData->cJittedRejitVersions = cJittedRejitVersions;
+ }
+
+ // Reverted ReJitInfos
+ if (rgRevertedRejitData == NULL)
+ {
+ // No reverted rejit versions will be returned, but maybe caller wants a
+ // count of all versions
+ if (pcNeededRevertedRejitData != NULL)
+ {
+ *pcNeededRevertedRejitData = methodDescData->cJittedRejitVersions;
+ }
+ }
+ else
+ {
+ // Caller wants some reverted rejit versions. Gather reverted rejit version data to return
+ ULONG cReJitIds;
+ StackSArray<ReJITID> reJitIds;
+
+ // Prepare array to populate with rejitids. "+ 1" because GetReJITIDs
+ // returns all available rejitids, including the rejitid for the one non-reverted
+ // current version.
+ ReJITID * rgReJitIds = reJitIds.OpenRawBuffer(cRevertedRejitVersions + 1);
+ if (rgReJitIds != NULL)
+ {
+ hr = pReJitMgr->GetReJITIDs(pMD, cRevertedRejitVersions + 1, &cReJitIds, rgReJitIds);
+ if (SUCCEEDED(hr))
+ {
+ // Go through rejitids. For each reverted one, populate a entry in rgRevertedRejitData
+ reJitIds.CloseRawBuffer(cReJitIds);
+ ULONG iRejitDataReverted = 0;
+ for (COUNT_T i=0;
+ (i < cReJitIds) && (iRejitDataReverted < cRevertedRejitVersions);
+ i++)
+ {
+ ReJitInfo * pRejitInfo = pReJitMgr->FindReJitInfo(
+ pMD,
+ NULL /* pCodeStart */,
+ reJitIds[i]);
+
+ if ((pRejitInfo == NULL) ||
+ (pRejitInfo->m_pShared->GetState() != SharedReJitInfo::kStateReverted))
+ {
+ continue;
+ }
+
+ CopyReJitInfoToReJitData(pRejitInfo, &rgRevertedRejitData[iRejitDataReverted]);
+ iRejitDataReverted++;
+ }
+ // pcNeededRevertedRejitData != NULL as per condition at top of function (cuz rgRevertedRejitData !=
+ // NULL).
+ *pcNeededRevertedRejitData = iRejitDataReverted;
+ }
+ }
+ }
+ }
+ EX_CATCH
+ {
+ if (pcNeededRevertedRejitData != NULL)
+ *pcNeededRevertedRejitData = 0;
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+ hr = S_OK; // Failure to get rejitids is not fatal
+#endif // FEATURE_REJIT
+
+#if defined(HAVE_GCCOVER)
+ if (pMD->m_GcCover)
+ {
+ EX_TRY
+ {
+ // In certain minidumps, we won't save the gccover information.
+ // (it would be unwise to do so, it is heavy and not a customer scenario).
+ methodDescData->GCStressCodeCopy = HOST_CDADDR(pMD->m_GcCover) + offsetof(GCCoverageInfo, savedCode);
+ }
+ EX_CATCH
+ {
+ methodDescData->GCStressCodeCopy = 0;
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+ }
+ else
+#endif // HAVE_GCCOVER
+
+ // Set this above Dario since you know how to tell if dynamic
+ if (methodDescData->bIsDynamic)
+ {
+ DynamicMethodDesc *pDynamicMethod = PTR_DynamicMethodDesc(TO_TADDR(methodDesc));
+ if (pDynamicMethod)
+ {
+ LCGMethodResolver *pResolver = pDynamicMethod->GetLCGMethodResolver();
+ if (pResolver)
+ {
+ OBJECTREF value = pResolver->GetManagedResolver();
+ if (value)
+ {
+ FieldDesc *pField = (&g_Mscorlib)->GetField(FIELD__DYNAMICRESOLVER__DYNAMIC_METHOD);
+ _ASSERTE(pField);
+ value = pField->GetRefValue(value);
+ if (value)
+ {
+ methodDescData->managedDynamicMethodObject = PTR_HOST_TO_TADDR(value);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetMethodDescTransparencyData(CLRDATA_ADDRESS methodDesc, struct DacpMethodDescTransparencyData *data)
+{
+ if (methodDesc == 0 || data == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ MethodDesc *pMD = PTR_MethodDesc(TO_TADDR(methodDesc));
+ if (!DacValidateMD(pMD))
+ {
+ hr = E_INVALIDARG;
+ }
+ else
+ {
+ ZeroMemory(data, sizeof(DacpMethodDescTransparencyData));
+
+ if (pMD->HasCriticalTransparentInfo())
+ {
+ data->bHasCriticalTransparentInfo = pMD->HasCriticalTransparentInfo();
+ data->bIsCritical = pMD->IsCritical();
+ data->bIsTreatAsSafe = pMD->IsTreatAsSafe();
+ }
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetCodeHeaderData(CLRDATA_ADDRESS ip, struct DacpCodeHeaderData *codeHeaderData)
+{
+ if (ip == 0 || codeHeaderData == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ EECodeInfo codeInfo(TO_TADDR(ip));
+
+ if (!codeInfo.IsValid())
+ {
+ // We may be able to walk stubs to find a method desc if it's not a jitted method.
+ MethodDesc *methodDescI = MethodTable::GetMethodDescForSlotAddress(TO_TADDR(ip));
+ if (methodDescI == NULL)
+ {
+ hr = E_INVALIDARG;
+ }
+ else
+ {
+ codeHeaderData->MethodDescPtr = HOST_CDADDR(methodDescI);
+ codeHeaderData->JITType = TYPE_UNKNOWN;
+ codeHeaderData->GCInfo = NULL;
+ codeHeaderData->MethodStart = NULL;
+ codeHeaderData->MethodSize = 0;
+ codeHeaderData->ColdRegionStart = NULL;
+ }
+ }
+ else
+ {
+ codeHeaderData->MethodDescPtr = HOST_CDADDR(codeInfo.GetMethodDesc());
+
+ GetJITMethodInfo(&codeInfo, &codeHeaderData->JITType, &codeHeaderData->GCInfo);
+
+ codeHeaderData->MethodStart =
+ (CLRDATA_ADDRESS) codeInfo.GetStartAddress();
+ size_t methodSize = codeInfo.GetCodeManager()->GetFunctionSize(codeInfo.GetGCInfoToken());
+ _ASSERTE(FitsIn<DWORD>(methodSize));
+ codeHeaderData->MethodSize = static_cast<DWORD>(methodSize);
+
+ IJitManager::MethodRegionInfo methodRegionInfo = {NULL, 0, NULL, 0};
+ codeInfo.GetMethodRegionInfo(&methodRegionInfo);
+
+ codeHeaderData->HotRegionSize = (DWORD) methodRegionInfo.hotSize;
+ codeHeaderData->ColdRegionSize = (DWORD) methodRegionInfo.coldSize;
+ codeHeaderData->ColdRegionStart = (CLRDATA_ADDRESS) methodRegionInfo.coldStartAddress;
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetMethodDescPtrFromFrame(CLRDATA_ADDRESS frameAddr, CLRDATA_ADDRESS * ppMD)
+{
+ if (frameAddr == 0 || ppMD == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ Frame *pFrame = PTR_Frame(TO_TADDR(frameAddr));
+ CLRDATA_ADDRESS methodDescAddr = HOST_CDADDR(pFrame->GetFunction());
+ if ((methodDescAddr == NULL) || !DacValidateMD(PTR_MethodDesc(TO_TADDR(methodDescAddr))))
+ {
+ hr = E_INVALIDARG;
+ }
+ else
+ {
+ *ppMD = methodDescAddr;
+ hr = S_OK;
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetMethodDescPtrFromIP(CLRDATA_ADDRESS ip, CLRDATA_ADDRESS * ppMD)
+{
+ if (ip == 0 || ppMD == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ EECodeInfo codeInfo(TO_TADDR(ip));
+
+ if (!codeInfo.IsValid())
+ {
+ hr = E_FAIL;
+ }
+ else
+ {
+ CLRDATA_ADDRESS pMD = HOST_CDADDR(codeInfo.GetMethodDesc());
+ if ((pMD == NULL) || !DacValidateMD(PTR_MethodDesc(TO_TADDR(pMD))))
+ {
+ hr = E_INVALIDARG;
+ }
+ else
+ {
+ *ppMD = pMD;
+ hr = S_OK;
+ }
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetMethodDescName(CLRDATA_ADDRESS methodDesc, unsigned int count, __out_z __inout_ecount(count) wchar_t *name, unsigned int *pNeeded)
+{
+ if (methodDesc == 0)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ MethodDesc* pMD = PTR_MethodDesc(TO_TADDR(methodDesc));
+ StackSString str;
+
+ EX_TRY
+ {
+ TypeString::AppendMethodInternal(str, pMD, TypeString::FormatSignature|TypeString::FormatNamespace|TypeString::FormatFullInst);
+ }
+ EX_CATCH
+ {
+ hr = E_FAIL;
+ if (pMD->IsDynamicMethod())
+ {
+ if (pMD->IsLCGMethod() || pMD->IsILStub())
+ {
+ // In heap dumps, trying to format the signature can fail
+ // in certain cases because StoredSigMethodDesc::m_pSig points
+ // to the IMAGE_MAPPED layout (in the PEImage::m_pLayouts array).
+ // We save only the IMAGE_LOADED layout to the heap dump. Rather
+ // than bloat the dump, we just drop the signature in these
+ // cases.
+
+ str.Clear();
+ TypeString::AppendMethodInternal(str, pMD, TypeString::FormatNamespace|TypeString::FormatFullInst);
+ hr = S_OK;
+ }
+ }
+ else
+ {
+#ifdef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+ if (MdCacheGetEEName(TO_TADDR(methodDesc), str))
+ {
+ hr = S_OK;
+ }
+ else
+ {
+#endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+ str.Clear();
+ Module* pModule = pMD->GetModule();
+ if (pModule)
+ {
+ WCHAR path[MAX_LONGPATH];
+ COUNT_T nChars = 0;
+ if (pModule->GetPath().DacGetUnicode(NumItems(path), path, &nChars) &&
+ nChars > 0 && nChars <= NumItems(path))
+ {
+ WCHAR* pFile = path + nChars - 1;
+ while ((pFile >= path) && (*pFile != W('\\')))
+ {
+ pFile--;
+ }
+ pFile++;
+ if (*pFile)
+ {
+ str.Append(pFile);
+ str.Append(W("!Unknown"));
+ hr = S_OK;
+ }
+ }
+ }
+#ifdef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+ }
+#endif
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ if (SUCCEEDED(hr))
+ {
+
+ const wchar_t *val = str.GetUnicode();
+
+ if (pNeeded)
+ *pNeeded = str.GetCount() + 1;
+
+ if (name && count)
+ {
+ wcsncpy_s(name, count, val, _TRUNCATE);
+ name[count-1] = 0;
+ }
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetDomainFromContext(CLRDATA_ADDRESS contextAddr, CLRDATA_ADDRESS *domain)
+{
+ if (contextAddr == 0 || domain == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ Context* context = PTR_Context(TO_TADDR(contextAddr));
+ *domain = HOST_CDADDR(context->GetDomain());
+
+ SOSDacLeave();
+ return hr;
+}
+
+
+HRESULT
+ClrDataAccess::GetObjectStringData(CLRDATA_ADDRESS obj, unsigned int count, __out_z __inout_ecount(count) wchar_t *stringData, unsigned int *pNeeded)
+{
+ if (obj == 0)
+ return E_INVALIDARG;
+
+ if ((stringData == 0 || count <= 0) && (pNeeded == NULL))
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ TADDR mtTADDR = DACGetMethodTableFromObjectPointer(TO_TADDR(obj), m_pTarget);
+ MethodTable *mt = PTR_MethodTable(mtTADDR);
+
+ // Object must be a string
+ BOOL bFree = FALSE;
+ if (!DacValidateMethodTable(mt, bFree))
+ hr = E_INVALIDARG;
+ else if (HOST_CDADDR(mt) != HOST_CDADDR(g_pStringClass))
+ hr = E_INVALIDARG;
+
+ if (SUCCEEDED(hr))
+ {
+ PTR_StringObject str(TO_TADDR(obj));
+ ULONG32 needed = (ULONG32)str->GetStringLength() + 1;
+
+ if (stringData && count > 0)
+ {
+ if (count > needed)
+ count = needed;
+
+ TADDR pszStr = TO_TADDR(obj)+offsetof(StringObject, m_Characters);
+ hr = m_pTarget->ReadVirtual(pszStr, (PBYTE)stringData, count * sizeof(wchar_t), &needed);
+
+ if (SUCCEEDED(hr))
+ stringData[count - 1] = W('\0');
+ else
+ stringData[0] = W('\0');
+ }
+ else
+ {
+ hr = E_INVALIDARG;
+ }
+
+ if (pNeeded)
+ *pNeeded = needed;
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetObjectClassName(CLRDATA_ADDRESS obj, unsigned int count, __out_z __inout_ecount(count) wchar_t *className, unsigned int *pNeeded)
+{
+ if (obj == 0)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ // Don't turn the Object into a pointer, it is too costly on
+ // scans of the gc heap.
+ MethodTable *mt = NULL;
+ TADDR mtTADDR = DACGetMethodTableFromObjectPointer(CLRDATA_ADDRESS_TO_TADDR(obj), m_pTarget);
+ if (mtTADDR != NULL)
+ mt = PTR_MethodTable(mtTADDR);
+ else
+ hr = E_INVALIDARG;
+
+ BOOL bFree = FALSE;
+ if (SUCCEEDED(hr) && !DacValidateMethodTable(mt, bFree))
+ hr = E_INVALIDARG;
+
+ if (SUCCEEDED(hr))
+ {
+ // There is a case where metadata was unloaded and the AppendType call will fail.
+ // This is when an AppDomain has been unloaded but not yet collected.
+ PEFile *pPEFile = mt->GetModule()->GetFile();
+ if (pPEFile->GetNativeImage() == NULL && pPEFile->GetILimage() == NULL)
+ {
+ if (pNeeded)
+ *pNeeded = 16;
+
+ if (className)
+ wcsncpy_s(className, count, W("<Unloaded Type>"), _TRUNCATE);
+ }
+ else
+ {
+ StackSString s;
+ TypeString::AppendType(s, TypeHandle(mt), TypeString::FormatNamespace|TypeString::FormatFullInst);
+ const wchar_t *val = s.GetUnicode();
+
+ if (pNeeded)
+ *pNeeded = s.GetCount() + 1;
+
+ if (className && count)
+ {
+ wcsncpy_s(className, count, val, _TRUNCATE);
+ className[count-1] = 0;
+ }
+ }
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetMethodDescFromToken(CLRDATA_ADDRESS moduleAddr, mdToken token, CLRDATA_ADDRESS *methodDesc)
+{
+ if (moduleAddr == 0 || methodDesc == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ Module* pModule = PTR_Module(TO_TADDR(moduleAddr));
+ TypeHandle th;
+ switch (TypeFromToken(token))
+ {
+ case mdtFieldDef:
+ *methodDesc = HOST_CDADDR(pModule->LookupFieldDef(token));
+ break;
+ case mdtMethodDef:
+ *methodDesc = HOST_CDADDR(pModule->LookupMethodDef(token));
+ break;
+ case mdtTypeDef:
+ th = pModule->LookupTypeDef(token);
+ *methodDesc = th.AsTAddr();
+ break;
+ case mdtTypeRef:
+ th = pModule->LookupTypeRef(token);
+ *methodDesc = th.AsTAddr();
+ break;
+ default:
+ hr = E_INVALIDARG;
+ break;
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::TraverseModuleMap(ModuleMapType mmt, CLRDATA_ADDRESS moduleAddr, MODULEMAPTRAVERSE pCallback, LPVOID token)
+{
+ if (moduleAddr == 0)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ Module* pModule = PTR_Module(TO_TADDR(moduleAddr));
+
+ // We want to traverse these two tables, passing callback information
+ switch (mmt)
+ {
+ case TYPEDEFTOMETHODTABLE:
+ {
+ LookupMap<PTR_MethodTable>::Iterator typeIter(&pModule->m_TypeDefToMethodTableMap);
+ for (int i = 0; typeIter.Next(); i++)
+ {
+ if (typeIter.GetElement())
+ {
+ MethodTable* pMT = typeIter.GetElement();
+ (pCallback)(i,PTR_HOST_TO_TADDR(pMT), token);
+ }
+ }
+ }
+ break;
+ case TYPEREFTOMETHODTABLE:
+ {
+ LookupMap<PTR_TypeRef>::Iterator typeIter(&pModule->m_TypeRefToMethodTableMap);
+ for (int i = 0; typeIter.Next(); i++)
+ {
+ if (typeIter.GetElement())
+ {
+ MethodTable* pMT = TypeHandle::FromTAddr(dac_cast<TADDR>(typeIter.GetElement())).GetMethodTable();
+ (pCallback)(i,PTR_HOST_TO_TADDR(pMT), token);
+ }
+ }
+ }
+ break;
+ default:
+ hr = E_INVALIDARG;
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetModule(CLRDATA_ADDRESS addr, IXCLRDataModule **mod)
+{
+ if (addr == 0 || mod == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ Module* pModule = PTR_Module(TO_TADDR(addr));
+ *mod = new ClrDataModule(this, pModule);
+ SOSDacLeave();
+
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetModuleData(CLRDATA_ADDRESS addr, struct DacpModuleData *ModuleData)
+{
+ if (addr == 0 || ModuleData == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ Module* pModule = PTR_Module(TO_TADDR(addr));
+
+ ZeroMemory(ModuleData,sizeof(DacpModuleData));
+ ModuleData->Address = addr;
+ ModuleData->File = HOST_CDADDR(pModule->GetFile());
+ COUNT_T metadataSize = 0;
+ if (pModule->GetFile()->HasNativeImage())
+ {
+ ModuleData->ilBase = (CLRDATA_ADDRESS)PTR_TO_TADDR(pModule->GetFile()->GetLoadedNative()->GetBase());
+ }
+ else
+ if (!pModule->GetFile()->IsDynamic())
+ {
+ ModuleData->ilBase = (CLRDATA_ADDRESS)(ULONG_PTR) pModule->GetFile()->GetIJWBase();
+ }
+
+ ModuleData->metadataStart = (CLRDATA_ADDRESS)dac_cast<TADDR>(pModule->GetFile()->GetLoadedMetadata(&metadataSize));
+ ModuleData->metadataSize = (SIZE_T) metadataSize;
+
+ ModuleData->bIsReflection = pModule->IsReflection();
+ ModuleData->bIsPEFile = pModule->IsPEFile();
+ ModuleData->Assembly = HOST_CDADDR(pModule->GetAssembly());
+ ModuleData->dwModuleID = pModule->GetModuleID();
+ ModuleData->dwModuleIndex = pModule->GetModuleIndex().m_dwIndex;
+ ModuleData->dwTransientFlags = pModule->m_dwTransientFlags;
+
+ EX_TRY
+ {
+ //
+ // In minidump's case, these data structure is not avaiable.
+ //
+ ModuleData->TypeDefToMethodTableMap = PTR_CDADDR(pModule->m_TypeDefToMethodTableMap.pTable);
+ ModuleData->TypeRefToMethodTableMap = PTR_CDADDR(pModule->m_TypeRefToMethodTableMap.pTable);
+ ModuleData->MethodDefToDescMap = PTR_CDADDR(pModule->m_MethodDefToDescMap.pTable);
+ ModuleData->FieldDefToDescMap = PTR_CDADDR(pModule->m_FieldDefToDescMap.pTable);
+ ModuleData->MemberRefToDescMap = NULL;
+ ModuleData->FileReferencesMap = PTR_CDADDR(pModule->m_FileReferencesMap.pTable);
+ ModuleData->ManifestModuleReferencesMap = PTR_CDADDR(pModule->m_ManifestModuleReferencesMap.pTable);
+
+#ifdef FEATURE_MIXEDMODE // IJW
+ ModuleData->pThunkHeap = HOST_CDADDR(pModule->m_pThunkHeap);
+#endif // FEATURE_MIXEDMODE // IJW
+ }
+ EX_CATCH
+ {
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetILForModule(CLRDATA_ADDRESS moduleAddr, DWORD rva, CLRDATA_ADDRESS *il)
+{
+ if (moduleAddr == 0 || il == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ Module* pModule = PTR_Module(TO_TADDR(moduleAddr));
+ *il = (TADDR)(CLRDATA_ADDRESS)pModule->GetIL(rva);
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetMethodTableData(CLRDATA_ADDRESS mt, struct DacpMethodTableData *MTData)
+{
+ if (mt == 0 || MTData == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ MethodTable* pMT = PTR_MethodTable(TO_TADDR(mt));
+ BOOL bIsFree = FALSE;
+ if (!DacValidateMethodTable(pMT, bIsFree))
+ {
+ hr = E_INVALIDARG;
+ }
+ else
+ {
+ ZeroMemory(MTData,sizeof(DacpMethodTableData));
+ MTData->BaseSize = pMT->GetBaseSize();
+ if(pMT->IsString())
+ MTData->BaseSize -= sizeof(WCHAR);
+ MTData->ComponentSize = (DWORD)pMT->GetComponentSize();
+ MTData->bIsFree = bIsFree;
+ if(!bIsFree)
+ {
+ MTData->Module = HOST_CDADDR(pMT->GetModule());
+ MTData->Class = HOST_CDADDR(pMT->GetClass());
+ MTData->ParentMethodTable = HOST_CDADDR(pMT->GetParentMethodTable());;
+ MTData->wNumInterfaces = pMT->GetNumInterfaces();
+ MTData->wNumMethods = pMT->GetNumMethods();
+ MTData->wNumVtableSlots = pMT->GetNumVtableSlots();
+ MTData->wNumVirtuals = pMT->GetNumVirtuals();
+ MTData->cl = pMT->GetCl();
+ MTData->dwAttrClass = pMT->GetAttrClass();
+ MTData->bContainsPointers = pMT->ContainsPointers();
+ MTData->bIsShared = (pMT->IsDomainNeutral() ? TRUE : FALSE); // flags & enum_flag_DomainNeutral
+ MTData->bIsDynamic = pMT->IsDynamicStatics();
+ }
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetMethodTableName(CLRDATA_ADDRESS mt, unsigned int count, __out_z __inout_ecount(count) wchar_t *mtName, unsigned int *pNeeded)
+{
+ if (mt == 0)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ MethodTable *pMT = PTR_MethodTable(TO_TADDR(mt));
+ BOOL free = FALSE;
+
+ if (mt == HOST_CDADDR(g_pFreeObjectMethodTable))
+ {
+ if (pNeeded)
+ *pNeeded = 5;
+
+ if (mtName && count)
+ wcsncpy_s(mtName, count, W("Free"), _TRUNCATE);
+ }
+ else if (!DacValidateMethodTable(pMT, free))
+ {
+ hr = E_INVALIDARG;
+ }
+ else
+ {
+ // There is a case where metadata was unloaded and the AppendType call will fail.
+ // This is when an AppDomain has been unloaded but not yet collected.
+ PEFile *pPEFile = pMT->GetModule()->GetFile();
+ if (pPEFile->GetNativeImage() == NULL && pPEFile->GetILimage() == NULL)
+ {
+ if (pNeeded)
+ *pNeeded = 16;
+
+ if (mtName)
+ wcsncpy_s(mtName, count, W("<Unloaded Type>"), _TRUNCATE);
+ }
+ else
+ {
+ StackSString s;
+#ifdef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+ EX_TRY
+ {
+#endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+
+ TypeString::AppendType(s, TypeHandle(pMT), TypeString::FormatNamespace|TypeString::FormatFullInst);
+
+#ifdef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+ }
+ EX_CATCH
+ {
+ if (!MdCacheGetEEName(dac_cast<TADDR>(pMT), s))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+#endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS
+
+ if (s.IsEmpty())
+ {
+ hr = E_OUTOFMEMORY;
+ }
+ else
+ {
+ const wchar_t *val = s.GetUnicode();
+
+ if (pNeeded)
+ *pNeeded = s.GetCount() + 1;
+
+ if (mtName && count)
+ {
+ wcsncpy_s(mtName, count, val, _TRUNCATE);
+ mtName[count-1] = 0;
+ }
+ }
+ }
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetFieldDescData(CLRDATA_ADDRESS addr, struct DacpFieldDescData *FieldDescData)
+{
+ if (addr == 0 || FieldDescData == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+ FieldDesc* pFieldDesc = PTR_FieldDesc(TO_TADDR(addr));
+ FieldDescData->Type = pFieldDesc->GetFieldType();
+ FieldDescData->sigType = FieldDescData->Type;
+
+ EX_TRY
+ {
+ // minidump case, we do not have the field's type's type handle!
+ // Strike should be able to form name based on the metadata token in
+ // the field desc. Find type is using look up map which is huge. We cannot
+ // drag in this data structure in minidump's case.
+ //
+ TypeHandle th = pFieldDesc->LookupFieldTypeHandle();
+ MethodTable *pMt = th.GetMethodTable();
+ if (pMt)
+ {
+ FieldDescData->MTOfType = HOST_CDADDR(th.GetMethodTable());
+ }
+ else
+ {
+ FieldDescData->MTOfType = NULL;
+ }
+ }
+ EX_CATCH
+ {
+ FieldDescData->MTOfType = NULL;
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ // TODO: This is not currently useful, I need to get the module of the
+ // type definition not that of the field description.
+
+ // TODO: Is there an easier way to get this information?
+ // I'm getting the typeDef of a (possibly unloaded) type.
+ MetaSig tSig(pFieldDesc);
+ tSig.NextArg();
+ SigPointer sp1 = tSig.GetArgProps();
+ CorElementType et;
+ hr = sp1.GetElemType(&et); // throw away the value, we just need to walk past.
+
+ if (SUCCEEDED(hr))
+ {
+ if (et == ELEMENT_TYPE_CLASS || et == ELEMENT_TYPE_VALUETYPE) // any other follows token?
+ {
+ hr = sp1.GetToken(&(FieldDescData->TokenOfType));
+ }
+ else
+ {
+ // There is no encoded token of field type
+ FieldDescData->TokenOfType = mdTypeDefNil;
+ if (FieldDescData->MTOfType == NULL)
+ {
+ // If there is no encoded token (that is, it is primitive type) and no MethodTable for it, remember the
+ // element_type from signature
+ //
+ FieldDescData->sigType = et;
+ }
+ }
+ }
+
+ FieldDescData->ModuleOfType = HOST_CDADDR(pFieldDesc->GetModule());
+ FieldDescData->mb = pFieldDesc->GetMemberDef();
+ FieldDescData->MTOfEnclosingClass = HOST_CDADDR(pFieldDesc->GetApproxEnclosingMethodTable());
+ FieldDescData->dwOffset = pFieldDesc->GetOffset();
+ FieldDescData->bIsThreadLocal = pFieldDesc->IsThreadStatic();
+#ifdef FEATURE_REMOTING
+ FieldDescData->bIsContextLocal = pFieldDesc->IsContextStatic();;
+#else
+ FieldDescData->bIsContextLocal = FALSE;
+#endif
+ FieldDescData->bIsStatic = pFieldDesc->IsStatic();
+ FieldDescData->NextField = HOST_CDADDR(PTR_FieldDesc(PTR_HOST_TO_TADDR(pFieldDesc) + sizeof(FieldDesc)));
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetMethodTableFieldData(CLRDATA_ADDRESS mt, struct DacpMethodTableFieldData *data)
+{
+ if (mt == 0 || data == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ MethodTable* pMT = PTR_MethodTable(TO_TADDR(mt));
+ BOOL bIsFree = FALSE;
+ if (!pMT || !DacValidateMethodTable(pMT, bIsFree))
+ {
+ hr = E_INVALIDARG;
+ }
+ else
+ {
+ data->wNumInstanceFields = pMT->GetNumInstanceFields();
+ data->wNumStaticFields = pMT->GetNumStaticFields();
+ data->wNumThreadStaticFields = pMT->GetNumThreadStaticFields();
+
+ data->FirstField = PTR_TO_TADDR(pMT->GetClass()->GetFieldDescList());
+
+#ifdef FEATURE_REMOTING
+ BOOL hasContextStatics = pMT->HasContextStatics();
+
+ data->wContextStaticsSize = (hasContextStatics) ? pMT->GetContextStaticsSize() : 0;
+ _ASSERTE(!hasContextStatics || FitsIn<WORD>(pMT->GetContextStaticsOffset()));
+ data->wContextStaticOffset = (hasContextStatics) ? static_cast<WORD>(pMT->GetContextStaticsOffset()) : 0;
+#else
+ data->wContextStaticsSize = 0;
+ data->wContextStaticOffset = 0;
+#endif
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetMethodTableTransparencyData(CLRDATA_ADDRESS mt, struct DacpMethodTableTransparencyData *pTransparencyData)
+{
+ if (mt == 0 || pTransparencyData == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ MethodTable *pMT = PTR_MethodTable(TO_TADDR(mt));
+ BOOL bIsFree = FALSE;
+ if (!DacValidateMethodTable(pMT, bIsFree))
+ {
+ hr = E_INVALIDARG;
+ }
+ else
+ {
+ ZeroMemory(pTransparencyData, sizeof(DacpMethodTableTransparencyData));
+
+ EEClass * pClass = pMT->GetClass();
+ if (pClass->HasCriticalTransparentInfo())
+ {
+ pTransparencyData->bHasCriticalTransparentInfo = pClass->HasCriticalTransparentInfo();
+ pTransparencyData->bIsCritical = pClass->IsCritical() || pClass->IsAllCritical();
+ pTransparencyData->bIsTreatAsSafe = pClass->IsTreatAsSafe();
+ }
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetMethodTableForEEClass(CLRDATA_ADDRESS eeClass, CLRDATA_ADDRESS *value)
+{
+ if (eeClass == 0 || value == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ EEClass * pClass = PTR_EEClass(TO_TADDR(eeClass));
+ if (!DacValidateEEClass(pClass))
+ {
+ hr = E_INVALIDARG;
+ }
+ else
+ {
+ *value = HOST_CDADDR(pClass->GetMethodTable());
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetFrameName(CLRDATA_ADDRESS vtable, unsigned int count, __out_z __inout_ecount(count) wchar_t *frameName, unsigned int *pNeeded)
+{
+ if (vtable == 0)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ PWSTR pszName = DacGetVtNameW(CLRDATA_ADDRESS_TO_TADDR(vtable));
+ if (pszName == NULL)
+ {
+ hr = E_INVALIDARG;
+ }
+ else
+ {
+ // Turn from bytes to wide characters
+ unsigned int len = (unsigned int)wcslen(pszName);
+
+ if (frameName)
+ {
+ wcsncpy_s(frameName, count, pszName, _TRUNCATE);
+
+ if (pNeeded)
+ {
+ if (count < len)
+ *pNeeded = count - 1;
+ else
+ *pNeeded = len;
+ }
+ }
+ else if (pNeeded)
+ {
+ *pNeeded = len + 1;
+ }
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetPEFileName(CLRDATA_ADDRESS addr, unsigned int count, __out_z __inout_ecount(count) wchar_t *fileName, unsigned int *pNeeded)
+{
+ if (addr == 0 || (fileName == NULL && pNeeded == NULL) || (fileName != NULL && count == 0))
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+ PEFile* pPEFile = PTR_PEFile(TO_TADDR(addr));
+
+ // Turn from bytes to wide characters
+ if (!pPEFile->GetPath().IsEmpty())
+ {
+ if (!pPEFile->GetPath().DacGetUnicode(count, fileName, pNeeded))
+ hr = E_FAIL;
+ }
+ else if (!pPEFile->IsDynamic())
+ {
+ PEAssembly *pAssembly = pPEFile->GetAssembly();
+ StackSString displayName;
+ pAssembly->GetDisplayName(displayName, 0);
+
+ if (displayName.IsEmpty())
+ {
+ if (fileName)
+ fileName[0] = 0;
+
+ if (pNeeded)
+ *pNeeded = 1;
+ }
+ else
+ {
+ unsigned int len = displayName.GetCount()+1;
+
+ if (fileName)
+ {
+ wcsncpy_s(fileName, count, displayName.GetUnicode(), _TRUNCATE);
+
+ if (count < len)
+ len = count;
+ }
+
+ if (pNeeded)
+ *pNeeded = len;
+ }
+ }
+ else
+ {
+ if (fileName && count)
+ fileName[0] = 0;
+
+ if (pNeeded)
+ *pNeeded = 1;
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetPEFileBase(CLRDATA_ADDRESS addr, CLRDATA_ADDRESS *base)
+{
+ if (addr == 0 || base == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ PEFile* pPEFile = PTR_PEFile(TO_TADDR(addr));
+
+ // More fields later?
+ if (pPEFile->HasNativeImage())
+ *base = TO_CDADDR(PTR_TO_TADDR(pPEFile->GetLoadedNative()->GetBase()));
+ else if (!pPEFile->IsDynamic())
+ *base = TO_CDADDR(pPEFile->GetIJWBase());
+ else
+ *base = NULL;
+
+ SOSDacLeave();
+ return hr;
+}
+
+DWORD DACGetNumComponents(TADDR addr, ICorDebugDataTarget* target)
+{
+ // For an object pointer, this attempts to read the number of
+ // array components.
+ addr+=sizeof(size_t);
+ ULONG32 returned = 0;
+ DWORD Value = NULL;
+ HRESULT hr = target->ReadVirtual(addr, (PBYTE)&Value, sizeof(DWORD), &returned);
+
+ if ((hr != S_OK) || (returned != sizeof(DWORD)))
+ {
+ return 0;
+ }
+ return Value;
+}
+
+HRESULT
+ClrDataAccess::GetObjectData(CLRDATA_ADDRESS addr, struct DacpObjectData *objectData)
+{
+ if (addr == 0 || objectData == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ ZeroMemory (objectData, sizeof(DacpObjectData));
+ TADDR mtTADDR = DACGetMethodTableFromObjectPointer(CLRDATA_ADDRESS_TO_TADDR(addr),m_pTarget);
+ if (mtTADDR==NULL)
+ hr = E_INVALIDARG;
+
+ BOOL bFree = FALSE;
+ MethodTable *mt = NULL;
+ if (SUCCEEDED(hr))
+ {
+ mt = PTR_MethodTable(mtTADDR);
+ if (!DacValidateMethodTable(mt, bFree))
+ hr = E_INVALIDARG;
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ objectData->MethodTable = HOST_CDADDR(mt);
+ objectData->Size = mt->GetBaseSize();
+ if (mt->GetComponentSize())
+ {
+ objectData->Size += (DACGetNumComponents(CLRDATA_ADDRESS_TO_TADDR(addr),m_pTarget) * mt->GetComponentSize());
+ objectData->dwComponentSize = mt->GetComponentSize();
+ }
+
+ if (bFree)
+ {
+ objectData->ObjectType = OBJ_FREE;
+ }
+ else
+ {
+ if (objectData->MethodTable == HOST_CDADDR(g_pStringClass))
+ {
+ objectData->ObjectType = OBJ_STRING;
+ }
+ else if (objectData->MethodTable == HOST_CDADDR(g_pObjectClass))
+ {
+ objectData->ObjectType = OBJ_OBJECT;
+ }
+ else if (mt->IsArray())
+ {
+ objectData->ObjectType = OBJ_ARRAY;
+
+ // For now, go ahead and instantiate array classes.
+ // TODO: avoid instantiating even object Arrays in the host.
+ // NOTE: This code is carefully written to deal with MethodTable fields
+ // in the array object having the mark bit set (because we may
+ // be in mark phase when this function is called).
+ ArrayBase *pArrayObj = PTR_ArrayBase(TO_TADDR(addr));
+ objectData->ElementType = mt->GetArrayElementType();
+
+ TypeHandle thElem = mt->GetApproxArrayElementTypeHandle();
+
+ TypeHandle thCur = thElem;
+ while (thCur.IsTypeDesc())
+ thCur = thCur.AsArray()->GetArrayElementTypeHandle();
+
+ TADDR mtCurTADDR = thCur.AsTAddr();
+ if (!DacValidateMethodTable(PTR_MethodTable(mtCurTADDR), bFree))
+ {
+ hr = E_INVALIDARG;
+ }
+ else
+ {
+ objectData->ElementTypeHandle = (CLRDATA_ADDRESS)(thElem.AsTAddr());
+ objectData->dwRank = mt->GetRank();
+ objectData->dwNumComponents = pArrayObj->GetNumComponents ();
+ objectData->ArrayDataPtr = PTR_CDADDR(pArrayObj->GetDataPtr (TRUE));
+ objectData->ArrayBoundsPtr = HOST_CDADDR(pArrayObj->GetBoundsPtr());
+ objectData->ArrayLowerBoundsPtr = HOST_CDADDR(pArrayObj->GetLowerBoundsPtr());
+ }
+ }
+ else
+ {
+ objectData->ObjectType = OBJ_OTHER;
+ }
+ }
+ }
+
+#ifdef FEATURE_COMINTEROP
+ if (SUCCEEDED(hr))
+ {
+ EX_TRY_ALLOW_DATATARGET_MISSING_MEMORY
+ {
+ PTR_SyncBlock pSyncBlk = DACGetSyncBlockFromObjectPointer(CLRDATA_ADDRESS_TO_TADDR(addr), m_pTarget);
+ if (pSyncBlk != NULL)
+ {
+ // see if we have an RCW and/or CCW associated with this object
+ PTR_InteropSyncBlockInfo pInfo = pSyncBlk->GetInteropInfoNoCreate();
+ if (pInfo != NULL)
+ {
+ objectData->RCW = TO_CDADDR(pInfo->DacGetRawRCW());
+ objectData->CCW = HOST_CDADDR(pInfo->GetCCW());
+ }
+ }
+ }
+ EX_END_CATCH_ALLOW_DATATARGET_MISSING_MEMORY;
+ }
+#endif // FEATURE_COMINTEROP
+
+ SOSDacLeave();
+
+ return hr;
+}
+
+HRESULT ClrDataAccess::GetAppDomainList(unsigned int count, CLRDATA_ADDRESS values[], unsigned int *fetched)
+{
+ SOSDacEnter();
+
+ AppDomainIterator ai(FALSE);
+ unsigned int i = 0;
+ while (ai.Next() && (i < count))
+ {
+ if (values)
+ values[i] = HOST_CDADDR(ai.GetDomain());
+ i++;
+ }
+
+ if (fetched)
+ *fetched = i;
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetAppDomainStoreData(struct DacpAppDomainStoreData *adsData)
+{
+ SOSDacEnter();
+
+ adsData->systemDomain = HOST_CDADDR(SystemDomain::System());
+ adsData->sharedDomain = HOST_CDADDR(SharedDomain::GetDomain());
+
+ // Get an accurate count of appdomains.
+ adsData->DomainCount = 0;
+ AppDomainIterator ai(FALSE);
+ while (ai.Next())
+ adsData->DomainCount++;
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetAppDomainData(CLRDATA_ADDRESS addr, struct DacpAppDomainData *appdomainData)
+{
+ SOSDacEnter();
+
+ if (addr == 0)
+ {
+ hr = E_INVALIDARG;
+ }
+ else
+ {
+ PTR_BaseDomain pBaseDomain = PTR_BaseDomain(TO_TADDR(addr));
+
+ ZeroMemory(appdomainData, sizeof(DacpAppDomainData));
+ appdomainData->AppDomainPtr = PTR_CDADDR(pBaseDomain);
+ PTR_LoaderAllocator pLoaderAllocator = pBaseDomain->GetLoaderAllocator();
+ appdomainData->pHighFrequencyHeap = HOST_CDADDR(pLoaderAllocator->GetHighFrequencyHeap());
+ appdomainData->pLowFrequencyHeap = HOST_CDADDR(pLoaderAllocator->GetLowFrequencyHeap());
+ appdomainData->pStubHeap = HOST_CDADDR(pLoaderAllocator->GetStubHeap());
+ appdomainData->appDomainStage = STAGE_OPEN;
+
+ if (pBaseDomain->IsSharedDomain())
+ {
+ #ifdef FEATURE_LOADER_OPTIMIZATION
+ SharedDomain::SharedAssemblyIterator i;
+ while (i.Next())
+ {
+ appdomainData->AssemblyCount++;
+ }
+ #endif // FEATURE_LOADER_OPTIMIZATION
+ }
+ else if (pBaseDomain->IsAppDomain())
+ {
+ AppDomain * pAppDomain = pBaseDomain->AsAppDomain();
+ appdomainData->DomainLocalBlock = appdomainData->AppDomainPtr +
+ offsetof(AppDomain, m_sDomainLocalBlock);
+ appdomainData->pDomainLocalModules = PTR_CDADDR(pAppDomain->m_sDomainLocalBlock.m_pModuleSlots);
+
+ appdomainData->dwId = pAppDomain->GetId().m_dwId;
+ appdomainData->appDomainStage = (DacpAppDomainDataStage)pAppDomain->m_Stage.Load();
+ if (pAppDomain->IsActive())
+ {
+ // The assembly list is not valid in a closed appdomain.
+ AppDomain::AssemblyIterator i = pAppDomain->IterateAssembliesEx((AssemblyIterationFlags)(
+ kIncludeLoading | kIncludeLoaded | kIncludeExecution));
+ CollectibleAssemblyHolder<DomainAssembly *> pDomainAssembly;
+
+ while (i.Next(pDomainAssembly.This()))
+ {
+ if (pDomainAssembly->IsLoaded())
+ {
+ appdomainData->AssemblyCount++;
+ }
+ }
+
+ AppDomain::FailedAssemblyIterator j = pAppDomain->IterateFailedAssembliesEx();
+ while (j.Next())
+ {
+ appdomainData->FailedAssemblyCount++;
+ }
+ }
+#ifndef FEATURE_PAL
+ // MiniDumpNormal doesn't guarantee to dump the SecurityDescriptor, let it fail.
+ EX_TRY
+ {
+ appdomainData->AppSecDesc = HOST_CDADDR(pAppDomain->GetSecurityDescriptor());
+ }
+ EX_CATCH
+ {
+ HRESULT hrExc = GET_EXCEPTION()->GetHR();
+ if (hrExc != HRESULT_FROM_WIN32(ERROR_READ_FAULT)
+ && hrExc != CORDBG_E_READVIRTUAL_FAILURE)
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+#endif // FEATURE_PAL
+ }
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetFailedAssemblyData(CLRDATA_ADDRESS assembly, unsigned int *pContext, HRESULT *pResult)
+{
+ if (assembly == NULL || (pContext == NULL && pResult == NULL))
+ {
+ return E_INVALIDARG;
+ }
+
+ SOSDacEnter();
+
+ FailedAssembly* pAssembly = PTR_FailedAssembly(TO_TADDR(assembly));
+ if (!pAssembly)
+ {
+ hr = E_INVALIDARG;
+ }
+ else
+ {
+#ifdef FEATURE_FUSION
+ if (pContext)
+ *pContext = pAssembly->context;
+#endif
+ if (pResult)
+ *pResult = pAssembly->error;
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetFailedAssemblyLocation(CLRDATA_ADDRESS assembly, unsigned int count,
+ __out_z __inout_ecount(count) wchar_t *location, unsigned int *pNeeded)
+{
+ if (assembly == NULL || (location == NULL && pNeeded == NULL) || (location != NULL && count == 0))
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+ FailedAssembly* pAssembly = PTR_FailedAssembly(TO_TADDR(assembly));
+
+ // Turn from bytes to wide characters
+ if (!pAssembly->location.IsEmpty())
+ {
+ if (!pAssembly->location.DacGetUnicode(count, location, pNeeded))
+ {
+ hr = E_FAIL;
+ }
+ }
+ else
+ {
+ if (pNeeded)
+ *pNeeded = 1;
+
+ if (location)
+ location[0] = 0;
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetFailedAssemblyDisplayName(CLRDATA_ADDRESS assembly, unsigned int count, __out_z __inout_ecount(count) wchar_t *name, unsigned int *pNeeded)
+{
+ if (assembly == NULL || (name == NULL && pNeeded == NULL) || (name != NULL && count == 0))
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+ FailedAssembly* pAssembly = PTR_FailedAssembly(TO_TADDR(assembly));
+
+ if (!pAssembly->displayName.IsEmpty())
+ {
+ if (!pAssembly->displayName.DacGetUnicode(count, name, pNeeded))
+ {
+ hr = E_FAIL;
+ }
+ }
+ else
+ {
+ if (pNeeded)
+ *pNeeded = 1;
+
+ if (name)
+ name[0] = 0;
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+
+HRESULT
+ClrDataAccess::GetAssemblyList(CLRDATA_ADDRESS addr, int count, CLRDATA_ADDRESS values[], int *pNeeded)
+{
+ if (addr == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ BaseDomain* pBaseDomain = PTR_BaseDomain(TO_TADDR(addr));
+
+ int n=0;
+ if (pBaseDomain->IsSharedDomain())
+ {
+#ifdef FEATURE_LOADER_OPTIMIZATION
+ SharedDomain::SharedAssemblyIterator i;
+ if (values)
+ {
+ while (i.Next() && n < count)
+ values[n++] = HOST_CDADDR(i.GetAssembly());
+ }
+ else
+ {
+ while (i.Next())
+ n++;
+ }
+
+ if (pNeeded)
+ *pNeeded = n;
+#else
+ hr = E_UNEXPECTED;
+#endif
+ }
+ else if (pBaseDomain->IsAppDomain())
+ {
+ AppDomain::AssemblyIterator i = pBaseDomain->AsAppDomain()->IterateAssembliesEx(
+ (AssemblyIterationFlags)(kIncludeLoading | kIncludeLoaded | kIncludeExecution));
+ CollectibleAssemblyHolder<DomainAssembly *> pDomainAssembly;
+
+ if (values)
+ {
+ while (i.Next(pDomainAssembly.This()) && (n < count))
+ {
+ if (pDomainAssembly->IsLoaded())
+ {
+ CollectibleAssemblyHolder<Assembly *> pAssembly = pDomainAssembly->GetAssembly();
+ // Note: DAC doesn't need to keep the assembly alive - see code:CollectibleAssemblyHolder#CAH_DAC
+ values[n++] = HOST_CDADDR(pAssembly.Extract());
+ }
+ }
+ }
+ else
+ {
+ while (i.Next(pDomainAssembly.This()))
+ if (pDomainAssembly->IsLoaded())
+ n++;
+ }
+
+ if (pNeeded)
+ *pNeeded = n;
+ }
+ else
+ {
+ // The only other type of BaseDomain is the SystemDomain, and we shouldn't be asking
+ // for the assemblies in it.
+ _ASSERTE(false);
+ hr = E_INVALIDARG;
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetFailedAssemblyList(CLRDATA_ADDRESS appDomain, int count,
+ CLRDATA_ADDRESS values[], unsigned int *pNeeded)
+{
+ if ((appDomain == NULL) || (values == NULL && pNeeded == NULL))
+ {
+ return E_INVALIDARG;
+ }
+
+ SOSDacEnter();
+ AppDomain* pAppDomain = PTR_AppDomain(TO_TADDR(appDomain));
+
+ int n=0;
+ AppDomain::FailedAssemblyIterator i = pAppDomain->IterateFailedAssembliesEx();
+ while (i.Next() && n<=count)
+ {
+ if (values)
+ values[n] = HOST_CDADDR(i.GetFailedAssembly());
+
+ n++;
+ }
+
+ if (pNeeded)
+ *pNeeded = n;
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetAppDomainName(CLRDATA_ADDRESS addr, unsigned int count, __out_z __inout_ecount(count) wchar_t *name, unsigned int *pNeeded)
+{
+ SOSDacEnter();
+
+ PTR_BaseDomain pBaseDomain = PTR_BaseDomain(TO_TADDR(addr));
+ if (!pBaseDomain->IsAppDomain())
+ {
+ // Shared domain and SystemDomain don't have this field.
+ if (pNeeded)
+ *pNeeded = 1;
+ if (name)
+ name[0] = 0;
+ }
+ else
+ {
+ AppDomain* pAppDomain = pBaseDomain->AsAppDomain();
+
+ if (!pAppDomain->m_friendlyName.IsEmpty())
+ {
+ if (!pAppDomain->m_friendlyName.DacGetUnicode(count, name, pNeeded))
+ {
+ hr = E_FAIL;
+ }
+ }
+ else
+ {
+ if (pNeeded)
+ *pNeeded = 1;
+ if (name)
+ name[0] = 0;
+
+ hr = S_OK;
+ }
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetApplicationBase(CLRDATA_ADDRESS appDomain, int count,
+ __out_z __inout_ecount(count) wchar_t *base, unsigned int *pNeeded)
+{
+ if (appDomain == NULL || (base == NULL && pNeeded == NULL) || (base != NULL && count == 0))
+ {
+ return E_INVALIDARG;
+ }
+
+ SOSDacEnter();
+ AppDomain* pAppDomain = PTR_AppDomain(TO_TADDR(appDomain));
+
+ // Turn from bytes to wide characters
+ if ((PTR_BaseDomain(pAppDomain) == PTR_BaseDomain(SharedDomain::GetDomain())) ||
+ (PTR_BaseDomain(pAppDomain) == PTR_BaseDomain(SystemDomain::System())))
+ {
+ // Shared domain and SystemDomain don't have this field.
+ if (base)
+ base[0] = 0;
+
+ if (pNeeded)
+ *pNeeded = 1;
+ }
+
+ if (!pAppDomain->m_applicationBase.IsEmpty())
+ {
+ if (!pAppDomain->m_applicationBase.
+ DacGetUnicode(count, base, pNeeded))
+ {
+ hr = E_FAIL;
+ }
+ }
+ else
+ {
+ if (base)
+ base[0] = 0;
+
+ if (pNeeded)
+ *pNeeded = 1;
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetPrivateBinPaths(CLRDATA_ADDRESS appDomain, int count,
+ __out_z __inout_ecount(count) wchar_t *paths, unsigned int *pNeeded)
+{
+ if (appDomain == NULL || (paths == NULL && pNeeded == NULL) || (paths != NULL && count == 0))
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+ AppDomain* pAppDomain = PTR_AppDomain(TO_TADDR(appDomain));
+
+ // Turn from bytes to wide characters
+ if ((PTR_BaseDomain(pAppDomain) == PTR_BaseDomain(SharedDomain::GetDomain())) ||
+ (PTR_BaseDomain(pAppDomain) == PTR_BaseDomain(SystemDomain::System())))
+ {
+ // Shared domain and SystemDomain don't have this field.
+ if (pNeeded)
+ *pNeeded = 1;
+
+ if (paths)
+ paths[0] = 0;
+
+ hr = S_OK;
+ }
+
+ if (!pAppDomain->m_privateBinPaths.IsEmpty())
+ {
+ if (!pAppDomain->m_privateBinPaths.DacGetUnicode(count, paths, pNeeded))
+ {
+ hr = E_FAIL;
+ }
+ }
+ else
+ {
+ if (paths)
+ paths[0] = 0;
+
+ if (pNeeded)
+ *pNeeded = 1;
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetAppDomainConfigFile(CLRDATA_ADDRESS appDomain, int count,
+ __out_z __inout_ecount(count) wchar_t *configFile, unsigned int *pNeeded)
+{
+ if (appDomain == NULL || (configFile == NULL && pNeeded == NULL) || (configFile != NULL && count == 0))
+ {
+ return E_INVALIDARG;
+ }
+
+ SOSDacEnter();
+ AppDomain* pAppDomain = PTR_AppDomain(TO_TADDR(appDomain));
+
+ // Turn from bytes to wide characters
+
+ if ((PTR_BaseDomain(pAppDomain) == PTR_BaseDomain(SharedDomain::GetDomain())) ||
+ (PTR_BaseDomain(pAppDomain) == PTR_BaseDomain(SystemDomain::System())))
+ {
+ // Shared domain and SystemDomain don't have this field.
+ if (configFile)
+ configFile[0] = 0;
+
+ if (pNeeded)
+ *pNeeded = 1;
+ }
+
+ if (!pAppDomain->m_configFile.IsEmpty())
+ {
+ if (!pAppDomain->m_configFile.DacGetUnicode(count, configFile, pNeeded))
+ {
+ hr = E_FAIL;
+ }
+ }
+ else
+ {
+ if (configFile)
+ configFile[0] = 0;
+
+ if (pNeeded)
+ *pNeeded = 1;
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetAssemblyData(CLRDATA_ADDRESS cdBaseDomainPtr, CLRDATA_ADDRESS assembly, struct DacpAssemblyData *assemblyData)
+{
+ if (assembly == NULL && cdBaseDomainPtr == NULL)
+ {
+ return E_INVALIDARG;
+ }
+
+ SOSDacEnter();
+
+ Assembly* pAssembly = PTR_Assembly(TO_TADDR(assembly));
+
+ // Make sure conditionally-assigned fields like AssemblySecDesc, LoadContext, etc. are zeroed
+ ZeroMemory(assemblyData, sizeof(DacpAssemblyData));
+
+ // If the specified BaseDomain is an AppDomain, get a pointer to it
+ AppDomain * pDomain = NULL;
+ if (cdBaseDomainPtr != NULL)
+ {
+ assemblyData->BaseDomainPtr = cdBaseDomainPtr;
+ PTR_BaseDomain baseDomain = PTR_BaseDomain(TO_TADDR(cdBaseDomainPtr));
+ if( baseDomain->IsAppDomain() )
+ pDomain = baseDomain->AsAppDomain();
+ }
+
+ assemblyData->AssemblyPtr = HOST_CDADDR(pAssembly);
+ assemblyData->ClassLoader = HOST_CDADDR(pAssembly->GetLoader());
+ assemblyData->ParentDomain = HOST_CDADDR(pAssembly->GetDomain());
+ if (pDomain != NULL)
+ assemblyData->AssemblySecDesc = HOST_CDADDR(pAssembly->GetSecurityDescriptor(pDomain));
+ assemblyData->isDynamic = pAssembly->IsDynamic();
+ assemblyData->ModuleCount = 0;
+ assemblyData->isDomainNeutral = pAssembly->IsDomainNeutral();
+
+ if (pAssembly->GetManifestFile())
+ {
+#ifdef FEATURE_FUSION
+ assemblyData->LoadContext = pAssembly->GetManifestFile()->GetLoadContext();
+ assemblyData->dwLocationFlags = pAssembly->GetManifestFile()->GetLocationFlags();
+#endif
+
+ }
+
+ ModuleIterator mi = pAssembly->IterateModules();
+ while (mi.Next())
+ {
+ assemblyData->ModuleCount++;
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetAssemblyName(CLRDATA_ADDRESS assembly, unsigned int count, __out_z __inout_ecount(count) wchar_t *name, unsigned int *pNeeded)
+{
+ SOSDacEnter();
+ Assembly* pAssembly = PTR_Assembly(TO_TADDR(assembly));
+
+ if (name)
+ name[0] = 0;
+
+ if (!pAssembly->GetManifestFile()->GetPath().IsEmpty())
+ {
+ if (!pAssembly->GetManifestFile()->GetPath().DacGetUnicode(count, name, pNeeded))
+ hr = E_FAIL;
+ else if (name)
+ name[count-1] = 0;
+ }
+ else if (!pAssembly->GetManifestFile()->IsDynamic())
+ {
+ StackSString displayName;
+ pAssembly->GetManifestFile()->GetDisplayName(displayName, 0);
+
+ const wchar_t *val = displayName.GetUnicode();
+
+ if (pNeeded)
+ *pNeeded = displayName.GetCount() + 1;
+
+ if (name && count)
+ {
+ wcsncpy_s(name, count, val, _TRUNCATE);
+ name[count-1] = 0;
+ }
+ }
+ else
+ {
+ hr = E_FAIL;
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetAssemblyLocation(CLRDATA_ADDRESS assembly, int count, __out_z __inout_ecount(count) wchar_t *location, unsigned int *pNeeded)
+{
+ if ((assembly == NULL) || (location == NULL && pNeeded == NULL) || (location != NULL && count == 0))
+ {
+ return E_INVALIDARG;
+ }
+
+ SOSDacEnter();
+
+ Assembly* pAssembly = PTR_Assembly(TO_TADDR(assembly));
+
+ // Turn from bytes to wide characters
+ if (!pAssembly->GetManifestFile()->GetPath().IsEmpty())
+ {
+ if (!pAssembly->GetManifestFile()->GetPath().
+ DacGetUnicode(count, location, pNeeded))
+ {
+ hr = E_FAIL;
+ }
+ }
+ else
+ {
+ if (location)
+ location[0] = 0;
+
+ if (pNeeded)
+ *pNeeded = 1;
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetAssemblyModuleList(CLRDATA_ADDRESS assembly, unsigned int count, CLRDATA_ADDRESS modules[], unsigned int *pNeeded)
+{
+ if (assembly == 0)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ Assembly* pAssembly = PTR_Assembly(TO_TADDR(assembly));
+ ModuleIterator mi = pAssembly->IterateModules();
+ unsigned int n = 0;
+ if (modules)
+ {
+ while (mi.Next() && n < count)
+ modules[n++] = HOST_CDADDR(mi.GetModule());
+ }
+ else
+ {
+ while (mi.Next())
+ n++;
+ }
+
+ if (pNeeded)
+ *pNeeded = n;
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetGCHeapDetails(CLRDATA_ADDRESS heap, struct DacpGcHeapDetails *details)
+{
+ if (heap == 0 || details == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ // doesn't make sense to call this on WKS mode
+ if (!GCHeap::IsServerHeap())
+ hr = E_INVALIDARG;
+ else
+#ifdef FEATURE_SVR_GC
+ hr = ServerGCHeapDetails(heap, details);
+#else
+ hr = E_NOTIMPL;
+#endif
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetGCHeapStaticData(struct DacpGcHeapDetails *detailsData)
+{
+ if (detailsData == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ detailsData->lowest_address = PTR_CDADDR(g_lowest_address);
+ detailsData->highest_address = PTR_CDADDR(g_highest_address);
+ detailsData->card_table = PTR_CDADDR(g_card_table);
+
+ detailsData->heapAddr = NULL;
+
+ detailsData->alloc_allocated = PTR_CDADDR(WKS::gc_heap::alloc_allocated);
+ detailsData->ephemeral_heap_segment = PTR_CDADDR(WKS::gc_heap::ephemeral_heap_segment);
+
+#ifdef BACKGROUND_GC
+ detailsData->mark_array = PTR_CDADDR(WKS::gc_heap::mark_array);
+ detailsData->current_c_gc_state = (CLRDATA_ADDRESS)(ULONG_PTR)WKS::gc_heap::current_c_gc_state;
+ detailsData->next_sweep_obj = PTR_CDADDR(WKS::gc_heap::next_sweep_obj);
+ detailsData->saved_sweep_ephemeral_seg = PTR_CDADDR(WKS::gc_heap::saved_sweep_ephemeral_seg);
+ detailsData->saved_sweep_ephemeral_start = PTR_CDADDR(WKS::gc_heap::saved_sweep_ephemeral_start);
+ detailsData->background_saved_lowest_address = PTR_CDADDR(WKS::gc_heap::background_saved_lowest_address);
+ detailsData->background_saved_highest_address = PTR_CDADDR(WKS::gc_heap::background_saved_highest_address);
+#endif //BACKGROUND_GC
+
+ for (int i=0;i<NUMBERGENERATIONS;i++)
+ {
+ detailsData->generation_table[i].start_segment = (CLRDATA_ADDRESS)dac_cast<TADDR>(WKS::generation_table[i].start_segment);
+ detailsData->generation_table[i].allocation_start = (CLRDATA_ADDRESS)(ULONG_PTR) WKS::generation_table[i].allocation_start;
+ detailsData->generation_table[i].allocContextPtr = (CLRDATA_ADDRESS)(ULONG_PTR) WKS::generation_table[i].allocation_context.alloc_ptr;
+ detailsData->generation_table[i].allocContextLimit = (CLRDATA_ADDRESS)(ULONG_PTR) WKS::generation_table[i].allocation_context.alloc_limit;
+ }
+
+ TADDR pFillPointerArray = TO_TADDR(WKS::gc_heap::finalize_queue.GetAddr()) + offsetof(WKS::CFinalize,m_FillPointers);
+ for(int i=0;i<(NUMBERGENERATIONS+WKS::CFinalize::ExtraSegCount);i++)
+ {
+ ULONG32 returned = 0;
+ size_t pValue;
+ hr = m_pTarget->ReadVirtual(pFillPointerArray+(i*sizeof(size_t)), (PBYTE)&pValue, sizeof(size_t), &returned);
+ if (SUCCEEDED(hr))
+ {
+ if (returned == sizeof(size_t))
+ detailsData->finalization_fill_pointers[i] = (CLRDATA_ADDRESS) pValue;
+ else
+ hr = E_FAIL;
+ }
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetHeapSegmentData(CLRDATA_ADDRESS seg, struct DacpHeapSegmentData *heapSegment)
+{
+ if (seg == 0 || heapSegment == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ if (GCHeap::IsServerHeap())
+ {
+#if !defined(FEATURE_SVR_GC)
+ _ASSERTE(0);
+#else // !defined(FEATURE_SVR_GC)
+ hr = GetServerHeapData(seg, heapSegment);
+#endif //!defined(FEATURE_SVR_GC)
+ }
+ else
+ {
+ WKS::heap_segment *pSegment = __DPtr<WKS::heap_segment>(TO_TADDR(seg));
+ if (!pSegment)
+ {
+ hr = E_INVALIDARG;
+ }
+ else
+ {
+ heapSegment->segmentAddr = seg;
+ heapSegment->allocated = (CLRDATA_ADDRESS)(ULONG_PTR) pSegment->allocated;
+ heapSegment->committed = (CLRDATA_ADDRESS)(ULONG_PTR) pSegment->committed;
+ heapSegment->reserved = (CLRDATA_ADDRESS)(ULONG_PTR) pSegment->reserved;
+ heapSegment->used = (CLRDATA_ADDRESS)(ULONG_PTR) pSegment->used;
+ heapSegment->mem = (CLRDATA_ADDRESS)(ULONG_PTR) pSegment->mem;
+ heapSegment->next = (CLRDATA_ADDRESS)dac_cast<TADDR>(pSegment->next);
+ heapSegment->flags = pSegment->flags;
+ heapSegment->gc_heap = NULL;
+ heapSegment->background_allocated = (CLRDATA_ADDRESS)(ULONG_PTR)pSegment->background_allocated;
+ }
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetGCHeapList(unsigned int count, CLRDATA_ADDRESS heaps[], unsigned int *pNeeded)
+{
+ SOSDacEnter();
+
+ // make sure we called this in appropriate circumstances (i.e., we have multiple heaps)
+ if (GCHeap::IsServerHeap())
+ {
+#if !defined(FEATURE_SVR_GC)
+ _ASSERTE(0);
+#else // !defined(FEATURE_SVR_GC)
+ int heapCount = GCHeapCount();
+ if (pNeeded)
+ *pNeeded = heapCount;
+
+ if (heaps)
+ {
+ // get the heap locations
+ if (count == heapCount)
+ hr = GetServerHeaps(heaps, m_pTarget);
+ else
+ hr = E_INVALIDARG;
+ }
+#endif // !defined(FEATURE_SVR_GC)
+ }
+ else
+ {
+ hr = E_FAIL; // doesn't make sense to call this on WKS mode
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetGCHeapData(struct DacpGcHeapData *gcheapData)
+{
+ if (gcheapData == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ // Now get the heap type. The first data member of the GCHeap class is the GC_HEAP_TYPE, which has
+ // three possible values:
+ // GC_HEAP_INVALID = 0,
+ // GC_HEAP_WKS = 1,
+ // GC_HEAP_SVR = 2
+
+ TADDR gcHeapLocation = g_pGCHeap.GetAddrRaw (); // get the starting address of the global GCHeap instance
+ size_t gcHeapValue = 0; // this will hold the heap type
+ ULONG32 returned = 0;
+
+ // @todo Microsoft: we should probably be capturing the HRESULT from ReadVirtual. We could
+ // provide a more informative error message. E_FAIL is a wretchedly vague thing to return.
+ hr = m_pTarget->ReadVirtual(gcHeapLocation, (PBYTE)&gcHeapValue, sizeof(gcHeapValue), &returned);
+
+ //@todo Microsoft: We have an enumerated type, we probably should use the symbolic name
+ // we have GC_HEAP_INVALID if gcHeapValue == 0, so we're done
+ if (SUCCEEDED(hr) && ((returned != sizeof(gcHeapValue)) || (gcHeapValue == 0)))
+ hr = E_FAIL;
+
+ if (SUCCEEDED(hr))
+ {
+ // Now we can get other important information about the heap
+ gcheapData->g_max_generation = GCHeap::GetMaxGeneration();
+ gcheapData->bServerMode = GCHeap::IsServerHeap();
+ gcheapData->bGcStructuresValid = GCScan::GetGcRuntimeStructuresValid();
+ if (GCHeap::IsServerHeap())
+ {
+#if !defined (FEATURE_SVR_GC)
+ _ASSERTE(0);
+ gcheapData->HeapCount = 1;
+#else // !defined (FEATURE_SVR_GC)
+ gcheapData->HeapCount = GCHeapCount();
+#endif // !defined (FEATURE_SVR_GC)
+ }
+ else
+ {
+ gcheapData->HeapCount = 1;
+ }
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetOOMStaticData(struct DacpOomData *oomData)
+{
+ if (oomData == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ memset(oomData, 0, sizeof(DacpOomData));
+
+ if (!GCHeap::IsServerHeap())
+ {
+ oom_history* pOOMInfo = &(WKS::gc_heap::oom_info);
+ oomData->reason = pOOMInfo->reason;
+ oomData->alloc_size = pOOMInfo->alloc_size;
+ oomData->available_pagefile_mb = pOOMInfo->available_pagefile_mb;
+ oomData->gc_index = pOOMInfo->gc_index;
+ oomData->fgm = pOOMInfo->fgm;
+ oomData->size = pOOMInfo->size;
+ oomData->loh_p = pOOMInfo->loh_p;
+ }
+ else
+ {
+ hr = E_FAIL;
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetOOMData(CLRDATA_ADDRESS oomAddr, struct DacpOomData *data)
+{
+ if (oomAddr == 0 || data == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+ memset(data, 0, sizeof(DacpOomData));
+
+ if (!GCHeap::IsServerHeap())
+ hr = E_FAIL; // doesn't make sense to call this on WKS mode
+
+#ifdef FEATURE_SVR_GC
+ else
+ hr = ServerOomData(oomAddr, data);
+#else
+ _ASSERTE_MSG(false, "IsServerHeap returned true but FEATURE_SVR_GC not defined");
+ hr = E_NOTIMPL;
+#endif //FEATURE_SVR_GC
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetGCGlobalMechanisms(size_t* globalMechanisms)
+{
+#ifdef GC_CONFIG_DRIVEN
+ if (globalMechanisms == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+ memset(globalMechanisms, 0, (sizeof(size_t) * MAX_GLOBAL_GC_MECHANISMS_COUNT));
+
+ for (int i = 0; i < MAX_GLOBAL_GC_MECHANISMS_COUNT; i++)
+ {
+ globalMechanisms[i] = gc_global_mechanisms[i];
+ }
+
+ SOSDacLeave();
+ return hr;
+#else
+ return E_NOTIMPL;
+#endif //GC_CONFIG_DRIVEN
+}
+
+HRESULT
+ClrDataAccess::GetGCInterestingInfoStaticData(struct DacpGCInterestingInfoData *data)
+{
+#ifdef GC_CONFIG_DRIVEN
+ if (data == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+ memset(data, 0, sizeof(DacpGCInterestingInfoData));
+
+ if (!GCHeap::IsServerHeap())
+ {
+ for (int i = 0; i < NUM_GC_DATA_POINTS; i++)
+ data->interestingDataPoints[i] = WKS::interesting_data_per_heap[i];
+ for (int i = 0; i < MAX_COMPACT_REASONS_COUNT; i++)
+ data->compactReasons[i] = WKS::compact_reasons_per_heap[i];
+ for (int i = 0; i < MAX_EXPAND_MECHANISMS_COUNT; i++)
+ data->expandMechanisms[i] = WKS::expand_mechanisms_per_heap[i];
+ for (int i = 0; i < MAX_GC_MECHANISM_BITS_COUNT; i++)
+ data->bitMechanisms[i] = WKS::interesting_mechanism_bits_per_heap[i];
+ }
+ else
+ {
+ hr = E_FAIL;
+ }
+
+ SOSDacLeave();
+ return hr;
+#else
+ return E_NOTIMPL;
+#endif //GC_CONFIG_DRIVEN
+}
+
+HRESULT
+ClrDataAccess::GetGCInterestingInfoData(CLRDATA_ADDRESS interestingInfoAddr, struct DacpGCInterestingInfoData *data)
+{
+#ifdef GC_CONFIG_DRIVEN
+ if (interestingInfoAddr == 0 || data == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+ memset(data, 0, sizeof(DacpGCInterestingInfoData));
+
+ if (!GCHeap::IsServerHeap())
+ hr = E_FAIL; // doesn't make sense to call this on WKS mode
+
+#ifdef FEATURE_SVR_GC
+ else
+ hr = ServerGCInterestingInfoData(interestingInfoAddr, data);
+#else
+ _ASSERTE_MSG(false, "IsServerHeap returned true but FEATURE_SVR_GC not defined");
+ hr = E_NOTIMPL;
+#endif //FEATURE_SVR_GC
+
+ SOSDacLeave();
+ return hr;
+#else
+ return E_NOTIMPL;
+#endif //GC_CONFIG_DRIVEN
+}
+
+HRESULT
+ClrDataAccess::GetHeapAnalyzeData(CLRDATA_ADDRESS addr, struct DacpGcHeapAnalyzeData *data)
+{
+ if (addr == 0 || data == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ if (!GCHeap::IsServerHeap())
+ hr = E_FAIL; // doesn't make sense to call this on WKS mode
+
+#ifdef FEATURE_SVR_GC
+ else
+ hr = ServerGCHeapAnalyzeData(addr, data);
+#else
+ _ASSERTE_MSG(false, "IsServerHeap returned true but FEATURE_SVR_GC not defined");
+ hr = E_NOTIMPL;
+#endif //FEATURE_SVR_GC
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetHeapAnalyzeStaticData(struct DacpGcHeapAnalyzeData *analyzeData)
+{
+ if (analyzeData == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ analyzeData->internal_root_array = PTR_CDADDR(WKS::gc_heap::internal_root_array);
+ analyzeData->internal_root_array_index = (size_t) WKS::gc_heap::internal_root_array_index;
+ analyzeData->heap_analyze_success = (BOOL) WKS::gc_heap::heap_analyze_success;
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetUsefulGlobals(struct DacpUsefulGlobalsData *globalsData)
+{
+ if (globalsData == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ PTR_ArrayTypeDesc objArray = g_pPredefinedArrayTypes[ELEMENT_TYPE_OBJECT];
+ if (objArray)
+ globalsData->ArrayMethodTable = HOST_CDADDR(objArray->GetMethodTable());
+ else
+ globalsData->ArrayMethodTable = 0;
+
+ globalsData->StringMethodTable = HOST_CDADDR(g_pStringClass);
+ globalsData->ObjectMethodTable = HOST_CDADDR(g_pObjectClass);
+ globalsData->ExceptionMethodTable = HOST_CDADDR(g_pExceptionClass);
+ globalsData->FreeMethodTable = HOST_CDADDR(g_pFreeObjectMethodTable);
+
+ SOSDacLeave();
+ return hr;
+}
+
+
+HRESULT
+ClrDataAccess::GetNestedExceptionData(CLRDATA_ADDRESS exception, CLRDATA_ADDRESS *exceptionObject, CLRDATA_ADDRESS *nextNestedException)
+{
+ if (exception == 0 || exceptionObject == NULL || nextNestedException == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+#ifdef WIN64EXCEPTIONS
+ ExceptionTracker *pExData = PTR_ExceptionTracker(TO_TADDR(exception));
+#else
+ ExInfo *pExData = PTR_ExInfo(TO_TADDR(exception));
+#endif // _WIN64
+
+ if (!pExData)
+ {
+ hr = E_INVALIDARG;
+ }
+ else
+ {
+ *exceptionObject = TO_CDADDR(*PTR_TADDR(pExData->m_hThrowable));
+ *nextNestedException = PTR_HOST_TO_TADDR(pExData->m_pPrevNestedInfo);
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+
+HRESULT
+ClrDataAccess::GetDomainLocalModuleData(CLRDATA_ADDRESS addr, struct DacpDomainLocalModuleData *pLocalModuleData)
+{
+ if (addr == 0 || pLocalModuleData == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ DomainLocalModule* pLocalModule = PTR_DomainLocalModule(TO_TADDR(addr));
+
+ pLocalModuleData->pGCStaticDataStart = TO_CDADDR(PTR_TO_TADDR(pLocalModule->GetPrecomputedGCStaticsBasePointer()));
+ pLocalModuleData->pNonGCStaticDataStart = TO_CDADDR(pLocalModule->GetPrecomputedNonGCStaticsBasePointer());
+ pLocalModuleData->pDynamicClassTable = PTR_CDADDR(pLocalModule->m_pDynamicClassTable.Load());
+ pLocalModuleData->pClassData = (TADDR) (PTR_HOST_MEMBER_TADDR(DomainLocalModule, pLocalModule, m_pDataBlob));
+
+ SOSDacLeave();
+ return hr;
+}
+
+
+HRESULT
+ClrDataAccess::GetDomainLocalModuleDataFromModule(CLRDATA_ADDRESS addr, struct DacpDomainLocalModuleData *pLocalModuleData)
+{
+ if (addr == 0 || pLocalModuleData == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ Module* pModule = PTR_Module(TO_TADDR(addr));
+ if( pModule->GetAssembly()->IsDomainNeutral() )
+ {
+ // The module is loaded domain-neutral, then we need to know the specific AppDomain in order to
+ // choose a DomainLocalModule instance. Rather than try and guess an AppDomain (eg. based on
+ // whatever the current debugger thread is in), we'll fail and force the debugger to explicitly use
+ // a specific AppDomain.
+ hr = E_INVALIDARG;
+ }
+ else
+ {
+ DomainLocalModule* pLocalModule = PTR_DomainLocalModule(pModule->GetDomainLocalModule(NULL));
+ if (!pLocalModule)
+ {
+ hr = E_INVALIDARG;
+ }
+ else
+ {
+ pLocalModuleData->pGCStaticDataStart = TO_CDADDR(PTR_TO_TADDR(pLocalModule->GetPrecomputedGCStaticsBasePointer()));
+ pLocalModuleData->pNonGCStaticDataStart = TO_CDADDR(pLocalModule->GetPrecomputedNonGCStaticsBasePointer());
+ pLocalModuleData->pDynamicClassTable = PTR_CDADDR(pLocalModule->m_pDynamicClassTable.Load());
+ pLocalModuleData->pClassData = (TADDR) (PTR_HOST_MEMBER_TADDR(DomainLocalModule, pLocalModule, m_pDataBlob));
+ }
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetDomainLocalModuleDataFromAppDomain(CLRDATA_ADDRESS appDomainAddr, int moduleID, struct DacpDomainLocalModuleData *pLocalModuleData)
+{
+ if (appDomainAddr == 0 || moduleID < 0 || pLocalModuleData == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ pLocalModuleData->appDomainAddr = appDomainAddr;
+ pLocalModuleData->ModuleID = moduleID;
+
+ AppDomain *pAppDomain = PTR_AppDomain(TO_TADDR(appDomainAddr));
+ ModuleIndex index = Module::IDToIndex(moduleID);
+ DomainLocalModule* pLocalModule = pAppDomain->GetDomainLocalBlock()->GetModuleSlot(index);
+ if (!pLocalModule)
+ {
+ hr = E_INVALIDARG;
+ }
+ else
+ {
+ pLocalModuleData->pGCStaticDataStart = TO_CDADDR(PTR_TO_TADDR(pLocalModule->GetPrecomputedGCStaticsBasePointer()));
+ pLocalModuleData->pNonGCStaticDataStart = TO_CDADDR(pLocalModule->GetPrecomputedNonGCStaticsBasePointer());
+ pLocalModuleData->pDynamicClassTable = PTR_CDADDR(pLocalModule->m_pDynamicClassTable.Load());
+ pLocalModuleData->pClassData = (TADDR) (PTR_HOST_MEMBER_TADDR(DomainLocalModule, pLocalModule, m_pDataBlob));
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+
+
+
+HRESULT
+ClrDataAccess::GetThreadLocalModuleData(CLRDATA_ADDRESS thread, unsigned int index, struct DacpThreadLocalModuleData *pLocalModuleData)
+{
+ if (pLocalModuleData == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ pLocalModuleData->threadAddr = thread;
+ pLocalModuleData->ModuleIndex = index;
+
+ PTR_Thread pThread = PTR_Thread(TO_TADDR(thread));
+ PTR_ThreadLocalBlock pLocalBlock = ThreadStatics::GetCurrentTLBIfExists(pThread, NULL);
+ if (!pLocalBlock)
+ {
+ hr = E_INVALIDARG;
+ }
+ else
+ {
+ PTR_ThreadLocalModule pLocalModule = pLocalBlock->GetTLMIfExists(ModuleIndex(index));
+ if (!pLocalModule)
+ {
+ hr = E_INVALIDARG;
+ }
+ else
+ {
+ pLocalModuleData->pGCStaticDataStart = TO_CDADDR(PTR_TO_TADDR(pLocalModule->GetPrecomputedGCStaticsBasePointer()));
+ pLocalModuleData->pNonGCStaticDataStart = TO_CDADDR(pLocalModule->GetPrecomputedNonGCStaticsBasePointer());
+ pLocalModuleData->pDynamicClassTable = PTR_CDADDR(pLocalModule->m_pDynamicClassTable);
+ pLocalModuleData->pClassData = (TADDR) (PTR_HOST_MEMBER_TADDR(ThreadLocalModule, pLocalModule, m_pDataBlob));
+ }
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+
+HRESULT ClrDataAccess::GetHandleEnum(ISOSHandleEnum **ppHandleEnum)
+{
+ unsigned int types[] = {HNDTYPE_WEAK_SHORT, HNDTYPE_WEAK_LONG, HNDTYPE_STRONG, HNDTYPE_PINNED, HNDTYPE_VARIABLE, HNDTYPE_DEPENDENT,
+ HNDTYPE_ASYNCPINNED, HNDTYPE_SIZEDREF,
+#ifdef FEATURE_COMINTEROP
+ HNDTYPE_REFCOUNTED, HNDTYPE_WEAK_WINRT
+#endif
+ };
+
+ return GetHandleEnumForTypes(types, _countof(types), ppHandleEnum);
+}
+
+HRESULT ClrDataAccess::GetHandleEnumForTypes(unsigned int types[], unsigned int count, ISOSHandleEnum **ppHandleEnum)
+{
+ if (ppHandleEnum == 0)
+ return E_POINTER;
+
+ SOSDacEnter();
+
+ DacHandleWalker *walker = new DacHandleWalker();
+
+ HRESULT hr = walker->Init(this, types, count);
+
+ if (SUCCEEDED(hr))
+ hr = walker->QueryInterface(__uuidof(ISOSHandleEnum), (void**)ppHandleEnum);
+
+ if (FAILED(hr))
+ delete walker;
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT ClrDataAccess::GetHandleEnumForGC(unsigned int gen, ISOSHandleEnum **ppHandleEnum)
+{
+ if (ppHandleEnum == 0)
+ return E_POINTER;
+
+ SOSDacEnter();
+
+ unsigned int types[] = {HNDTYPE_WEAK_SHORT, HNDTYPE_WEAK_LONG, HNDTYPE_STRONG, HNDTYPE_PINNED, HNDTYPE_VARIABLE, HNDTYPE_DEPENDENT,
+ HNDTYPE_ASYNCPINNED, HNDTYPE_SIZEDREF,
+#ifdef FEATURE_COMINTEROP
+ HNDTYPE_REFCOUNTED, HNDTYPE_WEAK_WINRT
+#endif
+ };
+
+ DacHandleWalker *walker = new DacHandleWalker();
+
+ HRESULT hr = walker->Init(this, types, _countof(types), gen);
+ if (SUCCEEDED(hr))
+ hr = walker->QueryInterface(__uuidof(ISOSHandleEnum), (void**)ppHandleEnum);
+
+ if (FAILED(hr))
+ delete walker;
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::TraverseEHInfo(CLRDATA_ADDRESS ip, DUMPEHINFO pFunc, LPVOID token)
+{
+ if (ip == 0 || pFunc == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ EECodeInfo codeInfo(TO_TADDR(ip));
+ if (!codeInfo.IsValid())
+ {
+ hr = E_INVALIDARG;
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ EH_CLAUSE_ENUMERATOR EnumState;
+ EE_ILEXCEPTION_CLAUSE EHClause;
+ unsigned EHCount;
+
+ EHCount = codeInfo.GetJitManager()->InitializeEHEnumeration(codeInfo.GetMethodToken(), &EnumState);
+ for (unsigned i = 0; i < EHCount; i++)
+ {
+ codeInfo.GetJitManager()->GetNextEHClause(&EnumState, &EHClause);
+
+ DACEHInfo deh;
+ ZeroMemory(&deh,sizeof(deh));
+
+ if (IsFault(&EHClause))
+ {
+ deh.clauseType = EHFault;
+ }
+ else if (IsFinally(&EHClause))
+ {
+ deh.clauseType = EHFinally;
+ }
+ else if (IsFilterHandler(&EHClause))
+ {
+ deh.clauseType = EHFilter;
+ deh.filterOffset = EHClause.FilterOffset;
+ }
+ else if (IsTypedHandler(&EHClause))
+ {
+ deh.clauseType = EHTyped;
+ deh.isCatchAllHandler = (&EHClause.TypeHandle == (void*)(size_t)mdTypeRefNil);
+ }
+ else
+ {
+ deh.clauseType = EHUnknown;
+ }
+
+ if (HasCachedTypeHandle(&EHClause))
+ {
+ deh.mtCatch = TO_CDADDR(&EHClause.TypeHandle);
+ }
+ else if(!IsFaultOrFinally(&EHClause))
+ {
+ // the module of the token (whether a ref or def token) is the same as the module of the method containing the EH clause
+ deh.moduleAddr = HOST_CDADDR(codeInfo.GetMethodDesc()->GetModule());
+ deh.tokCatch = EHClause.ClassToken;
+ }
+
+ deh.tryStartOffset = EHClause.TryStartPC;
+ deh.tryEndOffset = EHClause.TryEndPC;
+ deh.handlerStartOffset = EHClause.HandlerStartPC;
+ deh.handlerEndOffset = EHClause.HandlerEndPC;
+ deh.isDuplicateClause = IsDuplicateClause(&EHClause);
+
+ if (!(pFunc)(i, EHCount, &deh, token))
+ {
+ // User wants to stop the enumeration
+ hr = E_ABORT;
+ break;
+ }
+ }
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::TraverseRCWCleanupList(CLRDATA_ADDRESS cleanupListPtr, VISITRCWFORCLEANUP pFunc, LPVOID token)
+{
+#ifdef FEATURE_COMINTEROP
+ if (pFunc == 0)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+ RCWCleanupList *pList = g_pRCWCleanupList;
+
+ if (cleanupListPtr)
+ {
+ pList = PTR_RCWCleanupList(TO_TADDR(cleanupListPtr));
+ }
+
+ if (pList)
+ {
+ PTR_RCW pBucket = dac_cast<PTR_RCW>(TO_TADDR(pList->m_pFirstBucket));
+ while (pBucket != NULL)
+ {
+ PTR_RCW pRCW = pBucket;
+ Thread *pSTAThread = pRCW->GetSTAThread();
+ LPVOID pCtxCookie = pRCW->GetWrapperCtxCookie();
+ BOOL bIsFreeThreaded = pRCW->IsFreeThreaded();
+
+ while (pRCW)
+ {
+ (pFunc)(HOST_CDADDR(pRCW),(CLRDATA_ADDRESS)pCtxCookie, (CLRDATA_ADDRESS)(TADDR)pSTAThread, bIsFreeThreaded, token);
+ pRCW = pRCW->m_pNextRCW;
+ }
+ pBucket = pBucket->m_pNextCleanupBucket;
+ }
+ }
+
+ SOSDacLeave();
+ return hr;
+#else
+ return E_NOTIMPL;
+#endif // FEATURE_COMINTEROP
+}
+
+HRESULT
+ClrDataAccess::TraverseLoaderHeap(CLRDATA_ADDRESS loaderHeapAddr, VISITHEAP pFunc)
+{
+ if (loaderHeapAddr == 0 || pFunc == 0)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ LoaderHeap *pLoaderHeap = PTR_LoaderHeap(TO_TADDR(loaderHeapAddr));
+ PTR_LoaderHeapBlock block = pLoaderHeap->m_pFirstBlock;
+ while (block.IsValid())
+ {
+ TADDR addr = PTR_TO_TADDR(block->pVirtualAddress);
+ size_t size = block->dwVirtualSize;
+
+ BOOL bCurrentBlock = (block == pLoaderHeap->m_pCurBlock);
+
+ pFunc(addr,size,bCurrentBlock);
+
+ block = block->pNext;
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::TraverseVirtCallStubHeap(CLRDATA_ADDRESS pAppDomain, VCSHeapType heaptype, VISITHEAP pFunc)
+{
+ if (pAppDomain == 0)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ BaseDomain* pBaseDomain = PTR_BaseDomain(TO_TADDR(pAppDomain));
+ VirtualCallStubManager *pVcsMgr = PTR_VirtualCallStubManager((TADDR)pBaseDomain->GetLoaderAllocator()->GetVirtualCallStubManager());
+ if (!pVcsMgr)
+ {
+ hr = E_POINTER;
+ }
+ else
+ {
+ LoaderHeap *pLoaderHeap = NULL;
+ switch(heaptype)
+ {
+ case IndcellHeap:
+ pLoaderHeap = pVcsMgr->indcell_heap;
+ break;
+ case LookupHeap:
+ pLoaderHeap = pVcsMgr->lookup_heap;
+ break;
+ case ResolveHeap:
+ pLoaderHeap = pVcsMgr->resolve_heap;
+ break;
+ case DispatchHeap:
+ pLoaderHeap = pVcsMgr->dispatch_heap;
+ break;
+ case CacheEntryHeap:
+ pLoaderHeap = pVcsMgr->cache_entry_heap;
+ break;
+ default:
+ hr = E_INVALIDARG;
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ PTR_LoaderHeapBlock block = pLoaderHeap->m_pFirstBlock;
+ while (block.IsValid())
+ {
+ TADDR addr = PTR_TO_TADDR(block->pVirtualAddress);
+ size_t size = block->dwVirtualSize;
+
+ BOOL bCurrentBlock = (block == pLoaderHeap->m_pCurBlock);
+ pFunc(addr, size, bCurrentBlock);
+
+ block = block->pNext;
+ }
+ }
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+
+HRESULT
+ClrDataAccess::GetSyncBlockData(unsigned int SBNumber, struct DacpSyncBlockData *pSyncBlockData)
+{
+ if (pSyncBlockData == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ ZeroMemory(pSyncBlockData,sizeof(DacpSyncBlockData));
+ pSyncBlockData->SyncBlockCount = (SyncBlockCache::s_pSyncBlockCache->m_FreeSyncTableIndex) - 1;
+ PTR_SyncTableEntry ste = PTR_SyncTableEntry(dac_cast<TADDR>(g_pSyncTable)+(sizeof(SyncTableEntry) * SBNumber));
+ pSyncBlockData->bFree = ((dac_cast<TADDR>(ste->m_Object.Load())) & 1);
+
+ if (pSyncBlockData->bFree == FALSE)
+ {
+ pSyncBlockData->Object = (CLRDATA_ADDRESS)dac_cast<TADDR>(ste->m_Object.Load());
+
+ if (ste->m_SyncBlock != NULL)
+ {
+ SyncBlock *pBlock = PTR_SyncBlock(ste->m_SyncBlock);
+ pSyncBlockData->SyncBlockPointer = HOST_CDADDR(pBlock);
+#ifdef FEATURE_COMINTEROP
+ if (pBlock->m_pInteropInfo)
+ {
+ pSyncBlockData->COMFlags |= (pBlock->m_pInteropInfo->DacGetRawRCW() != 0) ? SYNCBLOCKDATA_COMFLAGS_RCW : 0;
+ pSyncBlockData->COMFlags |= (pBlock->m_pInteropInfo->GetCCW() != NULL) ? SYNCBLOCKDATA_COMFLAGS_CCW : 0;
+#ifdef FEATURE_COMINTEROP_UNMANAGED_ACTIVATION
+ pSyncBlockData->COMFlags |= (pBlock->m_pInteropInfo->GetComClassFactory() != NULL) ? SYNCBLOCKDATA_COMFLAGS_CF : 0;
+#endif // FEATURE_COMINTEROP_UNMANAGED_ACTIVATION
+ }
+#endif // FEATURE_COMINTEROP
+
+ pSyncBlockData->MonitorHeld = pBlock->m_Monitor.m_MonitorHeld;
+ pSyncBlockData->Recursion = pBlock->m_Monitor.m_Recursion;
+ pSyncBlockData->HoldingThread = HOST_CDADDR(pBlock->m_Monitor.m_HoldingThread);
+
+ if (pBlock->GetAppDomainIndex().m_dwIndex)
+ {
+ pSyncBlockData->appDomainPtr = PTR_HOST_TO_TADDR(
+ SystemDomain::TestGetAppDomainAtIndex(pBlock->GetAppDomainIndex()));
+ }
+
+ // TODO: Microsoft, implement the wait list
+ pSyncBlockData->AdditionalThreadCount = 0;
+
+ if (pBlock->m_Link.m_pNext != NULL)
+ {
+ PTR_SLink pLink = pBlock->m_Link.m_pNext;
+ do
+ {
+ pSyncBlockData->AdditionalThreadCount++;
+ pLink = pBlock->m_Link.m_pNext;
+ }
+ while ((pLink != NULL) &&
+ (pSyncBlockData->AdditionalThreadCount < 1000));
+ }
+ }
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetSyncBlockCleanupData(CLRDATA_ADDRESS syncBlock, struct DacpSyncBlockCleanupData *syncBlockCData)
+{
+ if (syncBlock == 0 || syncBlockCData == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ ZeroMemory (syncBlockCData, sizeof(DacpSyncBlockCleanupData));
+ SyncBlock *pBlock = NULL;
+
+ if (syncBlock == NULL && SyncBlockCache::s_pSyncBlockCache->m_pCleanupBlockList)
+ {
+ pBlock = (SyncBlock *) PTR_SyncBlock(
+ PTR_HOST_TO_TADDR(SyncBlockCache::s_pSyncBlockCache->m_pCleanupBlockList) - offsetof(SyncBlock, m_Link));
+ }
+ else
+ {
+ pBlock = PTR_SyncBlock(TO_TADDR(syncBlock));
+ }
+
+ if (pBlock)
+ {
+ syncBlockCData->SyncBlockPointer = HOST_CDADDR(pBlock);
+ if (pBlock->m_Link.m_pNext)
+ {
+ syncBlockCData->nextSyncBlock = (CLRDATA_ADDRESS)
+ (PTR_HOST_TO_TADDR(pBlock->m_Link.m_pNext) - offsetof(SyncBlock, m_Link));
+ }
+
+#ifdef FEATURE_COMINTEROP
+ if (pBlock->m_pInteropInfo->DacGetRawRCW())
+ syncBlockCData->blockRCW = (CLRDATA_ADDRESS) pBlock->m_pInteropInfo->DacGetRawRCW();
+#ifdef FEATURE_COMINTEROP_UNMANAGED_ACTIVATION
+ if (pBlock->m_pInteropInfo->GetComClassFactory())
+ syncBlockCData->blockClassFactory = (CLRDATA_ADDRESS) (TADDR) pBlock->m_pInteropInfo->GetComClassFactory();
+#endif // FEATURE_COMINTEROP_UNMANAGED_ACTIVATION
+ if (pBlock->m_pInteropInfo->GetCCW())
+ syncBlockCData->blockCCW = (CLRDATA_ADDRESS) dac_cast<TADDR>(pBlock->m_pInteropInfo->GetCCW());
+#endif // FEATURE_COMINTEROP
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT
+ClrDataAccess::GetJitHelperFunctionName(CLRDATA_ADDRESS ip, unsigned int count, __out_z __inout_ecount(count) char *name, unsigned int *pNeeded)
+{
+ SOSDacEnter();
+
+ PCSTR pszHelperName = GetJitHelperName(TO_TADDR(ip));
+ if (pszHelperName == NULL)
+ {
+ hr = E_INVALIDARG;
+ }
+ else
+ {
+ unsigned int len = (unsigned int)strlen(pszHelperName) + 1;
+
+ if (pNeeded)
+ *pNeeded = len;
+
+ if (name)
+ {
+ if (count < len)
+ hr = E_FAIL;
+ else
+ strcpy_s(name, count, pszHelperName);
+ }
+ }
+
+ SOSDacLeave();
+ return hr;
+};
+
+HRESULT
+ClrDataAccess::GetJumpThunkTarget(T_CONTEXT *ctx, CLRDATA_ADDRESS *targetIP, CLRDATA_ADDRESS *targetMD)
+{
+ if (ctx == NULL || targetIP == NULL || targetMD == NULL)
+ return E_INVALIDARG;
+
+#ifdef _TARGET_AMD64_
+ SOSDacEnter();
+
+ if (!GetAnyThunkTarget(ctx, targetIP, targetMD))
+ hr = E_FAIL;
+
+ SOSDacLeave();
+ return hr;
+#else
+ return E_FAIL;
+#endif // _WIN64
+}
+
+
+#ifdef _PREFAST_
+#pragma warning(push)
+#pragma warning(disable:21000) // Suppress PREFast warning about overly large function
+#endif
+STDMETHODIMP
+ClrDataAccess::Request(IN ULONG32 reqCode,
+ IN ULONG32 inBufferSize,
+ IN BYTE* inBuffer,
+ IN ULONG32 outBufferSize,
+ OUT BYTE* outBuffer)
+{
+ HRESULT status;
+
+ DAC_ENTER();
+
+ EX_TRY
+ {
+ switch(reqCode)
+ {
+ case CLRDATA_REQUEST_REVISION:
+ if (inBufferSize != 0 ||
+ inBuffer ||
+ outBufferSize != sizeof(ULONG32))
+ {
+ status = E_INVALIDARG;
+ }
+ else
+ {
+ *(ULONG32*)outBuffer = 9;
+ status = S_OK;
+ }
+ break;
+
+ default:
+ status = E_INVALIDARG;
+ break;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), this, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+#ifdef _PREFAST_
+#pragma warning(pop)
+#endif
+
+void
+ClrDataAccess::EnumWksGlobalMemoryRegions(CLRDataEnumMemoryFlags flags)
+{
+ SUPPORTS_DAC;
+ WKS::gc_heap::ephemeral_heap_segment.EnumMem();
+ WKS::gc_heap::alloc_allocated.EnumMem();
+ WKS::gc_heap::finalize_queue.EnumMem();
+ WKS::generation_table.EnumMem();
+ WKS::gc_heap::oom_info.EnumMem();
+
+ if (WKS::generation_table.IsValid())
+ {
+ // enumerating the generations from max (which is normally gen2) to max+1 gives you
+ // the segment list for all the normal segements plus the large heap segment (max+1)
+ // this is the convention in the GC so it is repeated here
+ for (ULONG i = GCHeap::GetMaxGeneration(); i <= GCHeap::GetMaxGeneration()+1; i++)
+ {
+ __DPtr<WKS::heap_segment> seg = dac_cast<TADDR>(WKS::generation_table[i].start_segment);
+ while (seg)
+ {
+ DacEnumMemoryRegion(dac_cast<TADDR>(seg), sizeof(WKS::heap_segment));
+
+ seg = __DPtr<WKS::heap_segment>(dac_cast<TADDR>(seg->next));
+ }
+ }
+ }
+}
+
+HRESULT
+ClrDataAccess::GetClrWatsonBuckets(CLRDATA_ADDRESS thread, void *pGenericModeBlock)
+{
+#ifdef FEATURE_PAL
+ // This API is not available under FEATURE_PAL
+ return E_FAIL;
+#else // FEATURE_PAL
+ if (thread == 0 || pGenericModeBlock == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+
+ Thread * pThread = PTR_Thread(TO_TADDR(thread));
+ hr = GetClrWatsonBucketsWorker(pThread, reinterpret_cast<GenericModeBlock *>(pGenericModeBlock));
+
+ SOSDacLeave();
+ return hr;
+#endif // FEATURE_PAL
+}
+
+#ifndef FEATURE_PAL
+
+HRESULT ClrDataAccess::GetClrWatsonBucketsWorker(Thread * pThread, GenericModeBlock * pGM)
+{
+ if ((pThread == NULL) || (pGM == NULL))
+ {
+ return E_INVALIDARG;
+ }
+
+ // By default, there are no buckets
+ PTR_VOID pBuckets = NULL;
+
+ // Get the handle to the throwble
+ OBJECTHANDLE ohThrowable = pThread->GetThrowableAsHandle();
+ if (ohThrowable != NULL)
+ {
+ // Get the object from handle and check if the throwable is preallocated or not
+ OBJECTREF oThrowable = ObjectFromHandle(ohThrowable);
+ if (oThrowable != NULL)
+ {
+ // Does the throwable have buckets?
+ if (((EXCEPTIONREF)oThrowable)->AreWatsonBucketsPresent())
+ {
+ // Get the watson buckets from the throwable for non-preallocated
+ // exceptions
+ U1ARRAYREF refWatsonBucketArray = ((EXCEPTIONREF)oThrowable)->GetWatsonBucketReference();
+ pBuckets = dac_cast<PTR_VOID>(refWatsonBucketArray->GetDataPtr());
+ }
+ else
+ {
+ // This is a preallocated exception object - check if the UE Watson bucket tracker
+ // has any bucket details
+ pBuckets = pThread->GetExceptionState()->GetUEWatsonBucketTracker()->RetrieveWatsonBuckets();
+ if (pBuckets == NULL)
+ {
+ // Since the UE watson bucket tracker does not have them, look up the current
+ // exception tracker
+ if (pThread->GetExceptionState()->GetCurrentExceptionTracker() != NULL)
+ {
+ pBuckets = pThread->GetExceptionState()->GetCurrentExceptionTracker()->GetWatsonBucketTracker()->RetrieveWatsonBuckets();
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ // Debuger.Break doesn't have a throwable, but saves Watson buckets in EHWatsonBucketTracker.
+ pBuckets = pThread->GetExceptionState()->GetUEWatsonBucketTracker()->RetrieveWatsonBuckets();
+ }
+
+ // If pBuckets is non-null, it is the address of a Watson GenericModeBlock in the target process.
+ if (pBuckets != NULL)
+ {
+ ULONG32 returned = 0;
+ HRESULT hr = m_pTarget->ReadVirtual(dac_cast<TADDR>(pBuckets), reinterpret_cast<BYTE *>(pGM), sizeof(*pGM), &returned);
+ if (FAILED(hr))
+ {
+ hr = CORDBG_E_READVIRTUAL_FAILURE;
+ }
+ if (SUCCEEDED(hr) && (returned != sizeof(*pGM)))
+ {
+ hr = HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY);
+ }
+ return hr;
+ }
+ else
+ {
+ // Buckets are not available
+ return S_FALSE;
+ }
+}
+
+#endif // FEATURE_PAL
+
+HRESULT ClrDataAccess::GetTLSIndex(ULONG *pIndex)
+{
+ if (pIndex == NULL)
+ return E_INVALIDARG;
+
+ SOSDacEnter();
+ if (CExecutionEngine::GetTlsIndex() == TLS_OUT_OF_INDEXES)
+ {
+ *pIndex = 0;
+ hr = S_FALSE;
+ }
+ else
+ {
+ *pIndex = CExecutionEngine::GetTlsIndex();
+ }
+
+ SOSDacLeave();
+ return hr;
+}
+
+HRESULT ClrDataAccess::GetDacModuleHandle(HMODULE *phModule)
+{
+ if(phModule == NULL)
+ return E_INVALIDARG;
+ *phModule = GetModuleInst();
+ return S_OK;
+}
+
+HRESULT ClrDataAccess::GetRCWData(CLRDATA_ADDRESS addr, struct DacpRCWData *rcwData)
+{
+ if (addr == 0 || rcwData == NULL)
+ return E_INVALIDARG;
+
+#ifdef FEATURE_COMINTEROP
+ SOSDacEnter();
+
+ ZeroMemory (rcwData, sizeof(DacpRCWData));
+
+ PTR_RCW pRCW = dac_cast<PTR_RCW>(CLRDATA_ADDRESS_TO_TADDR(addr));
+
+ rcwData->identityPointer = TO_CDADDR(pRCW->m_pIdentity);
+ rcwData->unknownPointer = TO_CDADDR(pRCW->GetRawIUnknown_NoAddRef());
+ rcwData->vtablePtr = TO_CDADDR(pRCW->m_vtablePtr);
+ rcwData->creatorThread = TO_CDADDR(pRCW->m_pCreatorThread);
+ rcwData->ctxCookie = TO_CDADDR(pRCW->GetWrapperCtxCookie());
+ rcwData->refCount = pRCW->m_cbRefCount;
+
+ rcwData->isJupiterObject = pRCW->IsJupiterObject();
+ rcwData->supportsIInspectable = pRCW->SupportsIInspectable();
+ rcwData->isAggregated = pRCW->IsURTAggregated();
+ rcwData->isContained = pRCW->IsURTContained();
+ rcwData->jupiterObject = TO_CDADDR(pRCW->GetJupiterObject());
+ rcwData->isFreeThreaded = pRCW->IsFreeThreaded();
+ rcwData->isDisconnected = pRCW->IsDisconnected();
+
+ if (pRCW->m_SyncBlockIndex != 0)
+ {
+ PTR_SyncTableEntry ste = PTR_SyncTableEntry(dac_cast<TADDR>(g_pSyncTable) + (sizeof(SyncTableEntry) * pRCW->m_SyncBlockIndex));
+ rcwData->managedObject = PTR_CDADDR(ste->m_Object.Load());
+ }
+
+ // count the number of cached interface pointers
+ rcwData->interfaceCount = 0;
+ RCW::CachedInterfaceEntryIterator it = pRCW->IterateCachedInterfacePointers();
+ while (it.Next())
+ {
+ if (it.GetEntry()->m_pUnknown.Load() != NULL)
+ rcwData->interfaceCount++;
+ }
+
+ SOSDacLeave();
+ return hr;
+#else
+ return E_NOTIMPL;
+#endif
+}
+
+HRESULT ClrDataAccess::GetRCWInterfaces(CLRDATA_ADDRESS rcw, unsigned int count, struct DacpCOMInterfacePointerData interfaces[], unsigned int *pNeeded)
+{
+ if (rcw == 0)
+ return E_INVALIDARG;
+
+#ifdef FEATURE_COMINTEROP
+
+ SOSDacEnter();
+ PTR_RCW pRCW = dac_cast<PTR_RCW>(CLRDATA_ADDRESS_TO_TADDR(rcw));
+ if (interfaces == NULL)
+ {
+ if (pNeeded)
+ {
+ unsigned int c = 0;
+ RCW::CachedInterfaceEntryIterator it = pRCW->IterateCachedInterfacePointers();
+ while (it.Next())
+ {
+ if (it.GetEntry()->m_pUnknown.Load() != NULL)
+ c++;
+ }
+
+ *pNeeded = c;
+ }
+ else
+ {
+ hr = E_INVALIDARG;
+ }
+ }
+ else
+ {
+ ZeroMemory(interfaces, sizeof(DacpCOMInterfacePointerData) * count);
+
+ unsigned int itemIndex = 0;
+ RCW::CachedInterfaceEntryIterator it = pRCW->IterateCachedInterfacePointers();
+ while (it.Next())
+ {
+ InterfaceEntry *pEntry = it.GetEntry();
+ if (pEntry->m_pUnknown.Load() != NULL)
+ {
+ if (itemIndex >= count)
+ {
+ // the outBuffer is too small
+ hr = E_INVALIDARG;
+ break;
+ }
+ else
+ {
+ interfaces[itemIndex].interfacePtr = TO_CDADDR(pEntry->m_pUnknown.Load());
+ interfaces[itemIndex].methodTable = TO_CDADDR(pEntry->m_pMT.Load());
+ interfaces[itemIndex].comContext = TO_CDADDR(it.GetCtxCookie());
+ itemIndex++;
+ }
+ }
+ }
+
+ if (SUCCEEDED(hr) && pNeeded)
+ *pNeeded = itemIndex;
+ }
+
+ SOSDacLeave();
+ return hr;
+#else
+ return E_NOTIMPL;
+#endif
+}
+
+#ifdef FEATURE_COMINTEROP
+PTR_ComCallWrapper ClrDataAccess::DACGetCCWFromAddress(CLRDATA_ADDRESS addr)
+{
+ PTR_ComCallWrapper pCCW = NULL;
+
+ // first check whether the address is our COM IP
+ TADDR pPtr = CLRDATA_ADDRESS_TO_TADDR(addr);
+
+ ULONG32 returned = 0;
+ if (m_pTarget->ReadVirtual(pPtr, (PBYTE)&pPtr, sizeof(TADDR), &returned) == S_OK &&
+ returned == sizeof(TADDR))
+ {
+ // this should be the vtable pointer - dereference the 2nd slot
+ if (m_pTarget->ReadVirtual(pPtr + sizeof(PBYTE) * TEAR_OFF_SLOT, (PBYTE)&pPtr, sizeof(TADDR), &returned) == S_OK &&
+ returned == sizeof(TADDR))
+ {
+
+#ifdef DBG_TARGET_ARM
+ // clear the THUMB bit on pPtr before comparing with known vtable entry
+ pPtr &= ~THUMB_CODE;
+#endif
+
+ if (pPtr == GetEEFuncEntryPoint(TEAR_OFF_STANDARD))
+ {
+ // Points to ComCallWrapper
+ PTR_IUnknown pUnk(CLRDATA_ADDRESS_TO_TADDR(addr));
+ pCCW = ComCallWrapper::GetWrapperFromIP(pUnk);
+ }
+ else if (pPtr == GetEEFuncEntryPoint(TEAR_OFF_SIMPLE) || pPtr == GetEEFuncEntryPoint(TEAR_OFF_SIMPLE_INNER))
+ {
+ // Points to SimpleComCallWrapper
+ PTR_IUnknown pUnk(CLRDATA_ADDRESS_TO_TADDR(addr));
+ pCCW = SimpleComCallWrapper::GetWrapperFromIP(pUnk)->GetMainWrapper();
+ }
+ }
+ }
+
+ if (pCCW == NULL)
+ {
+ // no luck interpreting the address as a COM interface pointer - it must be a CCW address
+ pCCW = dac_cast<PTR_ComCallWrapper>(CLRDATA_ADDRESS_TO_TADDR(addr));
+ }
+
+ if (pCCW->IsLinked())
+ pCCW = ComCallWrapper::GetStartWrapper(pCCW);
+
+ return pCCW;
+}
+
+PTR_IUnknown ClrDataAccess::DACGetCOMIPFromCCW(PTR_ComCallWrapper pCCW, int vtableIndex)
+{
+ if (pCCW->m_rgpIPtr[vtableIndex] != NULL)
+ {
+ PTR_IUnknown pUnk = dac_cast<PTR_IUnknown>(dac_cast<TADDR>(pCCW) + offsetof(ComCallWrapper, m_rgpIPtr[vtableIndex]));
+
+ PTR_ComMethodTable pCMT = ComMethodTable::ComMethodTableFromIP(pUnk);
+ if (pCMT->IsLayoutComplete())
+ {
+ // return only fully laid out vtables
+ return pUnk;
+ }
+ }
+ return NULL;
+}
+#endif
+
+
+HRESULT ClrDataAccess::GetCCWData(CLRDATA_ADDRESS ccw, struct DacpCCWData *ccwData)
+{
+ if (ccw == 0 || ccwData == NULL)
+ return E_INVALIDARG;
+
+#ifdef FEATURE_COMINTEROP
+ SOSDacEnter();
+ ZeroMemory (ccwData, sizeof(DacpCCWData));
+
+ PTR_ComCallWrapper pCCW = DACGetCCWFromAddress(ccw);
+ PTR_SimpleComCallWrapper pSimpleCCW = pCCW->GetSimpleWrapper();
+
+ ccwData->outerIUnknown = TO_CDADDR(pSimpleCCW->m_pOuter);
+ ccwData->refCount = pSimpleCCW->GetRefCount();
+ ccwData->isNeutered = pSimpleCCW->IsNeutered();
+ ccwData->ccwAddress = TO_CDADDR(dac_cast<TADDR>(pCCW));
+
+ ccwData->jupiterRefCount = pSimpleCCW->GetJupiterRefCount();
+ ccwData->isPegged = pSimpleCCW->IsPegged();
+ ccwData->isGlobalPegged = RCWWalker::IsGlobalPeggingOn();
+ ccwData->hasStrongRef = pCCW->IsWrapperActive();
+ ccwData->handle = pCCW->GetObjectHandle();
+ ccwData->isExtendsCOMObject = pCCW->GetSimpleWrapper()->IsExtendsCOMObject();
+ ccwData->isAggregated = pCCW->GetSimpleWrapper()->IsAggregated();
+
+ if (pCCW->GetObjectHandle() != NULL)
+ ccwData->managedObject = PTR_CDADDR(ObjectFromHandle(pCCW->GetObjectHandle()));
+
+ // count the number of COM vtables
+ ccwData->interfaceCount = 0;
+ while (pCCW != NULL)
+ {
+ for (int i = 0; i < ComCallWrapper::NumVtablePtrs; i++)
+ {
+ if (DACGetCOMIPFromCCW(pCCW, i) != NULL)
+ ccwData->interfaceCount++;
+ }
+ pCCW = ComCallWrapper::GetNext(pCCW);
+ }
+
+ SOSDacLeave();
+ return hr;
+#else
+ return E_NOTIMPL;
+#endif
+}
+
+HRESULT ClrDataAccess::GetCCWInterfaces(CLRDATA_ADDRESS ccw, unsigned int count, struct DacpCOMInterfacePointerData interfaces[], unsigned int *pNeeded)
+{
+ if (ccw == 0)
+ return E_INVALIDARG;
+
+#ifdef FEATURE_COMINTEROP
+ SOSDacEnter();
+ PTR_ComCallWrapper pCCW = DACGetCCWFromAddress(ccw);
+
+ if (interfaces == NULL)
+ {
+ if (pNeeded)
+ {
+ unsigned int c = 0;
+ while (pCCW != NULL)
+ {
+ for (int i = 0; i < ComCallWrapper::NumVtablePtrs; i++)
+ if (DACGetCOMIPFromCCW(pCCW, i) != NULL)
+ c++;
+ pCCW = ComCallWrapper::GetNext(pCCW);
+ }
+
+ *pNeeded = c;
+ }
+ else
+ {
+ hr = E_INVALIDARG;
+ }
+ }
+ else
+ {
+ ZeroMemory(interfaces, sizeof(DacpCOMInterfacePointerData) * count);
+
+ PTR_ComCallWrapperTemplate pCCWTemplate = pCCW->GetSimpleWrapper()->GetComCallWrapperTemplate();
+ unsigned int itemIndex = 0;
+ unsigned int wrapperOffset = 0;
+ while (pCCW != NULL && SUCCEEDED(hr))
+ {
+ for (int i = 0; i < ComCallWrapper::NumVtablePtrs && SUCCEEDED(hr); i++)
+ {
+ PTR_IUnknown pUnk = DACGetCOMIPFromCCW(pCCW, i);
+ if (pUnk != NULL)
+ {
+ if (itemIndex >= count)
+ {
+ // the outBuffer is too small
+ hr = E_INVALIDARG;
+ break;
+ }
+
+ interfaces[itemIndex].interfacePtr = PTR_CDADDR(pUnk);
+
+ // if this is the first ComCallWrapper, the 0th vtable slots is special
+ if (wrapperOffset == 0 && i == ComCallWrapper::Slot_Basic)
+ {
+ // this is IDispatch/IUnknown
+ interfaces[itemIndex].methodTable = NULL;
+ }
+ else
+ {
+ // this slot represents the class interface or an interface implemented by the class
+ DWORD ifaceMapIndex = wrapperOffset + i - ComCallWrapper::Slot_FirstInterface;
+
+ PTR_ComMethodTable pCMT = ComMethodTable::ComMethodTableFromIP(pUnk);
+ interfaces[itemIndex].methodTable = PTR_CDADDR(pCMT->GetMethodTable());
+ }
+
+ itemIndex++;
+ }
+ }
+
+ pCCW = ComCallWrapper::GetNext(pCCW);
+ wrapperOffset += ComCallWrapper::NumVtablePtrs;
+ }
+
+ if (SUCCEEDED(hr) && pNeeded)
+ *pNeeded = itemIndex;
+ }
+
+ SOSDacLeave();
+ return hr;
+#else
+ return E_NOTIMPL;
+#endif
+}
+
+HRESULT ClrDataAccess::GetObjectExceptionData(CLRDATA_ADDRESS objAddr, struct DacpExceptionObjectData *data)
+{
+ if (data == NULL)
+ return E_POINTER;
+
+ SOSDacEnter();
+
+ PTR_ExceptionObject pObj = dac_cast<PTR_ExceptionObject>(TO_TADDR(objAddr));
+
+ data->Message = TO_CDADDR(dac_cast<TADDR>(pObj->GetMessage()));
+ data->InnerException = TO_CDADDR(dac_cast<TADDR>(pObj->GetInnerException()));
+ data->StackTrace = TO_CDADDR(dac_cast<TADDR>(pObj->GetStackTraceArrayObject()));
+ data->WatsonBuckets = TO_CDADDR(dac_cast<TADDR>(pObj->GetWatsonBucketReference()));
+ data->StackTraceString = TO_CDADDR(dac_cast<TADDR>(pObj->GetStackTraceString()));
+ data->RemoteStackTraceString = TO_CDADDR(dac_cast<TADDR>(pObj->GetRemoteStackTraceString()));
+ data->HResult = pObj->GetHResult();
+ data->XCode = pObj->GetXCode();
+
+ SOSDacLeave();
+
+ return hr;
+}
+
+HRESULT ClrDataAccess::IsRCWDCOMProxy(CLRDATA_ADDRESS rcwAddr, BOOL* isDCOMProxy)
+{
+ if (isDCOMProxy == nullptr)
+ {
+ return E_POINTER;
+ }
+
+ *isDCOMProxy = FALSE;
+
+#ifdef FEATURE_COMINTEROP
+ SOSDacEnter();
+
+ PTR_RCW pRCW = dac_cast<PTR_RCW>(CLRDATA_ADDRESS_TO_TADDR(rcwAddr));
+ *isDCOMProxy = pRCW->IsDCOMProxy();
+
+ SOSDacLeave();
+
+ return S_OK;
+#else
+ return E_NOTIMPL;
+#endif // FEATURE_COMINTEROP
+}
+
+HRESULT ClrDataAccess::GetClrNotification(CLRDATA_ADDRESS arguments[], int count, int *pNeeded)
+{
+ SOSDacEnter();
+
+ *pNeeded = MAX_CLR_NOTIFICATION_ARGS;
+
+ if (g_clrNotificationArguments[0] == NULL)
+ {
+ hr = E_FAIL;
+ }
+ else
+ {
+ for (int i = 0; i < count && i < MAX_CLR_NOTIFICATION_ARGS; i++)
+ {
+ arguments[i] = g_clrNotificationArguments[i];
+ }
+ }
+
+ SOSDacLeave();
+
+ return hr;;
+} \ No newline at end of file
diff --git a/src/debug/daccess/request_svr.cpp b/src/debug/daccess/request_svr.cpp
new file mode 100644
index 0000000000..429f30020f
--- /dev/null
+++ b/src/debug/daccess/request_svr.cpp
@@ -0,0 +1,348 @@
+// 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.
+//*****************************************************************************
+
+//
+// File: request.cpp
+//
+// CorDataAccess::Request implementation.
+//
+//*****************************************************************************
+
+#include "stdafx.h"
+#include "dacdbiinterface.h"
+#include "dacdbiimpl.h"
+
+#if defined(FEATURE_SVR_GC)
+
+#include <sigformat.h>
+#include <win32threadpool.h>
+
+#include <gceesvr.cpp>
+
+
+int GCHeapCount()
+{
+ return SVR::gc_heap::n_heaps;
+}
+
+HRESULT GetServerHeapData(CLRDATA_ADDRESS addr, DacpHeapSegmentData *pSegment)
+{
+ // get field values (target addresses) for the heap segment at addr
+ if (!addr)
+ {
+ // PREfix.
+ return E_INVALIDARG;
+ }
+
+ // marshal the segment from target to host
+ SVR::heap_segment *pHeapSegment =
+ __DPtr<SVR::heap_segment>(TO_TADDR(addr));
+
+ // initialize fields by copying from the marshaled segment (note that these are all target addresses)
+ pSegment->segmentAddr = addr;
+ pSegment->allocated = (CLRDATA_ADDRESS)(ULONG_PTR) pHeapSegment->allocated;
+ pSegment->committed = (CLRDATA_ADDRESS)(ULONG_PTR) pHeapSegment->committed;
+ pSegment->reserved = (CLRDATA_ADDRESS)(ULONG_PTR) pHeapSegment->reserved;
+ pSegment->used = (CLRDATA_ADDRESS)(ULONG_PTR) pHeapSegment->used;
+ pSegment->mem = (CLRDATA_ADDRESS)(ULONG_PTR) (pHeapSegment->mem);
+ pSegment->next = (CLRDATA_ADDRESS)dac_cast<TADDR>(pHeapSegment->next);
+ pSegment->gc_heap = (CLRDATA_ADDRESS)(ULONG_PTR) pHeapSegment->heap;
+
+ return S_OK;
+}
+
+HRESULT GetServerHeaps(CLRDATA_ADDRESS pGCHeaps[], ICorDebugDataTarget * pTarget)
+{
+ // @todo Microsoft: It would be good to have an assert here to ensure pGCHeaps is large enough to
+ // hold all the addresses. Currently we check that in the only caller, but if we were to call this from
+ // somewhere else in the future, we could have a buffer overrun.
+
+ // The runtime declares its own global array of gc heap addresses for multiple heap scenarios. We need to get
+ // its starting address. This expression is a little tricky to parse, but in DAC builds, g_heaps is
+ // a DAC global (__GlobalPtr). The __GlobalPtr<...>::GetAddr() function gets the starting address of that global, but
+ // be sure to note this is a target address. We'll use this as our source for getting our local list of
+ // heap addresses.
+ TADDR ptr = SVR::gc_heap::g_heaps.GetAddr();
+ ULONG32 bytesRead = 0;
+
+ for (int i=0;i<GCHeapCount();i++)
+ {
+
+ LPVOID pGCHeapAddr;
+
+ // read the i-th element of g_heaps into pGCHeapAddr
+ // @todo Microsoft: Again, if we capture the HRESULT from ReadVirtual, we can print a more explanatory
+ // failure message.
+ if (pTarget->ReadVirtual(ptr + i*sizeof(TADDR),
+ (PBYTE) &pGCHeapAddr, sizeof(TADDR),
+ &bytesRead) != S_OK)
+ {
+ return E_FAIL;
+ }
+ if (bytesRead != sizeof(LPVOID))
+ {
+ return E_FAIL;
+ }
+
+ // store the heap's starting address in our array.
+ pGCHeaps[i] = (CLRDATA_ADDRESS)(ULONG_PTR) pGCHeapAddr;
+ }
+ return S_OK;
+}
+
+#define PTR_CDADDR(ptr) TO_CDADDR(PTR_TO_TADDR(ptr))
+#define HOST_CDADDR(host) TO_CDADDR(PTR_HOST_TO_TADDR(host))
+
+typedef DPTR(class SVR::gc_heap) PTR_SVR_gc_heap;
+
+HRESULT ClrDataAccess::GetServerAllocData(unsigned int count, struct DacpGenerationAllocData *data, unsigned int *pNeeded)
+{
+ unsigned int heaps = (unsigned int)SVR::gc_heap::n_heaps;
+ if (pNeeded)
+ *pNeeded = heaps;
+
+ if (data)
+ {
+ if (count > heaps)
+ count = heaps;
+
+ for (int n=0;n<SVR::gc_heap::n_heaps;n++)
+ {
+ PTR_SVR_gc_heap pHeap = PTR_SVR_gc_heap(SVR::gc_heap::g_heaps[n]);
+ for (int i=0;i<NUMBERGENERATIONS;i++)
+ {
+ data[n].allocData[i].allocBytes = (CLRDATA_ADDRESS)(ULONG_PTR) pHeap->generation_table[i].allocation_context.alloc_bytes;
+ data[n].allocData[i].allocBytesLoh = (CLRDATA_ADDRESS)(ULONG_PTR) pHeap->generation_table[i].allocation_context.alloc_bytes_loh;
+ }
+ }
+ }
+
+ return S_OK;
+}
+
+HRESULT ClrDataAccess::ServerGCHeapDetails(CLRDATA_ADDRESS heapAddr, DacpGcHeapDetails *detailsData)
+{
+ if (!heapAddr)
+ {
+ // PREfix.
+ return E_INVALIDARG;
+ }
+
+ SVR::gc_heap *pHeap = PTR_SVR_gc_heap(TO_TADDR(heapAddr));
+ int i;
+
+ //get global information first
+ detailsData->heapAddr = heapAddr;
+
+ detailsData->lowest_address = PTR_CDADDR(g_lowest_address);
+ detailsData->highest_address = PTR_CDADDR(g_highest_address);
+ detailsData->card_table = PTR_CDADDR(g_card_table);
+
+ // now get information specific to this heap (server mode gives us several heaps; we're getting
+ // information about only one of them.
+ detailsData->alloc_allocated = (CLRDATA_ADDRESS)(ULONG_PTR) pHeap->alloc_allocated;
+ detailsData->ephemeral_heap_segment = (CLRDATA_ADDRESS)(ULONG_PTR) pHeap->ephemeral_heap_segment;
+
+ // get bounds for the different generations
+ for (i=0; i<NUMBERGENERATIONS; i++)
+ {
+ detailsData->generation_table[i].start_segment = (CLRDATA_ADDRESS)dac_cast<TADDR>(pHeap->generation_table[i].start_segment);
+ detailsData->generation_table[i].allocation_start = (CLRDATA_ADDRESS)(ULONG_PTR) pHeap->generation_table[i].allocation_start;
+ detailsData->generation_table[i].allocContextPtr = (CLRDATA_ADDRESS)(ULONG_PTR) pHeap->generation_table[i].allocation_context.alloc_ptr;
+ detailsData->generation_table[i].allocContextLimit = (CLRDATA_ADDRESS)(ULONG_PTR) pHeap->generation_table[i].allocation_context.alloc_limit;
+ }
+
+ // since these are all TADDRS, we have to compute the address of the m_FillPointers field explicitly
+ TADDR pFillPointerArray = dac_cast<TADDR>(pHeap->finalize_queue) + offsetof(SVR::CFinalize,m_FillPointers);
+
+ for(i=0; i<(NUMBERGENERATIONS+SVR::CFinalize::ExtraSegCount); i++)
+ {
+ ULONG32 returned = 0;
+ size_t pValue;
+ HRESULT hr = m_pTarget->ReadVirtual(pFillPointerArray+(i*sizeof(TADDR)),
+ (PBYTE)&pValue,
+ sizeof(TADDR),
+ &returned);
+ if (FAILED(hr) || (returned != sizeof(TADDR)))
+ {
+ return E_FAIL;
+ }
+
+ detailsData->finalization_fill_pointers[i] = (CLRDATA_ADDRESS) pValue;
+ }
+
+ return S_OK;
+}
+
+HRESULT
+ClrDataAccess::ServerOomData(CLRDATA_ADDRESS addr, DacpOomData *oomData)
+{
+ SVR::gc_heap *pHeap = PTR_SVR_gc_heap(TO_TADDR(addr));
+
+ oom_history* pOOMInfo = (oom_history*)((TADDR)pHeap + offsetof(SVR::gc_heap,oom_info));
+ oomData->reason = pOOMInfo->reason;
+ oomData->alloc_size = pOOMInfo->alloc_size;
+ oomData->available_pagefile_mb = pOOMInfo->available_pagefile_mb;
+ oomData->gc_index = pOOMInfo->gc_index;
+ oomData->fgm = pOOMInfo->fgm;
+ oomData->size = pOOMInfo->size;
+ oomData->loh_p = pOOMInfo->loh_p;
+
+ return S_OK;
+}
+
+HRESULT
+ClrDataAccess::ServerGCInterestingInfoData(CLRDATA_ADDRESS addr, DacpGCInterestingInfoData *interestingInfoData)
+{
+#ifdef GC_CONFIG_DRIVEN
+ SVR::gc_heap *pHeap = PTR_SVR_gc_heap(TO_TADDR(addr));
+
+ size_t* dataPoints = (size_t*)&(pHeap->interesting_data_per_heap);
+ for (int i = 0; i < NUM_GC_DATA_POINTS; i++)
+ interestingInfoData->interestingDataPoints[i] = dataPoints[i];
+ size_t* mechanisms = (size_t*)&(pHeap->compact_reasons_per_heap);
+ for (int i = 0; i < MAX_COMPACT_REASONS_COUNT; i++)
+ interestingInfoData->compactReasons[i] = mechanisms[i];
+ mechanisms = (size_t*)&(pHeap->expand_mechanisms_per_heap);
+ for (int i = 0; i < MAX_EXPAND_MECHANISMS_COUNT; i++)
+ interestingInfoData->expandMechanisms[i] = mechanisms[i];
+ mechanisms = (size_t*)&(pHeap->interesting_mechanism_bits_per_heap);
+ for (int i = 0; i < MAX_GC_MECHANISM_BITS_COUNT; i++)
+ interestingInfoData->bitMechanisms[i] = mechanisms[i];
+
+ return S_OK;
+#else
+ return E_NOTIMPL;
+#endif //GC_CONFIG_DRIVEN
+}
+
+HRESULT ClrDataAccess::ServerGCHeapAnalyzeData(CLRDATA_ADDRESS heapAddr, DacpGcHeapAnalyzeData *analyzeData)
+{
+ if (!heapAddr)
+ {
+ // PREfix.
+ return E_INVALIDARG;
+ }
+
+ SVR::gc_heap *pHeap = PTR_SVR_gc_heap(TO_TADDR(heapAddr));
+
+ analyzeData->heapAddr = heapAddr;
+ analyzeData->internal_root_array = (CLRDATA_ADDRESS)(ULONG_PTR) pHeap->internal_root_array;
+ analyzeData->internal_root_array_index = (size_t) pHeap->internal_root_array_index;
+ analyzeData->heap_analyze_success = (BOOL)pHeap->heap_analyze_success;
+
+ return S_OK;
+}
+
+void
+ClrDataAccess::EnumSvrGlobalMemoryRegions(CLRDataEnumMemoryFlags flags)
+{
+ SUPPORTS_DAC;
+ SVR::gc_heap::n_heaps.EnumMem();
+ DacEnumMemoryRegion(SVR::gc_heap::g_heaps.GetAddr(),
+ sizeof(TADDR) * SVR::gc_heap::n_heaps);
+
+ SVR::gc_heap::g_heaps.EnumMem();
+
+ for (int i=0;i<SVR::gc_heap::n_heaps;i++)
+ {
+ PTR_SVR_gc_heap pHeap = PTR_SVR_gc_heap(SVR::gc_heap::g_heaps[i]);
+
+ DacEnumMemoryRegion(dac_cast<TADDR>(pHeap), sizeof(SVR::gc_heap));
+ DacEnumMemoryRegion(dac_cast<TADDR>(pHeap->finalize_queue), sizeof(SVR::CFinalize));
+
+ // enumerating the generations from max (which is normally gen2) to max+1 gives you
+ // the segment list for all the normal segements plus the large heap segment (max+1)
+ // this is the convention in the GC so it is repeated here
+ for (ULONG i = GCHeap::GetMaxGeneration(); i <= GCHeap::GetMaxGeneration()+1; i++)
+ {
+ __DPtr<SVR::heap_segment> seg = dac_cast<TADDR>(pHeap->generation_table[i].start_segment);
+ while (seg)
+ {
+ DacEnumMemoryRegion(PTR_HOST_TO_TADDR(seg), sizeof(SVR::heap_segment));
+
+ seg = __DPtr<SVR::heap_segment>(dac_cast<TADDR>(seg->next));
+ }
+ }
+ }
+}
+
+DWORD DacGetNumHeaps()
+{
+ if (GCHeap::IsServerHeap())
+ return (DWORD)SVR::gc_heap::n_heaps;
+
+ // workstation gc
+ return 1;
+}
+
+HRESULT DacHeapWalker::InitHeapDataSvr(HeapData *&pHeaps, size_t &pCount)
+{
+ // Scrape basic heap details
+ int heaps = SVR::gc_heap::n_heaps;
+ pCount = heaps;
+ pHeaps = new (nothrow) HeapData[heaps];
+ if (pHeaps == NULL)
+ return E_OUTOFMEMORY;
+
+ for (int i = 0; i < heaps; ++i)
+ {
+ // Basic heap info.
+ PTR_SVR_gc_heap heap = PTR_SVR_gc_heap(SVR::gc_heap::g_heaps[i]);
+
+ pHeaps[i].YoungestGenPtr = (CORDB_ADDRESS)heap->generation_table[0].allocation_context.alloc_ptr;
+ pHeaps[i].YoungestGenLimit = (CORDB_ADDRESS)heap->generation_table[0].allocation_context.alloc_limit;
+
+ pHeaps[i].Gen0Start = (CORDB_ADDRESS)heap->generation_table[0].allocation_start;
+ pHeaps[i].Gen0End = (CORDB_ADDRESS)heap->alloc_allocated;
+ pHeaps[i].Gen1Start = (CORDB_ADDRESS)heap->generation_table[1].allocation_start;
+
+ // Segments
+ int count = GetSegmentCount(heap->generation_table[NUMBERGENERATIONS-1].start_segment);
+ count += GetSegmentCount(heap->generation_table[NUMBERGENERATIONS-2].start_segment);
+
+ pHeaps[i].SegmentCount = count;
+ pHeaps[i].Segments = new (nothrow) SegmentData[count];
+ if (pHeaps[i].Segments == NULL)
+ return E_OUTOFMEMORY;
+
+ // Small object heap segments
+ SVR::PTR_heap_segment seg = heap->generation_table[NUMBERGENERATIONS-2].start_segment;
+ int j = 0;
+ for (; seg && (j < count); ++j)
+ {
+ pHeaps[i].Segments[j].Start = (CORDB_ADDRESS)seg->mem;
+ if (seg.GetAddr() == TO_TADDR(heap->ephemeral_heap_segment))
+ {
+ pHeaps[i].Segments[j].End = (CORDB_ADDRESS)heap->alloc_allocated;
+ pHeaps[i].EphemeralSegment = j;
+ pHeaps[i].Segments[j].Generation = 1;
+ }
+ else
+ {
+ pHeaps[i].Segments[j].End = (CORDB_ADDRESS)seg->allocated;
+ pHeaps[i].Segments[j].Generation = 2;
+ }
+
+ seg = seg->next;
+ }
+
+
+ // Large object heap segments
+ seg = heap->generation_table[NUMBERGENERATIONS-1].start_segment;
+ for (; seg && (j < count); ++j)
+ {
+ pHeaps[i].Segments[j].Generation = 3;
+ pHeaps[i].Segments[j].Start = (CORDB_ADDRESS)seg->mem;
+ pHeaps[i].Segments[j].End = (CORDB_ADDRESS)seg->allocated;
+
+ seg = seg->next;
+ }
+ }
+
+ return S_OK;
+}
+
+#endif // defined(FEATURE_SVR_GC)
diff --git a/src/debug/daccess/stack.cpp b/src/debug/daccess/stack.cpp
new file mode 100644
index 0000000000..b235366bb5
--- /dev/null
+++ b/src/debug/daccess/stack.cpp
@@ -0,0 +1,1434 @@
+// 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.
+//*****************************************************************************
+// File: stack.cpp
+//
+
+//
+// CLRData stack walking.
+//
+//*****************************************************************************
+
+#include "stdafx.h"
+
+//----------------------------------------------------------------------------
+//
+// ClrDataStackWalk.
+//
+//----------------------------------------------------------------------------
+
+ClrDataStackWalk::ClrDataStackWalk(ClrDataAccess* dac,
+ Thread* thread,
+ ULONG32 flags)
+{
+ m_dac = dac;
+ m_dac->AddRef();
+ m_instanceAge = m_dac->m_instanceAge;
+ m_thread = thread;
+ m_walkFlags = flags;
+ m_refs = 1;
+ m_stackPrev = 0;
+
+ INDEBUG( m_framesUnwound = 0; )
+}
+
+ClrDataStackWalk::~ClrDataStackWalk(void)
+{
+ m_dac->Release();
+}
+
+STDMETHODIMP
+ClrDataStackWalk::QueryInterface(THIS_
+ IN REFIID interfaceId,
+ OUT PVOID* iface)
+{
+ if (IsEqualIID(interfaceId, IID_IUnknown) ||
+ IsEqualIID(interfaceId, __uuidof(IXCLRDataStackWalk)))
+ {
+ AddRef();
+ *iface = static_cast<IUnknown*>
+ (static_cast<IXCLRDataStackWalk*>(this));
+ return S_OK;
+ }
+ else
+ {
+ *iface = NULL;
+ return E_NOINTERFACE;
+ }
+}
+
+STDMETHODIMP_(ULONG)
+ClrDataStackWalk::AddRef(THIS)
+{
+ return InterlockedIncrement(&m_refs);
+}
+
+STDMETHODIMP_(ULONG)
+ClrDataStackWalk::Release(THIS)
+{
+ SUPPORTS_DAC_HOST_ONLY;
+ LONG newRefs = InterlockedDecrement(&m_refs);
+ if (newRefs == 0)
+ {
+ delete this;
+ }
+ return newRefs;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataStackWalk::GetContext(
+ /* [in] */ ULONG32 contextFlags,
+ /* [in] */ ULONG32 contextBufSize,
+ /* [out] */ ULONG32 *contextSize,
+ /* [size_is][out] */ BYTE contextBuf[ ])
+{
+ HRESULT status;
+
+ if (contextSize)
+ {
+ *contextSize = ContextSizeForFlags(contextFlags);
+ }
+
+ if (!CheckContextSizeForFlags(contextBufSize, contextFlags))
+ {
+ return E_INVALIDARG;
+ }
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if (!m_frameIter.IsValid())
+ {
+ status = S_FALSE;
+ }
+ else
+ {
+ *(PT_CONTEXT)contextBuf = m_context;
+ UpdateContextFromRegDisp(&m_regDisp, (PT_CONTEXT)contextBuf);
+ status = S_OK;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataStackWalk::SetContext(
+ /* [in] */ ULONG32 contextSize,
+ /* [size_is][in] */ BYTE context[ ])
+{
+ return SetContext2(m_frameIter.m_crawl.IsActiveFrame() ?
+ CLRDATA_STACK_SET_CURRENT_CONTEXT :
+ CLRDATA_STACK_SET_UNWIND_CONTEXT,
+ contextSize, context);
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataStackWalk::SetContext2(
+ /* [in] */ ULONG32 flags,
+ /* [in] */ ULONG32 contextSize,
+ /* [size_is][in] */ BYTE context[ ])
+{
+ HRESULT status;
+
+ if ((flags & ~(CLRDATA_STACK_SET_CURRENT_CONTEXT |
+ CLRDATA_STACK_SET_UNWIND_CONTEXT)) != 0 ||
+ !CheckContextSizeForBuffer(contextSize, context))
+ {
+ return E_INVALIDARG;
+ }
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ // Copy the context to local state so
+ // that its lifetime extends beyond this call.
+ m_context = *(PT_CONTEXT)context;
+ m_thread->FillRegDisplay(&m_regDisp, &m_context);
+ m_frameIter.ResetRegDisp(&m_regDisp, (flags & CLRDATA_STACK_SET_CURRENT_CONTEXT) != 0);
+ m_stackPrev = (TADDR)GetRegdisplaySP(&m_regDisp);
+ FilterFrames();
+ status = S_OK;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataStackWalk::Next(void)
+{
+ HRESULT status = E_FAIL;
+
+ INDEBUG( static const int kFrameToReturnForever = 56; )
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if (!m_frameIter.IsValid())
+ {
+ status = S_FALSE;
+ }
+ else
+#if defined(_DEBUG)
+ // m_framesUnwound is not incremented unless the special config value is set below in this function.
+ if (m_framesUnwound < kFrameToReturnForever)
+#endif // defined(_DEBUG)
+ {
+ // Default the previous stack value.
+ m_stackPrev = (TADDR)GetRegdisplaySP(&m_regDisp);
+ StackWalkAction action = m_frameIter.Next();
+ switch(action)
+ {
+ case SWA_CONTINUE:
+ // We sucessfully unwound a frame so update
+ // the previous stack pointer before going into
+ // filtering to get the amount of stack skipped
+ // by the filtering.
+ m_stackPrev = (TADDR)GetRegdisplaySP(&m_regDisp);
+ FilterFrames();
+ status = m_frameIter.IsValid() ? S_OK : S_FALSE;
+ break;
+ case SWA_ABORT:
+ status = S_FALSE;
+ break;
+ default:
+ status = E_FAIL;
+ break;
+ }
+ }
+
+#if defined(_DEBUG)
+ // Test hook: when testing on debug builds, we want an easy way to test that the target
+ // stack behaves as if it's smashed in a particular way. It would be very difficult to create
+ // a test that carefully broke the stack in a way that would force the stackwalker to report
+ // success on the same frame forever, and have that corruption be reliable over time. However, it's
+ // pretty easy for us to control the number of frames on the stack for tests that use this specific
+ // internal flag.
+ if (CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DumpGeneration_IntentionallyCorruptDataFromTarget))
+ {
+ if (m_framesUnwound >= kFrameToReturnForever)
+ {
+ status = S_OK;
+ }
+ else
+ {
+ m_framesUnwound++;
+ }
+ }
+#endif // defined(_DEBUG)
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataStackWalk::GetStackSizeSkipped(
+ /* [out] */ ULONG64 *stackSizeSkipped)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if (m_stackPrev)
+ {
+ *stackSizeSkipped =
+ (TADDR)GetRegdisplaySP(&m_regDisp) - m_stackPrev;
+ status = S_OK;
+ }
+ else
+ {
+ status = S_FALSE;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataStackWalk::GetFrameType(
+ /* [out] */ CLRDataSimpleFrameType *simpleType,
+ /* [out] */ CLRDataDetailedFrameType *detailedType)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if (m_frameIter.IsValid())
+ {
+ RawGetFrameType(simpleType, detailedType);
+ status = S_OK;
+ }
+ else
+ {
+ status = S_FALSE;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataStackWalk::GetFrame(
+ /* [out] */ IXCLRDataFrame **frame)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ ClrDataFrame* dataFrame = NULL;
+ if (!m_frameIter.IsValid())
+ {
+ status = E_INVALIDARG;
+ goto Exit;
+ }
+
+ CLRDataSimpleFrameType simpleType;
+ CLRDataDetailedFrameType detailedType;
+
+ RawGetFrameType(&simpleType, &detailedType);
+ dataFrame =
+ new (nothrow) ClrDataFrame(m_dac, simpleType, detailedType,
+ m_frameIter.m_crawl.GetAppDomain(),
+ m_frameIter.m_crawl.GetFunction());
+ if (!dataFrame)
+ {
+ status = E_OUTOFMEMORY;
+ goto Exit;
+ }
+
+ dataFrame->m_context = m_context;
+ UpdateContextFromRegDisp(&m_regDisp, &dataFrame->m_context);
+ m_thread->FillRegDisplay(&dataFrame->m_regDisp,
+ &dataFrame->m_context);
+
+ *frame = static_cast<IXCLRDataFrame*>(dataFrame);
+ status = S_OK;
+
+ Exit: ;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataStackWalk::Request(
+ /* [in] */ ULONG32 reqCode,
+ /* [in] */ ULONG32 inBufferSize,
+ /* [size_is][in] */ BYTE *inBuffer,
+ /* [in] */ ULONG32 outBufferSize,
+ /* [size_is][out] */ BYTE *outBuffer)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ switch(reqCode)
+ {
+ case CLRDATA_REQUEST_REVISION:
+ if (inBufferSize != 0 ||
+ inBuffer ||
+ outBufferSize != sizeof(ULONG32))
+ {
+ status = E_INVALIDARG;
+ }
+ else
+ {
+ *(ULONG32*)outBuffer = 1;
+ status = S_OK;
+ }
+ break;
+
+ case CLRDATA_STACK_WALK_REQUEST_SET_FIRST_FRAME:
+ // This code should be removed once the Windows debuggers stop using the old DAC API.
+ if ((inBufferSize != sizeof(ULONG32)) ||
+ (outBufferSize != 0))
+ {
+ status = E_INVALIDARG;
+ break;
+ }
+
+ m_frameIter.SetIsFirstFrame(*(ULONG32 UNALIGNED *)inBuffer != 0);
+ status = S_OK;
+ break;
+
+ case DACSTACKPRIV_REQUEST_FRAME_DATA:
+ if ((inBufferSize != 0) ||
+ (inBuffer != NULL) ||
+ (outBufferSize != sizeof(DacpFrameData)))
+ {
+ status = E_INVALIDARG;
+ break;
+ }
+ if (!m_frameIter.IsValid())
+ {
+ status = E_INVALIDARG;
+ break;
+ }
+
+ DacpFrameData* frameData;
+
+ frameData = (DacpFrameData*)outBuffer;
+ frameData->frameAddr =
+ TO_CDADDR(PTR_HOST_TO_TADDR(m_frameIter.m_crawl.GetFrame()));
+ status = S_OK;
+ break;
+
+ default:
+ status = E_INVALIDARG;
+ break;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT
+ClrDataStackWalk::Init(void)
+{
+ if (m_thread->IsUnstarted())
+ {
+ return E_FAIL;
+ }
+
+ if (m_thread->GetFilterContext())
+ {
+ m_context = *m_thread->GetFilterContext();
+ }
+ else
+ {
+ DacGetThreadContext(m_thread, &m_context);
+ }
+ m_thread->FillRegDisplay(&m_regDisp, &m_context);
+
+ m_stackPrev = (TADDR)GetRegdisplaySP(&m_regDisp);
+
+ ULONG32 iterFlags = NOTIFY_ON_NO_FRAME_TRANSITIONS;
+
+ // If the filter is only allowing method frames
+ // turn on the appropriate iterator flag.
+ if ((m_walkFlags & SIMPFRAME_ALL) ==
+ CLRDATA_SIMPFRAME_MANAGED_METHOD)
+ {
+ iterFlags |= FUNCTIONSONLY;
+ }
+
+ m_frameIter.Init(m_thread, NULL, &m_regDisp, iterFlags);
+ if (m_frameIter.GetFrameState() == StackFrameIterator::SFITER_UNINITIALIZED)
+ {
+ return E_FAIL;
+ }
+ FilterFrames();
+
+ return S_OK;
+}
+
+void
+ClrDataStackWalk::FilterFrames(void)
+{
+ //
+ // Advance to a state compatible with the
+ // current filtering flags.
+ //
+
+ while (m_frameIter.IsValid())
+ {
+ switch(m_frameIter.GetFrameState())
+ {
+ case StackFrameIterator::SFITER_FRAMELESS_METHOD:
+ if (m_walkFlags & CLRDATA_SIMPFRAME_MANAGED_METHOD)
+ {
+ return;
+ }
+ break;
+ case StackFrameIterator::SFITER_FRAME_FUNCTION:
+ case StackFrameIterator::SFITER_SKIPPED_FRAME_FUNCTION:
+ case StackFrameIterator::SFITER_NO_FRAME_TRANSITION:
+ if (m_walkFlags & CLRDATA_SIMPFRAME_RUNTIME_UNMANAGED_CODE)
+ {
+ return;
+ }
+ break;
+ default:
+ break;
+ }
+
+ m_frameIter.Next();
+ }
+}
+
+void
+ClrDataStackWalk::RawGetFrameType(
+ /* [out] */ CLRDataSimpleFrameType* simpleType,
+ /* [out] */ CLRDataDetailedFrameType* detailedType)
+{
+ if (simpleType)
+ {
+ switch(m_frameIter.GetFrameState())
+ {
+ case StackFrameIterator::SFITER_FRAMELESS_METHOD:
+ *simpleType = CLRDATA_SIMPFRAME_MANAGED_METHOD;
+ break;
+ case StackFrameIterator::SFITER_FRAME_FUNCTION:
+ case StackFrameIterator::SFITER_SKIPPED_FRAME_FUNCTION:
+ *simpleType = CLRDATA_SIMPFRAME_RUNTIME_UNMANAGED_CODE;
+ break;
+ default:
+ *simpleType = CLRDATA_SIMPFRAME_UNRECOGNIZED;
+ break;
+ }
+ }
+
+ if (detailedType)
+ {
+ if (m_frameIter.m_crawl.GetFrame() && m_frameIter.m_crawl.GetFrame()->GetFrameAttribs() & Frame::FRAME_ATTR_EXCEPTION)
+ *detailedType = CLRDATA_DETFRAME_EXCEPTION_FILTER;
+ else
+ *detailedType = CLRDATA_DETFRAME_UNRECOGNIZED;
+ }
+}
+
+//----------------------------------------------------------------------------
+//
+// ClrDataFrame.
+//
+//----------------------------------------------------------------------------
+
+ClrDataFrame::ClrDataFrame(ClrDataAccess* dac,
+ CLRDataSimpleFrameType simpleType,
+ CLRDataDetailedFrameType detailedType,
+ AppDomain* appDomain,
+ MethodDesc* methodDesc)
+{
+ m_dac = dac;
+ m_dac->AddRef();
+ m_instanceAge = m_dac->m_instanceAge;
+ m_simpleType = simpleType;
+ m_detailedType = detailedType;
+ m_appDomain = appDomain;
+ m_methodDesc = methodDesc;
+ m_refs = 1;
+ m_methodSig = NULL;
+ m_localSig = NULL;
+}
+
+ClrDataFrame::~ClrDataFrame(void)
+{
+ delete m_methodSig;
+ delete m_localSig;
+ m_dac->Release();
+}
+
+STDMETHODIMP
+ClrDataFrame::QueryInterface(THIS_
+ IN REFIID interfaceId,
+ OUT PVOID* iface)
+{
+ if (IsEqualIID(interfaceId, IID_IUnknown) ||
+ IsEqualIID(interfaceId, __uuidof(IXCLRDataFrame)))
+ {
+ AddRef();
+ *iface = static_cast<IUnknown*>
+ (static_cast<IXCLRDataFrame*>(this));
+ return S_OK;
+ }
+ else if (IsEqualIID(interfaceId, __uuidof(IXCLRDataFrame2)))
+ {
+ AddRef();
+ *iface = static_cast<IUnknown*>
+ (static_cast<IXCLRDataFrame2*>(this));
+ return S_OK;
+ }
+ else
+ {
+ *iface = NULL;
+ return E_NOINTERFACE;
+ }
+}
+
+STDMETHODIMP_(ULONG)
+ClrDataFrame::AddRef(THIS)
+{
+ return InterlockedIncrement(&m_refs);
+}
+
+STDMETHODIMP_(ULONG)
+ClrDataFrame::Release(THIS)
+{
+ SUPPORTS_DAC_HOST_ONLY;
+ LONG newRefs = InterlockedDecrement(&m_refs);
+ if (newRefs == 0)
+ {
+ delete this;
+ }
+ return newRefs;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataFrame::GetContext(
+ /* [in] */ ULONG32 contextFlags,
+ /* [in] */ ULONG32 contextBufSize,
+ /* [out] */ ULONG32 *contextSize,
+ /* [size_is][out] */ BYTE contextBuf[ ])
+{
+ HRESULT status;
+
+ if (contextSize)
+ {
+ *contextSize = ContextSizeForFlags(contextFlags);
+ }
+
+ if (!CheckContextSizeForFlags(contextBufSize, contextFlags))
+ {
+ return E_INVALIDARG;
+ }
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ *(PT_CONTEXT)contextBuf = m_context;
+ status = S_OK;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataFrame::GetFrameType(
+ /* [out] */ CLRDataSimpleFrameType *simpleType,
+ /* [out] */ CLRDataDetailedFrameType *detailedType)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ *simpleType = m_simpleType;
+ *detailedType = m_detailedType;
+ status = S_OK;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataFrame::GetAppDomain(
+ /* [out] */ IXCLRDataAppDomain **appDomain)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if (m_appDomain)
+ {
+ ClrDataAppDomain* dataAppDomain =
+ new (nothrow) ClrDataAppDomain(m_dac, m_appDomain);
+ if (!dataAppDomain)
+ {
+ status = E_OUTOFMEMORY;
+ }
+ else
+ {
+ *appDomain = static_cast<IXCLRDataAppDomain*>(dataAppDomain);
+ status = S_OK;
+ }
+ }
+ else
+ {
+ *appDomain = NULL;
+ status = S_FALSE;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataFrame::GetNumArguments(
+ /* [out] */ ULONG32 *numArgs)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if (!m_methodDesc)
+ {
+ status = E_NOINTERFACE;
+ }
+ else
+ {
+ MetaSig* sig;
+
+ status = GetMethodSig(&sig, numArgs);
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataFrame::GetArgumentByIndex(
+ /* [in] */ ULONG32 index,
+ /* [out] */ IXCLRDataValue **arg,
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part(bufLen, *nameLen) WCHAR name[ ])
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if (nameLen)
+ {
+ *nameLen = 0;
+ }
+
+ if (!m_methodDesc)
+ {
+ status = E_NOINTERFACE;
+ goto Exit;
+ }
+
+ MetaSig* sig;
+ ULONG32 numArgs;
+
+ if (FAILED(status = GetMethodSig(&sig, &numArgs)))
+ {
+ goto Exit;
+ }
+
+ if (index >= numArgs)
+ {
+ status = E_INVALIDARG;
+ goto Exit;
+ }
+
+ if ((bufLen && name) || nameLen)
+ {
+ if (index == 0 && sig->HasThis())
+ {
+ if (nameLen)
+ {
+ *nameLen = 5;
+ }
+
+ StringCchCopy(name, bufLen, W("this"));
+ }
+ else
+ {
+ if (!m_methodDesc->IsNoMetadata())
+ {
+ IMDInternalImport* mdImport = m_methodDesc->GetMDImport();
+ mdParamDef paramToken;
+ LPCSTR paramName;
+ USHORT seq;
+ DWORD attr;
+
+ // Param indexing is 1-based.
+ ULONG32 mdIndex = index + 1;
+
+ // 'this' doesn't show up in the signature but
+ // is present in the dac API indexing so adjust the
+ // index down for methods with 'this'.
+ if (sig->HasThis())
+ {
+ mdIndex--;
+ }
+
+ status = mdImport->FindParamOfMethod(
+ m_methodDesc->GetMemberDef(),
+ mdIndex,
+ &paramToken);
+ if (status == S_OK)
+ {
+ status = mdImport->GetParamDefProps(
+ paramToken,
+ &seq,
+ &attr,
+ &paramName);
+ if ((status == S_OK) && (paramName != NULL))
+ {
+ if ((status = ConvertUtf8(paramName,
+ bufLen, nameLen, name)) != S_OK)
+ {
+ goto Exit;
+ }
+ }
+ }
+ }
+ else
+ {
+ if (nameLen)
+ {
+ *nameLen = 1;
+ }
+
+ name[0] = 0;
+ }
+ }
+ }
+
+ status = ValueFromDebugInfo(sig, true, index, index, arg);
+
+ Exit: ;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataFrame::GetNumLocalVariables(
+ /* [out] */ ULONG32 *numLocals)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if (!m_methodDesc)
+ {
+ status = E_NOINTERFACE;
+ }
+ else
+ {
+ MetaSig* sig;
+
+ status = GetLocalSig(&sig, numLocals);
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataFrame::GetLocalVariableByIndex(
+ /* [in] */ ULONG32 index,
+ /* [out] */ IXCLRDataValue **localVariable,
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part(bufLen, *nameLen) WCHAR name[ ])
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if (!m_methodDesc)
+ {
+ status = E_NOINTERFACE;
+ goto Exit;
+ }
+
+ MetaSig* sig;
+ ULONG32 numLocals;
+
+ if (FAILED(status = GetLocalSig(&sig, &numLocals)))
+ {
+ goto Exit;
+ }
+
+ if (index >= numLocals)
+ {
+ status = E_INVALIDARG;
+ goto Exit;
+ }
+
+ MetaSig* argSig;
+ ULONG32 numArgs;
+
+ if (FAILED(status = GetMethodSig(&argSig, &numArgs)))
+ {
+ goto Exit;
+ }
+
+ // Can't get names for locals in the Whidbey runtime.
+ if (bufLen && name)
+ {
+ if (nameLen)
+ {
+ *nameLen = 1;
+ }
+
+ name[0] = 0;
+ }
+
+ // The locals are indexed immediately following the arguments
+ // in the NativeVarInfos.
+ status = ValueFromDebugInfo(sig, false, index, index + numArgs,
+ localVariable);
+
+ Exit: ;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataFrame::GetNumTypeArguments(
+ /* [out] */ ULONG32 *numTypeArgs)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ // XXX Microsoft.
+ status = E_NOTIMPL;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataFrame::GetTypeArgumentByIndex(
+ /* [in] */ ULONG32 index,
+ /* [out] */ IXCLRDataTypeInstance **typeArg)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ // XXX Microsoft.
+ status = E_NOTIMPL;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+
+HRESULT STDMETHODCALLTYPE
+ClrDataFrame::GetExactGenericArgsToken(
+ /* [out] */ IXCLRDataValue ** genericToken)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if (!m_methodDesc)
+ {
+ status = E_NOINTERFACE;
+ goto Exit;
+ }
+
+ MetaSig* sig;
+ ULONG32 numLocals;
+
+ if (FAILED(status = GetLocalSig(&sig, &numLocals)))
+ {
+ goto Exit;
+ }
+
+ // The locals are indexed immediately following the arguments
+ // in the NativeVarInfos.
+ status = ValueFromDebugInfo(sig, false, 1, (DWORD)ICorDebugInfo::TYPECTXT_ILNUM,
+ genericToken);
+ Exit: ;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataFrame::GetCodeName(
+ /* [in] */ ULONG32 flags,
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *symbolLen,
+ /* [size_is][out] */ __out_ecount_opt(bufLen) WCHAR symbolBuf[ ])
+{
+ HRESULT status = E_FAIL;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ TADDR pcAddr = PCODEToPINSTR(GetControlPC(&m_regDisp));
+ status = m_dac->
+ RawGetMethodName(TO_CDADDR(pcAddr), flags,
+ bufLen, symbolLen, symbolBuf,
+ NULL);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataFrame::GetMethodInstance(
+ /* [out] */ IXCLRDataMethodInstance **method)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if (!m_methodDesc)
+ {
+ status = E_NOINTERFACE;
+ }
+ else
+ {
+ ClrDataMethodInstance* dataMethod =
+ new (nothrow) ClrDataMethodInstance(m_dac,
+ m_appDomain,
+ m_methodDesc);
+ *method = static_cast<IXCLRDataMethodInstance*>(dataMethod);
+ status = dataMethod ? S_OK : E_OUTOFMEMORY;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataFrame::Request(
+ /* [in] */ ULONG32 reqCode,
+ /* [in] */ ULONG32 inBufferSize,
+ /* [size_is][in] */ BYTE *inBuffer,
+ /* [in] */ ULONG32 outBufferSize,
+ /* [size_is][out] */ BYTE *outBuffer)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ switch(reqCode)
+ {
+ case CLRDATA_REQUEST_REVISION:
+ if (inBufferSize != 0 ||
+ inBuffer ||
+ outBufferSize != sizeof(ULONG32))
+ {
+ status = E_INVALIDARG;
+ }
+ else
+ {
+ *(ULONG32*)outBuffer = 1;
+ status = S_OK;
+ }
+ break;
+
+ default:
+ status = E_INVALIDARG;
+ break;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT
+ClrDataFrame::GetMethodSig(MetaSig** sig,
+ ULONG32* count)
+{
+ if (!m_methodSig)
+ {
+ m_methodSig = new (nothrow) MetaSig(m_methodDesc);
+ if (!m_methodSig)
+ {
+ return E_OUTOFMEMORY;
+ }
+ }
+
+ *sig = m_methodSig;
+ *count = m_methodSig->NumFixedArgs() +
+ (m_methodSig->HasThis() ? 1 : 0);
+ return *count ? S_OK : S_FALSE;
+}
+
+HRESULT
+ClrDataFrame::GetLocalSig(MetaSig** sig,
+ ULONG32* count)
+{
+ HRESULT hr;
+ if (!m_localSig)
+ {
+ // It turns out we cannot really get rid of this check. Dynamic methods
+ // (including IL stubs) do not have their local sig's available after JIT time.
+ if (!m_methodDesc->IsIL())
+ {
+ *sig = NULL;
+ *count = 0;
+ return S_FALSE;
+ }
+
+ COR_ILMETHOD_DECODER methodDecoder(m_methodDesc->GetILHeader());
+ mdSignature localSig = methodDecoder.GetLocalVarSigTok() ?
+ methodDecoder.GetLocalVarSigTok() : mdSignatureNil;
+ if (localSig == mdSignatureNil)
+ {
+ *sig = NULL;
+ *count = 0;
+ return S_FALSE;
+ }
+
+ ULONG tokenSigLen;
+ PCCOR_SIGNATURE tokenSig;
+ IfFailRet(m_methodDesc->GetModule()->GetMDImport()->GetSigFromToken(
+ localSig,
+ &tokenSigLen,
+ &tokenSig));
+
+ SigTypeContext typeContext(m_methodDesc, TypeHandle());
+ m_localSig = new (nothrow)
+ MetaSig(tokenSig,
+ tokenSigLen,
+ m_methodDesc->GetModule(),
+ &typeContext,
+ MetaSig::sigLocalVars);
+ if (!m_localSig)
+ {
+ return E_OUTOFMEMORY;
+ }
+ }
+
+ *sig = m_localSig;
+ *count = m_localSig->NumFixedArgs();
+ return S_OK;
+}
+
+HRESULT
+ClrDataFrame::ValueFromDebugInfo(MetaSig* sig,
+ bool isArg,
+ DWORD sigIndex,
+ DWORD varInfoSlot,
+ IXCLRDataValue** _value)
+{
+ HRESULT status;
+ ULONG32 numVarInfo;
+ NewHolder<ICorDebugInfo::NativeVarInfo> varInfo(NULL);
+ ULONG32 codeOffset;
+ ULONG32 valueFlags;
+ ULONG32 i;
+
+ TADDR ip = PCODEToPINSTR(GetControlPC(&m_regDisp));
+ if ((status = m_dac->GetMethodVarInfo(m_methodDesc,
+ ip,
+ &numVarInfo,
+ &varInfo,
+ &codeOffset)) != S_OK)
+ {
+ // We have signature info indicating that there
+ // are values, but couldn't find any location info.
+ // Optimized routines may have eliminated all
+ // traditional variable locations, so just treat
+ // this as a no-location case just like not being
+ // able to find a matching lifetime.
+ numVarInfo = 0;
+ }
+
+ for (i = 0; i < numVarInfo; i++)
+ {
+ if (varInfo[i].startOffset <= codeOffset &&
+ varInfo[i].endOffset >= codeOffset &&
+ varInfo[i].varNumber == varInfoSlot &&
+ varInfo[i].loc.vlType != ICorDebugInfo::VLT_INVALID)
+ {
+ break;
+ }
+ }
+
+ ULONG64 baseAddr;
+ NativeVarLocation locs[MAX_NATIVE_VAR_LOCS];
+ ULONG32 numLocs;
+
+ if (i >= numVarInfo)
+ {
+ numLocs = 0;
+ }
+ else
+ {
+ numLocs = NativeVarLocations(varInfo[i].loc, &m_context,
+ NumItems(locs), locs);
+ }
+
+ if (numLocs == 1 && !locs[0].contextReg)
+ {
+ baseAddr = TO_CDADDR(locs[0].addr);
+ }
+ else
+ {
+ baseAddr = 0;
+ }
+
+ TypeHandle argType;
+
+ sig->Reset();
+ if (isArg && sigIndex == 0 && sig->HasThis())
+ {
+ argType = TypeHandle(m_methodDesc->GetMethodTable());
+ valueFlags = CLRDATA_VALUE_IS_REFERENCE;
+ }
+ else
+ {
+ // 'this' doesn't show up in the signature but
+ // is present in the indexing so adjust the
+ // index down for methods with 'this'.
+ if (isArg && sig->HasThis())
+ {
+
+ sigIndex--;
+ }
+
+ do
+ {
+ sig->NextArg();
+ }
+ while (sigIndex-- > 0);
+
+ // == FailIfNotLoaded
+ // Will also return null if type is not restored
+ argType = sig->GetLastTypeHandleThrowing(ClassLoader::DontLoadTypes);
+ if (argType.IsNull())
+ {
+ // XXX Microsoft - Sometimes types can't be looked
+ // up and this at least allows the value to be used,
+ // but is it the right behavior?
+ argType = TypeHandle(MscorlibBinder::GetElementType(ELEMENT_TYPE_U8));
+ valueFlags = 0;
+ }
+ else
+ {
+ valueFlags = GetTypeFieldValueFlags(argType, NULL, 0, false);
+
+ // If this is a primitive variable and the actual size is smaller than what we have been told,
+ // then lower the size so that we won't read in trash memory (e.g. reading 4 bytes for a short).
+ if ((valueFlags & CLRDATA_VALUE_IS_PRIMITIVE) != 0)
+ {
+ if (numLocs == 1)
+ {
+ UINT actualSize = argType.GetSize();
+ if (actualSize < locs[0].size)
+ {
+ locs[0].size = actualSize;
+ }
+ }
+ }
+ }
+ }
+
+ ClrDataValue* value = new (nothrow)
+ ClrDataValue(m_dac,
+ m_appDomain,
+ NULL,
+ valueFlags,
+ argType,
+ baseAddr,
+ numLocs,
+ locs);
+ if (!value)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ *_value = value;
+ return S_OK;
+}
diff --git a/src/debug/daccess/stdafx.cpp b/src/debug/daccess/stdafx.cpp
new file mode 100644
index 0000000000..f508973779
--- /dev/null
+++ b/src/debug/daccess/stdafx.cpp
@@ -0,0 +1,12 @@
+// 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.
+//*****************************************************************************
+// File: stdafx.cpp
+//
+
+//
+// Host for precompiled headers.
+//
+//*****************************************************************************
+#include "stdafx.h" // Precompiled header key.
diff --git a/src/debug/daccess/stdafx.h b/src/debug/daccess/stdafx.h
new file mode 100644
index 0000000000..5c2d37688b
--- /dev/null
+++ b/src/debug/daccess/stdafx.h
@@ -0,0 +1,111 @@
+// 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.
+//*****************************************************************************
+// File: stdafx.h
+//
+
+//
+//*****************************************************************************
+/* XXX Fri 10/14/2005
+ * prevent winioctl from defining something called "Unknown"
+ */
+#define _WINIOCTL_
+
+// Define ALLOW_VMPTR_ACCESS to grant DAC access to VMPTR
+#define ALLOW_VMPTR_ACCESS
+
+// Prevent the inclusion of Random.h from disabling rand(). rand() is used by some other headers we include
+// and there's no reason why DAC should be forbidden from using it.
+#define DO_NOT_DISABLE_RAND
+
+#define USE_COM_CONTEXT_DEF
+
+#include <stdint.h>
+#include <windows.h>
+
+#include <winwrap.h>
+
+#include <dbghelp.h>
+
+#include <wchar.h>
+#include <stdio.h>
+
+#include <dbgtargetcontext.h>
+
+#include <cor.h>
+#include <dacprivate.h>
+#include <sospriv.h>
+
+#include <common.h>
+#include <codeman.h>
+#include <debugger.h>
+#include <controller.h>
+#include <eedbginterfaceimpl.h>
+#include <methoditer.h>
+
+#include <xcordebug.h>
+#include "dacimpl.h"
+
+#if defined(FEATURE_APPX_BINDER)
+#include <clrprivbinderappx.h>
+#endif // defined(FEATURE_APPX)
+
+#define STRSAFE_NO_DEPRECATE
+#include <strsafe.h>
+#undef _ftcscat
+#undef _ftcscpy
+
+// from ntstatus.h
+#define STATUS_STOWED_EXCEPTION ((NTSTATUS)0xC000027BL)
+
+// unpublished Windows structures. these will be published soon in a new header.
+// copying here for now and we'll use the windows header when it's available.
+// this is tracked with issue 824225
+#ifndef _STOWED_EXCEPTION_TEMP_DEFINITION
+#define _STOWED_EXCEPTION_TEMP_DEFINITION
+
+typedef struct _STOWED_EXCEPTION_INFORMATION_HEADER {
+ ULONG Size;
+ ULONG Signature;
+} STOWED_EXCEPTION_INFORMATION_HEADER, *PSTOWED_EXCEPTION_INFORMATION_HEADER;
+
+typedef struct _STOWED_EXCEPTION_INFORMATION_V2 {
+ STOWED_EXCEPTION_INFORMATION_HEADER Header;
+
+ HRESULT ResultCode;
+
+ struct {
+ DWORD ExceptionForm : 2;
+ DWORD ThreadId : 30;
+ };
+
+ union {
+ struct {
+ PVOID ExceptionAddress;
+
+ ULONG StackTraceWordSize; // sizeof (PVOID)
+ ULONG StackTraceWords; // number of words pointed to by StackTrace
+ PVOID StackTrace; // StackTrace buffer
+ };
+ struct {
+ PWSTR ErrorText;
+ };
+ };
+
+ ULONG NestedExceptionType;
+ PVOID NestedException; // opaque exception addendum
+} STOWED_EXCEPTION_INFORMATION_V2, *PSTOWED_EXCEPTION_INFORMATION_V2;
+
+//
+// Nested exception: type definition macro (byte swap). Assumes little-endian.
+//
+#define STOWED_EXCEPTION_NESTED_TYPE(t) ((((((ULONG)(t)) >> 24) & 0xFF) << 0) | \
+ (((((ULONG)(t)) >> 16) & 0xFF) << 8) | \
+ (((((ULONG)(t)) >> 8) & 0xFF) << 16) | \
+ (((((ULONG)(t)) >> 0) & 0xFF) << 24))
+
+#define STOWED_EXCEPTION_INFORMATION_V2_SIGNATURE 'SE02'
+#define STOWED_EXCEPTION_NESTED_TYPE_LEO STOWED_EXCEPTION_NESTED_TYPE('LEO1') // language exception object
+
+#endif // _STOWED_EXCEPTION_TEMP_DEFINITION
diff --git a/src/debug/daccess/task.cpp b/src/debug/daccess/task.cpp
new file mode 100644
index 0000000000..601ad401af
--- /dev/null
+++ b/src/debug/daccess/task.cpp
@@ -0,0 +1,5335 @@
+// 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.
+//*****************************************************************************
+// File: task.cpp
+//
+
+//
+// ClrDataTask.
+//
+//*****************************************************************************
+
+#include "stdafx.h"
+
+// XXX Microsoft - Why aren't these extra MD APIs in a header?
+STDAPI GetMDPublicInterfaceFromInternal(
+ void *pIUnkPublic, // [IN] Given scope.
+ REFIID riid, // [in] The interface desired.
+ void **ppIUnkInternal); // [out] Return interface on success.
+
+STDAPI GetMetaDataPublicInterfaceFromInternal(
+ void *pv, // [IN] Given interface.
+ REFIID riid, // [IN] desired interface.
+ void **ppv) // [OUT] returned interface
+{
+ return GetMDPublicInterfaceFromInternal(pv, riid, ppv);
+}
+
+//----------------------------------------------------------------------------
+//
+// ClrDataTask.
+//
+//----------------------------------------------------------------------------
+
+ClrDataTask::ClrDataTask(ClrDataAccess* dac,
+ Thread* thread)
+{
+ m_dac = dac;
+ m_dac->AddRef();
+ m_instanceAge = m_dac->m_instanceAge;
+ m_thread = thread;
+ m_refs = 1;
+}
+
+ClrDataTask::~ClrDataTask(void)
+{
+ m_dac->Release();
+}
+
+STDMETHODIMP
+ClrDataTask::QueryInterface(THIS_
+ IN REFIID interfaceId,
+ OUT PVOID* iface)
+{
+ if (IsEqualIID(interfaceId, IID_IUnknown) ||
+ IsEqualIID(interfaceId, __uuidof(IXCLRDataTask)))
+ {
+ AddRef();
+ *iface = static_cast<IUnknown*>
+ (static_cast<IXCLRDataTask*>(this));
+ return S_OK;
+ }
+ else
+ {
+ *iface = NULL;
+ return E_NOINTERFACE;
+ }
+}
+
+STDMETHODIMP_(ULONG)
+ClrDataTask::AddRef(THIS)
+{
+ return InterlockedIncrement(&m_refs);
+}
+
+STDMETHODIMP_(ULONG)
+ClrDataTask::Release(THIS)
+{
+ SUPPORTS_DAC_HOST_ONLY;
+ LONG newRefs = InterlockedDecrement(&m_refs);
+ if (newRefs == 0)
+ {
+ delete this;
+ }
+ return newRefs;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTask::GetProcess(
+ /* [out] */ IXCLRDataProcess **process)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ *process = static_cast<IXCLRDataProcess*>(m_dac);
+ m_dac->AddRef();
+ status = S_OK;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTask::GetCurrentAppDomain(
+ /* [out] */ IXCLRDataAppDomain **appDomain)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if (m_thread->GetDomain())
+ {
+ *appDomain = new (nothrow)
+ ClrDataAppDomain(m_dac, m_thread->GetDomain());
+ status = *appDomain ? S_OK : E_OUTOFMEMORY;
+ }
+ else
+ {
+ status = E_INVALIDARG;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTask::GetName(
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(bufLen, *nameLen) WCHAR name[ ])
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ // XXX - Microsoft.
+ status = E_NOTIMPL;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTask::GetUniqueID(
+ /* [out] */ ULONG64 *id)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ *id = m_thread->GetThreadId();
+ status = S_OK;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTask::GetFlags(
+ /* [out] */ ULONG32 *flags)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ // XXX Microsoft - GC check.
+ *flags = CLRDATA_TASK_DEFAULT;
+ status = S_OK;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTask::IsSameObject(
+ /* [in] */ IXCLRDataTask* task)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = PTR_HOST_TO_TADDR(m_thread) ==
+ PTR_HOST_TO_TADDR(((ClrDataTask*)task)->m_thread) ?
+ S_OK : S_FALSE;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTask::GetManagedObject(
+ /* [out] */ IXCLRDataValue **value)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ // XXX Microsoft.
+ status = E_NOTIMPL;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTask::GetDesiredExecutionState(
+ /* [out] */ ULONG32 *state)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ // XXX Microsoft.
+ status = E_NOTIMPL;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTask::SetDesiredExecutionState(
+ /* [in] */ ULONG32 state)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ // XXX Microsoft.
+ status = E_NOTIMPL;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTask::CreateStackWalk(
+ /* [in] */ ULONG32 flags,
+ /* [out] */ IXCLRDataStackWalk **stackWalk)
+{
+ HRESULT status;
+
+ if (flags & ~SIMPFRAME_ALL)
+ {
+ return E_INVALIDARG;
+ }
+
+ DAC_ENTER_SUB(m_dac);
+
+ ClrDataStackWalk* walkClass = NULL;
+
+ EX_TRY
+ {
+ walkClass = new (nothrow) ClrDataStackWalk(m_dac, m_thread, flags);
+
+ if (!walkClass)
+ {
+ status = E_OUTOFMEMORY;
+ }
+ else if ((status = walkClass->Init()) != S_OK)
+ {
+ delete walkClass;
+ }
+ else
+ {
+ *stackWalk = static_cast<IXCLRDataStackWalk*>(walkClass);
+ }
+ }
+ EX_CATCH
+ {
+ if (walkClass)
+ {
+ delete walkClass;
+ }
+
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTask::GetOSThreadID(
+ /* [out] */ ULONG32 *id)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if (m_thread->GetOSThreadId() &&
+ m_thread->GetOSThreadId() != 0xbaadf00d)
+ {
+ *id = m_thread->GetOSThreadId();
+ status = S_OK;
+ }
+ else
+ {
+ *id = 0;
+ status = S_FALSE;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTask::GetContext(
+ /* [in] */ ULONG32 contextFlags,
+ /* [in] */ ULONG32 contextBufSize,
+ /* [out] */ ULONG32 *contextSize,
+ /* [size_is][out] */ BYTE contextBuf[ ])
+{
+ HRESULT status;
+
+ if (contextSize)
+ {
+ *contextSize = ContextSizeForFlags(contextFlags);
+ }
+
+ if (!CheckContextSizeForFlags(contextBufSize, contextFlags))
+ {
+ return E_INVALIDARG;
+ }
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if (m_thread->GetOSThreadId())
+ {
+ status = m_dac->m_pTarget->
+ GetThreadContext(m_thread->GetOSThreadId(),
+ contextFlags,
+ contextBufSize,
+ contextBuf);
+ }
+ else
+ {
+ status = E_INVALIDARG;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTask::SetContext(
+ /* [in] */ ULONG32 contextSize,
+ /* [size_is][in] */ BYTE context[ ])
+{
+ HRESULT status;
+
+ if (!CheckContextSizeForBuffer(contextSize, context))
+ {
+ return E_INVALIDARG;
+ }
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if (m_thread->GetOSThreadId())
+ {
+ status = m_dac->m_pMutableTarget->
+ SetThreadContext(m_thread->GetOSThreadId(),
+ contextSize,
+ context);
+ }
+ else
+ {
+ status = E_INVALIDARG;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTask::GetCurrentExceptionState(
+ /* [out] */ IXCLRDataExceptionState **exception)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = ClrDataExceptionState::NewFromThread(m_dac,
+ m_thread,
+ NULL,
+ exception);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTask::GetLastExceptionState(
+ /* [out] */ IXCLRDataExceptionState **exception)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if (m_thread->m_LastThrownObjectHandle)
+ {
+ *exception = new (nothrow)
+ ClrDataExceptionState(m_dac,
+ m_thread->GetDomain(),
+ m_thread,
+ CLRDATA_EXCEPTION_PARTIAL,
+ NULL,
+ m_thread->m_LastThrownObjectHandle,
+ NULL);
+ status = *exception ? S_OK : E_OUTOFMEMORY;
+ }
+ else
+ {
+ status = E_NOINTERFACE;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataTask::Request(
+ /* [in] */ ULONG32 reqCode,
+ /* [in] */ ULONG32 inBufferSize,
+ /* [size_is][in] */ BYTE *inBuffer,
+ /* [in] */ ULONG32 outBufferSize,
+ /* [size_is][out] */ BYTE *outBuffer)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ switch(reqCode)
+ {
+ case CLRDATA_REQUEST_REVISION:
+ if (inBufferSize != 0 ||
+ inBuffer ||
+ outBufferSize != sizeof(ULONG32))
+ {
+ status = E_INVALIDARG;
+ }
+ else
+ {
+ *(ULONG32*)outBuffer = 3;
+ status = S_OK;
+ }
+ break;
+
+ default:
+ status = E_INVALIDARG;
+ break;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+//----------------------------------------------------------------------------
+//
+// ClrDataAppDomain.
+//
+//----------------------------------------------------------------------------
+
+ClrDataAppDomain::ClrDataAppDomain(ClrDataAccess* dac,
+ AppDomain* appDomain)
+{
+ m_dac = dac;
+ m_dac->AddRef();
+ m_instanceAge = m_dac->m_instanceAge;
+ m_appDomain = appDomain;
+ m_refs = 1;
+}
+
+ClrDataAppDomain::~ClrDataAppDomain(void)
+{
+ m_dac->Release();
+}
+
+STDMETHODIMP
+ClrDataAppDomain::QueryInterface(THIS_
+ IN REFIID interfaceId,
+ OUT PVOID* iface)
+{
+ if (IsEqualIID(interfaceId, IID_IUnknown) ||
+ IsEqualIID(interfaceId, __uuidof(IXCLRDataAppDomain)))
+ {
+ AddRef();
+ *iface = static_cast<IUnknown*>
+ (static_cast<IXCLRDataAppDomain*>(this));
+ return S_OK;
+ }
+ else
+ {
+ *iface = NULL;
+ return E_NOINTERFACE;
+ }
+}
+
+STDMETHODIMP_(ULONG)
+ClrDataAppDomain::AddRef(THIS)
+{
+ return InterlockedIncrement(&m_refs);
+}
+
+STDMETHODIMP_(ULONG)
+ClrDataAppDomain::Release(THIS)
+{
+ SUPPORTS_DAC_HOST_ONLY;
+ LONG newRefs = InterlockedDecrement(&m_refs);
+ if (newRefs == 0)
+ {
+ delete this;
+ }
+ return newRefs;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAppDomain::GetProcess(
+ /* [out] */ IXCLRDataProcess **process)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ *process = static_cast<IXCLRDataProcess*>(m_dac);
+ m_dac->AddRef();
+ status = S_OK;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAppDomain::GetName(
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(bufLen, *nameLen) WCHAR name[ ])
+{
+ HRESULT status = S_OK;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ bool isUtf8;
+ PVOID rawName = m_appDomain->GetFriendlyNameNoSet(&isUtf8);
+ if (rawName)
+ {
+ if (isUtf8)
+ {
+ status = ConvertUtf8((LPCUTF8)rawName,
+ bufLen, nameLen, name);
+ }
+ else
+ {
+ status = StringCchCopy(name, bufLen, (PCWSTR)rawName) == S_OK ?
+ S_OK : S_FALSE;
+ if (nameLen)
+ {
+ size_t cchName = wcslen((PCWSTR)rawName) + 1;
+ if (FitsIn<ULONG32>(cchName))
+ {
+ *nameLen = (ULONG32) cchName;
+ }
+ else
+ {
+ status = COR_E_OVERFLOW;
+ }
+ }
+ }
+ }
+ else
+ {
+ status = E_NOINTERFACE;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAppDomain::GetFlags(
+ /* [out] */ ULONG32 *flags)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ *flags = CLRDATA_DOMAIN_DEFAULT;
+ status = S_OK;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAppDomain::IsSameObject(
+ /* [in] */ IXCLRDataAppDomain* appDomain)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = PTR_HOST_TO_TADDR(m_appDomain) ==
+ PTR_HOST_TO_TADDR(((ClrDataAppDomain*)appDomain)->m_appDomain) ?
+ S_OK : S_FALSE;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAppDomain::GetManagedObject(
+ /* [out] */ IXCLRDataValue **value)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ // XXX Microsoft.
+ status = E_NOTIMPL;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAppDomain::GetUniqueID(
+ /* [out] */ ULONG64 *id)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ *id = m_appDomain->GetId().m_dwId;
+ status = S_OK;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAppDomain::Request(
+ /* [in] */ ULONG32 reqCode,
+ /* [in] */ ULONG32 inBufferSize,
+ /* [size_is][in] */ BYTE *inBuffer,
+ /* [in] */ ULONG32 outBufferSize,
+ /* [size_is][out] */ BYTE *outBuffer)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = E_INVALIDARG;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+//----------------------------------------------------------------------------
+//
+// ClrDataAssembly.
+//
+//----------------------------------------------------------------------------
+
+ClrDataAssembly::ClrDataAssembly(ClrDataAccess* dac,
+ Assembly* assembly)
+{
+ m_dac = dac;
+ m_dac->AddRef();
+ m_instanceAge = m_dac->m_instanceAge;
+ m_refs = 1;
+ m_assembly = assembly;
+}
+
+ClrDataAssembly::~ClrDataAssembly(void)
+{
+ m_dac->Release();
+}
+
+STDMETHODIMP
+ClrDataAssembly::QueryInterface(THIS_
+ IN REFIID interfaceId,
+ OUT PVOID* iface)
+{
+ if (IsEqualIID(interfaceId, IID_IUnknown) ||
+ IsEqualIID(interfaceId, __uuidof(IXCLRDataAssembly)))
+ {
+ AddRef();
+ *iface = static_cast<IUnknown*>
+ (static_cast<IXCLRDataAssembly*>(this));
+ return S_OK;
+ }
+ else
+ {
+ *iface = NULL;
+ return E_NOINTERFACE;
+ }
+}
+
+STDMETHODIMP_(ULONG)
+ClrDataAssembly::AddRef(THIS)
+{
+ return InterlockedIncrement(&m_refs);
+}
+
+STDMETHODIMP_(ULONG)
+ClrDataAssembly::Release(THIS)
+{
+ SUPPORTS_DAC_HOST_ONLY;
+ LONG newRefs = InterlockedDecrement(&m_refs);
+ if (newRefs == 0)
+ {
+ delete this;
+ }
+ return newRefs;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAssembly::StartEnumModules(
+ /* [out] */ CLRDATA_ENUM* handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ Assembly::ModuleIterator* iter = new (nothrow)
+ Assembly::ModuleIterator;
+ if (iter)
+ {
+ *iter = m_assembly->IterateModules();
+ *handle = TO_CDENUM(iter);
+ status = S_OK;
+ }
+ else
+ {
+ status = E_OUTOFMEMORY;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAssembly::EnumModule(
+ /* [in, out] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataModule **mod)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ Assembly::ModuleIterator* iter =
+ FROM_CDENUM(Assembly::ModuleIterator, *handle);
+ if (iter->Next())
+ {
+ *mod = new (nothrow)
+ ClrDataModule(m_dac, iter->GetModule());
+ status = *mod ? S_OK : E_OUTOFMEMORY;
+ }
+ else
+ {
+ status = S_FALSE;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAssembly::EndEnumModules(
+ /* [in] */ CLRDATA_ENUM handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ Assembly::ModuleIterator* iter =
+ FROM_CDENUM(Assembly::ModuleIterator, handle);
+ delete iter;
+ status = S_OK;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAssembly::StartEnumAppDomains(
+ /* [out] */ CLRDATA_ENUM* handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ // XXX Microsoft.
+ status = E_NOTIMPL;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAssembly::EnumAppDomain(
+ /* [in, out] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataAppDomain **appDomain)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ // XXX Microsoft.
+ status = E_NOTIMPL;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAssembly::EndEnumAppDomains(
+ /* [in] */ CLRDATA_ENUM handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ // XXX Microsoft.
+ status = E_NOTIMPL;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAssembly::GetName(
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(bufLen, *nameLen) WCHAR name[ ])
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = ConvertUtf8(m_assembly->GetSimpleName(),
+ bufLen, nameLen, name);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAssembly::GetFileName(
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(bufLen, *nameLen) WCHAR name[ ])
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ COUNT_T _nameLen;
+
+ if (m_assembly->GetManifestFile()->GetPath().
+ DacGetUnicode(bufLen, name, &_nameLen))
+ {
+ if (nameLen)
+ {
+ *nameLen = _nameLen;
+ }
+ status = S_OK;
+ }
+ else
+ {
+ status = E_FAIL;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAssembly::GetDisplayName(
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(bufLen, *nameLen) WCHAR name[ ])
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ // XXX Microsoft.
+ status = E_NOTIMPL;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAssembly::GetFlags(
+ /* [out] */ ULONG32 *flags)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ *flags = CLRDATA_ASSEMBLY_DEFAULT;
+ status = S_OK;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAssembly::IsSameObject(
+ /* [in] */ IXCLRDataAssembly* assembly)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = (PTR_HOST_TO_TADDR(m_assembly) ==
+ PTR_HOST_TO_TADDR(((ClrDataAssembly*)assembly)->
+ m_assembly)) ?
+ S_OK : S_FALSE;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataAssembly::Request(
+ /* [in] */ ULONG32 reqCode,
+ /* [in] */ ULONG32 inBufferSize,
+ /* [size_is][in] */ BYTE *inBuffer,
+ /* [in] */ ULONG32 outBufferSize,
+ /* [size_is][out] */ BYTE *outBuffer)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ switch(reqCode)
+ {
+ case CLRDATA_REQUEST_REVISION:
+ if (inBufferSize != 0 ||
+ inBuffer ||
+ outBufferSize != sizeof(ULONG32))
+ {
+ status = E_INVALIDARG;
+ }
+ else
+ {
+ *(ULONG32*)outBuffer = 2;
+ status = S_OK;
+ }
+ break;
+
+ default:
+ status = E_INVALIDARG;
+ break;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+//----------------------------------------------------------------------------
+//
+// ClrDataModule.
+//
+//----------------------------------------------------------------------------
+
+ClrDataModule::ClrDataModule(ClrDataAccess* dac,
+ Module* module)
+{
+ m_dac = dac;
+ m_dac->AddRef();
+ m_instanceAge = m_dac->m_instanceAge;
+ m_refs = 1;
+ m_module = module;
+ m_mdImport = NULL;
+ m_setExtents = false;
+}
+
+ClrDataModule::~ClrDataModule(void)
+{
+ m_dac->Release();
+ if (m_mdImport)
+ {
+ m_mdImport->Release();
+ }
+}
+
+STDMETHODIMP
+ClrDataModule::QueryInterface(THIS_
+ IN REFIID interfaceId,
+ OUT PVOID* iface)
+{
+ _ASSERTE(iface != NULL);
+
+ if (IsEqualIID(interfaceId, IID_IUnknown) ||
+ IsEqualIID(interfaceId, __uuidof(IXCLRDataModule)))
+ {
+ AddRef();
+ *iface = static_cast<IUnknown*>
+ (static_cast<IXCLRDataModule*>(this));
+ return S_OK;
+ }
+ else if (IsEqualIID(interfaceId, __uuidof(IXCLRDataModule2)))
+ {
+ AddRef();
+ *iface = static_cast<IUnknown*>
+ (static_cast<IXCLRDataModule2*>(this));
+ return S_OK;
+ }
+ else if (IsEqualIID(interfaceId, IID_IMetaDataImport))
+ {
+ return GetMdInterface(iface);
+ }
+ else
+ {
+ *iface = NULL;
+ return E_NOINTERFACE;
+ }
+}
+
+STDMETHODIMP_(ULONG)
+ClrDataModule::AddRef(THIS)
+{
+ return InterlockedIncrement(&m_refs);
+}
+
+STDMETHODIMP_(ULONG)
+ClrDataModule::Release(THIS)
+{
+ SUPPORTS_DAC_HOST_ONLY;
+ LONG newRefs = InterlockedDecrement(&m_refs);
+ if (newRefs == 0)
+ {
+ delete this;
+ }
+ return newRefs;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataModule::StartEnumAssemblies(
+ /* [out] */ CLRDATA_ENUM* handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ ProcessModIter* iter = new (nothrow) ProcessModIter;
+ if (iter)
+ {
+ *handle = TO_CDENUM(iter);
+ status = S_OK;
+ }
+ else
+ {
+ status = E_OUTOFMEMORY;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataModule::EnumAssembly(
+ /* [in, out] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataAssembly **assembly)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ ProcessModIter* iter = FROM_CDENUM(ProcessModIter, *handle);
+ Module* module;
+
+ //
+ // Iterate over all of the modules in the process.
+ // When this module is found, return the containing
+ // assembly.
+ // Is there a more direct way?
+ //
+
+ for (;;)
+ {
+ if (!(module = iter->NextModule()))
+ {
+ status = S_FALSE;
+ break;
+ }
+
+ if (PTR_HOST_TO_TADDR(module) == PTR_HOST_TO_TADDR(m_module))
+ {
+ *assembly = new (nothrow)
+ ClrDataAssembly(m_dac, iter->m_curAssem);
+ status = *assembly ? S_OK : E_OUTOFMEMORY;
+ break;
+ }
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataModule::EndEnumAssemblies(
+ /* [in] */ CLRDATA_ENUM handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ ProcessModIter* iter = FROM_CDENUM(ProcessModIter, handle);
+ delete iter;
+ status = S_OK;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataModule::StartEnumAppDomains(
+ /* [out] */ CLRDATA_ENUM* handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ // XXX Microsoft.
+ status = E_NOTIMPL;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataModule::EnumAppDomain(
+ /* [in, out] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataAppDomain **appDomain)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ // XXX Microsoft.
+ status = E_NOTIMPL;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataModule::EndEnumAppDomains(
+ /* [in] */ CLRDATA_ENUM handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ // XXX Microsoft.
+ status = E_NOTIMPL;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataModule::StartEnumTypeDefinitions(
+ /* [out] */ CLRDATA_ENUM* handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = MetaEnum::New(m_module,
+ mdtTypeDef,
+ 0,
+ NULL,
+ NULL,
+ handle);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataModule::EnumTypeDefinition(
+ /* [in, out] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataTypeDefinition **typeDefinition)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ mdTypeDef token;
+
+ if ((status = MetaEnum::CdNextToken(handle, &token)) == S_OK)
+ {
+ status = ClrDataTypeDefinition::
+ NewFromModule(m_dac,
+ m_module,
+ token,
+ NULL,
+ typeDefinition);
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataModule::EndEnumTypeDefinitions(
+ /* [in] */ CLRDATA_ENUM handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = MetaEnum::CdEnd(handle);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataModule::StartEnumTypeInstances(
+ /* [in] */ IXCLRDataAppDomain* appDomain,
+ /* [out] */ CLRDATA_ENUM* handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = MetaEnum::New(m_module,
+ mdtTypeDef,
+ 0,
+ appDomain,
+ NULL,
+ handle);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataModule::EnumTypeInstance(
+ /* [in, out] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataTypeInstance **typeInstance)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ for (;;)
+ {
+ AppDomain* appDomain;
+ mdTypeDef token;
+
+ if ((status = MetaEnum::
+ CdNextDomainToken(handle, &appDomain, &token)) != S_OK)
+ {
+ break;
+ }
+
+ // If the type hasn't been used there won't be anything
+ // loaded. It's not an instance, then, just keep going.
+ if ((status = ClrDataTypeInstance::
+ NewFromModule(m_dac,
+ appDomain,
+ m_module,
+ token,
+ NULL,
+ typeInstance)) != E_INVALIDARG)
+ {
+ break;
+ }
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataModule::EndEnumTypeInstances(
+ /* [in] */ CLRDATA_ENUM handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = MetaEnum::CdEnd(handle);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataModule::StartEnumTypeDefinitionsByName(
+ /* [in] */ LPCWSTR name,
+ /* [in] */ ULONG32 flags,
+ /* [out] */ CLRDATA_ENUM* handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = SplitName::CdStartType(name,
+ flags,
+ m_module,
+ NULL,
+ NULL,
+ NULL,
+ handle);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataModule::EnumTypeDefinitionByName(
+ /* [out][in] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataTypeDefinition **type)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ mdTypeDef token;
+
+ if ((status = SplitName::CdNextType(handle, &token)) == S_OK)
+ {
+ status = ClrDataTypeDefinition::
+ NewFromModule(m_dac,
+ m_module,
+ token,
+ NULL,
+ type);
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataModule::EndEnumTypeDefinitionsByName(
+ /* [in] */ CLRDATA_ENUM handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = SplitName::CdEnd(handle);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataModule::StartEnumTypeInstancesByName(
+ /* [in] */ LPCWSTR name,
+ /* [in] */ ULONG32 flags,
+ /* [in] */ IXCLRDataAppDomain *appDomain,
+ /* [out] */ CLRDATA_ENUM* handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = SplitName::CdStartType(name,
+ flags,
+ m_module,
+ NULL,
+ appDomain,
+ NULL,
+ handle);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataModule::EnumTypeInstanceByName(
+ /* [out][in] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataTypeInstance **type)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ SplitName* split; split = FROM_CDENUM(SplitName, *handle);
+
+ for (;;)
+ {
+ AppDomain* appDomain;
+ mdTypeDef token;
+
+ if ((status = SplitName::
+ CdNextDomainType(handle, &appDomain, &token)) != S_OK)
+ {
+ break;
+ }
+
+ // If the type hasn't been used there won't be anything
+ // loaded. It's not an instance, then, just keep going.
+ if ((status = ClrDataTypeInstance::
+ NewFromModule(m_dac,
+ appDomain,
+ m_module,
+ token,
+ NULL,
+ type)) != E_INVALIDARG)
+ {
+ break;
+ }
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataModule::EndEnumTypeInstancesByName(
+ /* [in] */ CLRDATA_ENUM handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = SplitName::CdEnd(handle);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataModule::GetTypeDefinitionByToken(
+ /* [in] */ mdTypeDef token,
+ /* [out] */ IXCLRDataTypeDefinition **typeDefinition)
+{
+ HRESULT status;
+
+ // This isn't critically necessary but it prevents
+ // an assert in the metadata code.
+ if (TypeFromToken(token) != mdtTypeDef)
+ {
+ return E_INVALIDARG;
+ }
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = ClrDataTypeDefinition::
+ NewFromModule(m_dac,
+ m_module,
+ token,
+ NULL,
+ typeDefinition);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataModule::StartEnumMethodDefinitionsByName(
+ /* [in] */ LPCWSTR name,
+ /* [in] */ ULONG32 flags,
+ /* [out] */ CLRDATA_ENUM* handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = SplitName::CdStartMethod(name,
+ flags,
+ m_module,
+ mdTypeDefNil,
+ NULL,
+ NULL,
+ NULL,
+ handle);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataModule::EnumMethodDefinitionByName(
+ /* [out][in] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataMethodDefinition **method)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ mdMethodDef token;
+
+ if ((status = SplitName::CdNextMethod(handle, &token)) == S_OK)
+ {
+ status = ClrDataMethodDefinition::
+ NewFromModule(m_dac,
+ m_module,
+ token,
+ NULL,
+ method);
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataModule::EndEnumMethodDefinitionsByName(
+ /* [in] */ CLRDATA_ENUM handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = SplitName::CdEnd(handle);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataModule::StartEnumMethodInstancesByName(
+ /* [in] */ LPCWSTR name,
+ /* [in] */ ULONG32 flags,
+ /* [in] */ IXCLRDataAppDomain* appDomain,
+ /* [out] */ CLRDATA_ENUM* handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = SplitName::CdStartMethod(name,
+ flags,
+ m_module,
+ mdTypeDefNil,
+ NULL,
+ appDomain,
+ NULL,
+ handle);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataModule::EnumMethodInstanceByName(
+ /* [out][in] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataMethodInstance **method)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ SplitName* split; split = FROM_CDENUM(SplitName, *handle);
+
+ for (;;)
+ {
+ AppDomain* appDomain;
+ mdMethodDef token;
+
+ if ((status = SplitName::
+ CdNextDomainMethod(handle, &appDomain, &token)) != S_OK)
+ {
+ break;
+ }
+
+ // If the method doesn't have a MethodDesc or hasn't
+ // been JIT'ed yet it's not an instance and should
+ // just be skipped.
+ if ((status = ClrDataMethodInstance::
+ NewFromModule(m_dac,
+ appDomain,
+ m_module,
+ token,
+ NULL,
+ method)) != E_INVALIDARG)
+ {
+ break;
+ }
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataModule::EndEnumMethodInstancesByName(
+ /* [in] */ CLRDATA_ENUM handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = SplitName::CdEnd(handle);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataModule::GetMethodDefinitionByToken(
+ /* [in] */ mdMethodDef token,
+ /* [out] */ IXCLRDataMethodDefinition **methodDefinition)
+{
+ HRESULT status;
+
+ // This isn't critically necessary but it prevents
+ // an assert in the metadata code.
+ if (TypeFromToken(token) != mdtMethodDef)
+ {
+ return E_INVALIDARG;
+ }
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = ClrDataMethodDefinition::
+ NewFromModule(m_dac,
+ m_module,
+ token,
+ NULL,
+ methodDefinition);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataModule::StartEnumDataByName(
+ /* [in] */ LPCWSTR name,
+ /* [in] */ ULONG32 flags,
+ /* [in] */ IXCLRDataAppDomain* appDomain,
+ /* [in] */ IXCLRDataTask* tlsTask,
+ /* [out] */ CLRDATA_ENUM* handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = SplitName::CdStartField(name,
+ flags,
+ INH_STATIC,
+ NULL,
+ TypeHandle(),
+ m_module,
+ mdTypeDefNil,
+ 0,
+ NULL,
+ tlsTask,
+ NULL,
+ appDomain,
+ NULL,
+ handle);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataModule::EnumDataByName(
+ /* [out][in] */ CLRDATA_ENUM* handle,
+ /* [out] */ IXCLRDataValue **value)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = SplitName::CdNextDomainField(m_dac, handle, value);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataModule::EndEnumDataByName(
+ /* [in] */ CLRDATA_ENUM handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = SplitName::CdEnd(handle);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataModule::GetName(
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(bufLen, *nameLen) WCHAR name[ ])
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = ConvertUtf8(m_module->GetSimpleName(),
+ bufLen, nameLen, name);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataModule::GetFileName(
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part(bufLen, *nameLen) WCHAR name[ ])
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ COUNT_T _nameLen;
+
+ // Try to get the file name through GetPath.
+ // If the returned name is empty, then try to get the guessed module file name.
+ // The guessed file name is propogated from metadata's module name.
+ //
+ if ((m_module->GetFile()->GetPath().DacGetUnicode(bufLen, name, &_nameLen) && name[0])||
+ (m_module->GetFile()->GetModuleFileNameHint().DacGetUnicode(bufLen, name, &_nameLen) && name[0]))
+ {
+ if (nameLen)
+ {
+ *nameLen = _nameLen;
+ }
+ status = S_OK;
+ }
+ else
+ {
+ status = E_FAIL;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataModule::GetVersionId(
+ /* [out] */ GUID* vid)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if (!m_module->GetFile()->HasMetadata())
+ {
+ status = E_NOINTERFACE;
+ }
+ else
+ {
+ GUID mdVid;
+
+ status = m_module->GetMDImport()->GetScopeProps(NULL, &mdVid);
+ if (SUCCEEDED(status))
+ {
+ *vid = mdVid;
+ }
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataModule::GetFlags(
+ /* [out] */ ULONG32 *flags)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ *flags = 0;
+
+ if (m_module->IsReflection())
+ {
+ (*flags) |= CLRDATA_MODULE_IS_DYNAMIC;
+ }
+ if (m_module->IsIStream())
+ {
+ (*flags) |= CLRDATA_MODULE_IS_MEMORY_STREAM;
+ }
+
+ status = S_OK;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataModule::IsSameObject(
+ /* [in] */ IXCLRDataModule* mod)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = (PTR_HOST_TO_TADDR(m_module) ==
+ PTR_HOST_TO_TADDR(((ClrDataModule*)mod)->
+ m_module)) ?
+ S_OK : S_FALSE;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataModule::StartEnumExtents(
+ /* [out] */ CLRDATA_ENUM* handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if (!m_setExtents)
+ {
+ PEFile* file = m_module->GetFile();
+ if (!file)
+ {
+ *handle = 0;
+ status = E_INVALIDARG;
+ goto Exit;
+ }
+
+ CLRDATA_MODULE_EXTENT* extent = m_extents;
+
+ if (file->GetLoadedImageContents() != NULL)
+ {
+ extent->base =
+ TO_CDADDR( PTR_TO_TADDR(file->GetLoadedImageContents(&extent->length)) );
+ extent->type = CLRDATA_MODULE_PE_FILE;
+ extent++;
+ }
+ if (file->HasNativeImage())
+ {
+ extent->base = TO_CDADDR(PTR_TO_TADDR(file->GetLoadedNative()->GetBase()));
+ extent->length = file->GetLoadedNative()->GetVirtualSize();
+ extent->type = CLRDATA_MODULE_PREJIT_FILE;
+ extent++;
+ }
+
+ m_setExtents = true;
+ m_extentsEnd = extent;
+ }
+
+ *handle = TO_CDENUM(m_extents);
+ status = m_extents != m_extentsEnd ? S_OK : S_FALSE;
+
+ Exit: ;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataModule::EnumExtent(
+ /* [in, out] */ CLRDATA_ENUM* handle,
+ /* [out] */ CLRDATA_MODULE_EXTENT *extent)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ CLRDATA_MODULE_EXTENT* curExtent =
+ FROM_CDENUM(CLRDATA_MODULE_EXTENT, *handle);
+ if (!m_setExtents ||
+ curExtent < m_extents ||
+ curExtent > m_extentsEnd)
+ {
+ status = E_INVALIDARG;
+ }
+ else if (curExtent < m_extentsEnd)
+ {
+ *extent = *curExtent++;
+ *handle = TO_CDENUM(curExtent);
+ status = S_OK;
+ }
+ else
+ {
+ status = S_FALSE;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataModule::EndEnumExtents(
+ /* [in] */ CLRDATA_ENUM handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ // Enumerator holds no resources.
+ status = S_OK;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT
+ClrDataModule::RequestGetModulePtr(
+ /* [in] */ ULONG32 inBufferSize,
+ /* [size_is][in] */ BYTE *inBuffer,
+ /* [in] */ ULONG32 outBufferSize,
+ /* [size_is][out] */ BYTE *outBuffer)
+{
+ // Validate params.
+ // Input: Nothing.
+ // Output: a DacpGetModuleAddress structure.
+ if ((inBufferSize != 0) ||
+ (inBuffer != NULL) ||
+ (outBufferSize != sizeof(DacpGetModuleAddress)) ||
+ (outBuffer == NULL))
+ {
+ return E_INVALIDARG;
+ }
+
+ DacpGetModuleAddress * outGMA = reinterpret_cast<DacpGetModuleAddress *> (outBuffer);
+
+ outGMA->ModulePtr = TO_CDADDR(PTR_HOST_TO_TADDR(m_module));
+ return S_OK;
+}
+
+HRESULT
+ClrDataModule::RequestGetModuleData(
+ /* [in] */ ULONG32 inBufferSize,
+ /* [size_is][in] */ BYTE *inBuffer,
+ /* [in] */ ULONG32 outBufferSize,
+ /* [size_is][out] */ BYTE *outBuffer)
+{
+ // Validate params.
+ // Input: Nothing.
+ // Output: a DacpGetModuleData structure.
+ if ((inBufferSize != 0) ||
+ (inBuffer != NULL) ||
+ (outBufferSize != sizeof(DacpGetModuleData)) ||
+ (outBuffer == NULL))
+ {
+ return E_INVALIDARG;
+ }
+
+ DacpGetModuleData * outGMD = reinterpret_cast<DacpGetModuleData *>(outBuffer);
+ ZeroMemory(outGMD, sizeof(DacpGetModuleData));
+
+ Module* pModule = GetModule();
+ PEFile *pPEFile = pModule->GetFile();
+
+ outGMD->PEFile = TO_CDADDR(PTR_HOST_TO_TADDR(pPEFile));
+ outGMD->IsDynamic = pModule->IsReflection();
+
+ if (pPEFile != NULL)
+ {
+ outGMD->IsInMemory = pPEFile->GetPath().IsEmpty();
+
+ COUNT_T peSize;
+ outGMD->LoadedPEAddress = TO_CDADDR(PTR_TO_TADDR(pPEFile->GetLoadedImageContents(&peSize)));
+ outGMD->LoadedPESize = (ULONG64)peSize;
+ outGMD->IsFileLayout = pPEFile->GetLoaded()->IsFlat();
+ }
+
+ // If there is a in memory symbol stream
+ CGrowableStream* stream = pModule->GetInMemorySymbolStream();
+ if (stream != NULL)
+ {
+ // Save the in-memory PDB address and size
+ MemoryRange range = stream->GetRawBuffer();
+ outGMD->InMemoryPdbAddress = TO_CDADDR(PTR_TO_TADDR(range.StartAddress()));
+ outGMD->InMemoryPdbSize = range.Size();
+ }
+
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataModule::Request(
+ /* [in] */ ULONG32 reqCode,
+ /* [in] */ ULONG32 inBufferSize,
+ /* [size_is][in] */ BYTE *inBuffer,
+ /* [in] */ ULONG32 outBufferSize,
+ /* [size_is][out] */ BYTE *outBuffer)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ switch(reqCode)
+ {
+ case CLRDATA_REQUEST_REVISION:
+ if (inBufferSize != 0 ||
+ inBuffer ||
+ outBufferSize != sizeof(ULONG32))
+ {
+ status = E_INVALIDARG;
+ }
+ else
+ {
+ *(ULONG32*)outBuffer = 3;
+ status = S_OK;
+ }
+ break;
+
+ case DACDATAMODULEPRIV_REQUEST_GET_MODULEPTR:
+ status = RequestGetModulePtr(inBufferSize, inBuffer, outBufferSize, outBuffer);
+ break;
+
+ case DACDATAMODULEPRIV_REQUEST_GET_MODULEDATA:
+ status = RequestGetModuleData(inBufferSize, inBuffer, outBufferSize, outBuffer);
+ break;
+
+ default:
+ status = E_INVALIDARG;
+ break;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataModule::SetJITCompilerFlags(
+ /* [in] */ DWORD dwFlags)
+{
+ // Note: this is similar but not equivalent to the DacDbi version of this function
+ HRESULT hr = S_OK;
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ // can't have a subset of these, eg 0x101, so make sure we have an exact match
+ if ((dwFlags != CORDEBUG_JIT_DEFAULT) &&
+ (dwFlags != CORDEBUG_JIT_DISABLE_OPTIMIZATION))
+ {
+ hr = E_INVALIDARG;
+ }
+#ifdef FEATURE_PREJIT
+ else if (m_module->HasNativeImage())
+ {
+ hr = CORDBG_E_CANT_CHANGE_JIT_SETTING_FOR_ZAP_MODULE;
+ }
+#endif
+ else
+ {
+ _ASSERTE(m_module != NULL);
+
+ BOOL fAllowJitOpts = ((dwFlags & CORDEBUG_JIT_DISABLE_OPTIMIZATION) != CORDEBUG_JIT_DISABLE_OPTIMIZATION);
+
+ // Initialize dwBits.
+ DWORD dwBits = (m_module->GetDebuggerInfoBits() & ~(DACF_ALLOW_JIT_OPTS | DACF_ENC_ENABLED));
+ dwBits &= DACF_CONTROL_FLAGS_MASK;
+
+ if (fAllowJitOpts)
+ {
+ dwBits |= DACF_ALLOW_JIT_OPTS;
+ }
+
+ // Settings from the debugger take precedence over all other settings.
+ dwBits |= DACF_USER_OVERRIDE;
+
+ // set flags. This will write back to the target
+ m_module->SetDebuggerInfoBits((DebuggerAssemblyControlFlags)dwBits);
+
+ _ASSERTE(SUCCEEDED(hr));
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &hr))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return hr;
+}
+
+HRESULT
+ClrDataModule::GetMdInterface(PVOID* retIface)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if (m_mdImport == NULL)
+ {
+ if (!m_module->GetFile()->HasMetadata())
+ {
+ status = E_NOINTERFACE;
+ goto Exit;
+ }
+
+ //
+ // Make sure internal MD is in RW format.
+ //
+ IMDInternalImport* rwMd;
+
+ status = ConvertMDInternalImport(m_module->GetMDImport(), &rwMd);
+ if (FAILED(status))
+ {
+ goto Exit;
+ }
+
+ // If no conversion took place the same interface was
+ // was returned without an AddRef. AddRef now so
+ // that rwMd has a reference either way.
+ if (status == S_FALSE)
+ {
+ rwMd->AddRef();
+ }
+
+ status = GetMDPublicInterfaceFromInternal((PVOID)rwMd,
+ IID_IMetaDataImport,
+ (PVOID*)&m_mdImport);
+
+ rwMd->Release();
+
+ if (status != S_OK)
+ {
+ goto Exit;
+ }
+ }
+
+ _ASSERTE(m_mdImport != NULL);
+ m_mdImport->AddRef();
+ *retIface = m_mdImport;
+ status = S_OK;
+
+ Exit: ;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+//----------------------------------------------------------------------------
+//
+// ClrDataMethodDefinition.
+//
+//----------------------------------------------------------------------------
+
+ClrDataMethodDefinition::ClrDataMethodDefinition(ClrDataAccess* dac,
+ Module* module,
+ mdMethodDef token,
+ MethodDesc* methodDesc)
+{
+ m_dac = dac;
+ m_dac->AddRef();
+ m_instanceAge = m_dac->m_instanceAge;
+ m_refs = 1;
+ m_module = module;
+ m_token = token;
+ m_methodDesc = methodDesc;
+}
+
+ClrDataMethodDefinition::~ClrDataMethodDefinition(void)
+{
+ m_dac->Release();
+}
+
+STDMETHODIMP
+ClrDataMethodDefinition::QueryInterface(THIS_
+ IN REFIID interfaceId,
+ OUT PVOID* iface)
+{
+ if (IsEqualIID(interfaceId, IID_IUnknown) ||
+ IsEqualIID(interfaceId, __uuidof(IXCLRDataMethodDefinition)))
+ {
+ AddRef();
+ *iface = static_cast<IUnknown*>
+ (static_cast<IXCLRDataMethodDefinition*>(this));
+ return S_OK;
+ }
+ else
+ {
+ *iface = NULL;
+ return E_NOINTERFACE;
+ }
+}
+
+STDMETHODIMP_(ULONG)
+ClrDataMethodDefinition::AddRef(THIS)
+{
+ return InterlockedIncrement(&m_refs);
+}
+
+STDMETHODIMP_(ULONG)
+ClrDataMethodDefinition::Release(THIS)
+{
+ SUPPORTS_DAC_HOST_ONLY;
+ LONG newRefs = InterlockedDecrement(&m_refs);
+ if (newRefs == 0)
+ {
+ delete this;
+ }
+ return newRefs;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataMethodDefinition::GetTypeDefinition(
+ /* [out] */ IXCLRDataTypeDefinition **typeDefinition)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ TypeHandle typeHandle;
+ mdTypeDef token;
+
+ if (m_methodDesc)
+ {
+ typeHandle = TypeHandle(m_methodDesc->GetMethodTable());
+ token = typeHandle.GetMethodTable()->GetCl();
+ }
+ else
+ {
+ if ((status = m_module->GetMDImport()->
+ GetParentToken(m_token, &token)) != S_OK)
+ {
+ goto Exit;
+ }
+ }
+
+ *typeDefinition = new (nothrow)
+ ClrDataTypeDefinition(m_dac, m_module, token, typeHandle);
+ status = *typeDefinition ? S_OK : E_OUTOFMEMORY;
+
+ Exit: ;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataMethodDefinition::StartEnumInstances(
+ /* [in] */ IXCLRDataAppDomain* appDomain,
+ /* [out] */ CLRDATA_ENUM *handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if (m_methodDesc)
+ {
+ status = EnumMethodInstances::CdStart(m_methodDesc, appDomain,
+ handle);
+ }
+ else
+ {
+ status = S_FALSE;
+ *handle = 0;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataMethodDefinition::EnumInstance(
+ /* [out][in] */ CLRDATA_ENUM *handle,
+ /* [out] */ IXCLRDataMethodInstance **instance)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = EnumMethodInstances::CdNext(m_dac, handle, instance);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataMethodDefinition::EndEnumInstances(
+ /* [in] */ CLRDATA_ENUM handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = EnumMethodInstances::CdEnd(handle);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataMethodDefinition::GetName(
+ /* [in] */ ULONG32 flags,
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part_opt(bufLen, *nameLen) WCHAR name[ ])
+{
+ HRESULT status;
+
+ if (flags != 0)
+ {
+ return E_INVALIDARG;
+ }
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if (m_methodDesc)
+ {
+ status = m_dac->GetFullMethodName(m_methodDesc,
+ bufLen, nameLen, name);
+ }
+ else
+ {
+ char methName[MAX_CLASSNAME_LENGTH];
+
+ status = GetFullMethodNameFromMetadata(m_module->GetMDImport(),
+ m_token,
+ NumItems(methName),
+ methName);
+ if (status == S_OK)
+ {
+ status = ConvertUtf8(methName, bufLen, nameLen, name);
+ }
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataMethodDefinition::GetTokenAndScope(
+ /* [out] */ mdToken *token,
+ /* [out] */ IXCLRDataModule **Module)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = S_OK;
+
+ if (token)
+ {
+ *token = m_token;
+ }
+
+ if (Module)
+ {
+ *Module = new (nothrow)
+ ClrDataModule(m_dac, m_module);
+ status = *Module ? S_OK : E_OUTOFMEMORY;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataMethodDefinition::GetFlags(
+ /* [out] */ ULONG32 *flags)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = GetSharedMethodFlags(m_methodDesc, flags);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataMethodDefinition::IsSameObject(
+ /* [in] */ IXCLRDataMethodDefinition* method)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if (m_methodDesc)
+ {
+ status = (PTR_HOST_TO_TADDR(m_methodDesc) ==
+ PTR_HOST_TO_TADDR(((ClrDataMethodDefinition*)method)->
+ m_methodDesc)) ?
+ S_OK : S_FALSE;
+ }
+ else
+ {
+ status = (PTR_HOST_TO_TADDR(m_module) ==
+ PTR_HOST_TO_TADDR(((ClrDataMethodDefinition*)method)->
+ m_module) &&
+ m_token == ((ClrDataMethodDefinition*)method)->m_token) ?
+ S_OK : S_FALSE;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataMethodDefinition::GetLatestEnCVersion(
+ /* [out] */ ULONG32* version)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ // XXX Microsoft.
+ *version = 0;
+ status = S_OK;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataMethodDefinition::StartEnumExtents(
+ /* [out] */ CLRDATA_ENUM *handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ COR_ILMETHOD* ilMeth = GetIlMethod();
+ status = ilMeth ? S_OK : S_FALSE;
+ *handle = TO_CDENUM(ilMeth);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataMethodDefinition::EnumExtent(
+ /* [out][in] */ CLRDATA_ENUM *handle,
+ /* [out] */ CLRDATA_METHDEF_EXTENT *extent)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if (*handle)
+ {
+ COR_ILMETHOD* ilMeth = FROM_CDENUM(COR_ILMETHOD, *handle);
+ COR_ILMETHOD_DECODER ilDec(ilMeth);
+ *handle = 0;
+
+ extent->startAddress = TO_CDADDR(PTR_HOST_TO_TADDR(ilMeth) +
+ 4 * ilDec.GetSize());
+ extent->endAddress = extent->startAddress +
+ ilDec.GetCodeSize() - 1;
+ extent->type = CLRDATA_METHDEF_IL;
+ // XXX Microsoft - EnC version.
+ extent->enCVersion = 0;
+
+ status = S_OK;
+ }
+ else
+ {
+ status = E_INVALIDARG;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataMethodDefinition::EndEnumExtents(
+ /* [in] */ CLRDATA_ENUM handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ // Nothing to do.
+ status = S_OK;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataMethodDefinition::GetCodeNotification(
+ /* [out] */ ULONG32 *flags)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ JITNotifications jn(m_dac->GetHostJitNotificationTable());
+ if (!jn.IsActive())
+ {
+ status = E_OUTOFMEMORY;
+ }
+ else
+ {
+ TADDR modulePtr = PTR_HOST_TO_TADDR(m_module);
+ *flags = jn.Requested(modulePtr, m_token);
+ status = S_OK;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataMethodDefinition::SetCodeNotification(
+ /* [in] */ ULONG32 flags)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if (!IsValidMethodCodeNotification(flags))
+ {
+ status = E_INVALIDARG;
+ }
+ else
+ {
+ JITNotifications jn(m_dac->GetHostJitNotificationTable());
+ if (!jn.IsActive())
+ {
+ status = E_OUTOFMEMORY;
+ }
+ else
+ {
+ TADDR modulePtr = PTR_HOST_TO_TADDR(m_module);
+ USHORT NType = jn.Requested(modulePtr, m_token);
+
+ if (NType == flags)
+ {
+ // notification already set
+ status = S_OK;
+ }
+ else
+ {
+ if (jn.SetNotification(modulePtr, m_token, flags) &&
+ jn.UpdateOutOfProcTable())
+ {
+ // new notification added
+ status = S_OK;
+ }
+ else
+ {
+ // error setting notification
+ status = E_FAIL;
+ }
+ }
+ }
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataMethodDefinition::GetRepresentativeEntryAddress(
+ /* [out] */ CLRDATA_ADDRESS* addr)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ COR_ILMETHOD* ilMeth = GetIlMethod();
+ if (ilMeth)
+ {
+ COR_ILMETHOD_DECODER ilDec(ilMeth);
+ *addr = TO_CDADDR(PTR_HOST_TO_TADDR(ilMeth) +
+ 4 * ilDec.GetSize());
+ status = S_OK;
+ }
+ else
+ {
+ status = E_UNEXPECTED;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataMethodDefinition::HasClassOrMethodInstantiation(
+ /* [out] */ BOOL* bGeneric)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if (m_methodDesc)
+ {
+ *bGeneric = m_methodDesc->HasClassOrMethodInstantiation();
+ status = S_OK;
+ }
+ else
+ {
+ status = E_UNEXPECTED;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataMethodDefinition::Request(
+ /* [in] */ ULONG32 reqCode,
+ /* [in] */ ULONG32 inBufferSize,
+ /* [size_is][in] */ BYTE *inBuffer,
+ /* [in] */ ULONG32 outBufferSize,
+ /* [size_is][out] */ BYTE *outBuffer)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ switch(reqCode)
+ {
+ case CLRDATA_REQUEST_REVISION:
+ if (inBufferSize != 0 ||
+ inBuffer ||
+ outBufferSize != sizeof(ULONG32))
+ {
+ status = E_INVALIDARG;
+ }
+ else
+ {
+ *(ULONG32*)outBuffer = 1;
+ status = S_OK;
+ }
+ break;
+
+ default:
+ status = E_INVALIDARG;
+ break;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+COR_ILMETHOD*
+ClrDataMethodDefinition::GetIlMethod(void)
+{
+ if (m_methodDesc)
+ {
+ if (!m_methodDesc->HasILHeader())
+ {
+ return NULL;
+ }
+ else
+ {
+ return m_methodDesc->GetILHeader();
+ }
+ }
+ else
+ {
+ ULONG ilRva;
+ ULONG implFlags;
+
+ if (FAILED(m_module->GetMDImport()->
+ GetMethodImplProps(m_token, &ilRva, &implFlags)))
+ {
+ return NULL;
+ }
+ if (!ilRva)
+ {
+ return NULL;
+ }
+ else
+ {
+ return DacGetIlMethod(m_module->GetIL((RVA)ilRva));
+ }
+ }
+}
+
+HRESULT
+ClrDataMethodDefinition::NewFromModule(ClrDataAccess* dac,
+ Module* module,
+ mdMethodDef token,
+ ClrDataMethodDefinition** methDef,
+ IXCLRDataMethodDefinition** pubMethDef)
+{
+ // The method may not have internal runtime data yet,
+ // so the absence of a MethodDesc is not a failure.
+ // It'll just produce a metadata-query MethoDefinition.
+ MethodDesc* methodDesc = module->LookupMethodDef(token);
+
+ ClrDataMethodDefinition* def = new (nothrow)
+ ClrDataMethodDefinition(dac, module, token, methodDesc);
+ if (!def)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ PREFIX_ASSUME(methDef || pubMethDef);
+
+ if (methDef)
+ {
+ *methDef = def;
+ }
+ if (pubMethDef)
+ {
+ *pubMethDef = def;
+ }
+
+ return S_OK;
+}
+
+HRESULT
+ClrDataMethodDefinition::GetSharedMethodFlags(MethodDesc* methodDesc,
+ ULONG32* flags)
+{
+ *flags = CLRDATA_METHOD_DEFAULT;
+
+ if (methodDesc)
+ {
+ MetaSig sig(methodDesc);
+
+ if (sig.HasThis())
+ {
+ (*flags) |= CLRDATA_METHOD_HAS_THIS;
+ }
+ }
+
+ return S_OK;
+}
+
+//----------------------------------------------------------------------------
+//
+// ClrDataMethodInstance.
+//
+//----------------------------------------------------------------------------
+
+ClrDataMethodInstance::ClrDataMethodInstance(ClrDataAccess* dac,
+ AppDomain* appDomain,
+ MethodDesc* methodDesc)
+{
+ m_dac = dac;
+ m_dac->AddRef();
+ m_instanceAge = m_dac->m_instanceAge;
+ m_refs = 1;
+ m_appDomain = appDomain;
+ m_methodDesc = methodDesc;
+}
+
+ClrDataMethodInstance::~ClrDataMethodInstance(void)
+{
+ m_dac->Release();
+}
+
+STDMETHODIMP
+ClrDataMethodInstance::QueryInterface(THIS_
+ IN REFIID interfaceId,
+ OUT PVOID* iface)
+{
+ if (IsEqualIID(interfaceId, IID_IUnknown) ||
+ IsEqualIID(interfaceId, __uuidof(IXCLRDataMethodInstance)))
+ {
+ AddRef();
+ *iface = static_cast<IUnknown*>
+ (static_cast<IXCLRDataMethodInstance*>(this));
+ return S_OK;
+ }
+ else
+ {
+ *iface = NULL;
+ return E_NOINTERFACE;
+ }
+}
+
+STDMETHODIMP_(ULONG)
+ClrDataMethodInstance::AddRef(THIS)
+{
+ SUPPORTS_DAC_HOST_ONLY;
+ return InterlockedIncrement(&m_refs);
+}
+
+STDMETHODIMP_(ULONG)
+ClrDataMethodInstance::Release(THIS)
+{
+ LONG newRefs = InterlockedDecrement(&m_refs);
+ if (newRefs == 0)
+ {
+ delete this;
+ }
+ return newRefs;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataMethodInstance::GetTypeInstance(
+ /* [out] */ IXCLRDataTypeInstance **typeInstance)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if (!m_appDomain)
+ {
+ status = E_UNEXPECTED;
+ }
+ else
+ {
+ *typeInstance = new (nothrow)
+ ClrDataTypeInstance(m_dac,
+ m_appDomain,
+ TypeHandle(m_methodDesc->
+ GetMethodTable()));
+ status = *typeInstance ? S_OK : E_OUTOFMEMORY;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataMethodInstance::GetDefinition(
+ /* [out] */ IXCLRDataMethodDefinition **methodDefinition)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ *methodDefinition = new (nothrow)
+ ClrDataMethodDefinition(m_dac,
+ m_methodDesc->GetModule(),
+ m_methodDesc->GetMemberDef(),
+ m_methodDesc);
+ status = *methodDefinition ? S_OK : E_OUTOFMEMORY;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataMethodInstance::GetTokenAndScope(
+ /* [out] */ mdToken *token,
+ /* [out] */ IXCLRDataModule **mod)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = S_OK;
+
+ if (token)
+ {
+ *token = m_methodDesc->GetMemberDef();
+ }
+
+ if (mod)
+ {
+ *mod = new (nothrow)
+ ClrDataModule(m_dac, m_methodDesc->GetModule());
+ status = *mod ? S_OK : E_OUTOFMEMORY;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataMethodInstance::GetName(
+ /* [in] */ ULONG32 flags,
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *nameLen,
+ /* [size_is][out] */ __out_ecount_part(bufLen, *nameLen) WCHAR name[ ])
+{
+ HRESULT status;
+
+ if (flags != 0)
+ {
+ return E_INVALIDARG;
+ }
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = m_dac->GetFullMethodName(m_methodDesc, bufLen, nameLen, name);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ else
+ {
+ static WCHAR nameUnk[] = W("Unknown");
+ wcscpy_s(name, bufLen, nameUnk);
+ if (nameLen != NULL)
+ {
+ *nameLen = _countof(nameUnk);
+ }
+ status = S_OK;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataMethodInstance::GetFlags(
+ /* [out] */ ULONG32 *flags)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = ClrDataMethodDefinition::
+ GetSharedMethodFlags(m_methodDesc, flags);
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataMethodInstance::IsSameObject(
+ /* [in] */ IXCLRDataMethodInstance* method)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ status = (PTR_HOST_TO_TADDR(m_appDomain) ==
+ PTR_HOST_TO_TADDR(((ClrDataMethodInstance*)method)->
+ m_appDomain) &&
+ PTR_HOST_TO_TADDR(m_methodDesc) ==
+ PTR_HOST_TO_TADDR(((ClrDataMethodInstance*)method)->
+ m_methodDesc)) ?
+ S_OK : S_FALSE;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataMethodInstance::GetEnCVersion(
+ /* [out] */ ULONG32* version)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ // XXX Microsoft.
+ *version = 0;
+ status = S_OK;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataMethodInstance::GetNumTypeArguments(
+ /* [out] */ ULONG32 *numTypeArgs)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ // XXX Microsoft.
+ status = E_NOTIMPL;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataMethodInstance::GetTypeArgumentByIndex(
+ /* [in] */ ULONG32 index,
+ /* [out] */ IXCLRDataTypeInstance **typeArg)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ // XXX Microsoft.
+ status = E_NOTIMPL;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataMethodInstance::GetILOffsetsByAddress(
+ /* [in] */ CLRDATA_ADDRESS address,
+ /* [in] */ ULONG32 offsetsLen,
+ /* [out] */ ULONG32 *offsetsNeeded,
+ /* [size_is][out] */ ULONG32 ilOffsets[ ])
+{
+ HRESULT status;
+ DebuggerILToNativeMap* map = NULL;
+ bool mapAllocated = false;
+
+ DAC_ENTER_SUB(m_dac);
+ EX_TRY
+ {
+ ULONG32 numMap;
+ ULONG32 codeOffset;
+ ULONG32 hits = 0;
+
+#ifdef _TARGET_ARM_
+ address &= ~THUMB_CODE; // on ARM windbg passes in an address with the mode flag set... need to workaround
+#endif
+ if ((status = m_dac->GetMethodNativeMap(m_methodDesc,
+ CLRDATA_ADDRESS_TO_TADDR(address),
+ &numMap,
+ &map,
+ &mapAllocated,
+ NULL,
+ &codeOffset)) != S_OK)
+ {
+ goto Exit;
+ }
+
+ for (ULONG32 i = 0; i < numMap; i++)
+ {
+ if (codeOffset >= map[i].nativeStartOffset &&
+ (((LONG)map[i].ilOffset == ICorDebugInfo::EPILOG &&
+ !map[i].nativeEndOffset) ||
+ codeOffset < map[i].nativeEndOffset))
+ {
+ hits++;
+
+ if (offsetsLen && ilOffsets)
+ {
+ *ilOffsets = map[i].ilOffset;
+ ilOffsets++;
+ offsetsLen--;
+ }
+ }
+ }
+
+ if (offsetsNeeded)
+ {
+ *offsetsNeeded = hits;
+ }
+ status = hits ? S_OK : E_NOINTERFACE;
+
+ Exit: ;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ if (mapAllocated)
+ {
+ delete [] map;
+ }
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataMethodInstance::GetAddressRangesByILOffset(
+ /* [in] */ ULONG32 ilOffset,
+ /* [in] */ ULONG32 rangesLen,
+ /* [out] */ ULONG32 *rangesNeeded,
+ /* [size_is][out] */ CLRDATA_ADDRESS_RANGE addressRanges[ ])
+{
+ HRESULT status;
+ DebuggerILToNativeMap* map = NULL;
+ bool mapAllocated = false;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ ULONG32 numMap;
+ CLRDATA_ADDRESS codeStart;
+ ULONG32 hits = 0;
+
+ if ((status = m_dac->GetMethodNativeMap(m_methodDesc,
+ 0,
+ &numMap,
+ &map,
+ &mapAllocated,
+ &codeStart,
+ NULL)) != S_OK)
+ {
+ goto Exit;
+ }
+
+ for (ULONG32 i = 0; i < numMap; i++)
+ {
+ if (map[i].ilOffset == ilOffset)
+ {
+ hits++;
+
+ if (rangesLen && addressRanges)
+ {
+ addressRanges->startAddress =
+ TO_CDADDR(codeStart + map[i].nativeStartOffset);
+ if ((LONG)map[i].ilOffset == ICorDebugInfo::EPILOG &&
+ !map[i].nativeEndOffset)
+ {
+ addressRanges->endAddress = 0;
+ }
+ else
+ {
+ addressRanges->endAddress =
+ TO_CDADDR(codeStart + map[i].nativeEndOffset);
+ }
+ addressRanges++;
+ rangesLen--;
+ }
+ }
+ }
+
+ if (rangesNeeded)
+ {
+ *rangesNeeded = hits;
+ }
+ status = hits ? S_OK : E_NOINTERFACE;
+
+ Exit: ;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ if (mapAllocated)
+ {
+ delete [] map;
+ }
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataMethodInstance::GetILAddressMap(
+ /* [in] */ ULONG32 mapLen,
+ /* [out] */ ULONG32 *mapNeeded,
+ /* [size_is][out] */ CLRDATA_IL_ADDRESS_MAP maps[ ])
+{
+ HRESULT status;
+ DebuggerILToNativeMap* map = NULL;
+ bool mapAllocated = false;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ ULONG32 numMap;
+ CLRDATA_ADDRESS codeStart;
+
+ if ((status = m_dac->GetMethodNativeMap(m_methodDesc,
+ 0,
+ &numMap,
+ &map,
+ &mapAllocated,
+ &codeStart,
+ NULL)) != S_OK)
+ {
+ goto Exit;
+ }
+
+ for (ULONG32 i = 0; i < numMap; i++)
+ {
+ if (mapLen && maps)
+ {
+ maps->ilOffset = map[i].ilOffset;
+ maps->startAddress =
+ TO_CDADDR(codeStart + map[i].nativeStartOffset);
+ maps->endAddress =
+ TO_CDADDR(codeStart + map[i].nativeEndOffset);
+ // XXX Microsoft - Define types as mapping of
+ // ICorDebugInfo::SourceTypes.
+ maps->type = CLRDATA_SOURCE_TYPE_INVALID;
+ maps++;
+ mapLen--;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ if (mapNeeded)
+ {
+ *mapNeeded = numMap;
+ }
+ status = numMap ? S_OK : E_NOINTERFACE;
+
+ Exit: ;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ if (mapAllocated)
+ {
+ delete [] map;
+ }
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataMethodInstance::StartEnumExtents(
+ /* [out] */ CLRDATA_ENUM *handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ METH_EXTENTS* extents;
+
+ if ((status = m_dac->
+ GetMethodExtents(m_methodDesc, &extents)) == S_OK)
+ {
+ *handle = TO_CDENUM(extents);
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataMethodInstance::EnumExtent(
+ /* [out][in] */ CLRDATA_ENUM *handle,
+ /* [out] */ CLRDATA_ADDRESS_RANGE *extent)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ METH_EXTENTS* extents =
+ FROM_CDENUM(METH_EXTENTS, *handle);
+ if (extents->curExtent >= extents->numExtents)
+ {
+ status = S_FALSE;
+ }
+ else
+ {
+ CLRDATA_ADDRESS_RANGE* curExtent =
+ extents->extents + extents->curExtent++;
+ *extent = *curExtent;
+ status = S_OK;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataMethodInstance::EndEnumExtents(
+ /* [in] */ CLRDATA_ENUM handle)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ delete FROM_CDENUM(METH_EXTENTS, handle);
+ status = S_OK;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataMethodInstance::GetRepresentativeEntryAddress(
+ /* [out] */ CLRDATA_ADDRESS* addr)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if (m_methodDesc->HasNativeCode())
+ {
+ *addr = TO_CDADDR(m_methodDesc->GetNativeCode());
+ status = S_OK;
+ }
+ else
+ {
+ status = E_UNEXPECTED;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataMethodInstance::Request(
+ /* [in] */ ULONG32 reqCode,
+ /* [in] */ ULONG32 inBufferSize,
+ /* [size_is][in] */ BYTE *inBuffer,
+ /* [in] */ ULONG32 outBufferSize,
+ /* [size_is][out] */ BYTE *outBuffer)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ switch(reqCode)
+ {
+ case CLRDATA_REQUEST_REVISION:
+ if (inBufferSize != 0 ||
+ inBuffer ||
+ outBufferSize != sizeof(ULONG32))
+ {
+ status = E_INVALIDARG;
+ }
+ else
+ {
+ *(ULONG32*)outBuffer = 1;
+ status = S_OK;
+ }
+ break;
+
+ default:
+ status = E_INVALIDARG;
+ break;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT
+ClrDataMethodInstance::NewFromModule(ClrDataAccess* dac,
+ AppDomain* appDomain,
+ Module* module,
+ mdMethodDef token,
+ ClrDataMethodInstance** methInst,
+ IXCLRDataMethodInstance** pubMethInst)
+{
+ MethodDesc* methodDesc = module->LookupMethodDef(token);
+ if (!methodDesc ||
+ !methodDesc->HasNativeCode())
+ {
+ return E_INVALIDARG;
+ }
+
+ ClrDataMethodInstance* inst = new (nothrow)
+ ClrDataMethodInstance(dac, appDomain, methodDesc);
+ if (!inst)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ PREFIX_ASSUME(methInst || pubMethInst);
+
+ if (methInst)
+ {
+ *methInst = inst;
+ }
+ if (pubMethInst)
+ {
+ *pubMethInst = inst;
+ }
+
+ return S_OK;
+}
+
+//----------------------------------------------------------------------------
+//
+// ClrDataExceptionState.
+//
+//----------------------------------------------------------------------------
+
+ClrDataExceptionState::ClrDataExceptionState(ClrDataAccess* dac,
+ AppDomain* appDomain,
+ Thread* thread,
+ ULONG32 flags,
+ ClrDataExStateType* exInfo,
+ OBJECTHANDLE throwable,
+ ClrDataExStateType* prevExInfo)
+{
+ m_dac = dac;
+ m_dac->AddRef();
+ m_instanceAge = m_dac->m_instanceAge;
+ m_appDomain = appDomain;
+ m_thread = thread;
+ m_flags = flags;
+ m_exInfo = exInfo;
+ m_throwable = throwable;
+ m_prevExInfo = prevExInfo;
+ m_refs = 1;
+}
+
+ClrDataExceptionState::~ClrDataExceptionState(void)
+{
+ m_dac->Release();
+}
+
+STDMETHODIMP
+ClrDataExceptionState::QueryInterface(THIS_
+ IN REFIID interfaceId,
+ OUT PVOID* iface)
+{
+ if (IsEqualIID(interfaceId, IID_IUnknown) ||
+ IsEqualIID(interfaceId, __uuidof(IXCLRDataExceptionState)))
+ {
+ AddRef();
+ *iface = static_cast<IUnknown*>
+ (static_cast<IXCLRDataExceptionState*>(this));
+ return S_OK;
+ }
+ else
+ {
+ *iface = NULL;
+ return E_NOINTERFACE;
+ }
+}
+
+STDMETHODIMP_(ULONG)
+ClrDataExceptionState::AddRef(THIS)
+{
+ return InterlockedIncrement(&m_refs);
+}
+
+STDMETHODIMP_(ULONG)
+ClrDataExceptionState::Release(THIS)
+{
+ SUPPORTS_DAC_HOST_ONLY;
+ LONG newRefs = InterlockedDecrement(&m_refs);
+ if (newRefs == 0)
+ {
+ delete this;
+ }
+ return newRefs;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataExceptionState::GetFlags(
+ /* [out] */ ULONG32 *flags)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ *flags = m_flags;
+
+ if (m_prevExInfo)
+ {
+ (*flags) |= CLRDATA_EXCEPTION_NESTED;
+ }
+
+ status = S_OK;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataExceptionState::GetPrevious(
+ /* [out] */ IXCLRDataExceptionState **exState)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ if (m_prevExInfo)
+ {
+ *exState = new (nothrow)
+ ClrDataExceptionState(m_dac,
+ m_appDomain,
+ m_thread,
+ CLRDATA_EXCEPTION_DEFAULT,
+ m_prevExInfo,
+ m_prevExInfo->m_hThrowable,
+ m_prevExInfo->m_pPrevNestedInfo);
+ status = *exState ? S_OK : E_OUTOFMEMORY;
+ }
+ else
+ {
+ *exState = NULL;
+ status = S_FALSE;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataExceptionState::GetManagedObject(
+ /* [out] */ IXCLRDataValue **value)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ PTR_UNCHECKED_OBJECTREF throwRef(m_throwable);
+ if (!throwRef.IsValid())
+ {
+ status = E_INVALIDARG;
+ goto Exit;
+ }
+
+ NativeVarLocation varLoc;
+ ClrDataValue* RefVal;
+
+ varLoc.addr = TO_CDADDR(m_throwable);
+ varLoc.size = sizeof(TADDR);
+ varLoc.contextReg = false;
+
+ RefVal = new (nothrow)
+ ClrDataValue(m_dac,
+ m_appDomain,
+ m_thread,
+ CLRDATA_VALUE_IS_REFERENCE,
+ TypeHandle((*throwRef)->GetMethodTable()),
+ varLoc.addr,
+ 1, &varLoc);
+ if (!RefVal)
+ {
+ status = E_OUTOFMEMORY;
+ goto Exit;
+ }
+
+ status = RefVal->GetAssociatedValue(value);
+
+ delete RefVal;
+
+ Exit: ;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataExceptionState::GetBaseType(
+ /* [out] */ CLRDataBaseExceptionType *type)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ // XXX Microsoft.
+ status = E_NOTIMPL;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataExceptionState::GetCode(
+ /* [out] */ ULONG32 *code)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ // XXX Microsoft.
+ status = E_NOTIMPL;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataExceptionState::GetString(
+ /* [in] */ ULONG32 bufLen,
+ /* [out] */ ULONG32 *strLen,
+ /* [size_is][out] */ __out_ecount_part(bufLen, *strLen) WCHAR str[ ])
+{
+ HRESULT status = E_FAIL;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ PTR_UNCHECKED_OBJECTREF throwRef(m_throwable);
+ STRINGREF message = EXCEPTIONREF(*throwRef)->GetMessage();
+
+ if (message == NULL)
+ {
+ if (strLen)
+ {
+ *strLen = 0;
+ }
+ if (bufLen >= 1)
+ {
+ str[0] = 0;
+ }
+ status = S_OK;
+ }
+ else
+ {
+ PWSTR msgStr = DacInstantiateStringW((TADDR)message->GetBuffer(),
+ message->GetStringLength(),
+ true);
+
+ status = StringCchCopy(str, bufLen, msgStr) == S_OK ? S_OK : S_FALSE;
+ if (strLen != NULL)
+ {
+ size_t cchName = wcslen(msgStr) + 1;
+ if (FitsIn<ULONG32>(cchName))
+ {
+ *strLen = (ULONG32) cchName;
+ }
+ else
+ {
+ status = COR_E_OVERFLOW;
+ }
+ }
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataExceptionState::IsSameState(
+ /* [in] */ EXCEPTION_RECORD64 *exRecord,
+ /* [in] */ ULONG32 contextSize,
+ /* [size_is][in] */ BYTE cxRecord[ ])
+{
+ return IsSameState2(CLRDATA_EXSAME_SECOND_CHANCE,
+ exRecord, contextSize, cxRecord);
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataExceptionState::IsSameState2(
+ /* [in] */ ULONG32 flags,
+ /* [in] */ EXCEPTION_RECORD64 *exRecord,
+ /* [in] */ ULONG32 contextSize,
+ /* [size_is][in] */ BYTE cxRecord[ ])
+{
+ HRESULT status;
+
+ if ((flags & ~(CLRDATA_EXSAME_SECOND_CHANCE |
+ CLRDATA_EXSAME_FIRST_CHANCE)) != 0)
+ {
+ return E_INVALIDARG;
+ }
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ PTR_EXCEPTION_RECORD infoExRecord;
+
+ // XXX Microsoft - This should also check that the
+ // context matches the context at the time
+ // of the exception, but it's not clear
+ // how to do that in all cases.
+
+ status = S_FALSE;
+
+ if (!m_exInfo)
+ {
+ // We don't have full state, but that's expected
+ // on a first chance exception so allow that.
+ if ((flags & CLRDATA_EXSAME_FIRST_CHANCE) != 0)
+ {
+ status = S_OK;
+ }
+
+ goto Exit;
+ }
+
+ infoExRecord = GetCurrentExceptionRecord();
+
+ if ((TADDR)infoExRecord->ExceptionAddress ==
+ (TADDR)exRecord->ExceptionAddress)
+ {
+ status = S_OK;
+ }
+
+ Exit: ;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataExceptionState::GetTask(
+ /* [out] */ IXCLRDataTask** task)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ *task = new (nothrow)
+ ClrDataTask(m_dac,
+ m_thread);
+ status = *task ? S_OK : E_OUTOFMEMORY;
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT STDMETHODCALLTYPE
+ClrDataExceptionState::Request(
+ /* [in] */ ULONG32 reqCode,
+ /* [in] */ ULONG32 inBufferSize,
+ /* [size_is][in] */ BYTE *inBuffer,
+ /* [in] */ ULONG32 outBufferSize,
+ /* [size_is][out] */ BYTE *outBuffer)
+{
+ HRESULT status;
+
+ DAC_ENTER_SUB(m_dac);
+
+ EX_TRY
+ {
+ switch(reqCode)
+ {
+ case CLRDATA_REQUEST_REVISION:
+ if (inBufferSize != 0 ||
+ inBuffer ||
+ outBufferSize != sizeof(ULONG32))
+ {
+ status = E_INVALIDARG;
+ }
+ else
+ {
+ *(ULONG32*)outBuffer = 2;
+ status = S_OK;
+ }
+ break;
+
+ default:
+ status = E_INVALIDARG;
+ break;
+ }
+ }
+ EX_CATCH
+ {
+ if (!DacExceptionFilter(GET_EXCEPTION(), m_dac, &status))
+ {
+ EX_RETHROW;
+ }
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ DAC_LEAVE();
+ return status;
+}
+
+HRESULT
+ClrDataExceptionState::NewFromThread(ClrDataAccess* dac,
+ Thread* thread,
+ ClrDataExceptionState** exception,
+ IXCLRDataExceptionState** pubException)
+{
+ if (!thread->HasException())
+ {
+ return E_NOINTERFACE;
+ }
+
+ ClrDataExStateType* exState;
+ ClrDataExceptionState* exIf;
+
+#ifdef WIN64EXCEPTIONS
+ exState = thread->GetExceptionState()->m_pCurrentTracker;
+#else
+ exState = &(thread->GetExceptionState()->m_currentExInfo);
+#endif // WIN64EXCEPTIONS
+
+ exIf = new (nothrow)
+ ClrDataExceptionState(dac,
+ thread->GetDomain(),
+ thread,
+ CLRDATA_EXCEPTION_DEFAULT,
+ exState,
+ exState->m_hThrowable,
+ exState->m_pPrevNestedInfo);
+ if (!exIf)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ PREFIX_ASSUME(exception || pubException);
+
+ if (exception)
+ {
+ *exception = exIf;
+ }
+ if (pubException)
+ {
+ *pubException = exIf;
+ }
+
+ return S_OK;
+}
+
+PTR_EXCEPTION_RECORD
+ClrDataExceptionState::GetCurrentExceptionRecord()
+{
+ PTR_EXCEPTION_RECORD pExRecord = NULL;
+
+#ifdef WIN64EXCEPTIONS
+ pExRecord = m_exInfo->m_ptrs.ExceptionRecord;
+#else // WIN64EXCEPTIONS
+ pExRecord = m_exInfo->m_pExceptionRecord;
+#endif // WIN64EXCEPTIONS
+
+ return pExRecord;
+}
+
+PTR_CONTEXT
+ClrDataExceptionState::GetCurrentContextRecord()
+{
+ PTR_CONTEXT pContext = NULL;
+
+#ifdef WIN64EXCEPTIONS
+ pContext = m_exInfo->m_ptrs.ContextRecord;
+#else // WIN64EXCEPTIONS
+ pContext = m_exInfo->m_pContext;
+#endif // WIN64EXCEPTIONS
+
+ return pContext;
+}
+
+//----------------------------------------------------------------------------
+//
+// EnumMethodDefinitions.
+//
+//----------------------------------------------------------------------------
+
+HRESULT
+EnumMethodDefinitions::Start(Module* mod,
+ bool useAddrFilter,
+ CLRDATA_ADDRESS addrFilter)
+{
+ m_module = mod;
+ m_useAddrFilter = useAddrFilter;
+ m_addrFilter = addrFilter;
+ m_typeToken = mdTokenNil;
+ m_needMethodStart = true;
+ return m_typeEnum.Start(m_module->GetMDImport(), mdtTypeDef, mdTokenNil);
+}
+
+HRESULT
+EnumMethodDefinitions::Next(ClrDataAccess* dac,
+ IXCLRDataMethodDefinition **method)
+{
+ HRESULT status;
+
+ NextType:
+ if (m_typeToken == mdTokenNil)
+ {
+ if ((status = m_typeEnum.NextToken(&m_typeToken, NULL, NULL)) != S_OK)
+ {
+ return status;
+ }
+
+ m_needMethodStart = true;
+ }
+
+ if (m_needMethodStart)
+ {
+ if ((status = m_methodEnum.
+ Start(m_module->GetMDImport(),
+ mdtMethodDef, m_typeToken)) != S_OK)
+ {
+ return status;
+ }
+
+ m_needMethodStart = false;
+ }
+
+ NextMethod:
+ mdToken methodToken;
+
+ if ((status = m_methodEnum.NextToken(&methodToken, NULL, NULL)) != S_OK)
+ {
+ if (status == S_FALSE)
+ {
+ m_typeToken = mdTokenNil;
+ goto NextType;
+ }
+
+ return status;
+ }
+
+ if (m_useAddrFilter)
+ {
+ ULONG ilRva;
+ ULONG implFlags;
+
+ status = m_module->GetMDImport()->
+ GetMethodImplProps(methodToken, &ilRva, &implFlags);
+ if (FAILED(status))
+ {
+ return status;
+ }
+ if (!ilRva)
+ {
+ goto NextMethod;
+ }
+
+ COR_ILMETHOD* ilMeth =
+ DacGetIlMethod(m_module->GetIL((RVA)ilRva));
+ COR_ILMETHOD_DECODER ilDec(ilMeth);
+
+ CLRDATA_ADDRESS start =
+ TO_CDADDR(PTR_HOST_TO_TADDR(ilMeth) + 4 * ilDec.GetSize());
+ if (m_addrFilter < start ||
+ m_addrFilter > start + ilDec.GetCodeSize() - 1)
+ {
+ goto NextMethod;
+ }
+ }
+
+ return ClrDataMethodDefinition::
+ NewFromModule(dac,
+ m_module,
+ methodToken,
+ NULL,
+ method);
+}
+
+HRESULT
+EnumMethodDefinitions::CdStart(Module* mod,
+ bool useAddrFilter,
+ CLRDATA_ADDRESS addrFilter,
+ CLRDATA_ENUM* handle)
+{
+ HRESULT status;
+
+ *handle = NULL;
+
+ if (!mod)
+ {
+ return S_FALSE;
+ }
+
+ EnumMethodDefinitions* iter = new (nothrow)
+ EnumMethodDefinitions;
+ if (!iter)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ if ((status = iter->Start(mod, useAddrFilter, addrFilter)) != S_OK)
+ {
+ delete iter;
+ return status;
+ }
+
+ *handle = TO_CDENUM(iter);
+ return S_OK;
+}
+
+HRESULT
+EnumMethodDefinitions::CdNext(ClrDataAccess* dac,
+ CLRDATA_ENUM* handle,
+ IXCLRDataMethodDefinition** method)
+{
+ EnumMethodDefinitions* iter = FROM_CDENUM(EnumMethodDefinitions, *handle);
+ if (!iter)
+ {
+ return S_FALSE;
+ }
+
+ return iter->Next(dac, method);
+}
+
+HRESULT
+EnumMethodDefinitions::CdEnd(CLRDATA_ENUM handle)
+{
+ EnumMethodDefinitions* iter = FROM_CDENUM(EnumMethodDefinitions, handle);
+ if (iter)
+ {
+ delete iter;
+ return S_OK;
+ }
+ else
+ {
+ return E_INVALIDARG;
+ }
+}
+
+//----------------------------------------------------------------------------
+//
+// EnumMethodInstances.
+//
+//----------------------------------------------------------------------------
+
+EnumMethodInstances::EnumMethodInstances(MethodDesc* methodDesc,
+ IXCLRDataAppDomain* givenAppDomain)
+ : m_domainIter(FALSE)
+{
+ m_methodDesc = methodDesc;
+ if (givenAppDomain)
+ {
+ m_givenAppDomain =
+ ((ClrDataAppDomain*)givenAppDomain)->GetAppDomain();
+ }
+ else
+ {
+ m_givenAppDomain = NULL;
+ }
+ m_givenAppDomainUsed = false;
+ m_appDomain = NULL;
+}
+
+HRESULT
+EnumMethodInstances::Next(ClrDataAccess* dac,
+ IXCLRDataMethodInstance **instance)
+{
+ NextDomain:
+ if (!m_appDomain)
+ {
+ if (m_givenAppDomainUsed ||
+ !m_domainIter.Next())
+ {
+ return S_FALSE;
+ }
+
+ if (m_givenAppDomain)
+ {
+ m_appDomain = m_givenAppDomain;
+ m_givenAppDomainUsed = true;
+ }
+ else
+ {
+ m_appDomain = m_domainIter.GetDomain();
+ }
+
+ m_methodIter.Start(m_appDomain,
+ m_methodDesc->GetModule(), // module
+ m_methodDesc->GetMemberDef(), // token
+ m_methodDesc); // intial method desc
+ }
+
+ NextMethod:
+ {
+ // Note: DAC doesn't need to keep the assembly alive - see code:CollectibleAssemblyHolder#CAH_DAC
+ CollectibleAssemblyHolder<DomainAssembly *> pDomainAssembly;
+ if (!m_methodIter.Next(pDomainAssembly.This()))
+ {
+ m_appDomain = NULL;
+ goto NextDomain;
+ }
+ }
+
+ if (!m_methodIter.Current()->HasNativeCode())
+ {
+ goto NextMethod;
+ }
+
+ *instance = new (nothrow)
+ ClrDataMethodInstance(dac,
+ m_appDomain,
+ m_methodIter.Current());
+ return *instance ? S_OK : E_OUTOFMEMORY;
+}
+
+HRESULT
+EnumMethodInstances::CdStart(MethodDesc* methodDesc,
+ IXCLRDataAppDomain* appDomain,
+ CLRDATA_ENUM* handle)
+{
+ if (!methodDesc->HasClassOrMethodInstantiation() &&
+ !methodDesc->HasNativeCode())
+ {
+ *handle = 0;
+ return S_FALSE;
+ }
+
+ EnumMethodInstances* iter = new (nothrow)
+ EnumMethodInstances(methodDesc, appDomain);
+ if (iter)
+ {
+ *handle = TO_CDENUM(iter);
+ return S_OK;
+ }
+ else
+ {
+ *handle = 0;
+ return E_OUTOFMEMORY;
+ }
+}
+
+HRESULT
+EnumMethodInstances::CdNext(ClrDataAccess* dac,
+ CLRDATA_ENUM* handle,
+ IXCLRDataMethodInstance** method)
+{
+ EnumMethodInstances* iter = FROM_CDENUM(EnumMethodInstances, *handle);
+ if (!iter)
+ {
+ return S_FALSE;
+ }
+
+ return iter->Next(dac, method);
+}
+
+HRESULT
+EnumMethodInstances::CdEnd(CLRDATA_ENUM handle)
+{
+ EnumMethodInstances* iter = FROM_CDENUM(EnumMethodInstances, handle);
+ if (iter)
+ {
+ delete iter;
+ return S_OK;
+ }
+ else
+ {
+ return E_INVALIDARG;
+ }
+}
diff --git a/src/debug/dbgutil/.gitmirror b/src/debug/dbgutil/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/debug/dbgutil/.gitmirror
@@ -0,0 +1 @@
+Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror. \ No newline at end of file
diff --git a/src/debug/dbgutil/CMakeLists.txt b/src/debug/dbgutil/CMakeLists.txt
new file mode 100644
index 0000000000..1c0d49a24e
--- /dev/null
+++ b/src/debug/dbgutil/CMakeLists.txt
@@ -0,0 +1,16 @@
+if(WIN32)
+ #use static crt
+ add_definitions(-MT)
+endif(WIN32)
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+set(DBGUTIL_SOURCES
+ dbgutil.cpp
+)
+
+if(CLR_CMAKE_PLATFORM_UNIX)
+ add_compile_options(-fPIC)
+endif(CLR_CMAKE_PLATFORM_UNIX)
+
+add_library_clr(dbgutil STATIC ${DBGUTIL_SOURCES})
diff --git a/src/debug/dbgutil/dbgutil.cpp b/src/debug/dbgutil/dbgutil.cpp
new file mode 100644
index 0000000000..a16849868c
--- /dev/null
+++ b/src/debug/dbgutil/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 <dbgutil.h>
+#include "corerror.h"
+#include <assert.h>
+#include <stdio.h>
+
+// 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/debug/dbgutil/dbgutil.props b/src/debug/dbgutil/dbgutil.props
new file mode 100644
index 0000000000..520b2c6010
--- /dev/null
+++ b/src/debug/dbgutil/dbgutil.props
@@ -0,0 +1,14 @@
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <TargetType>LIBRARY</TargetType>
+ <OutputPath>$(ClrLibDest)</OutputPath>
+ <LinkSubsystem>windows</LinkSubsystem>
+ <UseMsvcrt />
+ <ExceptionHandling>$(Sehonly)</ExceptionHandling>
+ <UserIncludes>$(UserIncludes);$(Clrbase)\src\Debug\inc;</UserIncludes>
+ <CDefines>$(CDefines);UNICODE;_UNICODE</CDefines>
+ </PropertyGroup>
+ <ItemGroup>
+ <CppCompile Include="$(Clrbase)\src\Debug\dbgutil\dbgutil.cpp" />
+ </ItemGroup>
+</Project>
diff --git a/src/debug/dbgutil/dirs.proj b/src/debug/dbgutil/dirs.proj
new file mode 100644
index 0000000000..f4c41b903a
--- /dev/null
+++ b/src/debug/dbgutil/dirs.proj
@@ -0,0 +1,16 @@
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\clr.props" />
+
+ <PropertyGroup>
+ <BuildInPhase1>true</BuildInPhase1>
+ <BuildInPhaseDefault>false</BuildInPhaseDefault>
+ <BuildCoreBinaries>true</BuildCoreBinaries>
+ <BuildSysBinaries>true</BuildSysBinaries>
+ </PropertyGroup>
+
+ <ItemGroup Condition="'$(BuildExePhase)' == '1'">
+ <ProjectFile Include="HostLocal\dbgutil.nativeproj" />
+ </ItemGroup>
+
+ <Import Project="$(_NTDRIVE)$(_NTROOT)\tools\Microsoft.DevDiv.Traversal.targets" />
+</Project>
diff --git a/src/debug/debug-pal/.gitmirror b/src/debug/debug-pal/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/debug/debug-pal/.gitmirror
@@ -0,0 +1 @@
+Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror. \ No newline at end of file
diff --git a/src/debug/debug-pal/CMakeLists.txt b/src/debug/debug-pal/CMakeLists.txt
new file mode 100644
index 0000000000..c96d7f9e06
--- /dev/null
+++ b/src/debug/debug-pal/CMakeLists.txt
@@ -0,0 +1,31 @@
+
+include_directories(../inc)
+include_directories(../../pal/inc)
+
+add_definitions(-DPAL_STDCPP_COMPAT=1)
+
+if(WIN32)
+ #use static crt
+ add_definitions(-MT)
+ add_definitions(-DWIN32_LEAN_AND_MEAN)
+ include_directories(../../inc) #needed for warning control
+
+ set(TWO_WAY_PIPE_SOURCES
+ win/twowaypipe.cpp
+ )
+endif(WIN32)
+
+if(CLR_CMAKE_PLATFORM_UNIX)
+ add_compile_options(-fPIC)
+
+ add_definitions(-DFEATURE_PAL)
+ add_definitions(-DPAL_IMPLEMENTATION)
+ add_definitions(-D_POSIX_C_SOURCE=200809L)
+
+ set(TWO_WAY_PIPE_SOURCES
+ unix/twowaypipe.cpp
+ )
+
+endif(CLR_CMAKE_PLATFORM_UNIX)
+
+_add_library(debug-pal STATIC ${TWO_WAY_PIPE_SOURCES})
diff --git a/src/debug/debug-pal/unix/.gitmirror b/src/debug/debug-pal/unix/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/debug/debug-pal/unix/.gitmirror
@@ -0,0 +1 @@
+Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror. \ No newline at end of file
diff --git a/src/debug/debug-pal/unix/twowaypipe.cpp b/src/debug/debug-pal/unix/twowaypipe.cpp
new file mode 100644
index 0000000000..db4599aeb9
--- /dev/null
+++ b/src/debug/debug-pal/unix/twowaypipe.cpp
@@ -0,0 +1,181 @@
+// 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 <pal.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <limits.h>
+#include <pal_assert.h>
+#include "twowaypipe.h"
+
+// Creates a server side of the pipe.
+// Id is used to create pipes names and uniquely identify the pipe on the machine.
+// true - success, false - failure (use GetLastError() for more details)
+bool TwoWayPipe::CreateServer(DWORD id)
+{
+ _ASSERTE(m_state == NotInitialized);
+ if (m_state != NotInitialized)
+ return false;
+
+ m_id = id;
+ PAL_GetTransportPipeName(m_inPipeName, id, "in");
+ PAL_GetTransportPipeName(m_outPipeName, id, "out");
+
+ if (mkfifo(m_inPipeName, S_IRWXU) == -1)
+ {
+ return false;
+ }
+
+ if (mkfifo(m_outPipeName, S_IRWXU) == -1)
+ {
+ unlink(m_inPipeName);
+ return false;
+ }
+
+ m_state = Created;
+ return true;
+}
+
+// Connects to a previously opened server side of the pipe.
+// Id is used to locate the pipe on the machine.
+// true - success, false - failure (use GetLastError() for more details)
+bool TwoWayPipe::Connect(DWORD id)
+{
+ _ASSERTE(m_state == NotInitialized);
+ if (m_state != NotInitialized)
+ return false;
+
+ m_id = id;
+ //"in" and "out" are switched deliberately, because we're on the client
+ PAL_GetTransportPipeName(m_inPipeName, id, "out");
+ PAL_GetTransportPipeName(m_outPipeName, id, "in");
+
+ // Pipe opening order is reversed compared to WaitForConnection()
+ // in order to avaid deadlock.
+ m_outboundPipe = open(m_outPipeName, O_WRONLY);
+ if (m_outboundPipe == INVALID_PIPE)
+ {
+ return false;
+ }
+
+ m_inboundPipe = open(m_inPipeName, O_RDONLY);
+ if (m_inboundPipe == INVALID_PIPE)
+ {
+ close(m_outboundPipe);
+ m_outboundPipe = INVALID_PIPE;
+ return false;
+ }
+
+ m_state = ClientConnected;
+ return true;
+
+}
+
+// Waits for incoming client connections, assumes GetState() == Created
+// true - success, false - failure (use GetLastError() for more details)
+bool TwoWayPipe::WaitForConnection()
+{
+ _ASSERTE(m_state == Created);
+ if (m_state != Created)
+ return false;
+
+ m_inboundPipe = open(m_inPipeName, O_RDONLY);
+ if (m_inboundPipe == INVALID_PIPE)
+ {
+ return false;
+ }
+
+ m_outboundPipe = open(m_outPipeName, O_WRONLY);
+ if (m_outboundPipe == INVALID_PIPE)
+ {
+ close(m_inboundPipe);
+ m_inboundPipe = INVALID_PIPE;
+ return false;
+ }
+
+ m_state = ServerConnected;
+ return true;
+}
+
+// Reads data from pipe. Returns number of bytes read or a negative number in case of an error.
+// use GetLastError() for more details
+// UNIXTODO - mjm 9/6/15 - does not set last error on failure
+int TwoWayPipe::Read(void *buffer, DWORD bufferSize)
+{
+ _ASSERTE(m_state == ServerConnected || m_state == ClientConnected);
+
+ int totalBytesRead = 0;
+ int bytesRead;
+ int cb = bufferSize;
+
+ while ((bytesRead = (int)read(m_inboundPipe, buffer, cb)) > 0)
+ {
+ totalBytesRead += bytesRead;
+ _ASSERTE(totalBytesRead <= bufferSize);
+ if (totalBytesRead >= bufferSize)
+ {
+ break;
+ }
+
+ buffer = (char*)buffer + bytesRead;
+ cb -= bytesRead;
+ }
+
+ return bytesRead == -1 ? -1 : totalBytesRead;
+}
+
+// Writes data to pipe. Returns number of bytes written or a negative number in case of an error.
+// use GetLastError() for more details
+// UNIXTODO - mjm 9/6/15 - does not set last error on failure
+int TwoWayPipe::Write(const void *data, DWORD dataSize)
+{
+ _ASSERTE(m_state == ServerConnected || m_state == ClientConnected);
+
+ int totalBytesWritten = 0;
+ int bytesWritten;
+ int cb = dataSize;
+
+ while ((bytesWritten = (int)write(m_outboundPipe, data, cb)) > 0)
+ {
+ totalBytesWritten += bytesWritten;
+ _ASSERTE(totalBytesWritten <= dataSize);
+ if (totalBytesWritten >= dataSize)
+ {
+ break;
+ }
+
+ data = (char*)data + bytesWritten;
+ cb -= bytesWritten;
+ }
+
+ return bytesWritten == -1 ? -1 : totalBytesWritten;
+}
+
+// Disconnect server or client side of the pipe.
+// true - success, false - failure (use GetLastError() for more details)
+bool TwoWayPipe::Disconnect()
+{
+ // IMPORTANT NOTE: This function must not call any signal unsafe functions
+ // since it is called from signal handlers.
+ // That includes ASSERT and TRACE macros.
+
+ if (m_state == ServerConnected || m_state == Created)
+ {
+ unlink(m_inPipeName);
+ unlink(m_outPipeName);
+ }
+
+ m_state = NotInitialized;
+ return true;
+}
+
+// Used by debugger side (RS) to cleanup the target (LS) named pipes
+// and semaphores when the debugger detects the debuggee process exited.
+void TwoWayPipe::CleanupTargetProcess()
+{
+ unlink(m_inPipeName);
+ unlink(m_outPipeName);
+}
diff --git a/src/debug/debug-pal/win/.gitmirror b/src/debug/debug-pal/win/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/debug/debug-pal/win/.gitmirror
@@ -0,0 +1 @@
+Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror. \ No newline at end of file
diff --git a/src/debug/debug-pal/win/twowaypipe.cpp b/src/debug/debug-pal/win/twowaypipe.cpp
new file mode 100644
index 0000000000..6e4c9127d7
--- /dev/null
+++ b/src/debug/debug-pal/win/twowaypipe.cpp
@@ -0,0 +1,210 @@
+// 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 <windows.h>
+#include <stdio.h>
+#include <wchar.h>
+#include <assert.h>
+#include "twowaypipe.h"
+
+#define _ASSERTE assert
+
+// This file contains implementation of a simple IPC mechanism - bidirectional named pipe.
+// It is implemented on top of two one-directional names pipes (fifos on UNIX)
+
+
+// Creates a server side of the pipe.
+// Id is used to create pipes names and uniquely identify the pipe on the machine.
+// true - success, false - failure (use GetLastError() for more details)
+bool TwoWayPipe::CreateServer(DWORD id)
+{
+ _ASSERTE(m_state == NotInitialized);
+ if (m_state != NotInitialized)
+ return false;
+
+ m_inboundPipe = CreateOneWayPipe(id, true);
+ if (m_inboundPipe == INVALID_HANDLE_VALUE)
+ {
+ return false;
+ }
+
+ m_outboundPipe = CreateOneWayPipe(id, false);
+ if (m_outboundPipe == INVALID_HANDLE_VALUE)
+ {
+ CloseHandle(m_inboundPipe);
+ m_inboundPipe = INVALID_HANDLE_VALUE;
+ return false;
+ }
+
+ m_state = Created;
+ return true;
+}
+
+
+// Connects to a previously opened server side of the pipe.
+// Id is used to locate the pipe on the machine.
+// true - success, false - failure (use GetLastError() for more details)
+bool TwoWayPipe::Connect(DWORD id)
+{
+ _ASSERTE(m_state == NotInitialized);
+ if (m_state != NotInitialized)
+ return false;
+
+ m_inboundPipe = OpenOneWayPipe(id, true);
+ if (m_inboundPipe == INVALID_HANDLE_VALUE)
+ {
+ return false;
+ }
+
+ m_outboundPipe = OpenOneWayPipe(id, false);
+ if (m_outboundPipe == INVALID_HANDLE_VALUE)
+ {
+ CloseHandle(m_inboundPipe);
+ m_inboundPipe = INVALID_HANDLE_VALUE;
+ return false;
+ }
+
+ m_state = ClientConnected;
+ return true;
+
+}
+
+// Waits for incoming client connections, assumes GetState() == Created
+// true - success, false - failure (use GetLastError() for more details)
+bool TwoWayPipe::WaitForConnection()
+{
+ _ASSERTE(m_state == Created);
+ if (m_state != Created)
+ return false;
+
+ if (!ConnectNamedPipe(m_inboundPipe, NULL))
+ {
+ auto error = GetLastError();
+ if (error != ERROR_PIPE_CONNECTED)
+ return false;
+ }
+
+ if (!ConnectNamedPipe(m_outboundPipe, NULL))
+ {
+ auto error = GetLastError();
+ if (error != ERROR_PIPE_CONNECTED)
+ return false;
+ }
+
+ m_state = ServerConnected;
+ return true;
+}
+
+// Reads data from pipe. Returns number of bytes read or a negative number in case of an error.
+// use GetLastError() for more details
+int TwoWayPipe::Read(void *buffer, DWORD bufferSize)
+{
+ _ASSERTE(m_state == ServerConnected || m_state == ClientConnected);
+ DWORD bytesRead;
+ BOOL ok = ReadFile(m_inboundPipe, buffer, bufferSize, &bytesRead, NULL);
+
+ if (ok)
+ {
+ return (int)bytesRead;
+ }
+ else
+ {
+ return -1;
+ }
+}
+
+// Writes data to pipe. Returns number of bytes written or a negative number in case of an error.
+// use GetLastError() for more details
+int TwoWayPipe::Write(const void *data, DWORD dataSize)
+{
+ _ASSERTE(m_state == ServerConnected || m_state == ClientConnected);
+ DWORD bytesWritten;
+ BOOL ok = WriteFile(m_outboundPipe, data, dataSize, &bytesWritten, NULL);
+
+ if (ok)
+ {
+ FlushFileBuffers(m_outboundPipe);
+ return (int)bytesWritten;
+ }
+ else
+ {
+ return -1;
+ }
+}
+
+// Disconnect server or client side of the pipe.
+// true - success, false - failure (use GetLastError() for more details)
+bool TwoWayPipe::Disconnect()
+{
+ if (m_state == ServerConnected)
+ {
+ DisconnectNamedPipe(m_outboundPipe);
+ DisconnectNamedPipe(m_inboundPipe);
+ CloseHandle(m_outboundPipe);
+ m_outboundPipe = INVALID_HANDLE_VALUE;
+ CloseHandle(m_inboundPipe);
+ m_inboundPipe = INVALID_HANDLE_VALUE;
+ m_state = NotInitialized;
+ return true;
+ }
+ else if (m_state == ClientConnected)
+ {
+ CloseHandle(m_outboundPipe);
+ m_outboundPipe = INVALID_HANDLE_VALUE;
+ CloseHandle(m_inboundPipe);
+ m_inboundPipe = INVALID_HANDLE_VALUE;
+ m_state = NotInitialized;
+ return true;
+ }
+ else
+ {
+ // nothign to do
+ return true;
+ }
+}
+
+#define PIPE_NAME_FORMAT_STR L"\\\\.\\pipe\\clr-debug-pipe-%d-%s"
+
+// Connects to a one sided pipe previously created by CreateOneWayPipe.
+// In order to successfully connect id and inbound flag should be the same.
+HANDLE TwoWayPipe::OpenOneWayPipe(DWORD id, bool inbound)
+{
+ WCHAR fullName[MAX_PATH];
+ // "in" and "out" are deliberately switched because we're opening a client side connection
+ int chars = swprintf_s(fullName, MAX_PATH, PIPE_NAME_FORMAT_STR, id, inbound ? L"out" : L"in");
+ _ASSERTE(chars > 0);
+
+ HANDLE handle = CreateFileW(
+ fullName,
+ inbound ? GENERIC_READ : GENERIC_WRITE,
+ 0, // no sharing
+ NULL, // default security attributes
+ OPEN_EXISTING, // opens existing pipe
+ 0, // default attributes
+ NULL); // no template file
+
+ return handle;
+}
+
+
+// Creates a one way pipe, id and inboud flag are used for naming.
+// Created pipe is supposed to be connected to by OpenOneWayPipe.
+HANDLE TwoWayPipe::CreateOneWayPipe(DWORD id, bool inbound)
+{
+ WCHAR fullName[MAX_PATH];
+ int chars = swprintf_s(fullName, MAX_PATH, PIPE_NAME_FORMAT_STR, id, inbound ? L"in" : L"out");
+ _ASSERTE(chars > 0);
+
+ HANDLE handle = CreateNamedPipeW(fullName,
+ (inbound ? PIPE_ACCESS_INBOUND : PIPE_ACCESS_OUTBOUND) | FILE_FLAG_FIRST_PIPE_INSTANCE,
+ PIPE_TYPE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS,
+ 1, // max number of instances
+ 4000, //in buffer size
+ 4000, //out buffer size
+ 0, // default timeout
+ NULL); // default security
+
+ return handle;
+}
+
diff --git a/src/debug/di/.gitmirror b/src/debug/di/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/debug/di/.gitmirror
@@ -0,0 +1 @@
+Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror. \ No newline at end of file
diff --git a/src/debug/di/CMakeLists.txt b/src/debug/di/CMakeLists.txt
new file mode 100644
index 0000000000..abede90d2e
--- /dev/null
+++ b/src/debug/di/CMakeLists.txt
@@ -0,0 +1,77 @@
+add_definitions(-DFEATURE_METADATA_CUSTOM_DATA_SOURCE -DFEATURE_METADATA_DEBUGGEE_DATA_SOURCE -DFEATURE_NO_HOST -DFEATURE_METADATA_LOAD_TRUSTED_IMAGES)
+
+set(CORDBDI_SOURCES
+ shimprocess.cpp
+ shimcallback.cpp
+ shimevents.cpp
+ shimdatatarget.cpp
+ shimstackwalk.cpp
+ breakpoint.cpp
+ cordb.cpp
+ divalue.cpp
+ dbgtransportmanager.cpp
+ hash.cpp
+ module.cpp
+ nativepipeline.cpp
+ platformspecific.cpp
+ process.cpp
+ rsappdomain.cpp
+ rsassembly.cpp
+ rsclass.cpp
+ rsfunction.cpp
+ rsmain.cpp
+ rsmda.cpp
+ rsregsetcommon.cpp
+ rsstackwalk.cpp
+ rsthread.cpp
+ rstype.cpp
+ shared.cpp
+ symbolinfo.cpp
+ valuehome.cpp
+)
+
+if(WIN32)
+ #use static crt
+ add_definitions(-MT)
+
+ if (CLR_CMAKE_TARGET_ARCH_AMD64 OR CLR_CMAKE_TARGET_ARCH_ARM64)
+ set(CORDBDI_SOURCES_ASM_FILE ${ARCH_SOURCES_DIR}/floatconversion.asm)
+ endif()
+ if (CLR_CMAKE_TARGET_ARCH_AMD64)
+ set(CORDBDI_SOURCES
+ ${CORDBDI_SOURCES}
+ ${CORDBDI_SOURCES_ASM_FILE}
+ )
+ elseif (CLR_CMAKE_TARGET_ARCH_ARM64 AND NOT DEFINED CLR_CROSS_COMPONENTS_BUILD)
+ convert_to_absolute_path(CORDBDI_SOURCES_ASM_FILE ${CORDBDI_SOURCES_ASM_FILE})
+ get_compile_definitions(ASM_DEFINITIONS)
+ set(ASM_OPTIONS /c /Zi /W3 /errorReport:prompt)
+ # asm files require preprocessing using cl.exe on arm64
+ get_filename_component(name ${CORDBDI_SOURCES_ASM_FILE} NAME_WE)
+ set(ASM_PREPROCESSED_FILE ${CMAKE_CURRENT_BINARY_DIR}/${name}.asm)
+ preprocess_def_file(${CORDBDI_SOURCES_ASM_FILE} ${ASM_PREPROCESSED_FILE})
+ set(CORDBDI_SOURCES_WKS_PREPROCESSED_ASM ${ASM_PREPROCESSED_FILE})
+
+ set_property(SOURCE ${CORDBDI_SOURCES_WKS_PREPROCESSED_ASM} PROPERTY COMPILE_DEFINITIONS ${ASM_DEFINITIONS})
+ set_property(SOURCE ${CORDBDI_SOURCES_WKS_PREPROCESSED_ASM} PROPERTY COMPILE_DEFINITIONS ${ASM_OPTIONS})
+ set(CORDBDI_SOURCES
+ ${CORDBDI_SOURCES}
+ ${CORDBDI_SOURCES_WKS_PREPROCESSED_ASM}
+ )
+ endif()
+elseif(CLR_CMAKE_PLATFORM_UNIX)
+ add_compile_options(-fPIC)
+
+ if(CLR_CMAKE_TARGET_ARCH_AMD64)
+ set(CORDBDI_SOURCES
+ ${CORDBDI_SOURCES}
+ ${ARCH_SOURCES_DIR}/floatconversion.S
+ )
+ endif()
+
+endif(WIN32)
+
+add_precompiled_header(stdafx.h stdafx.cpp CORDBDI_SOURCES)
+
+
+add_library_clr(cordbdi STATIC ${CORDBDI_SOURCES})
diff --git a/src/debug/di/DI.props b/src/debug/di/DI.props
new file mode 100644
index 0000000000..1d7336dab0
--- /dev/null
+++ b/src/debug/di/DI.props
@@ -0,0 +1,86 @@
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <!--*****************************************************-->
+ <!--This MSBuild project file was automatically generated-->
+ <!--from the original SOURCES/DIRS file by the KBC tool.-->
+ <!--*****************************************************-->
+ <!-- These features need to be enabled for each build artifact that wants to use them, they aren't controlled at the SKU level-->
+ <PropertyGroup>
+ <FeatureMetadataCustomDataSource>true</FeatureMetadataCustomDataSource>
+ <FeatureMetadataDebuggeeDataSource>true</FeatureMetadataDebuggeeDataSource>
+ </PropertyGroup>
+ <!--Import the settings-->
+ <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\clr.props" />
+ <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\src\Debug\XPlatCommon.props"/>
+ <!--Leaf project Properties-->
+ <PropertyGroup>
+ <UserIncludes>
+ $(UserIncludes);
+ ..;
+ ..\..\inc;
+ ..\..\inc\dump;
+ ..\..\..\vm;
+ $(VCToolsIncPath);
+ </UserIncludes>
+ <ClAdditionalOptions>$(ClAdditionalOptions) -DUNICODE -D_UNICODE -DFEATURE_NO_HOST -DFEATURE_METADATA_LOAD_TRUSTED_IMAGES</ClAdditionalOptions>
+ <OutputPath>$(ClrLibDest)</OutputPath>
+ <TargetType>LIBRARY</TargetType>
+ <PCHHeader Condition="'$(CCOVER)' == ''">stdafx.h</PCHHeader>
+ <EnableCxxPCHHeaders Condition="'$(CCOVER)' == ''">true</EnableCxxPCHHeaders>
+ <!--PCH: Both precompiled header and cpp are on the same ..\ path this is likely to be wrong.-->
+ <PCHCompile Condition="'$(CCOVER)' == ''">..\stdafx.cpp</PCHCompile>
+ <LinkNoLibraries>true</LinkNoLibraries>
+ <LinkUseCMT>true</LinkUseCMT>
+ <UseMsvcrt />
+ </PropertyGroup>
+ <!--Leaf Project Items-->
+ <ItemGroup>
+ <ProjectReference Condition="'$(XPlatHostLibBuildDir)'=='HostLocal'" Include="$(ClrSrcDirectory)inc\corguids.nativeproj" />
+ <ProjectReference Condition="'$(XPlatHostLibBuildDir)'=='HostWinx86'" Include="$(ClrSrcDirectory)incx86\corguids.nativeproj" />
+ <ProjectReference Condition="'$(XPlatHostLibBuildDir)'=='HostWinAMD64'" Include="$(ClrSrcDirectory)incamd64\corguids.nativeproj" />
+ </ItemGroup>
+ <ItemGroup>
+ <SourcesPublish Include="..\publish.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <SourcesShim Include="..\ShimProcess.cpp" />
+ <SourcesShim Include="..\ShimCallback.cpp" />
+ <SourcesShim Include="..\ShimEvents.cpp" />
+ <SourcesShim Include="..\ShimDataTarget.cpp" />
+ <SourcesShim Include="..\ShimStackWalk.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <SourcesRightside Include="..\breakpoint.cpp" />
+ <SourcesRightside Include="..\cordb.cpp" />
+ <SourcesRightside Include="..\DbgTransportManager.cpp" />
+ <SourcesRightside Include="..\DIValue.cpp" />
+ <SourcesRightside Include="..\hash.cpp" />
+ <SourcesRightside Include="..\module.cpp" />
+ <SourcesRightside Include="..\NativePipeline.cpp" />
+ <SourcesRightside Include="..\PlatformSpecific.cpp" />
+ <SourcesRightside Include="..\process.cpp" />
+ <SourcesRightside Include="..\RsAppDomain.cpp" />
+ <SourcesRightside Include="..\RsAssembly.cpp" />
+ <SourcesRightside Include="..\RsClass.cpp" />
+ <SourcesRightside Include="..\RsFunction.cpp" />
+ <SourcesRightside Include="..\RsMain.cpp" />
+ <SourcesRightside Include="..\RsMda.cpp" />
+ <SourcesRightside Include="..\RsRegSetCommon.cpp" />
+ <SourcesRightside Include="..\RsStackWalk.cpp" />
+ <SourcesRightside Include="..\RsThread.cpp" />
+ <SourcesRightside Include="..\RsType.cpp" />
+ <SourcesRightside Include="..\shared.cpp" />
+ <SourcesRightside Include="..\symbolinfo.cpp" />
+ <SourcesRightside Include="..\ValueHome.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <CppCompile Include="@(SourcesPublish)" />
+ <CppCompile Include="@(SourcesShim)" />
+ <CppCompile Include="@(SourcesRightside)" />
+ <AssembleAmd64 Condition="'$(BuildArchitecture)' == 'amd64' and '$(CrossTargetArchitecture)' != 'arm64'" Include="..\amd64\floatconversion.asm" />
+ </ItemGroup>
+ <ItemGroup Condition="'$(BuildArchitecture)' == 'arm64'">
+ <PreprocessAssembleArm Include="..\arm64\floatconversion.asm" />
+ <AssembleArm64 Include="$(IntermediateOutputDirectory)\floatconversion.i" />
+ </ItemGroup>
+ <!--Import the targets-->
+</Project>
diff --git a/src/debug/di/ICorDebugValueTypes.vsd b/src/debug/di/ICorDebugValueTypes.vsd
new file mode 100644
index 0000000000..b16ba39fe4
--- /dev/null
+++ b/src/debug/di/ICorDebugValueTypes.vsd
Binary files differ
diff --git a/src/debug/di/amd64/.gitmirror b/src/debug/di/amd64/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/debug/di/amd64/.gitmirror
@@ -0,0 +1 @@
+Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror. \ No newline at end of file
diff --git a/src/debug/di/amd64/FloatConversion.asm b/src/debug/di/amd64/FloatConversion.asm
new file mode 100644
index 0000000000..0de4a89bb4
--- /dev/null
+++ b/src/debug/di/amd64/FloatConversion.asm
@@ -0,0 +1,26 @@
+; 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.
+
+;// ==++==
+;//
+
+;//
+;// ==--==
+
+;// @dbgtodo Microsoft inspection: remove the implementation from vm\amd64\getstate.asm when we remove the
+;// ipc event to load the float state.
+
+;// this is the same implementation as the function of the same name in vm\amd64\getstate.asm and they must
+;// remain in sync.
+;// Arguments
+;// input: (in rcx) the M128 value to be converted to a double
+;// output: the double corresponding to the M128 input value
+
+_TEXT segment para 'CODE'
+FPFillR8 proc
+ movdqa xmm0, [rcx]
+ ret
+FPFillR8 endp
+
+END
diff --git a/src/debug/di/amd64/cordbregisterset.cpp b/src/debug/di/amd64/cordbregisterset.cpp
new file mode 100644
index 0000000000..8df779e758
--- /dev/null
+++ b/src/debug/di/amd64/cordbregisterset.cpp
@@ -0,0 +1,254 @@
+// 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.
+//*****************************************************************************
+// File: CordbRegisterSet.cpp
+//
+
+//
+//*****************************************************************************
+#include "primitives.h"
+
+
+HRESULT CordbRegisterSet::GetRegistersAvailable(ULONG64* pAvailable)
+{
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pAvailable, ULONG64*);
+
+ (*pAvailable) = SETBITULONG64( REGISTER_INSTRUCTION_POINTER )
+ | SETBITULONG64( REGISTER_STACK_POINTER );
+
+ if (!m_quickUnwind || m_active)
+ (*pAvailable) |= SETBITULONG64( REGISTER_AMD64_RBP )
+ | SETBITULONG64( REGISTER_AMD64_RAX )
+ | SETBITULONG64( REGISTER_AMD64_RCX )
+ | SETBITULONG64( REGISTER_AMD64_RDX )
+ | SETBITULONG64( REGISTER_AMD64_RBX )
+ | SETBITULONG64( REGISTER_AMD64_RSI )
+ | SETBITULONG64( REGISTER_AMD64_RDI )
+ | SETBITULONG64( REGISTER_AMD64_R8 )
+ | SETBITULONG64( REGISTER_AMD64_R9 )
+ | SETBITULONG64( REGISTER_AMD64_R10 )
+ | SETBITULONG64( REGISTER_AMD64_R11 )
+ | SETBITULONG64( REGISTER_AMD64_R12 )
+ | SETBITULONG64( REGISTER_AMD64_R13 )
+ | SETBITULONG64( REGISTER_AMD64_R14 )
+ | SETBITULONG64( REGISTER_AMD64_R15 );
+
+ if (m_active)
+ (*pAvailable) |= SETBITULONG64( REGISTER_AMD64_XMM0 )
+ | SETBITULONG64( REGISTER_AMD64_XMM1 )
+ | SETBITULONG64( REGISTER_AMD64_XMM2 )
+ | SETBITULONG64( REGISTER_AMD64_XMM3 )
+ | SETBITULONG64( REGISTER_AMD64_XMM4 )
+ | SETBITULONG64( REGISTER_AMD64_XMM5 )
+ | SETBITULONG64( REGISTER_AMD64_XMM6 )
+ | SETBITULONG64( REGISTER_AMD64_XMM7 )
+ | SETBITULONG64( REGISTER_AMD64_XMM8 )
+ | SETBITULONG64( REGISTER_AMD64_XMM9 )
+ | SETBITULONG64( REGISTER_AMD64_XMM10 )
+ | SETBITULONG64( REGISTER_AMD64_XMM11 )
+ | SETBITULONG64( REGISTER_AMD64_XMM12 )
+ | SETBITULONG64( REGISTER_AMD64_XMM13 )
+ | SETBITULONG64( REGISTER_AMD64_XMM14 )
+ | SETBITULONG64( REGISTER_AMD64_XMM15 );
+
+ return S_OK;
+}
+
+HRESULT CordbRegisterSet::GetRegisters(ULONG64 mask, ULONG32 regCount,
+ CORDB_REGISTER regBuffer[])
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ UINT iRegister = 0;
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(regBuffer, CORDB_REGISTER, regCount, true, true);
+
+ if ( mask & ( SETBITULONG64( REGISTER_AMD64_XMM0 )
+ | SETBITULONG64( REGISTER_AMD64_XMM1 )
+ | SETBITULONG64( REGISTER_AMD64_XMM2 )
+ | SETBITULONG64( REGISTER_AMD64_XMM3 )
+ | SETBITULONG64( REGISTER_AMD64_XMM4 )
+ | SETBITULONG64( REGISTER_AMD64_XMM5 )
+ | SETBITULONG64( REGISTER_AMD64_XMM6 )
+ | SETBITULONG64( REGISTER_AMD64_XMM7 )
+ | SETBITULONG64( REGISTER_AMD64_XMM8 )
+ | SETBITULONG64( REGISTER_AMD64_XMM9 )
+ | SETBITULONG64( REGISTER_AMD64_XMM10 )
+ | SETBITULONG64( REGISTER_AMD64_XMM11 )
+ | SETBITULONG64( REGISTER_AMD64_XMM12 )
+ | SETBITULONG64( REGISTER_AMD64_XMM13 )
+ | SETBITULONG64( REGISTER_AMD64_XMM14 )
+ | SETBITULONG64( REGISTER_AMD64_XMM15 ) ) )
+ {
+ HRESULT hr = S_OK;
+
+ if (!m_active)
+ return E_INVALIDARG;
+
+ if (!m_thread->m_fFloatStateValid)
+ {
+ EX_TRY
+ {
+ m_thread->LoadFloatState();
+ }
+ EX_CATCH_HRESULT(hr);
+
+ if ( !SUCCEEDED(hr) )
+ {
+ return hr;
+ }
+ LOG( ( LF_CORDB, LL_INFO1000, "CRS::GR: Loaded float state\n" ) );
+ }
+ }
+
+ // Make sure that the registers are really available
+ if ( mask & ( SETBITULONG64( REGISTER_AMD64_RBP )
+ | SETBITULONG64( REGISTER_AMD64_RAX )
+ | SETBITULONG64( REGISTER_AMD64_RCX )
+ | SETBITULONG64( REGISTER_AMD64_RDX )
+ | SETBITULONG64( REGISTER_AMD64_RBX )
+ | SETBITULONG64( REGISTER_AMD64_RSI )
+ | SETBITULONG64( REGISTER_AMD64_RDI )
+ | SETBITULONG64( REGISTER_AMD64_R8 )
+ | SETBITULONG64( REGISTER_AMD64_R9 )
+ | SETBITULONG64( REGISTER_AMD64_R10 )
+ | SETBITULONG64( REGISTER_AMD64_R11 )
+ | SETBITULONG64( REGISTER_AMD64_R12 )
+ | SETBITULONG64( REGISTER_AMD64_R13 )
+ | SETBITULONG64( REGISTER_AMD64_R14 )
+ | SETBITULONG64( REGISTER_AMD64_R15 ) ) )
+ {
+ if (!m_active && m_quickUnwind)
+ return E_INVALIDARG;
+ }
+
+ for ( int i = REGISTER_INSTRUCTION_POINTER
+ ; i<=REGISTER_AMD64_XMM15 && iRegister < regCount
+ ; i++)
+ {
+ if( mask & SETBITULONG64(i) )
+ {
+ switch( i )
+ {
+ case REGISTER_INSTRUCTION_POINTER:
+ regBuffer[iRegister++] = m_rd->PC; break;
+ case REGISTER_STACK_POINTER:
+ regBuffer[iRegister++] = m_rd->SP; break;
+ case REGISTER_AMD64_RBP:
+ regBuffer[iRegister++] = m_rd->Rbp; break;
+ case REGISTER_AMD64_RAX:
+ regBuffer[iRegister++] = m_rd->Rax; break;
+ case REGISTER_AMD64_RBX:
+ regBuffer[iRegister++] = m_rd->Rbx; break;
+ case REGISTER_AMD64_RCX:
+ regBuffer[iRegister++] = m_rd->Rcx; break;
+ case REGISTER_AMD64_RDX:
+ regBuffer[iRegister++] = m_rd->Rdx; break;
+ case REGISTER_AMD64_RSI:
+ regBuffer[iRegister++] = m_rd->Rsi; break;
+ case REGISTER_AMD64_RDI:
+ regBuffer[iRegister++] = m_rd->Rdi; break;
+ case REGISTER_AMD64_R8:
+ regBuffer[iRegister++] = m_rd->R8; break;
+ case REGISTER_AMD64_R9:
+ regBuffer[iRegister++] = m_rd->R9; break;
+ case REGISTER_AMD64_R10:
+ regBuffer[iRegister++] = m_rd->R10; break;
+ case REGISTER_AMD64_R11:
+ regBuffer[iRegister++] = m_rd->R11; break;
+ case REGISTER_AMD64_R12:
+ regBuffer[iRegister++] = m_rd->R12; break;
+ case REGISTER_AMD64_R13:
+ regBuffer[iRegister++] = m_rd->R13; break;
+ case REGISTER_AMD64_R14:
+ regBuffer[iRegister++] = m_rd->R14; break;
+ case REGISTER_AMD64_R15:
+ regBuffer[iRegister++] = m_rd->R15; break;
+
+ case REGISTER_AMD64_XMM0:
+ case REGISTER_AMD64_XMM1:
+ case REGISTER_AMD64_XMM2:
+ case REGISTER_AMD64_XMM3:
+ case REGISTER_AMD64_XMM4:
+ case REGISTER_AMD64_XMM5:
+ case REGISTER_AMD64_XMM6:
+ case REGISTER_AMD64_XMM7:
+ case REGISTER_AMD64_XMM8:
+ case REGISTER_AMD64_XMM9:
+ case REGISTER_AMD64_XMM10:
+ case REGISTER_AMD64_XMM11:
+ case REGISTER_AMD64_XMM12:
+ case REGISTER_AMD64_XMM13:
+ case REGISTER_AMD64_XMM14:
+ case REGISTER_AMD64_XMM15:
+ regBuffer[iRegister++] = *(CORDB_REGISTER*)
+ &(m_thread->m_floatValues[(i - REGISTER_AMD64_XMM0)]);
+ break;
+ }
+ }
+ }
+
+ _ASSERTE( iRegister <= regCount );
+ return S_OK;
+}
+
+
+HRESULT CordbRegisterSet::GetRegistersAvailable(ULONG32 regCount,
+ BYTE pAvailable[])
+{
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(pAvailable, CORDB_REGISTER, regCount, true, true);
+
+ // Defer to adapter for v1.0 interface
+ return GetRegistersAvailableAdapter(regCount, pAvailable);
+}
+
+
+HRESULT CordbRegisterSet::GetRegisters(ULONG32 maskCount, BYTE mask[],
+ ULONG32 regCount, CORDB_REGISTER regBuffer[])
+{
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(regBuffer, CORDB_REGISTER, regCount, true, true);
+
+ // Defer to adapter for v1.0 interface
+ return GetRegistersAdapter(maskCount, mask, regCount, regBuffer);
+}
+
+
+// This is just a convenience function to convert a regdisplay into a Context.
+// Since a context has more info than a regdisplay, the conversion isn't perfect
+// and the context can't be fully accurate.
+void CordbRegisterSet::InternalCopyRDToContext(DT_CONTEXT *pInputContext)
+{
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+ _ASSERTE(pInputContext);
+
+ if((pInputContext->ContextFlags & DT_CONTEXT_INTEGER)==DT_CONTEXT_INTEGER)
+ {
+ pInputContext->Rax = m_rd->Rax;
+ pInputContext->Rbx = m_rd->Rbx;
+ pInputContext->Rcx = m_rd->Rcx;
+ pInputContext->Rdx = m_rd->Rdx;
+ pInputContext->Rbp = m_rd->Rbp;
+ pInputContext->Rsi = m_rd->Rsi;
+ pInputContext->Rdi = m_rd->Rdi;
+ pInputContext->R8 = m_rd->R8;
+ pInputContext->R9 = m_rd->R9;
+ pInputContext->R10 = m_rd->R10;
+ pInputContext->R11 = m_rd->R11;
+ pInputContext->R12 = m_rd->R12;
+ pInputContext->R13 = m_rd->R13;
+ pInputContext->R14 = m_rd->R14;
+ pInputContext->R15 = m_rd->R15;
+ }
+
+
+ if((pInputContext->ContextFlags & DT_CONTEXT_CONTROL)==DT_CONTEXT_CONTROL)
+ {
+ pInputContext->Rip = m_rd->PC;
+ pInputContext->Rsp = m_rd->SP;
+ }
+}
diff --git a/src/debug/di/amd64/floatconversion.S b/src/debug/di/amd64/floatconversion.S
new file mode 100644
index 0000000000..70698d26cc
--- /dev/null
+++ b/src/debug/di/amd64/floatconversion.S
@@ -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.
+
+.intel_syntax noprefix
+#include <unixasmmacros.inc>
+
+LEAF_ENTRY FPFillR8, _TEXT
+ movdqa xmm0, xmmword ptr [rdi]
+ ret
+LEAF_END FPFillR8, _TEXT
diff --git a/src/debug/di/amd64/primitives.cpp b/src/debug/di/amd64/primitives.cpp
new file mode 100644
index 0000000000..33717cf1d0
--- /dev/null
+++ b/src/debug/di/amd64/primitives.cpp
@@ -0,0 +1,12 @@
+// 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 "../../shared/amd64/primitives.cpp"
+
+
diff --git a/src/debug/di/arm/.gitmirror b/src/debug/di/arm/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/debug/di/arm/.gitmirror
@@ -0,0 +1 @@
+Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror. \ No newline at end of file
diff --git a/src/debug/di/arm/cordbregisterset.cpp b/src/debug/di/arm/cordbregisterset.cpp
new file mode 100644
index 0000000000..092fd0c6b9
--- /dev/null
+++ b/src/debug/di/arm/cordbregisterset.cpp
@@ -0,0 +1,150 @@
+// 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.
+//*****************************************************************************
+// File: CordbRegisterSet.cpp
+//
+
+//
+//*****************************************************************************
+#include "primitives.h"
+
+HRESULT CordbRegisterSet::GetRegistersAvailable(ULONG64 *pAvailable)
+{
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pAvailable, ULONG64 *);
+
+ *pAvailable = SETBITULONG64(REGISTER_INSTRUCTION_POINTER)
+ | SETBITULONG64(REGISTER_STACK_POINTER)
+ | SETBITULONG64(REGISTER_ARM_R0)
+ | SETBITULONG64(REGISTER_ARM_R1)
+ | SETBITULONG64(REGISTER_ARM_R2)
+ | SETBITULONG64(REGISTER_ARM_R3)
+ | SETBITULONG64(REGISTER_ARM_R4)
+ | SETBITULONG64(REGISTER_ARM_R5)
+ | SETBITULONG64(REGISTER_ARM_R6)
+ | SETBITULONG64(REGISTER_ARM_R7)
+ | SETBITULONG64(REGISTER_ARM_R8)
+ | SETBITULONG64(REGISTER_ARM_R9)
+ | SETBITULONG64(REGISTER_ARM_R10)
+ | SETBITULONG64(REGISTER_ARM_R11)
+ | SETBITULONG64(REGISTER_ARM_R12)
+ | SETBITULONG64(REGISTER_ARM_LR);
+
+ return S_OK;
+}
+
+HRESULT CordbRegisterSet::GetRegisters(ULONG64 mask, ULONG32 regCount, CORDB_REGISTER regBuffer[])
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ UINT iRegister = 0;
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(regBuffer, CORDB_REGISTER, regCount, true, true);
+
+ // @ARMTODO: floating point support
+
+ for (int i = REGISTER_INSTRUCTION_POINTER;
+ i <= REGISTER_ARM_LR && iRegister < regCount;
+ i++)
+ {
+ if (mask & SETBITULONG64(i))
+ {
+ switch (i)
+ {
+ case REGISTER_INSTRUCTION_POINTER:
+ regBuffer[iRegister++] = m_rd->PC; break;
+ case REGISTER_STACK_POINTER:
+ regBuffer[iRegister++] = m_rd->SP; break;
+ case REGISTER_ARM_R0:
+ regBuffer[iRegister++] = m_rd->R0; break;
+ case REGISTER_ARM_R1:
+ regBuffer[iRegister++] = m_rd->R1; break;
+ case REGISTER_ARM_R2:
+ regBuffer[iRegister++] = m_rd->R2; break;
+ case REGISTER_ARM_R3:
+ regBuffer[iRegister++] = m_rd->R3; break;
+ case REGISTER_ARM_R4:
+ regBuffer[iRegister++] = m_rd->R4; break;
+ case REGISTER_ARM_R5:
+ regBuffer[iRegister++] = m_rd->R5; break;
+ case REGISTER_ARM_R6:
+ regBuffer[iRegister++] = m_rd->R6; break;
+ case REGISTER_ARM_R7:
+ regBuffer[iRegister++] = m_rd->R7; break;
+ case REGISTER_ARM_R8:
+ regBuffer[iRegister++] = m_rd->R8; break;
+ case REGISTER_ARM_R9:
+ regBuffer[iRegister++] = m_rd->R9; break;
+ case REGISTER_ARM_R10:
+ regBuffer[iRegister++] = m_rd->R10; break;
+ case REGISTER_ARM_R11:
+ regBuffer[iRegister++] = m_rd->R11; break;
+ case REGISTER_ARM_R12:
+ regBuffer[iRegister++] = m_rd->R12; break;
+ case REGISTER_ARM_LR:
+ regBuffer[iRegister++] = m_rd->LR; break;
+ }
+ }
+ }
+
+ _ASSERTE (iRegister <= regCount);
+ return S_OK;
+}
+
+HRESULT CordbRegisterSet::GetRegistersAvailable(ULONG32 regCount,
+ BYTE pAvailable[])
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(pAvailable, CORDB_REGISTER, regCount, true, true);
+
+ // Defer to adapter for v1.0 interface
+ return GetRegistersAvailableAdapter(regCount, pAvailable);
+}
+
+
+HRESULT CordbRegisterSet::GetRegisters(ULONG32 maskCount, BYTE mask[],
+ ULONG32 regCount, CORDB_REGISTER regBuffer[])
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(regBuffer, CORDB_REGISTER, regCount, true, true);
+
+ // Defer to adapter for v1.0 interface
+ return GetRegistersAdapter(maskCount, mask, regCount, regBuffer);
+}
+
+// This is just a convenience function to convert a regdisplay into a Context.
+// Since a context has more info than a regdisplay, the conversion isn't perfect
+// and the context can't be fully accurate.
+void CordbRegisterSet::InternalCopyRDToContext(DT_CONTEXT * pInputContext)
+{
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+ _ASSERTE(pInputContext);
+
+ if ((pInputContext->ContextFlags & DT_CONTEXT_INTEGER) == DT_CONTEXT_INTEGER)
+ {
+ pInputContext->R0 = m_rd->R0;
+ pInputContext->R1 = m_rd->R1;
+ pInputContext->R2 = m_rd->R2;
+ pInputContext->R3 = m_rd->R3;
+ pInputContext->R4 = m_rd->R4;
+ pInputContext->R5 = m_rd->R5;
+ pInputContext->R6 = m_rd->R6;
+ pInputContext->R7 = m_rd->R7;
+ pInputContext->R8 = m_rd->R8;
+ pInputContext->R9 = m_rd->R9;
+ pInputContext->R10 = m_rd->R10;
+ pInputContext->R11 = m_rd->R11;
+ }
+
+ if ((pInputContext->ContextFlags & DT_CONTEXT_CONTROL) == DT_CONTEXT_CONTROL)
+ {
+ pInputContext->Sp = m_rd->SP;
+ pInputContext->Lr = m_rd->LR;
+ pInputContext->Pc = m_rd->PC;
+ }
+}
diff --git a/src/debug/di/arm/primitives.cpp b/src/debug/di/arm/primitives.cpp
new file mode 100644
index 0000000000..52e3451cb1
--- /dev/null
+++ b/src/debug/di/arm/primitives.cpp
@@ -0,0 +1,7 @@
+// 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 "../../shared/arm/primitives.cpp"
diff --git a/src/debug/di/arm64/.gitmirror b/src/debug/di/arm64/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/debug/di/arm64/.gitmirror
@@ -0,0 +1 @@
+Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror. \ No newline at end of file
diff --git a/src/debug/di/arm64/cordbregisterset.cpp b/src/debug/di/arm64/cordbregisterset.cpp
new file mode 100644
index 0000000000..eab5ba403c
--- /dev/null
+++ b/src/debug/di/arm64/cordbregisterset.cpp
@@ -0,0 +1,145 @@
+// 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.
+//*****************************************************************************
+// File: CordbRegisterSet.cpp
+//
+
+//
+//*****************************************************************************
+#include "primitives.h"
+
+
+HRESULT CordbRegisterSet::GetRegistersAvailable(ULONG64* pAvailable)
+{
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pAvailable, ULONG64 *);
+
+ *pAvailable = SETBITULONG64(REGISTER_ARM64_PC)
+ | SETBITULONG64(REGISTER_ARM64_SP)
+ | SETBITULONG64(REGISTER_ARM64_X0)
+ | SETBITULONG64(REGISTER_ARM64_X1)
+ | SETBITULONG64(REGISTER_ARM64_X2)
+ | SETBITULONG64(REGISTER_ARM64_X3)
+ | SETBITULONG64(REGISTER_ARM64_X4)
+ | SETBITULONG64(REGISTER_ARM64_X5)
+ | SETBITULONG64(REGISTER_ARM64_X6)
+ | SETBITULONG64(REGISTER_ARM64_X7)
+ | SETBITULONG64(REGISTER_ARM64_X8)
+ | SETBITULONG64(REGISTER_ARM64_X9)
+ | SETBITULONG64(REGISTER_ARM64_X10)
+ | SETBITULONG64(REGISTER_ARM64_X11)
+ | SETBITULONG64(REGISTER_ARM64_X12)
+ | SETBITULONG64(REGISTER_ARM64_X13)
+ | SETBITULONG64(REGISTER_ARM64_X14)
+ | SETBITULONG64(REGISTER_ARM64_X15)
+ | SETBITULONG64(REGISTER_ARM64_X16)
+ | SETBITULONG64(REGISTER_ARM64_X17)
+ | SETBITULONG64(REGISTER_ARM64_X18)
+ | SETBITULONG64(REGISTER_ARM64_X19)
+ | SETBITULONG64(REGISTER_ARM64_X20)
+ | SETBITULONG64(REGISTER_ARM64_X21)
+ | SETBITULONG64(REGISTER_ARM64_X22)
+ | SETBITULONG64(REGISTER_ARM64_X23)
+ | SETBITULONG64(REGISTER_ARM64_X24)
+ | SETBITULONG64(REGISTER_ARM64_X25)
+ | SETBITULONG64(REGISTER_ARM64_X26)
+ | SETBITULONG64(REGISTER_ARM64_X27)
+ | SETBITULONG64(REGISTER_ARM64_X28)
+ | SETBITULONG64(REGISTER_ARM64_FP)
+ | SETBITULONG64(REGISTER_ARM64_LR);
+
+ return S_OK;
+}
+
+HRESULT CordbRegisterSet::GetRegisters(ULONG64 mask, ULONG32 regCount,
+ CORDB_REGISTER regBuffer[])
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ UINT iRegister = 0;
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(regBuffer, CORDB_REGISTER, regCount, true, true);
+
+ // @ARM64TODO: floating point support
+
+ for (int i = REGISTER_ARM64_PC;
+ i <= REGISTER_ARM64_LR && iRegister < regCount;
+ i++)
+ {
+ if (mask & SETBITULONG64(i))
+ {
+ if ((i >= REGISTER_ARM64_X0) && (i <= REGISTER_ARM64_X28))
+ {
+ regBuffer[iRegister++] = m_rd->X[i - REGISTER_ARM64_X0];
+ continue;
+ }
+
+ switch (i)
+ {
+ case REGISTER_ARM64_PC:
+ regBuffer[iRegister++] = m_rd->PC; break;
+ case REGISTER_ARM64_SP:
+ regBuffer[iRegister++] = m_rd->SP; break;
+ case REGISTER_ARM64_FP:
+ regBuffer[iRegister++] = m_rd->FP; break;
+ case REGISTER_ARM64_LR:
+ regBuffer[iRegister++] = m_rd->LR; break;
+ default:
+ _ASSERTE(false); break;
+ }
+ }
+ }
+
+ _ASSERTE (iRegister <= regCount);
+ return S_OK;
+}
+
+
+HRESULT CordbRegisterSet::GetRegistersAvailable(ULONG32 regCount,
+ BYTE pAvailable[])
+{
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(pAvailable, CORDB_REGISTER, regCount, true, true);
+
+ // Defer to adapter for v1.0 interface
+ return GetRegistersAvailableAdapter(regCount, pAvailable);
+}
+
+
+HRESULT CordbRegisterSet::GetRegisters(ULONG32 maskCount, BYTE mask[],
+ ULONG32 regCount, CORDB_REGISTER regBuffer[])
+{
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(regBuffer, CORDB_REGISTER, regCount, true, true);
+
+ // Defer to adapter for v1.0 interface
+ return GetRegistersAdapter(maskCount, mask, regCount, regBuffer);
+}
+
+
+// This is just a convenience function to convert a regdisplay into a Context.
+// Since a context has more info than a regdisplay, the conversion isn't perfect
+// and the context can't be fully accurate.
+void CordbRegisterSet::InternalCopyRDToContext(DT_CONTEXT *pInputContext)
+{ INTERNAL_SYNC_API_ENTRY(GetProcess());
+ _ASSERTE(pInputContext);
+
+ if ((pInputContext->ContextFlags & DT_CONTEXT_INTEGER) == DT_CONTEXT_INTEGER)
+ {
+ for (int i = 0 ; i < 29 ; ++i)
+ {
+ pInputContext->X[i] = m_rd->X[i];
+ }
+ }
+
+ if ((pInputContext->ContextFlags & DT_CONTEXT_CONTROL) == DT_CONTEXT_CONTROL)
+ {
+ pInputContext->Sp = m_rd->SP;
+ pInputContext->Lr = m_rd->LR;
+ pInputContext->Pc = m_rd->PC;
+ pInputContext->Fp = m_rd->FP;
+ }
+}
diff --git a/src/debug/di/arm64/floatconversion.asm b/src/debug/di/arm64/floatconversion.asm
new file mode 100644
index 0000000000..e478fd10fd
--- /dev/null
+++ b/src/debug/di/arm64/floatconversion.asm
@@ -0,0 +1,22 @@
+; 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 "ksarm64.h"
+
+;; Arguments
+;; input: (in X0) the _NEON128 value to be converted to a double
+;; output: the double corresponding to the _NEON128 input value
+
+ LEAF_ENTRY FPFillR8
+ LDR Q0, [X0]
+ ret lr
+ LEAF_END
+
+;; Must be at very end of file
+ END
diff --git a/src/debug/di/arm64/primitives.cpp b/src/debug/di/arm64/primitives.cpp
new file mode 100644
index 0000000000..b802d8c468
--- /dev/null
+++ b/src/debug/di/arm64/primitives.cpp
@@ -0,0 +1,7 @@
+// 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 "../../shared/arm64/primitives.cpp"
diff --git a/src/debug/di/breakpoint.cpp b/src/debug/di/breakpoint.cpp
new file mode 100644
index 0000000000..1e381a5f83
--- /dev/null
+++ b/src/debug/di/breakpoint.cpp
@@ -0,0 +1,722 @@
+// 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.
+//*****************************************************************************
+// File: breakpoint.cpp
+//
+
+//
+//*****************************************************************************
+#include "stdafx.h"
+
+/* ------------------------------------------------------------------------- *
+ * Breakpoint class
+ * ------------------------------------------------------------------------- */
+
+CordbBreakpoint::CordbBreakpoint(CordbProcess * pProcess, CordbBreakpointType bpType)
+ : CordbBase(pProcess, 0, enumCordbBreakpoint),
+ m_active(false), m_type(bpType)
+{
+}
+
+// Neutered by CordbAppDomain
+void CordbBreakpoint::Neuter()
+{
+ m_pAppDomain = NULL; // clear ref
+ CordbBase::Neuter();
+}
+
+HRESULT CordbBreakpoint::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugBreakpoint)
+ {
+ *pInterface = static_cast<ICorDebugBreakpoint*>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown *>(static_cast<ICorDebugBreakpoint*>(this));
+ }
+ else
+ {
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+HRESULT CordbBreakpoint::BaseIsActive(BOOL *pbActive)
+{
+ *pbActive = m_active ? TRUE : FALSE;
+
+ return S_OK;
+}
+
+/* ------------------------------------------------------------------------- *
+ * Function Breakpoint class
+ * ------------------------------------------------------------------------- */
+
+CordbFunctionBreakpoint::CordbFunctionBreakpoint(CordbCode *code,
+ SIZE_T offset)
+ : CordbBreakpoint(code->GetProcess(), CBT_FUNCTION),
+ m_code(code), m_offset(offset)
+{
+ // Remember the app domain we came from so that breakpoints can be
+ // deactivated from within the ExitAppdomain callback.
+ m_pAppDomain = m_code->GetAppDomain();
+ _ASSERTE(m_pAppDomain != NULL);
+}
+
+CordbFunctionBreakpoint::~CordbFunctionBreakpoint()
+{
+ // @todo- eventually get CordbFunctionBreakpoint rooted and enable this.
+ //_ASSERTE(this->IsNeutered());
+ //_ASSERTE(m_code == NULL);
+}
+
+void CordbFunctionBreakpoint::Neuter()
+{
+ Disconnect();
+ CordbBreakpoint::Neuter();
+}
+
+HRESULT CordbFunctionBreakpoint::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugFunctionBreakpoint)
+ {
+ *pInterface = static_cast<ICorDebugFunctionBreakpoint*>(this);
+ }
+ else
+ {
+ // Not looking for a function breakpoint? See if the base class handles
+ // this interface. (issue 143976)
+ return CordbBreakpoint::QueryInterface(id, pInterface);
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+HRESULT CordbFunctionBreakpoint::GetFunction(ICorDebugFunction **ppFunction)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppFunction, ICorDebugFunction **);
+
+ if (m_code == NULL)
+ {
+ return CORDBG_E_PROCESS_TERMINATED;
+ }
+ if (m_code->IsNeutered())
+ {
+ return CORDBG_E_CODE_NOT_AVAILABLE;
+ }
+
+ *ppFunction = static_cast<ICorDebugFunction *> (m_code->GetFunction());
+ (*ppFunction)->AddRef();
+
+ return S_OK;
+}
+
+// m_id is actually a LSPTR_BREAKPOINT. Get it as a type-safe member.
+LSPTR_BREAKPOINT CordbFunctionBreakpoint::GetLsPtrBP()
+{
+ LSPTR_BREAKPOINT p;
+ p.Set((void*) m_id);
+ return p;
+}
+
+HRESULT CordbFunctionBreakpoint::GetOffset(ULONG32 *pnOffset)
+{
+ //REVISIT_TODO: is this casting correct for ia64?
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pnOffset, SIZE_T *);
+
+ *pnOffset = (ULONG32)m_offset;
+
+ return S_OK;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Activates or removes a breakpoint
+//
+// Arguments:
+// fActivate - TRUE if to activate the breakpoint, else FALSE.
+//
+// Return Value:
+// S_OK if successful, else a specific error code detailing the type of failure.
+//
+//---------------------------------------------------------------------------------------
+HRESULT CordbFunctionBreakpoint::Activate(BOOL fActivate)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ OK_IF_NEUTERED(this); // we'll check again later
+
+ if (fActivate == (m_active == true) )
+ {
+ return S_OK;
+ }
+
+ // For backwards compat w/ everett, we let the other error codes
+ // take precedence over neutering error codes.
+ if ((m_code == NULL) || this->IsNeutered())
+ {
+ return CORDBG_E_PROCESS_TERMINATED;
+ }
+
+ HRESULT hr;
+ ATT_ALLOW_LIVE_DO_STOPGO(GetProcess());
+
+ // For legacy, check this error condition. We must do this under the stop-go lock to ensure
+ // that the m_code object was not deleted out from underneath us.
+ //
+ // 6/23/09 - This isn't just for legacy anymore, collectible types should be able to hit this
+ // by unloading the module containing the code this breakpoint is bound to.
+ if (m_code->IsNeutered())
+ {
+ return CORDBG_E_CODE_NOT_AVAILABLE;
+ }
+
+
+ //
+ // <REVISIT_TODO>@todo: when we implement module and value breakpoints, then
+ // we'll want to factor some of this code out.</REVISIT_TODO>
+ //
+ CordbProcess * pProcess = GetProcess();
+
+ RSLockHolder lockHolder(pProcess->GetProcessLock());
+ pProcess->ClearPatchTable(); // if we add something, then the right side
+ // view of the patch table is no longer valid
+
+ DebuggerIPCEvent * pEvent = (DebuggerIPCEvent *) _alloca(CorDBIPC_BUFFER_SIZE);
+
+ CordbAppDomain * pAppDomain = GetAppDomain();
+ _ASSERTE (pAppDomain != NULL);
+
+ if (fActivate)
+ {
+ pProcess->InitIPCEvent(pEvent, DB_IPCE_BREAKPOINT_ADD, true, pAppDomain->GetADToken());
+
+ pEvent->BreakpointData.funcMetadataToken = m_code->GetMetadataToken();
+ pEvent->BreakpointData.vmDomainFile = m_code->GetModule()->GetRuntimeDomainFile();
+ pEvent->BreakpointData.encVersion = m_code->GetVersion();
+
+ BOOL fIsIL = m_code->IsIL();
+
+ pEvent->BreakpointData.isIL = fIsIL ? true : false;
+ pEvent->BreakpointData.offset = m_offset;
+ if (fIsIL)
+ {
+ pEvent->BreakpointData.nativeCodeMethodDescToken = pEvent->BreakpointData.nativeCodeMethodDescToken.NullPtr();
+ }
+ else
+ {
+ pEvent->BreakpointData.nativeCodeMethodDescToken =
+ (m_code.GetValue()->AsNativeCode())->GetVMNativeCodeMethodDescToken().ToLsPtr();
+ }
+
+ // Note: we're sending a two-way event, so it blocks here
+ // until the breakpoint is really added and the reply event is
+ // copied over the event we sent.
+ lockHolder.Release();
+ hr = pProcess->SendIPCEvent(pEvent, CorDBIPC_BUFFER_SIZE);
+ lockHolder.Acquire();
+
+ hr = WORST_HR(hr, pEvent->hr);
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+
+ m_id = LsPtrToCookie(pEvent->BreakpointData.breakpointToken);
+
+ // If we weren't able to allocate the BP, we should have set the
+ // hr on the left side.
+ _ASSERTE(m_id != 0);
+
+
+ pAppDomain->m_breakpoints.AddBase(this);
+ m_active = true;
+
+ // Continue called automatically by StopContinueHolder
+ }
+ else
+ {
+ _ASSERTE (pAppDomain != NULL);
+
+ if (pProcess->IsSafeToSendEvents())
+ {
+ pProcess->InitIPCEvent(pEvent, DB_IPCE_BREAKPOINT_REMOVE, false, pAppDomain->GetADToken());
+
+ pEvent->BreakpointData.breakpointToken = GetLsPtrBP();
+
+ lockHolder.Release();
+ hr = pProcess->SendIPCEvent(pEvent, CorDBIPC_BUFFER_SIZE);
+ lockHolder.Acquire();
+
+ hr = WORST_HR(hr, pEvent->hr);
+ }
+ else
+ {
+ hr = CORDBHRFromProcessState(pProcess, pAppDomain);
+ }
+
+ pAppDomain->m_breakpoints.RemoveBase(LsPtrToCookie(GetLsPtrBP()));
+ m_active = false;
+ }
+
+ return hr;
+}
+
+void CordbFunctionBreakpoint::Disconnect()
+{
+ m_code.Clear();
+}
+
+/* ------------------------------------------------------------------------- *
+ * Stepper class
+ * ------------------------------------------------------------------------- */
+
+CordbStepper::CordbStepper(CordbThread *thread, CordbFrame *frame)
+ : CordbBase(thread->GetProcess(), 0, enumCordbStepper),
+ m_thread(thread), m_frame(frame),
+ m_stepperToken(0), m_active(false),
+ m_rangeIL(TRUE),
+ m_fIsJMCStepper(false),
+ m_rgfMappingStop(STOP_OTHER_UNMAPPED),
+ m_rgfInterceptStop(INTERCEPT_NONE)
+{
+}
+
+HRESULT CordbStepper::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugStepper)
+ *pInterface = static_cast<ICorDebugStepper *>(this);
+ else if (id == IID_ICorDebugStepper2)
+ *pInterface = static_cast<ICorDebugStepper2 *>(this);
+ else if (id == IID_IUnknown)
+ *pInterface = static_cast<IUnknown *>(static_cast<ICorDebugStepper *>(this));
+ else
+ return E_NOINTERFACE;
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+HRESULT CordbStepper::SetRangeIL(BOOL bIL)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ m_rangeIL = (bIL != FALSE);
+
+ return S_OK;
+}
+
+HRESULT CordbStepper::SetJMC(BOOL fIsJMCStepper)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ // Can't have JMC and stopping with anything else.
+ if (m_rgfMappingStop & STOP_ALL)
+ return E_INVALIDARG;
+
+ m_fIsJMCStepper = (fIsJMCStepper != FALSE);
+ return S_OK;
+}
+
+HRESULT CordbStepper::IsActive(BOOL *pbActive)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pbActive, BOOL *);
+
+ *pbActive = m_active;
+
+ return S_OK;
+}
+
+// M_id is a ptr to the stepper in the LS process.
+LSPTR_STEPPER CordbStepper::GetLsPtrStepper()
+{
+ LSPTR_STEPPER p;
+ p.Set((void*) m_id);
+ return p;
+}
+
+HRESULT CordbStepper::Deactivate()
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ if (!m_active)
+ return S_OK;
+
+ FAIL_IF_NEUTERED(this);
+
+ if (m_thread == NULL)
+ return CORDBG_E_PROCESS_TERMINATED;
+
+ HRESULT hr;
+ CordbProcess *process = GetProcess();
+ ATT_ALLOW_LIVE_DO_STOPGO(process);
+
+ process->Lock();
+
+ if (!m_active) // another thread may be deactivating (e.g. step complete event)
+ {
+ process->Unlock();
+ return S_OK;
+ }
+
+ CordbAppDomain *pAppDomain = GetAppDomain();
+ _ASSERTE (pAppDomain != NULL);
+
+ DebuggerIPCEvent event;
+ process->InitIPCEvent(&event,
+ DB_IPCE_STEP_CANCEL,
+ false,
+ pAppDomain->GetADToken());
+
+ event.StepData.stepperToken = GetLsPtrStepper();
+
+ process->Unlock();
+ hr = process->SendIPCEvent(&event, sizeof(DebuggerIPCEvent));
+ hr = WORST_HR(hr, event.hr);
+ process->Lock();
+
+
+ process->m_steppers.RemoveBase((ULONG_PTR)m_id);
+ m_active = false;
+
+ process->Unlock();
+
+ return hr;
+}
+
+HRESULT CordbStepper::SetInterceptMask(CorDebugIntercept mask)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ m_rgfInterceptStop = mask;
+ return S_OK;
+}
+
+HRESULT CordbStepper::SetUnmappedStopMask(CorDebugUnmappedStop mask)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ // You must be Win32 attached to stop in unmanaged code.
+ if ((mask & STOP_UNMANAGED) && !GetProcess()->IsInteropDebugging())
+ return E_INVALIDARG;
+
+ // Limitations on JMC Stepping - if JMC stepping is active,
+ // all other stop masks must be disabled.
+ // The jit can't place JMC probes before the prolog, so if we're
+ // we're JMC stepping, we'll stop after the prolog.
+ // The implementation for JMC stepping also doesn't let us stop in
+ // unmanaged code. (because there are no probes there).
+ // So enforce those implementation limitations here.
+ if (m_fIsJMCStepper)
+ {
+ if (mask & STOP_ALL)
+ return E_INVALIDARG;
+ }
+
+ // @todo- Ensure that we only set valid bits.
+
+
+ m_rgfMappingStop = mask;
+ return S_OK;
+}
+
+HRESULT CordbStepper::Step(BOOL bStepIn)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ if (m_thread == NULL)
+ return CORDBG_E_PROCESS_TERMINATED;
+
+ return StepRange(bStepIn, NULL, 0);
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Ships off a step-range command to the left-side. On the next continue the LS will
+// step across one range at a time.
+//
+// Arguments:
+// fStepIn - TRUE if this stepper should execute a step-in, else FALSE
+// rgRanges - Array of ranges that define a single step.
+// cRanges - Count of number of elements in rgRanges.
+//
+// Returns:
+// S_OK if the stepper is successfully set-up, else an appropriate error code.
+//
+HRESULT CordbStepper::StepRange(BOOL fStepIn,
+ COR_DEBUG_STEP_RANGE rgRanges[],
+ ULONG32 cRanges)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY_OR_NULL(rgRanges, COR_DEBUG_STEP_RANGE, cRanges, true, true);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ if (m_thread == NULL)
+ {
+ return CORDBG_E_PROCESS_TERMINATED;
+ }
+
+ HRESULT hr = S_OK;
+
+ if (m_active)
+ {
+ //
+ // Deactivate the current stepping.
+ // or return an error???
+ //
+ hr = Deactivate();
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+ }
+
+ // Validate step-ranges. Ranges are exclusive, so end offset
+ // should always be greater than start offset.
+ // Ranges don't have to be sorted.
+ // Zero ranges is ok; though they ought to just call Step() in that case.
+ for (ULONG32 i = 0; i < cRanges; i++)
+ {
+ if (rgRanges[i].startOffset >= rgRanges[i].endOffset)
+ {
+ STRESS_LOG2(LF_CORDB, LL_INFO10, "Illegal step range. 0x%x-0x%x\n", rgRanges[i].startOffset, rgRanges[i].endOffset);
+ return ErrWrapper(E_INVALIDARG);
+ }
+ }
+
+ CordbProcess * pProcess = GetProcess();
+
+ //
+ // Build step event
+ //
+
+ DebuggerIPCEvent * pEvent = reinterpret_cast<DebuggerIPCEvent *>(_alloca(CorDBIPC_BUFFER_SIZE));
+
+ pProcess->InitIPCEvent(pEvent, DB_IPCE_STEP, true, GetAppDomain()->GetADToken());
+
+ pEvent->StepData.vmThreadToken = m_thread->m_vmThreadToken;
+ pEvent->StepData.rgfMappingStop = m_rgfMappingStop;
+ pEvent->StepData.rgfInterceptStop = m_rgfInterceptStop;
+ pEvent->StepData.IsJMCStop = !!m_fIsJMCStepper;
+
+
+ if (m_frame == NULL)
+ {
+ pEvent->StepData.frameToken = LEAF_MOST_FRAME;
+ }
+ else
+ {
+ pEvent->StepData.frameToken = m_frame->GetFramePointer();
+ }
+
+ pEvent->StepData.stepIn = (fStepIn != 0);
+ pEvent->StepData.totalRangeCount = cRanges;
+ pEvent->StepData.rangeIL = m_rangeIL;
+
+ //
+ // Send ranges. We may have to send > 1 message.
+ //
+
+ COR_DEBUG_STEP_RANGE * pRangeStart = &(pEvent->StepData.range);
+ COR_DEBUG_STEP_RANGE * pRangeEnd = (reinterpret_cast<COR_DEBUG_STEP_RANGE *> (((BYTE *)pEvent) + CorDBIPC_BUFFER_SIZE)) - 1;
+
+ int cRangesToGo = cRanges;
+
+ if (cRangesToGo > 0)
+ {
+ while (cRangesToGo > 0)
+ {
+ //
+ // Find the number of ranges we can copy this time thru the loop
+ //
+ int cRangesToCopy;
+
+ if (cRangesToGo < (pRangeEnd - pRangeStart))
+ {
+ cRangesToCopy = cRangesToGo;
+ }
+ else
+ {
+ cRangesToCopy = (unsigned int)(pRangeEnd - pRangeStart);
+ }
+
+ //
+ // Copy the ranges into the IPC block now, 1-by-1
+ //
+ int cRangesCopied = 0;
+
+ while (cRangesCopied != cRangesToCopy)
+ {
+ pRangeStart[cRangesCopied] = rgRanges[cRanges - cRangesToGo + cRangesCopied];
+ cRangesCopied++;
+ }
+
+ pEvent->StepData.rangeCount = cRangesCopied;
+
+ cRangesToGo -= cRangesCopied;
+
+ //
+ // Send step event (two-way event here...)
+ //
+
+ hr = pProcess->SendIPCEvent(pEvent, CorDBIPC_BUFFER_SIZE);
+
+ hr = WORST_HR(hr, pEvent->hr);
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+ }
+ }
+ else
+ {
+ //
+ // Send step event without any ranges (two-way event here...)
+ //
+
+ hr = pProcess->SendIPCEvent(pEvent, CorDBIPC_BUFFER_SIZE);
+
+ hr = WORST_HR(hr, pEvent->hr);
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+ }
+
+ m_id = LsPtrToCookie(pEvent->StepData.stepperToken);
+
+ LOG((LF_CORDB,LL_INFO10000, "CS::SR: m_id:0x%x | 0x%x \n",
+ m_id,
+ LsPtrToCookie(pEvent->StepData.stepperToken)));
+
+#ifdef _DEBUG
+ CordbAppDomain *pAppDomain = GetAppDomain();
+#endif
+ _ASSERTE (pAppDomain != NULL);
+
+ pProcess->Lock();
+
+ pProcess->m_steppers.AddBase(this);
+ m_active = true;
+
+ pProcess->Unlock();
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Ships off a step-out command to the left-side. On the next continue the LS will
+// execute a step-out
+//
+// Returns:
+// S_OK if the stepper is successfully set-up, else an appropriate error code.
+//
+HRESULT CordbStepper::StepOut()
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ if (m_thread == NULL)
+ {
+ return CORDBG_E_PROCESS_TERMINATED;
+ }
+
+ HRESULT hr;
+
+ if (m_active)
+ {
+ //
+ // Deactivate the current stepping.
+ // or return an error???
+ //
+
+ hr = Deactivate();
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+ }
+
+ CordbProcess * pProcess = GetProcess();
+
+ // We don't do native step-out.
+ if (pProcess->SupportsVersion(ver_ICorDebugProcess2))
+ {
+ if ((m_rgfMappingStop & STOP_UNMANAGED) != 0)
+ {
+ return ErrWrapper(CORDBG_E_CANT_INTEROP_STEP_OUT);
+ }
+ }
+
+ //
+ // Build step event
+ //
+
+ DebuggerIPCEvent * pEvent = (DebuggerIPCEvent *) _alloca(CorDBIPC_BUFFER_SIZE);
+
+ pProcess->InitIPCEvent(pEvent, DB_IPCE_STEP_OUT, true, GetAppDomain()->GetADToken());
+
+ pEvent->StepData.vmThreadToken = m_thread->m_vmThreadToken;
+ pEvent->StepData.rgfMappingStop = m_rgfMappingStop;
+ pEvent->StepData.rgfInterceptStop = m_rgfInterceptStop;
+ pEvent->StepData.IsJMCStop = !!m_fIsJMCStepper;
+
+ if (m_frame == NULL)
+ {
+ pEvent->StepData.frameToken = LEAF_MOST_FRAME;
+ }
+ else
+ {
+ pEvent->StepData.frameToken = m_frame->GetFramePointer();
+ }
+
+ pEvent->StepData.totalRangeCount = 0;
+
+ // Note: two-way event here...
+ hr = pProcess->SendIPCEvent(pEvent, CorDBIPC_BUFFER_SIZE);
+
+ hr = WORST_HR(hr, pEvent->hr);
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ m_id = LsPtrToCookie(pEvent->StepData.stepperToken);
+
+#ifdef _DEBUG
+ CordbAppDomain * pAppDomain = GetAppDomain();
+#endif
+ _ASSERTE (pAppDomain != NULL);
+
+ pProcess->Lock();
+
+ pProcess->m_steppers.AddBase(this);
+ m_active = true;
+
+ pProcess->Unlock();
+
+ return S_OK;
+}
diff --git a/src/debug/di/classfactory.h b/src/debug/di/classfactory.h
new file mode 100644
index 0000000000..5109f3a637
--- /dev/null
+++ b/src/debug/di/classfactory.h
@@ -0,0 +1,82 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+//*****************************************************************************
+// ClassFactory.h
+//
+
+//
+// Class factories are used by the pluming in COM to activate new objects.
+// This module contains the class factory code to instantiate the debugger
+// objects described in RSPriv.h.
+//
+//*****************************************************************************
+#ifndef __ClassFactory__h__
+#define __ClassFactory__h__
+
+#include "rspriv.h"
+
+
+// This typedef is for a function which will create a new instance of an object.
+typedef HRESULT (__stdcall * PFN_CREATE_OBJ)(REFIID riid, void **ppvObject);
+
+
+//*****************************************************************************
+// One class factory object satifies all of our clsid's, to reduce overall
+// code bloat.
+//*****************************************************************************
+class CClassFactory :
+ public IClassFactory
+{
+ CClassFactory() { } // Can't use without data.
+
+public:
+ CClassFactory(PFN_CREATE_OBJ pfnCreateObject)
+ : m_cRef(1), m_pfnCreateObject(pfnCreateObject)
+ { }
+
+ virtual ~CClassFactory() {}
+
+ //
+ // IUnknown methods.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE QueryInterface(
+ REFIID riid,
+ void **ppvObject);
+
+ virtual ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (InterlockedIncrement(&m_cRef));
+ }
+
+ virtual ULONG STDMETHODCALLTYPE Release()
+ {
+ LONG cRef = InterlockedDecrement(&m_cRef);
+ if (cRef <= 0)
+ delete this;
+ return (cRef);
+ }
+
+
+ //
+ // IClassFactory methods.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE CreateInstance(
+ IUnknown *pUnkOuter,
+ REFIID riid,
+ void **ppvObject);
+
+ virtual HRESULT STDMETHODCALLTYPE LockServer(
+ BOOL fLock);
+
+
+private:
+ LONG m_cRef; // Reference count.
+ PFN_CREATE_OBJ m_pfnCreateObject; // Creation function for an instance.
+};
+
+
+
+#endif // __ClassFactory__h__
diff --git a/src/debug/di/cordb.cpp b/src/debug/di/cordb.cpp
new file mode 100644
index 0000000000..497225fd67
--- /dev/null
+++ b/src/debug/di/cordb.cpp
@@ -0,0 +1,572 @@
+// 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.
+//*****************************************************************************
+// CorDB.cpp
+//
+
+//
+// Dll* routines for entry points, and support for COM framework. The class
+// factory and other routines live in this module.
+//
+//*****************************************************************************
+#include "stdafx.h"
+#include "classfactory.h"
+#include "corsym.h"
+#include "contract.h"
+#include "metadataexports.h"
+#if defined(FEATURE_DBGIPC_TRANSPORT_DI)
+#include "dbgtransportsession.h"
+#include "dbgtransportmanager.h"
+#endif // FEATURE_DBGIPC_TRANSPORT_DI
+
+//********** Globals. *********************************************************
+#ifndef FEATURE_PAL
+HINSTANCE g_hInst; // Instance handle to this piece of code.
+#endif
+
+//-----------------------------------------------------------------------------
+// SxS Versioning story for Mscordbi (ICorDebug + friends)
+//-----------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+// In v1.0, we declared that mscordbi was a "shared" component, which means
+// that we promised to provide it from now until the end of time. So every CLR implementation
+// needs an Mscordbi that implements the everett guids for CorDebug + CorPublish.
+//
+// This works fine for CorPublish, which is truly shared.
+// CorDebug however is "versioned" not "shared" - each version of the CLR has its own disjoint copy.
+//
+// Thus creating a CorDebug object requires a version parameter.
+// CoCreateInstance doesn't have a the version param, so we use the new (v2.0+)
+// shim interface CreateDebuggingInterfaceFromVersion.
+//
+// ** So in summary: **
+// - Dlls don't do self-registration; they're registered by setup using .vrg files.
+// - All CLR versions (past + future) must have the same registry footprint w.r.t mscordbi.
+// This just means that all CLRs have the same mscordbi.vrg file.
+// - CorDebug is in fact versioned and each CLR version has its own copy.
+// - In v1.0/1.1, CorDebug was a CoClass. In v2.0+, it is not a CoClass and is created via the
+// CreateDebuggingInterfaceFromVersion shim API, which takes a version parameter.
+// - CorDebug must be SxS. V1.1 must only get the V1.1 version, and V2.0 must only get the V2.0 version.
+// V1.1: Clients will cocreate to get CorDebug. v1.1 will be the only mscordbi!DllGetClassObject
+// that provides a CorDebug, so CoCreateInstance will guarantee getting a v1.1 object.
+// V2.0: Clients use the new version-aware shim API, so it's not an issue.
+//
+// ** Preparing for Life in a Single-CLR world: **
+// In Orcas (v3), we expect to run on single-CLR. There will only be 1 mscordbi, and it will service all versions.
+// For whidbey (v2), we want to be able to flip a knob and pretend to be orcas (for testing purposes).
+//
+// Here's how to do that:
+// - copy whidbey mscordbi & dac over the everett mscordbi.
+// - When VS cocreates w/ the everett-guid, it will load the mscordbi on the everett path (
+// which will be whidbey dll), and ask for the everett guid.
+// - re-add CorDebug to the g_CoClasses list.
+
+
+//********** Locals. **********************************************************
+
+
+//********** Code. ************************************************************
+
+
+//*****************************************************************************
+// Standard public helper to create a Cordb object (ICorDebug instance).
+// This is used by the shim to get the Cordb object out of this module.
+// This is the creation path for V2.0+ for CorDebug using the in-process debugging
+// architecture (ICorDebug). In CLR v4+ debugger may choose to use the out-of-process
+// architecture to get an ICorDebugProcess directly (IClrDebugging::OpenVirtualProcess).
+//
+// This was used by the Mix07 release of Silverlight, but it didn't properly support versioning
+// and we no longer support it's debugger protocol so we require callers to use
+// code:CoreCLRCreateCordbObject instead.
+//
+// This is also still used on Mac - multi-instance debugging and debugger
+// versioning isn't really implemented there yet. This probably needs to change.
+//*****************************************************************************
+STDAPI CreateCordbObject(int iDebuggerVersion, IUnknown ** ppCordb)
+{
+#if defined(FEATURE_CORECLR) && !defined(FEATURE_DBGIPC_TRANSPORT_DI) && !defined(FEATURE_CORESYSTEM)
+ // This API should not be called for Windows CoreCLR unless we are doing interop-debugging
+ // (which is only supported internally). Use code:CoreCLRCreateCordbObject instead.
+ if (CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgEnableMixedModeDebugging) == 0)
+ {
+ _ASSERTE(!"Deprecated entry point CreateCordbObject() is called on Windows CoreCLR\n");
+ return E_NOTIMPL;
+ }
+#endif // FEATURE_CORECLR && !FEATURE_DBGIPC_TRANSPORT_DI
+
+ if (ppCordb == NULL)
+ {
+ return E_INVALIDARG;
+ }
+ if (iDebuggerVersion != CorDebugVersion_2_0 && iDebuggerVersion != CorDebugVersion_4_0)
+ {
+ return E_INVALIDARG;
+ }
+
+ return Cordb::CreateObject((CorDebugInterfaceVersion)iDebuggerVersion, IID_ICorDebug, (void **) ppCordb);
+}
+
+#if defined(FEATURE_CORECLR)
+//
+// Public API.
+// Telesto Creation path - only way to debug multi-instance.
+// This supercedes code:CreateCordbObject
+//
+// Arguments:
+// iDebuggerVersion - version of ICorDebug interfaces that the debugger is requesting
+// pid - pid of debuggee that we're attaching to.
+// hmodTargetCLR - module handle to clr in target pid that we're attaching to.
+// ppCordb - (out) the resulting ICorDebug object.
+//
+// Notes:
+// It's inconsistent that this takes a (handle, pid) but hands back an ICorDebug instead of an ICorDebugProcess.
+// Callers will need to call *ppCordb->DebugActiveProcess(pid).
+STDAPI CoreCLRCreateCordbObject(int iDebuggerVersion, DWORD pid, HMODULE hmodTargetCLR, IUnknown ** ppCordb)
+{
+ if (ppCordb == NULL)
+ {
+ return E_INVALIDARG;
+ }
+ if ((iDebuggerVersion < CorDebugVersion_2_0) ||
+ (iDebuggerVersion > CorDebugLatestVersion))
+ {
+ return E_INVALIDARG;
+ }
+
+ //
+ // Create the ICorDebug object
+ //
+ RSExtSmartPtr<ICorDebug> pCordb;
+ Cordb::CreateObject((CorDebugInterfaceVersion)iDebuggerVersion, IID_ICorDebug, (void **) &pCordb);
+
+ // @dbgtodo - we should stash the pid and validate that it's the same pid we're attaching to in ICorDebug::DebugActiveProcess.
+
+ //
+ // Associate it with the target instance
+ //
+ HRESULT hr = static_cast<Cordb*>(pCordb.GetValue())->SetTargetCLR(hmodTargetCLR);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ //
+ // Assign to out parameter.
+ //
+ hr = pCordb->QueryInterface(IID_IUnknown, (void**) ppCordb);
+
+ // Implicit release of pUnk, pCordb
+ return hr;
+}
+
+#endif // FEATURE_CORECLR
+
+
+
+
+//*****************************************************************************
+// The main dll entry point for this module. This routine is called by the
+// OS when the dll gets loaded. Control is simply deferred to the main code.
+//*****************************************************************************
+BOOL WINAPI DbgDllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
+{
+ // Save off the instance handle for later use.
+ switch (dwReason)
+ {
+
+ case DLL_PROCESS_ATTACH:
+ {
+#ifndef FEATURE_PAL
+ g_hInst = hInstance;
+#else
+ int err = PAL_InitializeDLL();
+ if(err != 0)
+ {
+ return FALSE;
+ }
+#endif
+
+#if defined(_DEBUG)
+ static int BreakOnDILoad = -1;
+ if (BreakOnDILoad == -1)
+ BreakOnDILoad = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_BreakOnDILoad);
+
+ if (BreakOnDILoad)
+ {
+ _ASSERTE(!"DI Loaded");
+ }
+#endif
+
+#if defined(LOGGING)
+ {
+ PathString rcFile;
+ WszGetModuleFileName(hInstance, rcFile);
+ LOG((LF_CORDB, LL_INFO10000,
+ "DI::DbgDllMain: load right side support from file '%s'\n",
+ rcFile.GetUnicode()));
+ }
+#endif
+
+#ifdef RSCONTRACTS
+ // alloc a TLS slot
+ DbgRSThread::s_TlsSlot = TlsAlloc();
+ _ASSERTE(DbgRSThread::s_TlsSlot != TLS_OUT_OF_INDEXES);
+#endif
+
+#if defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ g_pDbgTransportTarget = new (nothrow) DbgTransportTarget();
+ if (g_pDbgTransportTarget == NULL)
+ return FALSE;
+
+ if (FAILED(g_pDbgTransportTarget->Init()))
+ return FALSE;
+#endif // FEATURE_DBGIPC_TRANSPORT_DI
+ }
+ break;
+
+ case DLL_THREAD_DETACH:
+ {
+#ifdef STRESS_LOG
+ StressLog::ThreadDetach((ThreadStressLog*) ClrFlsGetValue(TlsIdx_StressLog));
+#endif
+
+#ifdef RSCONTRACTS
+ // DbgRSThread are lazily created when we call GetThread(),
+ // So we don't need to do anything in DLL_THREAD_ATTACH,
+ // But this is our only chance to destroy the thread object.
+ DbgRSThread * p = DbgRSThread::GetThread();
+
+ p->Destroy();
+#endif
+ }
+ break;
+
+ case DLL_PROCESS_DETACH:
+ {
+#if defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ if (g_pDbgTransportTarget != NULL)
+ {
+ g_pDbgTransportTarget->Shutdown();
+ delete g_pDbgTransportTarget;
+ g_pDbgTransportTarget = NULL;
+ }
+#endif // FEATURE_DBGIPC_TRANSPORT_DI
+
+#ifdef RSCONTRACTS
+ TlsFree(DbgRSThread::s_TlsSlot);
+ DbgRSThread::s_TlsSlot = TLS_OUT_OF_INDEXES;
+#endif
+ }
+ break;
+ }
+
+ return TRUE;
+}
+
+
+// The obsolete v1 CLSID - see comment above for details.
+static const GUID CLSID_CorDebug_V1 = {0x6fef44d0,0x39e7,0x4c77, { 0xbe,0x8e,0xc9,0xf8,0xcf,0x98,0x86,0x30}};
+
+#if defined(FEATURE_DBGIPC_TRANSPORT_DI)
+
+// GUID for pipe-based debugging (Unix platforms)
+const GUID CLSID_CorDebug_Telesto = {0x8bd1daae, 0x188e, 0x42f4, {0xb0, 0x09, 0x08, 0xfa, 0xfd, 0x17, 0x81, 0x3b}};
+
+// The debug engine needs to implement an internal Visual Studio debugger interface (defined by the CPDE)
+// which augments launch and attach requests so that we can obtain information from the port supplier (the
+// network address of the target in our case). See RSPriv.h for the definition of the interface. (We have to
+// hard code the IID and interface definition because VS does not export it, but it's not much of an issue
+// since COM interfaces are completely immutable).
+const GUID IID_IDebugRemoteCorDebug = {0x83C91210, 0xA34F, 0x427c, {0xB3, 0x5F, 0x79, 0xC3, 0x99, 0x5B, 0x3C, 0x14}};
+#endif // FEATURE_DBGIPC_TRANSPORT_DI
+
+//*****************************************************************************
+// Called by COM to get a class factory for a given CLSID. If it is one we
+// support, instantiate a class factory object and prepare for create instance.
+//*****************************************************************************
+STDAPI DllGetClassObjectInternal( // Return code.
+ REFCLSID rclsid, // The class to desired.
+ REFIID riid, // Interface wanted on class factory.
+ LPVOID FAR *ppv) // Return interface pointer here.
+{
+ HRESULT hr;
+ CClassFactory *pClassFactory; // To create class factory object.
+ PFN_CREATE_OBJ pfnCreateObject = NULL;
+
+
+#if defined(FEATURE_DBG_PUBLISH)
+ if (rclsid == CLSID_CorpubPublish)
+ {
+ pfnCreateObject = CorpubPublish::CreateObject;
+ }
+ else
+#endif
+#if defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ if (rclsid == CLSID_CorDebug_Telesto)
+ {
+ pfnCreateObject = Cordb::CreateObjectTelesto;
+ }
+#else // !FEATURE_DBGIPC_TRANSPORT_DI
+ if(rclsid == CLSID_CorDebug_V1)
+ {
+ if (0) // if (IsSingleCLR())
+ {
+ // Don't allow creating backwards objects until we ensure that the v2.0 Right-side
+ // is backwards compat. This may involve using CordbProcess::SupportsVersion to conditionally
+ // emulate old behavior.
+ // If emulating V1.0, QIs for V2.0 interfaces should fail.
+ _ASSERTE(!"Ensure that V2.0 RS is backwards compat");
+ pfnCreateObject = Cordb::CreateObjectV1;
+ }
+ }
+#endif // FEATURE_DBGIPC_TRANSPORT_DI
+
+ if (pfnCreateObject == NULL)
+ return (CLASS_E_CLASSNOTAVAILABLE);
+
+ // Allocate the new factory object. The ref count is set to 1 in the constructor.
+ pClassFactory = new (nothrow) CClassFactory(pfnCreateObject);
+ if (!pClassFactory)
+ return (E_OUTOFMEMORY);
+
+ // Pick the v-table based on the caller's request.
+ hr = pClassFactory->QueryInterface(riid, ppv);
+
+ // Always release the local reference, if QI failed it will be
+ // the only one and the object gets freed.
+ pClassFactory->Release();
+
+ return hr;
+}
+
+#if defined(FEATURE_DBGIPC_TRANSPORT_DI)
+// In V2 we started hiding DllGetClassObject because activation was no longer performed through COM directly
+// (we went through the shim). CoreCLR doesn't have a shim and we go back to the COM model so we re-expose
+// DllGetClassObject to make that work.
+
+STDAPI DllGetClassObject( // Return code.
+ REFCLSID rclsid, // The class to desired.
+ REFIID riid, // Interface wanted on class factory.
+ LPVOID FAR *ppv) // Return interface pointer here.
+{
+ return DllGetClassObjectInternal(rclsid, riid, ppv);
+}
+#endif // FEATURE_DBGIPC_TRANSPORT_DI
+
+
+//*****************************************************************************
+//
+//********** Class factory code.
+//
+//*****************************************************************************
+
+
+//*****************************************************************************
+// QueryInterface is called to pick a v-table on the co-class.
+//*****************************************************************************
+HRESULT STDMETHODCALLTYPE CClassFactory::QueryInterface(
+ REFIID riid,
+ void **ppvObject)
+{
+ HRESULT hr;
+
+ // Avoid confusion.
+ *ppvObject = NULL;
+
+ // Pick the right v-table based on the IID passed in.
+ if (riid == IID_IUnknown)
+ *ppvObject = (IUnknown *) this;
+ else if (riid == IID_IClassFactory)
+ *ppvObject = (IClassFactory *) this;
+
+ // If successful, add a reference for out pointer and return.
+ if (*ppvObject)
+ {
+ hr = S_OK;
+ AddRef();
+ }
+ else
+ hr = E_NOINTERFACE;
+ return (hr);
+}
+
+
+//*****************************************************************************
+// CreateInstance is called to create a new instance of the coclass for which
+// this class was created in the first place. The returned pointer is the
+// v-table matching the IID if there.
+//*****************************************************************************
+HRESULT STDMETHODCALLTYPE CClassFactory::CreateInstance(
+ IUnknown *pUnkOuter,
+ REFIID riid,
+ void **ppvObject)
+{
+ HRESULT hr;
+
+ // Avoid confusion.
+ *ppvObject = NULL;
+ _ASSERTE(m_pfnCreateObject);
+
+ // Aggregation is not supported by these objects.
+ if (pUnkOuter)
+ return (CLASS_E_NOAGGREGATION);
+
+ // Ask the object to create an instance of itself, and check the iid.
+ hr = (*m_pfnCreateObject)(riid, ppvObject);
+ return (hr);
+}
+
+
+HRESULT STDMETHODCALLTYPE CClassFactory::LockServer(
+ BOOL fLock)
+{
+//<TODO>@todo: hook up lock server logic.</TODO>
+ return (S_OK);
+}
+
+
+//*****************************************************************************
+// This helper provides access to the instance handle of the loaded image.
+//*****************************************************************************
+#ifndef FEATURE_PAL
+HINSTANCE GetModuleInst()
+{
+ return g_hInst;
+}
+#endif
+
+
+//-----------------------------------------------------------------------------
+// Substitute for mscoree
+//
+// Notes:
+// Mscordbi does not link with mscoree, provide a stub implementation.
+// Callers are in dead-code paths, but we still need to provide a stub. Ideally, we'd factor
+// out the callers too and then we wouldn't need an E_NOTIMPL stub.
+STDAPI GetRequestedRuntimeInfo(LPCWSTR pExe,
+ LPCWSTR pwszVersion,
+ LPCWSTR pConfigurationFile,
+ DWORD startupFlags,
+ DWORD runtimeInfoFlags,
+ __out_ecount_opt(dwDirectory) LPWSTR pDirectory,
+ DWORD dwDirectory,
+ DWORD *dwDirectoryLength,
+ __out_ecount_opt(cchBuffer) LPWSTR pVersion,
+ DWORD cchBuffer,
+ DWORD* dwlength)
+{
+ _ASSERTE(!"GetRequestedRuntimeInfo not impl");
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------------------------
+// Replacement for legacy shim API GetCORRequiredVersion(...) used in linked libraries.
+// Used in code:TiggerStorage::GetDefaultVersion#CallTo_CLRRuntimeHostInternal_GetImageVersionString.
+//
+// Notes:
+// Mscordbi does not statically link to mscoree.dll.
+// This is used in EnC for IMetadataEmit2::GetSaveSize to computer size of header.
+// see code:TiggerStorage::GetDefaultVersion.
+//
+// Implemented by returning the version we're built for. Mscordbi.dll has a tight coupling with
+// the CLR version, so this will match exactly the build version we're debugging.
+// One potential caveat is that the build version doesn't necessarily match the install string
+// (eg. we may install as "v4.0.x86chk" but that's not captured in the build version). But this should
+// be internal scenarios only, and shouldn't actually matter here. If it did, we could instead get
+// the last components of the directory name the current mscordbi.dll is located in.
+//
+HRESULT
+CLRRuntimeHostInternal_GetImageVersionString(
+ __out_ecount_part(*pcchBuffer, *pcchBuffer) LPWSTR wszBuffer,
+ DWORD *pcchBuffer)
+{
+ // Construct the cannoncial version string we're built as - eg. "v4.0.1234"
+ const WCHAR k_wszBuiltFor[] = W("v") VER_PRODUCTVERSION_NO_QFE_STR_L;
+
+ // Copy our buffer in
+ HRESULT hr = HRESULT_FROM_WIN32(wcscpy_s(wszBuffer, *pcchBuffer, k_wszBuiltFor));
+
+ // Hand out length regardless of success - like GetCORRequiredVersion
+ *pcchBuffer = _countof(k_wszBuiltFor);
+
+ return hr;
+} // CLRRuntimeHostInternal_GetImageVersionString
+
+
+#ifdef _TARGET_ARM_
+BOOL
+DbiGetThreadContext(HANDLE hThread,
+ DT_CONTEXT *lpContext)
+{
+ // if we aren't local debugging this isn't going to work
+#if !defined(_ARM_) || defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ _ASSERTE(!"Can't use local GetThreadContext remotely, this needed to go to datatarget");
+ return FALSE;
+#else
+ BOOL res = FALSE;
+ if (((ULONG)lpContext) & ~0x10)
+ {
+ CONTEXT *ctx = (CONTEXT*)_aligned_malloc(sizeof(CONTEXT), 16);
+ if (ctx)
+ {
+ ctx->ContextFlags = lpContext->ContextFlags;
+ if (::GetThreadContext(hThread, ctx))
+ {
+ *lpContext = *(DT_CONTEXT*)ctx;
+ res = TRUE;
+ }
+
+ _aligned_free(ctx);
+ }
+ else
+ {
+ // malloc does not set the last error, but the caller of GetThreadContext
+ // will expect it to be set on failure.
+ SetLastError(ERROR_OUTOFMEMORY);
+ }
+ }
+ else
+ {
+ res = ::GetThreadContext(hThread, (CONTEXT*)lpContext);
+ }
+
+ return res;
+#endif
+}
+
+BOOL
+DbiSetThreadContext(HANDLE hThread,
+ const DT_CONTEXT *lpContext)
+{
+#if !defined(_ARM_) || defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ _ASSERTE(!"Can't use local GetThreadContext remotely, this needed to go to datatarget");
+ return FALSE;
+#else
+ BOOL res = FALSE;
+ if (((ULONG)lpContext) & ~0x10)
+ {
+ CONTEXT *ctx = (CONTEXT*)_aligned_malloc(sizeof(CONTEXT), 16);
+ if (ctx)
+ {
+ *ctx = *(CONTEXT*)lpContext;
+ res = ::SetThreadContext(hThread, ctx);
+ _aligned_free(ctx);
+ }
+ else
+ {
+ // malloc does not set the last error, but the caller of SetThreadContext
+ // will expect it to be set on failure.
+ SetLastError(ERROR_OUTOFMEMORY);
+ }
+ }
+ else
+ {
+ res = ::SetThreadContext(hThread, (CONTEXT*)lpContext);
+ }
+
+ return res;
+#endif
+}
+#endif
diff --git a/src/debug/di/dbgtransportmanager.cpp b/src/debug/di/dbgtransportmanager.cpp
new file mode 100644
index 0000000000..77a3548ea5
--- /dev/null
+++ b/src/debug/di/dbgtransportmanager.cpp
@@ -0,0 +1,231 @@
+// 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 "stdafx.h"
+#include "dbgtransportsession.h"
+#include "dbgtransportmanager.h"
+#include "coreclrremotedebugginginterfaces.h"
+
+
+
+#ifdef FEATURE_DBGIPC_TRANSPORT_DI
+
+DbgTransportTarget *g_pDbgTransportTarget = NULL;
+
+DbgTransportTarget::DbgTransportTarget()
+{
+ memset(this, 0, sizeof(*this));
+}
+
+// Initialization routine called only by the DbgTransportManager.
+HRESULT DbgTransportTarget::Init()
+{
+ m_sLock.Init("DbgTransportTarget Lock", RSLock::cLockFlat, RSLock::LL_DBG_TRANSPORT_TARGET_LOCK);
+
+ return S_OK;
+}
+
+// Shutdown routine called only by the DbgTransportManager.
+void DbgTransportTarget::Shutdown()
+{
+ DbgTransportLog(LC_Always, "DbgTransportTarget shutting down");
+
+ {
+ RSLockHolder lock(&m_sLock);
+ while (m_pProcessList)
+ {
+ ProcessEntry *pDelProcess = m_pProcessList;
+ m_pProcessList = m_pProcessList->m_pNext;
+ delete pDelProcess;
+ }
+ }
+ m_sLock.Destroy();
+}
+
+
+// Given a PID attempt to find or create a DbgTransportSession instance to manage a connection to a runtime in
+// that process. Returns E_UNEXPECTED if the process can't be found. Also returns a handle that can be waited
+// on for process termination.
+HRESULT DbgTransportTarget::GetTransportForProcess(DWORD dwPID,
+ DbgTransportSession **ppTransport,
+ HANDLE *phProcessHandle)
+{
+ RSLockHolder lock(&m_sLock);
+ HRESULT hr = S_OK;
+
+ ProcessEntry *entry = LocateProcessByPID(dwPID);
+
+ if (entry == NULL)
+ {
+
+ NewHolder<ProcessEntry> newEntry = new(nothrow) ProcessEntry();
+ if (newEntry == NULL)
+ return E_OUTOFMEMORY;
+
+ NewHolder<DbgTransportSession> transport = new(nothrow) DbgTransportSession();
+ if (transport == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+
+ HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID);
+ if (hProcess == NULL)
+ {
+ transport->Shutdown();
+ return HRESULT_FROM_GetLastError();
+ }
+
+ // Initialize it (this immediately starts the remote connection process).
+ hr = transport->Init(dwPID, hProcess);
+ if (FAILED(hr))
+ {
+ transport->Shutdown();
+ CloseHandle(hProcess);
+ return hr;
+ }
+
+ entry = newEntry;
+ newEntry.SuppressRelease();
+ entry->m_dwPID = dwPID;
+ entry->m_hProcess = hProcess;
+ entry->m_transport = transport;
+ transport.SuppressRelease();
+ entry->m_cProcessRef = 0;
+
+ // Adding new entry to the list.
+ entry->m_pNext = m_pProcessList;
+ m_pProcessList = entry;
+ }
+
+ entry->m_cProcessRef++;
+ _ASSERTE(entry->m_cProcessRef > 0);
+ _ASSERTE(entry->m_transport != NULL);
+ _ASSERTE(entry->m_hProcess > 0);
+
+ *ppTransport = entry->m_transport;
+ if (!DuplicateHandle(GetCurrentProcess(),
+ entry->m_hProcess,
+ GetCurrentProcess(),
+ phProcessHandle,
+ 0, // ignored since we are going to pass DUPLICATE_SAME_ACCESS
+ FALSE,
+ DUPLICATE_SAME_ACCESS))
+ {
+ return HRESULT_FROM_GetLastError();
+ }
+
+ return hr;
+}
+
+
+// Release another reference to the transport associated with dwPID. Once all references are gone (modulo the
+// manager's own weak reference) clean up the transport and deallocate it.
+void DbgTransportTarget::ReleaseTransport(DbgTransportSession *pTransport)
+{
+ RSLockHolder lock(&m_sLock);
+
+ ProcessEntry *entry = m_pProcessList;
+
+ // Pointer to the pointer that points to *entry.
+ // It either points to m_pProcessList or m_pNext of some entry.
+ // It is used to fix the linked list after deletion of an entry.
+ ProcessEntry **prevPtr = &m_pProcessList;
+
+ // Looking for ProcessEntry with a given transport
+ while (entry)
+ {
+
+ _ASSERTE(entry->m_cProcessRef > 0);
+ _ASSERTE(entry->m_transport != NULL);
+ _ASSERTE(entry->m_hProcess > 0);
+
+ if (entry->m_transport == pTransport)
+ {
+ // Mark that it has one less holder now
+ entry->m_cProcessRef--;
+
+ // If no more holders remove the entry from the list and free resources
+ if (entry->m_cProcessRef == 0)
+ {
+ *prevPtr = entry->m_pNext;
+ delete entry;
+ }
+ return;
+ }
+ prevPtr = &entry->m_pNext;
+ entry = entry->m_pNext;
+ }
+
+ _ASSERTE(!"Trying to release transport that doesn't belong to this DbgTransportTarget");
+ pTransport->Shutdown();
+}
+
+HRESULT DbgTransportTarget::CreateProcess(LPCWSTR lpApplicationName,
+ LPCWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ LPVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation)
+{
+
+ BOOL result = WszCreateProcess(lpApplicationName,
+ lpCommandLine,
+ lpProcessAttributes,
+ lpThreadAttributes,
+ bInheritHandles,
+ dwCreationFlags,
+ lpEnvironment,
+ lpCurrentDirectory,
+ lpStartupInfo,
+ lpProcessInformation);
+
+ if (!result)
+ {
+ return HRESULT_FROM_GetLastError();
+ }
+
+ return S_OK;
+}
+
+// Kill the process identified by PID.
+void DbgTransportTarget::KillProcess(DWORD dwPID)
+{
+ HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, dwPID);
+ if (hProcess != NULL)
+ {
+ TerminateProcess(hProcess, 0);
+ CloseHandle(hProcess);
+ }
+}
+
+DbgTransportTarget::ProcessEntry::~ProcessEntry()
+{
+ CloseHandle(m_hProcess);
+ m_hProcess = NULL;
+
+ m_transport->Shutdown();
+ m_transport = NULL;
+}
+
+// Locate a process entry by PID. Assumes the lock is already held.
+DbgTransportTarget::ProcessEntry *DbgTransportTarget::LocateProcessByPID(DWORD dwPID)
+{
+ _ASSERTE(m_sLock.HasLock());
+
+ ProcessEntry *pProcess = m_pProcessList;
+ while (pProcess)
+ {
+ if (pProcess->m_dwPID == dwPID)
+ return pProcess;
+ pProcess = pProcess->m_pNext;
+ }
+ return NULL;
+}
+
+#endif // FEATURE_DBGIPC_TRANSPORT_DI
diff --git a/src/debug/di/dbgtransportmanager.h b/src/debug/di/dbgtransportmanager.h
new file mode 100644
index 0000000000..3a8013eae8
--- /dev/null
+++ b/src/debug/di/dbgtransportmanager.h
@@ -0,0 +1,87 @@
+// 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 __DBG_TRANSPORT_MANAGER_INCLUDED
+#define __DBG_TRANSPORT_MANAGER_INCLUDED
+
+#ifdef FEATURE_DBGIPC_TRANSPORT_DI
+
+#include "coreclrremotedebugginginterfaces.h"
+
+
+// TODO: Ideally we'd like to remove this class and don't do any process related book keeping in DBI.
+
+// This is a registry of all the processes a debugger knows about, different components call it in order to
+// obtain right instance of DbgTransportSession for a given PID. It keeps list of processes and transports for them.
+// It also handles things like creating and killing a process.
+
+// Usual lifecycle looks like this:
+// Debug a new process:
+// * CreateProcess(&pid)
+// * GetTransportForProcess(pid, &transport)
+// * ReleaseTransport(transport)
+// * KillProcess(pid)
+
+// Attach to an existing process:
+// * Obtain pid from a user
+// * GetTransportForProcess(pid, &transport)
+// * ReleaseTransport(transport)
+
+class DbgTransportTarget
+{
+public:
+ DbgTransportTarget();
+
+ // Given a PID attempt to find or create a DbgTransportSession instance to manage a connection to a
+ // runtime in that process. Returns E_UNEXPECTED if the process can't be found. Also returns a handle that
+ // can be waited on for process termination.
+ HRESULT GetTransportForProcess(DWORD dwPID, DbgTransportSession **ppTransport, HANDLE *phProcessHandle);
+
+ // Give back a previously acquired transport (if nobody else is using the transport it will close down the
+ // connection at this point).
+ void ReleaseTransport(DbgTransportSession *pTransport);
+
+ // When and if the process starts the runtime will be told to halt and wait for a debugger attach.
+ HRESULT CreateProcess(LPCWSTR lpApplicationName,
+ LPCWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ LPVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation);
+
+ // Kill the process identified by PID.
+ void KillProcess(DWORD dwPID);
+
+ HRESULT Init();
+ void Shutdown();
+
+private:
+ struct ProcessEntry
+ {
+ ProcessEntry *m_pNext; // Next entry in the list
+ DWORD m_dwPID; // Process ID for this entry
+ HANDLE m_hProcess; // Process handle
+ DbgTransportSession *m_transport; // Debugger's connection to the process
+ DWORD m_cProcessRef; // Ref count
+
+ ~ProcessEntry();
+ };
+
+ ProcessEntry *m_pProcessList; // Head of list of currently alive processes (unsorted)
+ RSLock m_sLock; // Lock protecting read and write access to the target list
+
+ // Locate a process entry by PID. Assumes the lock is already held.
+ ProcessEntry *LocateProcessByPID(DWORD dwPID);
+};
+
+extern DbgTransportTarget *g_pDbgTransportTarget;
+
+#endif // FEATURE_DBGIPC_TRANSPORT_DI
+
+#endif // __DBG_TRANSPORT_MANAGER_INCLUDED
diff --git a/src/debug/di/dbgtransportpipeline.cpp b/src/debug/di/dbgtransportpipeline.cpp
new file mode 100644
index 0000000000..e3a3a8a54d
--- /dev/null
+++ b/src/debug/di/dbgtransportpipeline.cpp
@@ -0,0 +1,457 @@
+// 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.
+//*****************************************************************************
+// File: DbgTransportPipeline.cpp
+//
+
+//
+// Implements the native pipeline for Mac debugging.
+//*****************************************************************************
+
+#include "stdafx.h"
+#include "nativepipeline.h"
+#include "dbgtransportsession.h"
+#include "dbgtransportmanager.h"
+
+
+DWORD GetProcessId(const DEBUG_EVENT * pEvent)
+{
+ return pEvent->dwProcessId;
+}
+DWORD GetThreadId(const DEBUG_EVENT * pEvent)
+{
+ return pEvent->dwThreadId;
+}
+
+// Get exception event
+BOOL IsExceptionEvent(const DEBUG_EVENT * pEvent, BOOL * pfFirstChance, const EXCEPTION_RECORD ** ppRecord)
+{
+ if (pEvent->dwDebugEventCode != EXCEPTION_DEBUG_EVENT)
+ {
+ *pfFirstChance = FALSE;
+ *ppRecord = NULL;
+ return FALSE;
+ }
+ *pfFirstChance = pEvent->u.Exception.dwFirstChance;
+ *ppRecord = &(pEvent->u.Exception.ExceptionRecord);
+ return TRUE;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// INativeEventPipeline is an abstraction over the Windows native debugging pipeline. This class is an
+// implementation which works over an SSL connection for debugging a target process on a Mac remotely.
+// It builds on top of code:DbgTransportTarget (which is a connection to the debugger proxy on the Mac) and
+// code:DbgTransportSession (which is a connection to the target process on the Mac). See
+// code:IEventChannel for more information.
+//
+// Assumptions:
+// This class is NOT thread-safe. Caller is assumed to have taken the appropriate measures for
+// synchronization.
+//
+
+class DbgTransportPipeline :
+ public INativeEventPipeline
+{
+public:
+ DbgTransportPipeline()
+ {
+ m_fRunning = FALSE;
+ m_hProcess = NULL;
+ m_pIPCEvent = reinterpret_cast<DebuggerIPCEvent * >(m_rgbIPCEventBuffer);
+ m_pProxy = NULL;
+ m_pTransport = NULL;
+ _ASSERTE(!IsTransportRunning());
+ }
+
+ virtual ~DbgTransportPipeline()
+ {
+ Dispose();
+ }
+
+ // Call to free up the pipeline.
+ virtual void Delete();
+
+ virtual BOOL DebugSetProcessKillOnExit(bool fKillOnExit);
+
+ // Create
+ virtual HRESULT CreateProcessUnderDebugger(
+ MachineInfo machineInfo,
+ LPCWSTR lpApplicationName,
+ LPCWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ LPVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation);
+
+ // Attach
+ virtual HRESULT DebugActiveProcess(MachineInfo machineInfo, DWORD processId);
+
+ // Detach
+ virtual HRESULT DebugActiveProcessStop(DWORD processId);
+
+ // Block and wait for the next debug event from the debuggee process.
+ virtual BOOL WaitForDebugEvent(DEBUG_EVENT * pEvent, DWORD dwTimeout, CordbProcess * pProcess);
+
+ virtual BOOL ContinueDebugEvent(
+ DWORD dwProcessId,
+ DWORD dwThreadId,
+ DWORD dwContinueStatus
+ );
+
+ // Return a handle which will be signaled when the debuggee process terminates.
+ virtual HANDLE GetProcessHandle();
+
+ // Terminate the debuggee process.
+ virtual BOOL TerminateProcess(UINT32 exitCode);
+
+#ifdef FEATURE_PAL
+ virtual void CleanupTargetProcess()
+ {
+ m_pTransport->CleanupTargetProcess();
+ }
+#endif
+
+private:
+ // Return TRUE if the transport is up and runnning
+ BOOL IsTransportRunning()
+ {
+ return m_fRunning;
+ };
+
+ // clean up all resources
+ void Dispose()
+ {
+ if (m_hProcess != NULL)
+ {
+ CloseHandle(m_hProcess);
+ }
+ m_hProcess = NULL;
+
+ if (m_pTransport)
+ {
+ if (m_ticket.IsValid())
+ {
+ m_pTransport->StopUsingAsDebugger(&m_ticket);
+ }
+ m_pProxy->ReleaseTransport(m_pTransport);
+ }
+ m_pTransport = NULL;
+ m_pProxy = NULL;
+ }
+
+ BOOL m_fRunning;
+
+ DWORD m_dwProcessId;
+ // This is actually a handle to an event. This is only valid for waiting on process termination.
+ HANDLE m_hProcess;
+
+ DbgTransportTarget * m_pProxy;
+ DbgTransportSession * m_pTransport;
+
+ // Any buffer for storing a DebuggerIPCEvent must be at least CorDBIPC_BUFFER_SIZE big. For simplicity
+ // sake I have added an extra field member which points to the buffer.
+ DebuggerIPCEvent * m_pIPCEvent;
+ BYTE m_rgbIPCEventBuffer[CorDBIPC_BUFFER_SIZE];
+ DebugTicket m_ticket;
+};
+
+// Allocate and return a pipeline object for this platform
+INativeEventPipeline * NewPipelineForThisPlatform()
+{
+ return new (nothrow) DbgTransportPipeline();
+}
+
+// Call to free up the lpProcessInformationpeline.
+void DbgTransportPipeline::Delete()
+{
+ delete this;
+}
+
+// set whether to kill outstanding debuggees when the debugger exits.
+BOOL DbgTransportPipeline::DebugSetProcessKillOnExit(bool fKillOnExit)
+{
+ // This is not supported or necessary for Mac debugging. The only reason we need this on Windows is to
+ // ask the OS not to terminate the debuggee when the debugger exits. The Mac debugging pipeline doesn't
+ // automatically kill the debuggee when the debugger exits.
+ return TRUE;
+}
+
+// Create an process under the debugger.
+HRESULT DbgTransportPipeline::CreateProcessUnderDebugger(
+ MachineInfo machineInfo,
+ LPCWSTR lpApplicationName,
+ LPCWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ LPVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation)
+{
+ // INativeEventPipeline has a 1:1 relationship with CordbProcess.
+ _ASSERTE(!IsTransportRunning());
+
+ // We don't support interop-debugging on the Mac.
+ _ASSERTE(!(dwCreationFlags & (DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS)));
+
+ // When we're using a transport we can't deal with creating a suspended process (we need the process to
+ // startup in order that it can start up a transport thread and reply to our messages).
+ _ASSERTE(!(dwCreationFlags & CREATE_SUSPENDED));
+
+ // Connect to the debugger proxy on the remote machine and ask it to create a process for us.
+ HRESULT hr = E_FAIL;
+
+ m_pProxy = g_pDbgTransportTarget;
+ hr = m_pProxy->CreateProcess(lpApplicationName,
+ lpCommandLine,
+ lpProcessAttributes,
+ lpThreadAttributes,
+ bInheritHandles,
+ dwCreationFlags,
+ lpEnvironment,
+ lpCurrentDirectory,
+ lpStartupInfo,
+ lpProcessInformation);
+
+ if (SUCCEEDED(hr))
+ {
+ // Establish a connection to the actual runtime to be debugged.
+ hr = m_pProxy->GetTransportForProcess(lpProcessInformation->dwProcessId,
+ &m_pTransport,
+ &m_hProcess);
+ if (SUCCEEDED(hr))
+ {
+ // Wait for the connection to become useable (or time out).
+ if (!m_pTransport->WaitForSessionToOpen(10000))
+ {
+ hr = CORDBG_E_TIMEOUT;
+ }
+ else
+ {
+ if (!m_pTransport->UseAsDebugger(&m_ticket))
+ {
+ hr = CORDBG_E_DEBUGGER_ALREADY_ATTACHED;
+ }
+ }
+ }
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ _ASSERTE((m_hProcess != NULL) && (m_hProcess != INVALID_HANDLE_VALUE));
+
+ m_dwProcessId = lpProcessInformation->dwProcessId;
+
+ // For Mac remote debugging, we don't actually have a process handle to hand back to the debugger.
+ // Instead, we return a handle to an event as the "process handle". The Win32 event thread also waits
+ // on this event handle, and the event will be signaled when the proxy notifies us that the process
+ // on the remote machine is terminated. However, normally the debugger calls CloseHandle() immediately
+ // on the "process handle" after CreateProcess() returns. Doing so causes the Win32 event thread to
+ // continue waiting on a closed event handle, and so it will never wake up.
+ // (In fact, in Whidbey, we also duplicate the process handle in code:CordbProcess::Init.)
+ if (!DuplicateHandle(GetCurrentProcess(),
+ m_hProcess,
+ GetCurrentProcess(),
+ &(lpProcessInformation->hProcess),
+ 0, // ignored since we are going to pass DUPLICATE_SAME_ACCESS
+ FALSE,
+ DUPLICATE_SAME_ACCESS))
+ {
+ hr = HRESULT_FROM_GetLastError();
+ }
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ m_fRunning = TRUE;
+ }
+ else
+ {
+ Dispose();
+ }
+
+ return hr;
+}
+
+// Attach the debugger to this process.
+HRESULT DbgTransportPipeline::DebugActiveProcess(MachineInfo machineInfo, DWORD processId)
+{
+ // INativeEventPipeline has a 1:1 relationship with CordbProcess.
+ _ASSERTE(!IsTransportRunning());
+
+ HRESULT hr = E_FAIL;
+
+ m_pProxy = g_pDbgTransportTarget;
+
+ // Establish a connection to the actual runtime to be debugged.
+ hr = m_pProxy->GetTransportForProcess(processId, &m_pTransport, &m_hProcess);
+ if (SUCCEEDED(hr))
+ {
+ // TODO: Pass this timeout as a parameter all the way from debugger
+ // Wait for the connection to become useable (or time out).
+ if (!m_pTransport->WaitForSessionToOpen(10000))
+ {
+ hr = CORDBG_E_TIMEOUT;
+ }
+ else
+ {
+ if (!m_pTransport->UseAsDebugger(&m_ticket))
+ {
+ hr = CORDBG_E_DEBUGGER_ALREADY_ATTACHED;
+ }
+ }
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ m_dwProcessId = processId;
+ m_fRunning = TRUE;
+ }
+ else
+ {
+ Dispose();
+ }
+
+ return hr;
+}
+
+// Detach
+HRESULT DbgTransportPipeline::DebugActiveProcessStop(DWORD processId)
+{
+ // The only way to tell the transport to detach from a process is by shutting it down.
+ // That will happen when we neuter the CordbProcess object.
+ return E_NOTIMPL;
+}
+
+// Block and wait for the next debug event from the debuggee process.
+BOOL DbgTransportPipeline::WaitForDebugEvent(DEBUG_EVENT * pEvent, DWORD dwTimeout, CordbProcess * pProcess)
+{
+ if (!IsTransportRunning())
+ {
+ return FALSE;
+ }
+
+ // We need to wait for a debug event from the transport and the process termination event.
+ // On Windows, process termination is communicated via a debug event as well, but that's not true for
+ // the Mac debugging transport.
+ DWORD cWaitSet = 2;
+ HANDLE rghWaitSet[2];
+ rghWaitSet[0] = m_pTransport->GetDebugEventReadyEvent();
+ rghWaitSet[1] = m_hProcess;
+
+ DWORD dwRet = ::WaitForMultipleObjectsEx(cWaitSet, rghWaitSet, FALSE, dwTimeout, FALSE);
+
+ if (dwRet == WAIT_OBJECT_0)
+ {
+ // The Mac debugging transport actually transmits IPC events and not debug events.
+ // We need to convert the IPC event to a debug event and pass it back to the caller.
+ m_pTransport->GetNextEvent(m_pIPCEvent, CorDBIPC_BUFFER_SIZE);
+
+ pEvent->dwProcessId = m_pIPCEvent->processId;
+ _ASSERTE(m_dwProcessId == m_pIPCEvent->processId);
+
+ // We are supposed to return a thread ID in the DEBUG_EVENT back to our caller.
+ // However, we don't actually store the thread ID in the DebuggerIPCEvent anymore. Instead,
+ // we just get a VMPTR_Thread, and so we need to find the thread ID associated with the VMPTR_Thread.
+ pEvent->dwThreadId = 0;
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ if (!m_pIPCEvent->vmThread.IsNull())
+ {
+ pEvent->dwThreadId = pProcess->GetDAC()->TryGetVolatileOSThreadID(m_pIPCEvent->vmThread);
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ if (FAILED(hr))
+ {
+ return FALSE;
+ }
+
+ // The Windows implementation stores the target address of the IPC event in the debug event.
+ // We can do that for Mac debugging, but that would require the caller to do another cross-machine
+ // ReadProcessMemory(). Since we have all the data in-proc already, we just store a local address.
+ //
+ // @dbgtodo Mac - We are using -1 as a dummy base address right now.
+ // Currently Mac remote debugging doesn't really support multi-instance.
+ InitEventForDebuggerNotification(pEvent, PTR_TO_CORDB_ADDRESS(reinterpret_cast<LPVOID>(-1)), m_pIPCEvent);
+
+ return TRUE;
+ }
+ else if (dwRet == (WAIT_OBJECT_0 + 1))
+ {
+ // The process has been terminated.
+
+ // We don't have a lot of information here.
+ pEvent->dwDebugEventCode = EXIT_PROCESS_DEBUG_EVENT;
+ pEvent->dwProcessId = m_dwProcessId;
+ pEvent->dwThreadId = 0; // On Windows this is the first thread created in the process.
+ pEvent->u.ExitProcess.dwExitCode = 0; // This is not passed back to us by the transport.
+
+ // Once the process termination event is signaled, we cannot send or receive any events.
+ // So we mark the transport as not running anymore.
+ m_fRunning = FALSE;
+ return TRUE;
+ }
+ else
+ {
+ // We may have timed out, or the actual wait operation may have failed.
+ // Either way, we don't have an event.
+ return FALSE;
+ }
+}
+
+BOOL DbgTransportPipeline::ContinueDebugEvent(
+ DWORD dwProcessId,
+ DWORD dwThreadId,
+ DWORD dwContinueStatus
+)
+{
+ if (!IsTransportRunning())
+ {
+ return FALSE;
+ }
+
+ // See code:INativeEventPipeline::ContinueDebugEvent.
+ return TRUE;
+}
+
+// Return a handle which will be signaled when the debuggee process terminates.
+HANDLE DbgTransportPipeline::GetProcessHandle()
+{
+ HANDLE hProcessTerminated;
+
+ if (!DuplicateHandle(GetCurrentProcess(),
+ m_hProcess,
+ GetCurrentProcess(),
+ &hProcessTerminated,
+ 0, // ignored since we are going to pass DUPLICATE_SAME_ACCESS
+ FALSE,
+ DUPLICATE_SAME_ACCESS))
+ {
+ return NULL;
+ }
+
+ // The handle returned here is only valid for waiting on process termination.
+ // See code:INativeEventPipeline::GetProcessHandle.
+ return hProcessTerminated;
+}
+
+// Terminate the debuggee process.
+BOOL DbgTransportPipeline::TerminateProcess(UINT32 exitCode)
+{
+ _ASSERTE(IsTransportRunning());
+
+ // The transport will still be running until the process termination handle is signaled.
+ m_pProxy->KillProcess(m_dwProcessId);
+ return TRUE;
+}
diff --git a/src/debug/di/dbi.sln b/src/debug/di/dbi.sln
new file mode 100644
index 0000000000..33cbec162c
--- /dev/null
+++ b/src/debug/di/dbi.sln
@@ -0,0 +1,20 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2012
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "dbi", "dbi.vcxproj", "{D8445C62-03DC-4D6A-A2F2-1AAF31577151}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Win32 = Debug|Win32
+ Release|Win32 = Release|Win32
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {D8445C62-03DC-4D6A-A2F2-1AAF31577151}.Debug|Win32.ActiveCfg = Debug|Win32
+ {D8445C62-03DC-4D6A-A2F2-1AAF31577151}.Debug|Win32.Build.0 = Debug|Win32
+ {D8445C62-03DC-4D6A-A2F2-1AAF31577151}.Release|Win32.ActiveCfg = Release|Win32
+ {D8445C62-03DC-4D6A-A2F2-1AAF31577151}.Release|Win32.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/src/debug/di/dbi.vcxproj b/src/debug/di/dbi.vcxproj
new file mode 100644
index 0000000000..08533b7240
--- /dev/null
+++ b/src/debug/di/dbi.vcxproj
@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{D8445C62-03DC-4D6A-A2F2-1AAF31577151}</ProjectGuid>
+ <Keyword>Win32Proj</Keyword>
+ <RootNamespace>dbi</RootNamespace>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v110</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v110</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <LinkIncremental>true</LinkIncremental>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <LinkIncremental>false</LinkIncremental>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;DBI_EXPORTS;DBG_TARGET_X86;_TARGET_X86_;VS_COMPILE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\inc;..\..\inc;C:\clr_next\src\InternalApis\Sys_clr\inc;C:\CLR_Next\binaries\amd64chk\IntraPartitionAPIs\clr\inc;C:\CLR_Next\src\ndp\Common\Inc\version;C:\CLR_Next\binaries\amd64chk\SysBuild\Version;..\inc\i386;..\inc\dump</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <SubSystem>Windows</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;DBI_EXPORTS;DBG_TARGET_X86;_TARGET_X86_;VS_COMPILE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\inc;..\..\inc;C:\clr_next\src\InternalApis\Sys_clr\inc;C:\CLR_Next\binaries\amd64chk\IntraPartitionAPIs\clr\inc;C:\CLR_Next\src\ndp\Common\Inc\version;C:\CLR_Next\binaries\amd64chk\SysBuild\Version;;..\inc\i386</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <SubSystem>Windows</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClInclude Include="classfactory.h" />
+ <ClInclude Include="DbgTransportManager.h" />
+ <ClInclude Include="DDPack.h" />
+ <ClInclude Include="EventChannel.h" />
+ <ClInclude Include="EventRedirectionPipeline.h" />
+ <ClInclude Include="helpers.h" />
+ <ClInclude Include="NativePipeline.h" />
+ <ClInclude Include="RsEnumerator.hpp" />
+ <ClInclude Include="RSPriv.h" />
+ <ClInclude Include="ShimDataTarget.h" />
+ <ClInclude Include="shimpriv.h" />
+ <ClInclude Include="StdAfx.h" />
+ <ClInclude Include="symbolinfo.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="RSPriv.inl" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="breakpoint.cpp" />
+ <ClCompile Include="cordb.cpp" />
+ <ClCompile Include="DbgTransportManager.cpp" />
+ <ClCompile Include="DbgTransportPipeline.cpp" />
+ <ClCompile Include="DDPack.cpp" />
+ <ClCompile Include="DIValue.cpp" />
+ <ClCompile Include="EventRedirectionPipeline.cpp" />
+ <ClCompile Include="hash.cpp" />
+ <ClCompile Include="i386\CordbRegisterSet.cpp" />
+ <ClCompile Include="i386\primitives.cpp" />
+ <ClCompile Include="LocalEventChannel.cpp" />
+ <ClCompile Include="module.cpp" />
+ <ClCompile Include="NativePipeline.cpp" />
+ <ClCompile Include="PlatformSpecific.cpp" />
+ <ClCompile Include="process.cpp" />
+ <ClCompile Include="publish.cpp" />
+ <ClCompile Include="RemoteEventChannel.cpp" />
+ <ClCompile Include="RotorPipeline.cpp" />
+ <ClCompile Include="RsAppDomain.cpp" />
+ <ClCompile Include="RsAssembly.cpp" />
+ <ClCompile Include="rsclass.cpp" />
+ <ClCompile Include="rsfunction.cpp" />
+ <ClCompile Include="RsMain.cpp" />
+ <ClCompile Include="RsMda.cpp" />
+ <ClCompile Include="RSRegSetCommon.cpp" />
+ <ClCompile Include="RsStackWalk.cpp" />
+ <ClCompile Include="RsThread.cpp" />
+ <ClCompile Include="RsType.cpp" />
+ <ClCompile Include="shared.cpp" />
+ <ClCompile Include="shimcallback.cpp" />
+ <ClCompile Include="ShimDataTarget.cpp" />
+ <ClCompile Include="ShimEvents.cpp" />
+ <ClCompile Include="ShimLocalDataTarget.cpp" />
+ <ClCompile Include="ShimProcess.cpp" />
+ <ClCompile Include="ShimRemoteDataTarget.cpp" />
+ <ClCompile Include="ShimStackWalk.cpp" />
+ <ClCompile Include="StdAfx.cpp" />
+ <ClCompile Include="symbolinfo.cpp" />
+ <ClCompile Include="ValueHome.cpp" />
+ <ClCompile Include="WindowsPipeline.cpp" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/src/debug/di/dirs.proj b/src/debug/di/dirs.proj
new file mode 100644
index 0000000000..c5a98947e5
--- /dev/null
+++ b/src/debug/di/dirs.proj
@@ -0,0 +1,18 @@
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <!--Import the settings-->
+ <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\clr.props" />
+
+ <PropertyGroup>
+ <BuildInPhase1>true</BuildInPhase1>
+ <BuildInPhaseDefault>false</BuildInPhaseDefault>
+ <BuildCoreBinaries>true</BuildCoreBinaries>
+ </PropertyGroup>
+
+ <!--The following projects will build during PHASE 1-->
+ <ItemGroup Condition="'$(BuildExePhase)' == '1'">
+ <ProjectFile Condition="'$(FeatureDbiDebugging)'=='true'" Include="hostlocal\di.nativeproj" />
+ </ItemGroup>
+
+ <!--Import the targets-->
+ <Import Project="$(_NTDRIVE)$(_NTROOT)\tools\Microsoft.DevDiv.Traversal.targets" />
+</Project>
diff --git a/src/debug/di/divalue.cpp b/src/debug/di/divalue.cpp
new file mode 100644
index 0000000000..50ecd68aa9
--- /dev/null
+++ b/src/debug/di/divalue.cpp
@@ -0,0 +1,4564 @@
+// 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.
+//*****************************************************************************
+// File: DIValue.cpp
+//
+
+//
+//*****************************************************************************
+#include "stdafx.h"
+#include "primitives.h"
+
+// copy from a MemoryRange to dest
+// Arguments:
+// input: source - MemoryRange describing the start address and size of the source buffer
+// output: dest - address of the buffer to which the source buffer is copied
+// Note: the buffer for dest must be allocated by the caller and must be large enough to hold the
+// bytes from the source buffer.
+void localCopy(void * dest, MemoryRange source)
+{
+ _ASSERTE(dest != NULL);
+ _ASSERTE(source.StartAddress() != NULL);
+
+ memcpy(dest, source.StartAddress(), source.Size());
+}
+
+// for an inheritance graph of the ICDValue types, // See file:./ICorDebugValueTypes.vsd for a diagram of the types.
+
+/* ------------------------------------------------------------------------- *
+ * CordbValue class
+ * ------------------------------------------------------------------------- */
+
+CordbValue::CordbValue(CordbAppDomain * appdomain,
+ CordbType * type,
+ CORDB_ADDRESS id,
+ bool isLiteral,
+ NeuterList * pList)
+ : CordbBase(
+ ((appdomain != NULL) ? (appdomain->GetProcess()) : (type->GetProcess())),
+ (UINT_PTR)id, enumCordbValue),
+ m_appdomain(appdomain),
+ m_type(type), // implicit InternalAddRef
+ //m_sigCopied(false),
+ m_size(0),
+ m_isLiteral(isLiteral)
+{
+ HRESULT hr = S_OK;
+
+ _ASSERTE(GetProcess() != NULL);
+
+ // Add to a neuter list. If none is provided, use the ExitProcess list as a default.
+ // The main neuter lists of interest here are:
+ // - CordbProcess::GetContinueNeuterList() - Shortest. Neuter when the process continues.
+ // - CordbAppDomain::GetExitNeuterList() - Middle. Neuter when the AD exits. Since most Values (except globals) are in
+ // a specific AD, this almost catches all; and keeps us safe in AD-unload scenarios.
+ // - CordbProcess::GetExitNeuterList() - Worst. Doesn't neuter until the process exits (or we detach).
+ // This could be a long time.
+ if (pList == NULL)
+ {
+ pList = GetProcess()->GetExitNeuterList();
+ }
+
+
+ EX_TRY
+ {
+ pList->Add(GetProcess(), this);
+ }
+ EX_CATCH_HRESULT(hr);
+ SetUnrecoverableIfFailed(GetProcess(), hr);
+} // CordbValue::CordbValue
+
+CordbValue::~CordbValue()
+{
+ DTOR_ENTRY(this);
+
+ _ASSERTE(this->IsNeutered());
+
+ _ASSERTE(m_type == NULL);
+} // CordbValue::~CordbValue
+
+void CordbValue::Neuter()
+{
+ m_appdomain = NULL;
+ m_type.Clear();
+
+ ValueHome * pValueHome = GetValueHome();
+ if (pValueHome != NULL)
+ {
+ pValueHome->Clear();
+ }
+ CordbBase::Neuter();
+} // CordbValue::Neuter
+
+// Helper for code:CordbValue::CreateValueByType. Create a new instance of CordbGenericValue
+// Arguments:
+// input: pAppdomain - appdomain to which the value belongs
+// pType - type of the value
+// remoteValue - remote address and size of the value
+// localValue - local address and size of the value
+// ppRemoteRegAddr - register address of the value
+// output: ppValue - the newly created instance of an ICDValue
+// Notes:
+// - only one of the three locations will be non-NULL
+// - Throws
+/* static */
+void CordbValue::CreateGenericValue(CordbAppDomain * pAppdomain,
+ CordbType * pType,
+ TargetBuffer remoteValue,
+ MemoryRange localValue,
+ EnregisteredValueHomeHolder * ppRemoteRegAddr,
+ ICorDebugValue** ppValue)
+{
+ LOG((LF_CORDB,LL_INFO100000,"CV::CreateValueByType CreateGenericValue\n"));
+ RSSmartPtr<CordbGenericValue> pGenValue;
+ // A generic value
+ // By using a RSSmartPtr we ensure that in both success and failure cases,
+ // this object is cleaned up properly (deleted or not depending on ref counts).
+ // Specifically, the object has probably been placed on a neuter list so we
+ // can't delete it (but this is a detail we shouldn't rely on)
+ pGenValue.Assign(new CordbGenericValue(pAppdomain,
+ pType,
+ remoteValue,
+ ppRemoteRegAddr));
+
+ pGenValue->Init(localValue); // throws
+
+ pGenValue->AddRef();
+ *ppValue = (ICorDebugValue *)(ICorDebugGenericValue *)pGenValue;
+} // CordbValue::CreateGenericValue
+
+// create a new instance of CordbVCObjectValue or CordbReferenceValue
+// Arguments:
+// input: pAppdomain - appdomain to which the value belongs
+// pType - type of the value
+// boxed - indicates whether the value is boxed
+// remoteValue - remote address and size of the value
+// localValue - local address and size of the value
+// ppRemoteRegAddr - register address of the value
+// output: ppValue - the newly created instance of an ICDValue
+// Notes:
+// - only one of the three locations will be non-NULL
+// - Throws error codes from reading process memory
+/* static */
+void CordbValue::CreateVCObjOrRefValue(CordbAppDomain * pAppdomain,
+ CordbType * pType,
+ bool boxed,
+ TargetBuffer remoteValue,
+ MemoryRange localValue,
+ EnregisteredValueHomeHolder * ppRemoteRegAddr,
+ ICorDebugValue** ppValue)
+
+{
+ HRESULT hr = S_OK;
+ LOG((LF_CORDB,LL_INFO1000000,"CV::CreateValueByType Creating ReferenceValue\n"));
+
+ // We either have a boxed or unboxed value type, or we have a value that's not a value type.
+ // For an unboxed value type, we'll create an instance of CordbVCObjectValue. Otherwise, we'll
+ // create an instance of CordbReferenceValue.
+
+ // do we have a value type?
+ bool isVCObject = pType->IsValueType(); // throws
+
+ if (!boxed && isVCObject)
+ {
+ RSSmartPtr<CordbVCObjectValue> pVCValue(new CordbVCObjectValue(pAppdomain,
+ pType,
+ remoteValue,
+ ppRemoteRegAddr));
+
+ IfFailThrow(pVCValue->Init(localValue));
+
+ pVCValue->AddRef();
+ *ppValue = (ICorDebugValue*)(ICorDebugObjectValue*)pVCValue;
+ }
+ else
+ {
+ // either the value is boxed or it's not a value type
+ RSSmartPtr<CordbReferenceValue> pRef;
+ hr = CordbReferenceValue::Build(pAppdomain,
+ pType,
+ remoteValue,
+ localValue,
+ VMPTR_OBJECTHANDLE::NullPtr(),
+ ppRemoteRegAddr, // Home
+ &pRef);
+ IfFailThrow(hr);
+ hr = pRef->QueryInterface(__uuidof(ICorDebugValue), (void**)ppValue);
+ _ASSERTE(SUCCEEDED(hr));
+ }
+} // CordbValue::CreateVCObjOrRefValue
+
+//
+// Create the proper ICDValue instance based on the given element type.
+// Arguments:
+// input: pAppdomain - appdomain to which the value belongs
+// pType - type of the value
+// boxed - indicates whether the value is boxed
+// remoteValue - remote address and size of the value
+// localValue - local address and size of the value
+// ppRemoteRegAddr - register address of the value
+// output: ppValue - the newly created instance of an ICDValue
+// Notes:
+// - Only one of the three locations, remoteValue, localValue or ppRemoteRegAddr, will be non-NULL.
+// - Throws.
+/*static*/ void CordbValue::CreateValueByType(CordbAppDomain * pAppdomain,
+ CordbType * pType,
+ bool boxed,
+ TargetBuffer remoteValue,
+ MemoryRange localValue,
+ EnregisteredValueHomeHolder * ppRemoteRegAddr,
+ ICorDebugValue** ppValue)
+{
+ INTERNAL_SYNC_API_ENTRY(pAppdomain->GetProcess()); //
+
+ // We'd really hope that our callers give us a valid appdomain, but in case
+ // they don't, we'll fail gracefully.
+ if ((pAppdomain != NULL) && pAppdomain->IsNeutered())
+ {
+ STRESS_LOG1(LF_CORDB, LL_EVERYTHING, "CVBT using neutered AP, %p\n", pAppdomain);
+ ThrowHR(E_INVALIDARG);
+ }
+
+ LOG((LF_CORDB,LL_INFO100000,"CV::CreateValueByType\n"));
+
+ *ppValue = NULL;
+
+ switch(pType->m_elementType)
+ {
+ case ELEMENT_TYPE_BOOLEAN:
+ case ELEMENT_TYPE_CHAR:
+ case ELEMENT_TYPE_I1:
+ case ELEMENT_TYPE_U1:
+ case ELEMENT_TYPE_I2:
+ case ELEMENT_TYPE_U2:
+ case ELEMENT_TYPE_I4:
+ case ELEMENT_TYPE_U4:
+ case ELEMENT_TYPE_R4:
+ case ELEMENT_TYPE_I8:
+ case ELEMENT_TYPE_U8:
+ case ELEMENT_TYPE_R8:
+ case ELEMENT_TYPE_I:
+ case ELEMENT_TYPE_U:
+ {
+ CreateGenericValue(pAppdomain, pType, remoteValue, localValue, ppRemoteRegAddr, ppValue); // throws
+ break;
+ }
+
+ case ELEMENT_TYPE_CLASS:
+ case ELEMENT_TYPE_OBJECT:
+ case ELEMENT_TYPE_STRING:
+ case ELEMENT_TYPE_PTR:
+ case ELEMENT_TYPE_BYREF:
+ case ELEMENT_TYPE_TYPEDBYREF:
+ case ELEMENT_TYPE_ARRAY:
+ case ELEMENT_TYPE_SZARRAY:
+ case ELEMENT_TYPE_FNPTR:
+ {
+ CreateVCObjOrRefValue(pAppdomain, pType, boxed, remoteValue, localValue, ppRemoteRegAddr, ppValue); // throws
+ break;
+ }
+
+ default:
+ _ASSERTE(!"Bad value type!");
+ ThrowHR(E_FAIL);
+ }
+} // CordbValue::CreateValueByType
+
+// Create the proper ICDValue instance based on the given remote heap object
+// Arguments:
+// pAppDomain - the app domain the remote object is in
+// vmObj - the remote object to get an ICDValue for
+ICorDebugValue* CordbValue::CreateHeapValue(CordbAppDomain* pAppDomain, VMPTR_Object vmObj)
+{
+ IDacDbiInterface* pDac = pAppDomain->GetProcess()->GetDAC();
+
+ TargetBuffer objBuffer = pDac->GetObjectContents(vmObj);
+ VOID* pRemoteAddr = CORDB_ADDRESS_TO_PTR(objBuffer.pAddress);
+ // This creates a local reference that has a remote address in it. Ie &pRemoteAddr is an address
+ // in the host address space and pRemoteAddr is an address in the target.
+ MemoryRange localReferenceDescription(&pRemoteAddr, sizeof(pRemoteAddr));
+ RSSmartPtr<CordbReferenceValue> pRefValue;
+ IfFailThrow(CordbReferenceValue::Build(pAppDomain,
+ NULL,
+ EMPTY_BUFFER,
+ localReferenceDescription,
+ VMPTR_OBJECTHANDLE::NullPtr(),
+ NULL,
+ &pRefValue));
+
+ // Dereference our temporary reference value to construct the heap value we want
+ ICorDebugValue* pExtValue;
+ IfFailThrow(pRefValue->Dereference(&pExtValue));
+ return pExtValue;
+}
+
+// Gets the size om bytes of a value from its type. If the value is complex, we assume it is represented as
+// a reference, since this is called for values that have been found on the stack, as an element of an
+// array (represented as CordbArrayValue) or field of an object (CordbObjectValue) or the result of a
+// func eval. For unboxed value types, we get the size of the entire value (it is not represented as a
+// reference).
+// Examples:
+// - int on the stack
+// => sizeof(int)
+// - int as a field in an object on the heap
+// =>sizeof(int)
+// - Boxed int on the heap
+// => size of a pointer
+// - Class Point { int x; int y}; // class will have a method table / object header which may increase size.
+// => size of a pointer
+// - Struct Point {int x; int y; }; // unboxed struct may not necessarily have the object header.
+// => 2 * sizeof(int)
+// - List<int>
+// => size of a pointer
+// Arguments: pType - the type of the value
+// boxing - indicates whether the value is boxed or not
+// Return Value: the size of the value
+// Notes: Throws
+// In general, this returns the unboxed size of the value, but if we have a type
+// that represents a non-generic and it's not an unboxed value type, we know that
+// it will be represented as a reference, so we return the size of a pointer instead.
+/* static */
+ULONG32 CordbValue::GetSizeForType(CordbType * pType, BoxedValue boxing)
+{
+ ULONG32 size = 0;
+
+ switch(pType->m_elementType)
+ {
+ case ELEMENT_TYPE_BOOLEAN:
+ case ELEMENT_TYPE_CHAR:
+ case ELEMENT_TYPE_I1:
+ case ELEMENT_TYPE_U1:
+ case ELEMENT_TYPE_I2:
+ case ELEMENT_TYPE_U2:
+ case ELEMENT_TYPE_I4:
+ case ELEMENT_TYPE_U4:
+ case ELEMENT_TYPE_R4:
+ case ELEMENT_TYPE_I8:
+ case ELEMENT_TYPE_U8:
+ case ELEMENT_TYPE_R8:
+ case ELEMENT_TYPE_I:
+ case ELEMENT_TYPE_U: pType->GetUnboxedObjectSize(&size); break;
+
+ case ELEMENT_TYPE_CLASS:
+ case ELEMENT_TYPE_OBJECT:
+ case ELEMENT_TYPE_STRING:
+ case ELEMENT_TYPE_PTR:
+ case ELEMENT_TYPE_BYREF:
+ case ELEMENT_TYPE_TYPEDBYREF:
+ case ELEMENT_TYPE_ARRAY:
+ case ELEMENT_TYPE_SZARRAY:
+ case ELEMENT_TYPE_FNPTR: {
+ bool isUnboxedVCObject = false;
+
+ if (boxing == kUnboxed)
+ {
+ isUnboxedVCObject = pType->IsValueType(); // throws
+ }
+ if (!isUnboxedVCObject)
+ {
+ // if it's not an unboxed value type (we're in the case
+ // for compound types), then it's a reference
+ // and we just want to return the size of a pointer
+ size = sizeof(void *);
+ }
+ else
+ {
+ pType->GetUnboxedObjectSize(&size);
+ }
+ } break;
+
+ default:
+ _ASSERTE(!"Bad value type!");
+}
+ return size;
+} // CordbValue::GetSizeForType
+
+
+HRESULT CordbValue::CreateBreakpoint(ICorDebugValueBreakpoint **ppBreakpoint)
+{
+ VALIDATE_POINTER_TO_OBJECT(ppBreakpoint, ICorDebugValueBreakpoint **);
+
+ return E_NOTIMPL;
+} // CordbValue::CreateBreakpoint
+
+// gets the exact type of a value
+// Arguments:
+// input: none (uses m_type field)
+// output: ppType - an instance of ICDType representing the exact type of the value
+// Return Value:
+HRESULT CordbValue::GetExactType(ICorDebugType **ppType)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(ppType, ICorDebugType **);
+ FAIL_IF_NEUTERED(this);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ *ppType = static_cast<ICorDebugType*> (m_type);
+
+ if (*ppType != NULL)
+ (*ppType)->AddRef();
+
+ return S_OK;
+} // CordbValue::GetExactType
+
+// CreateHandle for a heap object.
+// @todo: How to prevent this being called by non-heap object?
+// Arguments:
+// input: handleType - type of the handle to be created
+// output: ppHandle - on success, the newly created handle
+// Return Value: S_OK on success or E_INVALIDARG, E_OUTOFMEMORY, or CORDB_E_HELPER_MAY_DEADLOCK
+HRESULT CordbValue::InternalCreateHandle(CorDebugHandleType handleType,
+ ICorDebugHandleValue ** ppHandle)
+{
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+ LOG((LF_CORDB,LL_INFO1000,"CV::CreateHandle\n"));
+
+ DebuggerIPCEvent event;
+ CordbProcess *process;
+ BOOL fStrong = FALSE;
+
+ // @dbgtodo- , as part of inspection, convert this path to throwing.
+ if (ppHandle == NULL)
+ {
+ return E_INVALIDARG;
+ }
+
+ *ppHandle = NULL;
+
+ if (handleType == HANDLE_STRONG)
+ {
+ fStrong = TRUE;
+ }
+ else
+ {
+ _ASSERTE(handleType == HANDLE_WEAK_TRACK_RESURRECTION);
+ }
+
+
+ // Create the ICorDebugHandleValue object
+ RSInitHolder<CordbHandleValue> pHandle(new (nothrow) CordbHandleValue(m_appdomain, m_type, handleType) );
+
+ if (pHandle == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ // Send the event to create the handle.
+ process = m_appdomain->GetProcess();
+ _ASSERTE(process != NULL);
+
+ process->InitIPCEvent(&event,
+ DB_IPCE_CREATE_HANDLE,
+ true,
+ m_appdomain->GetADToken());
+
+ CORDB_ADDRESS addr = GetValueHome() != NULL ? GetValueHome()->GetAddress() : NULL;
+ event.CreateHandle.objectToken = CORDB_ADDRESS_TO_PTR(addr);
+ event.CreateHandle.fStrong = fStrong;
+
+ // Note: two-way event here...
+ HRESULT hr = process->SendIPCEvent(&event, sizeof(DebuggerIPCEvent));
+ hr = WORST_HR(hr, event.hr);
+
+ if (SUCCEEDED(hr))
+ {
+ _ASSERTE(event.type == DB_IPCE_CREATE_HANDLE_RESULT);
+
+ // Initialize the handle value object.
+ hr = pHandle->Init(event.CreateHandleResult.vmObjectHandle);
+ }
+
+ if (!SUCCEEDED(hr))
+ {
+ // Free the handle from the left-side.
+ pHandle->Dispose();
+
+ // The RSInitHolder will neuter and delete it.
+ return hr;
+ }
+
+ // Pass out the new handle value object.
+ pHandle.TransferOwnershipExternal(ppHandle);
+
+ return S_OK;
+} // CordbValue::InternalCreateHandle
+
+/* ------------------------------------------------------------------------- *
+ * Generic Value class
+ * ------------------------------------------------------------------------- */
+
+//
+// CordbGenericValue constructor that builds a generic value from
+// a remote address or register.
+// Arguments:
+// input: pAppdomain - the app domain to which the value belongs
+// pType - the type of the value
+// remoteValue - buffer (and size) of the remote location where
+// the value resides. This may be NULL if the value
+// is enregistered.
+// ppRemoteRegAddr - information describing the register in which the
+// value resides. This may be NULL--only one of
+// ppRemoteRegAddr and remoteValue will be non-NULL,
+// depending on whether the value is in a register or
+// memory.
+CordbGenericValue::CordbGenericValue(CordbAppDomain * pAppdomain,
+ CordbType * pType,
+ TargetBuffer remoteValue,
+ EnregisteredValueHomeHolder * ppRemoteRegAddr)
+ : CordbValue(pAppdomain, pType, remoteValue.pAddress, false),
+ m_pValueHome(NULL)
+{
+ _ASSERTE(pType->m_elementType != ELEMENT_TYPE_END);
+ _ASSERTE(pType->m_elementType != ELEMENT_TYPE_VOID);
+ _ASSERTE(pType->m_elementType < ELEMENT_TYPE_MAX);
+
+ // We can fill in the size now for generic values.
+ ULONG32 size;
+ HRESULT hr;
+ hr = pType->GetUnboxedObjectSize(&size);
+ _ASSERTE (!FAILED(hr));
+ m_size = size;
+
+ // now instantiate the value home
+ NewHolder<ValueHome> pHome(NULL);
+ if (remoteValue.IsEmpty())
+ {
+ pHome = (new RegisterValueHome(pAppdomain->GetProcess(), ppRemoteRegAddr));
+ }
+ else
+ {
+ pHome = (new RemoteValueHome(pAppdomain->GetProcess(), remoteValue));
+ }
+ m_pValueHome = pHome.GetValue(); // throws
+ pHome.SuppressRelease();
+} // CordbGenericValue::CordbGenericValue
+
+//
+// CordbGenericValue constructor that builds an empty generic value
+// from just an element type. Used for literal values for func evals
+// only.
+// Arguments:
+// input: pType - the type of the value
+CordbGenericValue::CordbGenericValue(CordbType * pType)
+ : CordbValue(NULL, pType, NULL, true),
+ m_pValueHome(NULL)
+{
+ // The only purpose of a literal value is to hold a RS literal value.
+ ULONG32 size;
+ HRESULT hr;
+ hr = pType->GetUnboxedObjectSize(&size);
+ _ASSERTE (!FAILED(hr));
+ m_size = size;
+
+ memset(m_pCopyOfData, 0, m_size);
+
+ // there is no value home for a literal so we leave it as NULL
+} // CordbGenericValue::CordbGenericValue
+
+// destructor
+CordbGenericValue::~CordbGenericValue()
+{
+ if (m_pValueHome != NULL)
+ {
+ delete m_pValueHome;
+ m_pValueHome = NULL;
+}
+} // CordbGenericValue::~CordbGenericValue
+
+HRESULT CordbGenericValue::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugValue)
+ {
+ *pInterface = static_cast<ICorDebugValue*>(static_cast<ICorDebugGenericValue*>(this));
+ }
+ else if (id == IID_ICorDebugValue2)
+ {
+ *pInterface = static_cast<ICorDebugValue2*>(this);
+ }
+ else if (id == IID_ICorDebugValue3)
+ {
+ *pInterface = static_cast<ICorDebugValue3*>(this);
+ }
+ else if (id == IID_ICorDebugGenericValue)
+ {
+ *pInterface = static_cast<ICorDebugGenericValue*>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugGenericValue*>(this));
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+} // CordbGenericValue::QueryInterface
+
+//
+// initialize a generic value by copying the necessary data, either
+// from the remote process or from another value in this process.
+// Argument:
+// input: localValue - RS location of value to be copied. This could be NULL or it
+// could be a field from the cached copy of a CordbVCObjectValue or CordbObjectValue
+// instance or an element from the cached copy of a CordbArrayValue instance
+// Note: Throws error codes from reading process memory
+void CordbGenericValue::Init(MemoryRange localValue)
+{
+ INTERNAL_SYNC_API_ENTRY(this->GetProcess());
+
+ if(!m_isLiteral)
+ {
+ // If neither localValue.StartAddress nor m_remoteValue.pAddress are set, then all that means
+ // is that we've got a pre-initialized 64-bit value.
+ if (localValue.StartAddress() != NULL)
+ {
+ // Copy the data out of the local address space.
+ localCopy(m_pCopyOfData, localValue);
+ }
+ else
+ {
+ m_pValueHome->GetValue(MemoryRange(m_pCopyOfData, m_size)); // throws
+ }
+ }
+} // CordbGenericValue::Init
+
+// gets the value (i.e., number, boolean or pointer value) for this instance of CordbGenericValue
+// Arguments:
+// output: pTo - the starting address of a buffer in which the value will be written. This buffer must
+// be guaranteed by the caller to be large enough to hold the value. There is no way for
+// us to check here if it is. This must be non-NULL.
+// Return Value: S_OK on success or E_INVALIDARG if the pTo is NULL
+HRESULT CordbGenericValue::GetValue(void *pTo)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(pTo, BYTE, m_size, false, true);
+
+ _ASSERTE(m_pCopyOfData != NULL);
+ // Copy out the value
+ memcpy(pTo, m_pCopyOfData, m_size);
+
+ return S_OK;
+} // CordbGenericValue::GetValue
+
+// Sets the value of this instance of CordbGenericValue
+// Arguments:
+// input: pFrom - pointer to a buffer holding the new value. We assume this is the same size as the
+// original value; we have no way to check. This must be non-NULL.
+// Return Value: S_OK on success or E_INVALIDARG if the pFrom is NULL
+HRESULT CordbGenericValue::SetValue(void *pFrom)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(pFrom, BYTE, m_size, true, false);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ // We only need to send to the left side to update values that are
+ // object references. For generic values, we can simply do a write
+ // memory.
+
+ EX_TRY
+ {
+ if(!m_isLiteral)
+ {
+ m_pValueHome->SetValue(MemoryRange(pFrom, m_size), m_type); // throws
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ IfFailRet(hr);
+
+ // That worked, so update the copy of the value we have in
+ // m_copyOfData.
+ memcpy(m_pCopyOfData, pFrom, m_size);
+
+ return hr;
+} // CordbGenericValue::SetValue
+
+// copies the value from this instance of CordbGenericValue iff the value represents a literal
+// Arguments:
+// output: pBuffer - pointer to the beginning of a caller-allocated buffer.This buffer must
+// be guaranteed by the caller to be large enough to hol
+// d the value. There is no way for
+// us to check here if it is. This must be non-NULL.
+// Return Value: true iff this is a literal value and pBuffer is a valid writeable address
+bool CordbGenericValue::CopyLiteralData(BYTE *pBuffer)
+{
+ INTERNAL_SYNC_API_ENTRY(this->GetProcess());
+ _ASSERTE(pBuffer != NULL);
+
+ // If this is a RS fabrication, copy the literal data into the
+ // given buffer and return true.
+ if (m_isLiteral)
+ {
+ _ASSERTE(m_size <= 8);
+ memcpy(pBuffer, m_pCopyOfData, m_size);
+ return true;
+ }
+ else
+ return false;
+} // CordbGenericValue::CopyLiteralData
+
+/* ------------------------------------------------------------------------- *
+ * Reference Value class
+ * ------------------------------------------------------------------------- */
+
+// constructor
+// Arguments:
+// input: pAppdomain - appdomain to which the value belongs
+// pType - the type of the referent (the object pointed to)
+// localValue - the RS address and size of the buffer from which the reference
+// will be copied. This will be NULL if either remoteValue,
+// ppRemoteRegAddr or vmObjectHandle is non-NULL. Otherwise, it will
+// point into the local cached copy of another instance of ICDValue
+// remoteValue - the LS address and size of the buffer from which the reference
+// will be copied. This will be NULL if either localValue,
+// ppRemoteRegAddr, or vmObjectHandle is non-NULL.
+// ppRemoteRegAddr - information about the register location of the buffer from which
+// the reference will be copied. This will be NULL if either localValue,
+// remoteValue, or vmObjectHandle is non-NULL.
+// vmObjectHandle - a LS object handle holding the reference. This will be NULL if either
+// localValue, remoteValue, or ppRemoteRegAddr is non-NULL.
+// Note: this may throw OOM
+CordbReferenceValue::CordbReferenceValue(CordbAppDomain * pAppdomain,
+ CordbType * pType,
+ MemoryRange localValue,
+ TargetBuffer remoteValue,
+ EnregisteredValueHomeHolder * ppRemoteRegAddr,
+ VMPTR_OBJECTHANDLE vmObjectHandle)
+ : CordbValue(pAppdomain, pType, remoteValue.pAddress, false,
+ // We'd like to change this to be a ContinueList so it gets neutered earlier,
+ // but it may be a breaking change
+ pAppdomain->GetSweepableExitNeuterList()),
+
+ m_realTypeOfTypedByref(NULL)
+{
+ memset(&m_info, 0, sizeof(m_info));
+
+ LOG((LF_CORDB,LL_EVERYTHING,"CRV::CRV: this:0x%x\n",this));
+ m_size = sizeof(void *);
+
+ // now instantiate the value home
+ NewHolder<ValueHome> pHome(NULL);
+
+ if (!vmObjectHandle.IsNull())
+ {
+ pHome = (new HandleValueHome(pAppdomain->GetProcess(), vmObjectHandle));
+ m_valueHome.SetObjHandleFlag(false);
+ }
+
+ else if (remoteValue.IsEmpty())
+ {
+ pHome = (new RegisterValueHome(pAppdomain->GetProcess(), ppRemoteRegAddr));
+ m_valueHome.SetObjHandleFlag(true);
+
+ }
+ else
+ {
+ pHome = (new RefRemoteValueHome(pAppdomain->GetProcess(), remoteValue));
+}
+ m_valueHome.m_pHome = pHome.GetValue(); // throws
+ pHome.SuppressRelease();
+} // CordbReferenceValue::CordbReferenceValue
+
+// CordbReferenceValue constructor that builds an empty reference value
+// from just an element type. Used for literal values for func evals
+// only.
+// Arguments:
+// input: pType - the type of the value
+CordbReferenceValue::CordbReferenceValue(CordbType * pType)
+ : CordbValue(NULL, pType, NULL, true, pType->GetAppDomain()->GetSweepableExitNeuterList())
+{
+ memset(&m_info, 0, sizeof(m_info));
+
+ // The only purpose of a literal value is to hold a RS literal value.
+ m_size = sizeof(void*);
+
+ // there is no value home for a literal
+ m_valueHome.m_pHome = NULL;
+} // CordbReferenceValue::CordbReferenceValue
+
+// copies the value from this instance of CordbReferenceValue iff the value represents a literal
+// Arguments:
+// output: pBuffer - pointer to the beginning of a caller-allocated buffer.This buffer must
+// be guaranteed by the caller to be large enough to hold the value.
+// There is no way for us to check here if it is. This must be non-NULL.
+// Return Value: true iff this is a literal value and pBuffer is a valid writeable address
+bool CordbReferenceValue::CopyLiteralData(BYTE *pBuffer)
+{
+ _ASSERTE(pBuffer != NULL);
+
+ // If this is a RS fabrication, then its a null reference.
+ if (m_isLiteral)
+ {
+ void *n = NULL;
+ memcpy(pBuffer, &n, sizeof(n));
+ return true;
+ }
+ else
+ return false;
+} // CordbReferenceValue::CopyLiteralData
+
+// destructor
+CordbReferenceValue::~CordbReferenceValue()
+{
+ DTOR_ENTRY(this);
+
+ LOG((LF_CORDB,LL_EVERYTHING,"CRV::~CRV: this:0x%x\n",this));
+
+ _ASSERTE(IsNeutered());
+} // CordbReferenceValue::~CordbReferenceValue
+
+void CordbReferenceValue::Neuter()
+{
+ if (m_valueHome.m_pHome != NULL)
+ {
+ m_valueHome.m_pHome->Clear();
+ delete m_valueHome.m_pHome;
+ m_valueHome.m_pHome = NULL;
+ }
+
+ m_realTypeOfTypedByref = NULL;
+ CordbValue::Neuter();
+} // CordbReferenceValue::Neuter
+
+
+HRESULT CordbReferenceValue::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugValue)
+ {
+ *pInterface = static_cast<ICorDebugValue*>(static_cast<ICorDebugReferenceValue*>(this));
+ }
+ else if (id == IID_ICorDebugValue2)
+ {
+ *pInterface = static_cast<ICorDebugValue2*>(this);
+ }
+ else if (id == IID_ICorDebugValue3)
+ {
+ *pInterface = static_cast<ICorDebugValue3*>(this);
+ }
+ else if (id == IID_ICorDebugReferenceValue)
+ {
+ *pInterface = static_cast<ICorDebugReferenceValue*>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugReferenceValue*>(this));
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+} // CordbReferenceValue::QueryInterface
+
+// gets the type of the referent of the object ref
+// Arguments:
+// output: pType - the type of the value. The caller must guarantee that pType is non-null.
+// Return Value: S_OK on success, E_INVALIDARG on failure
+HRESULT CordbReferenceValue::GetType(CorElementType *pType)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pType, CorElementType *);
+
+ if( m_type == NULL )
+ {
+ // We may not have a CordbType if we were created from a GC handle to NULL
+ _ASSERTE( m_info.objTypeData.elementType == ELEMENT_TYPE_CLASS );
+ _ASSERTE(!m_valueHome.ObjHandleIsNull());
+ _ASSERTE( m_info.objRef == NULL );
+ *pType = m_info.objTypeData.elementType;
+ }
+ else
+ {
+ // The element type stored in both places should match
+ _ASSERTE( m_info.objTypeData.elementType == m_type->m_elementType );
+ *pType = m_type->m_elementType;
+ }
+
+ return S_OK;
+} // CordbReferenceValue::GetType
+
+// gets the remote (LS) address of the reference. This may return NULL if the
+// reference is a literal or resides in a register.
+// Arguments:
+// output: pAddress - the LS location of the reference. The caller must guarantee pAddress is non-null,
+// but the contents may be null after the call if the reference is enregistered or is
+// the value of a field or element of some other Cordb*Value instance.
+// Return Value: S_OK on success or E_INVALIDARG if pAddress is null
+HRESULT CordbReferenceValue::GetAddress(CORDB_ADDRESS *pAddress)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(pAddress, CORDB_ADDRESS *);
+
+ *pAddress = m_valueHome.m_pHome ? m_valueHome.m_pHome->GetAddress() : NULL;
+ return (S_OK);
+}
+
+// Determines whether the reference is null
+// Arguments:
+// output - pfIsNull - pointer to a BOOL that will be set to true iff this represents a
+// null reference
+// Return Value: S_OK on success or E_INVALIDARG if pfIsNull is null
+HRESULT CordbReferenceValue::IsNull(BOOL * pfIsNull)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pfIsNull, BOOL *);
+
+ if (m_isLiteral || (m_info.objRef == NULL))
+ *pfIsNull = TRUE;
+ else
+ *pfIsNull = FALSE;
+
+ return S_OK;
+}
+
+// gets the value (object address) of this CordbReferenceValue
+// Arguments:
+// output: pTo - reference value
+// Return Value: S_OK on success or E_INVALIDARG if pAddress is null
+HRESULT CordbReferenceValue::GetValue(CORDB_ADDRESS *pAddress)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(pAddress, CORDB_ADDRESS *);
+ FAIL_IF_NEUTERED(this);
+
+ // Copy out the value, which is simply the value the object reference.
+ if (m_isLiteral)
+ *pAddress = NULL;
+ else
+ *pAddress = PTR_TO_CORDB_ADDRESS(m_info.objRef);
+
+ return S_OK;
+}
+
+// sets the value of the reference
+// Arguments:
+// input: address - the new reference--this must be a LS address
+// Return Value: S_OK on success or E_INVALIDARG or write process memory errors
+// Note: We make no effort to ensure that the new reference is of the same type as the old one.
+// We simply assume it is. As long as this assumption is correct, we only need to update information about
+// the referent if it's a string (its length can change).
+
+// @dbgtodo Microsoft inspection: consider whether it's worthwhile to verify that the type of the new referent is
+// the same as the type of the existing one. We'd have to do most of the work for a call to InitRef to do
+// this, since we need to know the type of the new referent.
+HRESULT CordbReferenceValue::SetValue(CORDB_ADDRESS address)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ HRESULT hr = S_OK;
+
+ // If this is a heap object, ideally we'd prevent violations of AppDomain isolation
+ // here. However, we have no reliable way of determining what AppDomain the address is in.
+
+ // Can't change literal refs.
+ if (m_isLiteral)
+ {
+ return E_INVALIDARG;
+ }
+
+ // Either we know the type, or it's a handle to a null value
+ _ASSERTE((m_type != NULL) ||
+ (!m_valueHome.ObjHandleIsNull() && (m_info.objRef == NULL)));
+
+ EX_TRY
+ {
+ m_valueHome.m_pHome->SetValue(MemoryRange(&address, sizeof(void *)), m_type); // throws
+ }
+ EX_CATCH_HRESULT(hr);
+
+ if (SUCCEEDED(hr))
+ {
+ // That worked, so update the copy of the value we have in
+ // our local cache.
+ m_info.objRef = CORDB_ADDRESS_TO_PTR(address);
+
+
+ if (m_info.objTypeData.elementType == ELEMENT_TYPE_STRING)
+ {
+ // update information about the string
+ InitRef(MemoryRange(&m_info.objRef, sizeof (void *)));
+ }
+
+ // All other data in m_info is no longer valid, and we may have invalidated other
+ // ICDRVs at this address. We have to invalidate all cached debuggee data.
+ m_appdomain->GetProcess()->m_continueCounter++;
+ }
+
+ return hr;
+} // CordbReferenceValue::SetValue
+
+HRESULT CordbReferenceValue::DereferenceStrong(ICorDebugValue **ppValue)
+{
+ return E_NOTIMPL;
+}
+
+// Get a new ICDValue instance to represent the referent of this object ref.
+// Arguments:
+// output: ppValue - the new ICDValue instance
+// Return Value: S_OK on success or E_INVALIDARG
+HRESULT CordbReferenceValue::Dereference(ICorDebugValue **ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ // Can't dereference literal refs.
+ if (m_isLiteral)
+ return E_INVALIDARG;
+
+ VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+
+ if (m_continueCounterLastSync != m_appdomain->GetProcess()->m_continueCounter)
+ {
+ IfFailRet(InitRef(MemoryRange(NULL, 0)));
+ }
+
+ EX_TRY
+ {
+ // We may know ahead of time (depending on the reference type) if
+ // the reference is bad.
+ if ((m_info.objRefBad) || (m_info.objRef == NULL))
+ {
+ ThrowHR(CORDBG_E_BAD_REFERENCE_VALUE);
+ }
+
+ hr = DereferenceCommon(m_appdomain, m_type, m_realTypeOfTypedByref, &m_info, ppValue);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+
+}
+
+//-----------------------------------------------------------------------------
+// Common helper to dereferefence.
+// Parameters:
+// pAppDomain, pType, pInfo - necessary paramters to create the value
+// pRealTypeOfTypedByref - type for a potential TypedByRef. Can be NULL if we know
+// that we're not a typed-byref (this is true if we're definitely an object handle)
+// ppValue - outparameter for newly created value. This will get an Ext AddRef.
+//-----------------------------------------------------------------------------
+HRESULT CordbReferenceValue::DereferenceCommon(
+ CordbAppDomain * pAppDomain,
+ CordbType * pType,
+ CordbType * pRealTypeOfTypedByref,
+ DebuggerIPCE_ObjectData * pInfo,
+ ICorDebugValue **ppValue
+)
+{
+ INTERNAL_SYNC_API_ENTRY(pAppDomain->GetProcess());
+
+ // pCachedObject may be NULL if we're not caching.
+ _ASSERTE(pType != NULL);
+ _ASSERTE(pAppDomain != NULL);
+ _ASSERTE(pInfo != NULL);
+ _ASSERTE(ppValue != NULL);
+
+ HRESULT hr = S_OK;
+ *ppValue = NULL; // just to be safe.
+
+ switch(pType->m_elementType)
+ {
+ case ELEMENT_TYPE_CLASS:
+ case ELEMENT_TYPE_OBJECT:
+ case ELEMENT_TYPE_STRING:
+ {
+ LOG((LF_CORDB, LL_INFO1000, "DereferenceInternal: type class/object/string\n"));
+ // An object value (possibly a string value, too.) If the class of this object is a value class,
+ // then we have a reference to a boxed object. So we create a box instead of an object value.
+ bool isBoxedVCObject = false;
+
+ if ((pType->m_pClass != NULL) && (pType->m_elementType != ELEMENT_TYPE_STRING))
+ {
+ EX_TRY
+ {
+ isBoxedVCObject = pType->m_pClass->IsValueClass();
+ }
+ EX_CATCH_HRESULT(hr);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+ }
+
+ if (isBoxedVCObject)
+ {
+ TargetBuffer remoteValue(PTR_TO_CORDB_ADDRESS(pInfo->objRef), (ULONG)pInfo->objSize);
+ EX_TRY
+ {
+ RSSmartPtr<CordbBoxValue> pBoxValue(new CordbBoxValue(
+ pAppDomain,
+ pType,
+ remoteValue,
+ (ULONG32)pInfo->objSize,
+ pInfo->objOffsetToVars));
+ pBoxValue->ExternalAddRef();
+ *ppValue = (ICorDebugValue*)(ICorDebugBoxValue*)pBoxValue;
+ }
+ EX_CATCH_HRESULT(hr);
+ }
+ else
+ {
+ RSSmartPtr<CordbObjectValue> pObj;
+ TargetBuffer remoteValue(PTR_TO_CORDB_ADDRESS(pInfo->objRef), (ULONG)pInfo->objSize);
+ // Note: we call Init() by default when we create (or refresh) a reference value, so we
+ // never have to do it again.
+ EX_TRY
+ {
+ pObj.Assign(new CordbObjectValue(pAppDomain, pType, remoteValue, pInfo));
+ IfFailThrow(pObj->Init());
+
+ pObj->ExternalAddRef();
+ *ppValue = static_cast<ICorDebugValue*>( static_cast<ICorDebugObjectValue*>(pObj) );
+ }
+ EX_CATCH_HRESULT(hr);
+ } // boxed?
+
+ break;
+ }
+
+ case ELEMENT_TYPE_ARRAY:
+ case ELEMENT_TYPE_SZARRAY:
+ {
+ LOG((LF_CORDB, LL_INFO1000, "DereferenceInternal: type array/szarray\n"));
+ TargetBuffer remoteValue(PTR_TO_CORDB_ADDRESS(pInfo->objRef), (ULONG)pInfo->objSize); // sizeof(void *)?
+ EX_TRY
+ {
+ RSSmartPtr<CordbArrayValue> pArrayValue(new CordbArrayValue(
+ pAppDomain,
+ pType,
+ pInfo,
+ remoteValue));
+
+ IfFailThrow(pArrayValue->Init());
+
+ pArrayValue->ExternalAddRef();
+ *ppValue = (ICorDebugValue*)(ICorDebugArrayValue*)pArrayValue;
+ }
+ EX_CATCH_HRESULT(hr);
+
+ break;
+ }
+
+ case ELEMENT_TYPE_BYREF:
+ case ELEMENT_TYPE_PTR:
+ {
+ //_ASSERTE(pInfo->objToken.IsNull()); // can't get this type w/ an object handle
+
+ LOG((LF_CORDB, LL_INFO1000, "DereferenceInternal: type byref/ptr\n"));
+ CordbType *ptrType;
+ pType->DestUnaryType(&ptrType);
+
+ CorElementType et = ptrType->m_elementType;
+
+ if (et == ELEMENT_TYPE_VOID)
+ {
+ *ppValue = NULL;
+ return CORDBG_S_VALUE_POINTS_TO_VOID;
+ }
+
+ TargetBuffer remoteValue(pInfo->objRef, GetSizeForType(ptrType, kUnboxed));
+ // Create a value for what this reference points to. Note:
+ // this could be almost any type of value.
+ EX_TRY
+ {
+ CordbValue::CreateValueByType(
+ pAppDomain,
+ ptrType,
+ false,
+ remoteValue,
+ MemoryRange(NULL, 0), // local value
+ NULL,
+ ppValue); // throws
+ }
+ EX_CATCH_HRESULT(hr);
+
+ break;
+ }
+
+ case ELEMENT_TYPE_TYPEDBYREF:
+ {
+ //_ASSERTE(pInfo->objToken.IsNull()); // can't get this type w/ an object handle
+ _ASSERTE(pRealTypeOfTypedByref != NULL);
+
+ LOG((LF_CORDB, LL_INFO1000, "DereferenceInternal: type typedbyref\n"));
+
+ TargetBuffer remoteValue(pInfo->objRef, sizeof(void *));
+ // Create the value for what this reference points
+ // to.
+ EX_TRY
+ {
+ CordbValue::CreateValueByType(
+ pAppDomain,
+ pRealTypeOfTypedByref,
+ false,
+ remoteValue,
+ MemoryRange(NULL, 0), // local value
+ NULL,
+ ppValue); // throws
+ }
+ EX_CATCH_HRESULT(hr);
+
+ break;
+ }
+
+ case ELEMENT_TYPE_FNPTR:
+ // Function pointers cannot be dereferenced; only the pointer value itself
+ // may be inspected--not what it points to.
+ *ppValue = NULL;
+ return CORDBG_E_VALUE_POINTS_TO_FUNCTION;
+
+ default:
+ LOG((LF_CORDB, LL_INFO1000, "DereferenceInternal: Fail!\n"));
+ _ASSERTE(!"Bad reference type!");
+ hr = E_FAIL;
+ break;
+ }
+
+ return hr;
+}
+
+// static helper to build a CordbReferenceValue from a general variable home.
+// We can find the CordbType from the object instance.
+HRESULT CordbReferenceValue::Build(CordbAppDomain * appdomain,
+ CordbType * type,
+ TargetBuffer remoteValue,
+ MemoryRange localValue,
+ VMPTR_OBJECTHANDLE vmObjectHandle,
+ EnregisteredValueHomeHolder * ppRemoteRegAddr,
+ CordbReferenceValue** ppValue)
+{
+ HRESULT hr = S_OK;
+
+ // We can find the AD from an object handle (but not a normal object), so the AppDomain may
+ // be NULL if if it's an OH.
+ //_ASSERTE((appdomain != NULL) || objectRefsInHandles);
+
+ // A reference, possibly to an object or value class
+ // Weak by default
+ EX_TRY
+ {
+ RSSmartPtr<CordbReferenceValue> pRefValue(new CordbReferenceValue(appdomain,
+ type,
+ localValue,
+ remoteValue,
+ ppRemoteRegAddr,
+ vmObjectHandle));
+ IfFailThrow(pRefValue->InitRef(localValue));
+
+ pRefValue->InternalAddRef();
+ *ppValue = pRefValue;
+ }
+ EX_CATCH_HRESULT(hr)
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// Static helper to build a CordbReferenceValue from a GCHandle
+// The LS can actually determine an AppDomain from an OBJECTHandles, however, the RS
+// should already have this infromation too, so we pass it in.
+// We also supply the AppDomain here because it provides the CordbValue with
+// process affinity.
+// Note that the GC handle may point to a NULL reference, in which case we should still create
+// an appropriate ICorDebugReferenceValue for which IsNull returns TRUE.
+//-----------------------------------------------------------------------------
+HRESULT CordbReferenceValue::BuildFromGCHandle(
+ CordbAppDomain *pAppDomain,
+ VMPTR_OBJECTHANDLE gcHandle,
+ ICorDebugReferenceValue ** pOutRef
+)
+{
+ _ASSERTE(pAppDomain != NULL);
+ _ASSERTE(pOutRef != NULL);
+
+ CordbProcess * pProc;
+ pProc = pAppDomain->GetProcess();
+ INTERNAL_SYNC_API_ENTRY(pProc);
+
+ HRESULT hr = S_OK;
+
+ *pOutRef = NULL;
+
+ // Make sure we even have a GC handle.
+ // Also, We may have a handle, but its contents may be null.
+ if (gcHandle.IsNull())
+ {
+ // We've seen this assert fire in the wild, but have never gotten a repro.
+ // so we'll include a runtime check to avoid the AV.
+ _ASSERTE(false || !"We got a bad reference value.");
+ return CORDBG_E_BAD_REFERENCE_VALUE;
+ }
+
+ // Now that we've got an AppDomain, we can go ahead and create the reference value normally.
+
+ RSSmartPtr<CordbReferenceValue> pRefValue;
+ TargetBuffer remoteValue;
+ EX_TRY
+ {
+ remoteValue.Init(pProc->GetDAC()->GetHandleAddressFromVmHandle(gcHandle), sizeof(void *));
+ }
+ EX_CATCH_HRESULT(hr);
+ IfFailRet(hr);
+
+ hr = CordbReferenceValue::Build(
+ pAppDomain,
+ NULL, // unknown type
+ remoteValue, // CORDB_ADDRESS remoteAddress,
+ MemoryRange(NULL, 0),
+ gcHandle, // objectRefsInHandles,
+ NULL, // EnregisteredValueHome * pRemoteRegAddr,
+ &pRefValue);
+
+ if (SUCCEEDED(hr))
+ {
+ pRefValue->QueryInterface(__uuidof(ICorDebugReferenceValue), (void**)pOutRef);
+ }
+
+ return hr;
+}
+
+// Helper function for SanityCheckPointer. Make an attempt to read memory at the address which is the value
+// of the reference.
+// Arguments: none
+// Notes:
+// - Throws
+// - m_info.objRefBad must be set to true before calling this function. If we throw, we'll
+// never end up setting m_info.objRefBad, but throwing indicates that the reference is
+// indeed bad. Only if we exit normally will we end up setting m_info.objRefBad to false.
+void CordbReferenceValue::TryDereferencingTarget()
+{
+ _ASSERTE(!!m_info.objRefBad == true);
+ // First get the referent type
+ CordbType * pReferentType;
+ m_type->DestUnaryType(&pReferentType);
+
+ // Next get the size
+ ULONG32 dataSize, sizeToRead;
+ IfFailThrow(pReferentType->GetUnboxedObjectSize(&dataSize));
+ if (dataSize <= 0)
+ sizeToRead = 1; // Read at least one byte.
+ else if (dataSize >= 8)
+ sizeToRead = 8; // Read at most eight bytes--this is just a perf improvement. Even if we read
+ // all the bytes, we are only able to determine that we can read those bytes,
+ // we can't really tell if the data we are reading is actually the data we
+ // want.
+ else sizeToRead = dataSize;
+
+ // Now see if we can read from the address where the object is supposed to be
+ BYTE dummy[8];
+
+ // Get a target buffer with the remote address and size of the object--since we don't know if the
+ // address if valid, this could throw or return a size that's complete garbage
+ TargetBuffer object(m_info.objRef, sizeToRead);
+
+ // now read target memory. This may throw ...
+ GetProcess()->SafeReadBuffer(object, dummy);
+
+} // CordbReferenceValue::TryDereferencingTarget
+
+// Do a sanity check on the pointer which is the value of the object reference. We can't efficiently ensure that
+// the pointer is really good, so we settle for a quick check just to make sure the memory at the address is
+// readable. We're actually just checking that we can dereference the pointer.
+// Arguments:
+// input: type - the type of the pointer to which the object reference points.
+// output: none, but fills in m_info.objRefBad
+// Note: Throws
+void CordbReferenceValue::SanityCheckPointer (CorElementType type)
+{
+ m_info.objRefBad = TRUE;
+ if (type != ELEMENT_TYPE_FNPTR)
+ {
+ // We should never dereference a function pointer, so all references
+ // are considered "bad."
+ if (m_info.objRef != NULL)
+ {
+ if (type == ELEMENT_TYPE_PTR)
+ {
+ // The only way to tell if the reference in PTR is bad or
+ // not is to try to deref the thing.
+ TryDereferencingTarget();
+ }
+ } // !m_info.m_basicData.m_vmObject.IsNull()
+ // else Null refs are considered "bad".
+ } // type != ELEMENT_TYPE_FNPTR
+
+ // we made it without throwing, so we'll assume (perhaps wrongly) that the ref is good
+ m_info.objRefBad = FALSE;
+
+} // CordbReferenceValue::SanityCheckPointer
+
+// get information about the reference when it's not an object address but another kind of pointer type:
+// ELEMENT_TYPE_BYREF, ELEMENT_TYPE_PTR or ELEMENT_TYPE_FNPTR
+// Arguments:
+// input: type - type of the referent
+// localValue - starting address and length of a local buffer containing the object ref
+// Notes:
+// - fills in the m_info field of "this"
+// - Throws (errors from reading process memory)
+void CordbReferenceValue::GetPointerData(CorElementType type, MemoryRange localValue)
+{
+ HRESULT hr = S_OK;
+ // Fill in the type since we will not be getting it from the DAC
+ m_info.objTypeData.elementType = type;
+
+ // First get the objRef
+ if (localValue.StartAddress() != NULL)
+ {
+ // localValue represents a buffer containing a copy of the objectRef that exists locally. It could be a
+ // component of a container type residing within a local cached copy belonging to some other
+ // Cordb*Value instance representing the container type. In this case it will be a field, array
+ // element, or referent of a different object reference for that other Cordb*Value instance. It
+ // could also be a pointer to the value of a local register display of the frame from which this object
+ // ref comes.
+
+ // For example, if we have a value class (represented by a CordbVCObject instance) with a field
+ // that is an object pointer, localValue will contain a pointer to that field in the local
+ // cache of the CordbVCObjectValue instance (CordbVCObjectValue::m_pObjectCopy).
+
+ // Note, though, that pLocalValue holds the address of a target object. We will cache
+ // the contents of pLocalValue (the object ref) here for efficiency of read access, but if we
+ // want to set the reference later (e.g., we want the object ref to point to NULL instead of an
+ // object), we'll have to set the object ref in the target, not our local copy.
+ // Host memory Target memory
+ // --------------- |
+ // CordbVCObjectValue::m_copyOfObject ----> | |
+ // | ... | |
+ // | |
+ // |---------------| | Object
+ // localAddress ---> | object addr |-------------> --------------
+ // |---------------| | ---> | |
+ // | ... | | | |
+ // --------------- | | --------------
+ // |
+ // CordbReferenceValue::m_info.objRef ---> --------------- | |
+ // | object addr |---------
+ // --------------- |
+
+ _ASSERTE(localValue.Size() == sizeof(void *));
+ localCopy(&(m_info.objRef), localValue);
+ }
+ else
+ {
+ // we have a non-local location, so we'll get the value of the ref from its home
+
+ // do some preinitialization in case we get an exception
+ EX_TRY
+ {
+ m_valueHome.m_pHome->GetValue(MemoryRange(&(m_info.objRef), sizeof(void*))); // throws
+ }
+ EX_CATCH_HRESULT(hr);
+ if (FAILED(hr))
+ {
+ m_info.objRef = NULL;
+ m_info.objRefBad = TRUE;
+ ThrowHR(hr);
+ }
+ }
+
+ EX_TRY
+ {
+ // If we made it this far, we need to sanity check the pointer--we'll just see if we can
+ // read at that address
+ SanityCheckPointer(type);
+ }
+ EX_CATCH_HRESULT(hr); // we don't need to do anything here, m_info.objRefBad will have been set to true
+
+} // CordbReferenceValue::GetPointerData
+
+// Helper function for CordbReferenceValue::GetObjectData: Sets default values for the fields in pObjectData
+// before processing begins. Not all will necessarily be initialized during processing.
+// Arguments:
+// input: objectType - type of the referent of the objRef being examined
+// output: pObjectData - information about the reference to be initialized
+void PreInitObjectData(DebuggerIPCE_ObjectData * pObjectData, void * objAddress, CorElementType objectType)
+{
+ _ASSERTE(pObjectData != NULL);
+
+ memset(pObjectData, 0, sizeof(DebuggerIPCE_ObjectData));
+ pObjectData->objRef = objAddress;
+ pObjectData->objTypeData.elementType = objectType;
+
+} // PreInitObjectData
+
+// get basic object specific data when a reference points to an object, plus extra data if the object is an
+// array or string
+// Arguments:
+// input: pProcess - process to which the object belongs
+// objectAddress - pointer to the TypedByRef object (this is the value of the object reference
+// or handle.
+// type - the type of the object referenced
+// vmAppDomain - appdomain to which the object belongs
+// output: pInfo - filled with information about the object to which the TypedByRef refers.
+// Note: Throws
+/* static */
+void CordbReferenceValue::GetObjectData(CordbProcess * pProcess,
+ void * objectAddress,
+ CorElementType type,
+ VMPTR_AppDomain vmAppdomain,
+ DebuggerIPCE_ObjectData * pInfo)
+{
+ IDacDbiInterface *pInterface = pProcess->GetDAC();
+ CORDB_ADDRESS objTargetAddr = PTR_TO_CORDB_ADDRESS(objectAddress);
+
+ // make sure we don't end up with old garbage values in case the reference is bad
+ PreInitObjectData(pInfo, objectAddress, type);
+
+ pInterface->GetBasicObjectInfo(objTargetAddr, type, vmAppdomain, pInfo);
+
+ if (!pInfo->objRefBad)
+ {
+ // for certain referent types, we need a bit more information:
+ if (pInfo->objTypeData.elementType == ELEMENT_TYPE_STRING)
+ {
+ pInterface->GetStringData(objTargetAddr, pInfo);
+ }
+ else if ((pInfo->objTypeData.elementType == ELEMENT_TYPE_ARRAY) ||
+ (pInfo->objTypeData.elementType == ELEMENT_TYPE_SZARRAY))
+ {
+ pInterface->GetArrayData(objTargetAddr, pInfo);
+ }
+ }
+
+} // CordbReferenceValue::GetObjectData
+
+// get information about a TypedByRef object when the reference is the address of a TypedByRef structure.
+// Arguments:
+// input: pProcess - process to which the object belongs
+// pTypedByRef - pointer to the TypedByRef object (this is the value of the object reference or
+// handle.
+// type - the type of the object referenced
+// vmAppDomain - appdomain to which the object belongs
+// output: pInfo - filled with information about the object to which the TypedByRef refers.
+// Note: Throws
+/* static */
+void CordbReferenceValue::GetTypedByRefData(CordbProcess * pProcess,
+ CORDB_ADDRESS pTypedByRef,
+ CorElementType type,
+ VMPTR_AppDomain vmAppDomain,
+ DebuggerIPCE_ObjectData * pInfo)
+{
+
+ // make sure we don't end up with old garbage values since we don't set all the values for TypedByRef objects
+ PreInitObjectData(pInfo, CORDB_ADDRESS_TO_PTR(pTypedByRef), type);
+
+ // Though pTypedByRef is the value of the object ref represented by an instance of CordbReferenceValue,
+ // it is not the address of an object, as we would ordinarily expect. Instead, in the special case of
+ // TypedByref objects, it is actually the address of the TypedByRef struct which contains the
+ // type and the object address.
+
+ pProcess->GetDAC()->GetTypedByRefInfo(pTypedByRef, vmAppDomain, pInfo);
+} // CordbReferenceValue::GetTypedByRefData
+
+// get the address of the object referenced
+// Arguments: none
+// Return Value: the address of the object referenced (i.e., the value of the object ref)
+// Note: Throws
+void * CordbReferenceValue::GetObjectAddress(MemoryRange localValue)
+{
+ void * objectAddress;
+ if (localValue.StartAddress() != NULL)
+ {
+ // the object ref comes from a local cached copy
+ _ASSERTE(localValue.Size() == sizeof(void *));
+ memcpy(&objectAddress, localValue.StartAddress(), localValue.Size());
+ }
+ else
+ {
+ _ASSERTE(m_valueHome.m_pHome != NULL);
+ m_valueHome.m_pHome->GetValue(MemoryRange(&objectAddress, sizeof(void *))); // throws
+ }
+ return objectAddress;
+} // CordbReferenceValue::GetObjectAddress
+
+// update type information after initializing -- when we initialize, we may get more exact type information
+// than we previously had
+// Arguments: none--uses and updates data members
+// Note: Throws
+void CordbReferenceValue::UpdateTypeInfo()
+{
+ // If the object type that we got back is different than the one we sent, then it means that we
+ // orignally had a CLASS and now have something more specific, like a SDARRAY, MDARRAY, or STRING or
+ // a constructed type.
+ // Update our signature accordingly, which is okay since we always have a copy of our sig. This
+ // ensures that the reference's signature accurately reflects what the Runtime knows it's pointing
+ // to.
+ //
+ // GENERICS: do this for all types: for example, an array might have been discovered to be a more
+ // specific kind of array (String[] where an Object[] was expected).
+ CordbType *newtype;
+
+ IfFailThrow(CordbType::TypeDataToType(m_appdomain, &m_info.objTypeData, &newtype));
+
+ _ASSERTE(newtype->m_elementType != ELEMENT_TYPE_VALUETYPE);
+ m_type.Assign(newtype); // implicit Release + AddRef
+
+ // For typed-byref's the act of dereferencing the object also reveals to us
+ // what the "real" type of the object is...
+ if (m_info.objTypeData.elementType == ELEMENT_TYPE_TYPEDBYREF)
+{
+ IfFailThrow(CordbType::TypeDataToType(m_appdomain,
+ &m_info.typedByrefInfo.typedByrefType,
+ &m_realTypeOfTypedByref));
+ }
+} // CordbReferenceValue::UpdateTypeInfo
+
+// Initialize this CordbReferenceValue. This may involve inspecting the LS to get information about the
+// referent.
+// Arguments:
+// input: localValue - buffer address and size of the RS location of the reference. (This may be NULL
+// if the reference didn't come from a local cached copy. See
+// code:CordbReferenceValue::GetPointerData for further explanation of local locations.)
+// Return Value: S_OK on success or E_INVALIDARG or write process memory errors on failure
+
+HRESULT CordbReferenceValue::InitRef(MemoryRange localValue)
+{
+ INTERNAL_SYNC_API_ENTRY(this->GetProcess());
+
+ HRESULT hr = S_OK;
+ CordbProcess * pProcess = GetProcess();
+
+ // Simple init needed for literal refs. Literals may have a null process / appdomain ptr.
+ if (m_isLiteral)
+ {
+ _ASSERTE(m_type != NULL);
+ m_info.objTypeData.elementType = m_type->m_elementType;
+ return hr;
+ }
+
+ _ASSERTE((pProcess->GetShim() == NULL) || pProcess->GetSynchronized());
+
+ // If the helper thread is dead, then pretend this is a bad reference.
+ if (GetProcess()->m_helperThreadDead)
+ {
+ m_info.objRef = NULL;
+ m_info.objRefBad = TRUE;
+ return hr;
+ }
+
+ m_continueCounterLastSync = pProcess->m_continueCounter;
+
+ // If no type provided, then it's b/c we're a class and we'll get the type when we get Created.
+ CorElementType type = (m_type != NULL) ? (m_type->m_elementType) : ELEMENT_TYPE_CLASS;
+ _ASSERTE (type != ELEMENT_TYPE_GENERICINST);
+ _ASSERTE (type != ELEMENT_TYPE_VAR);
+ _ASSERTE (type != ELEMENT_TYPE_MVAR);
+
+ EX_TRY
+ {
+ if ((type == ELEMENT_TYPE_BYREF) ||
+ (type == ELEMENT_TYPE_PTR) ||
+ (type == ELEMENT_TYPE_FNPTR))
+ {
+ // we know the size is just the size of a pointer, so we can just read process memory to get the
+ // information we need
+ GetPointerData(type, localValue);
+ }
+ else // we have to get more information about the object from the DAC
+ {
+ if (type == ELEMENT_TYPE_TYPEDBYREF)
+ {
+ _ASSERTE(m_valueHome.m_pHome != NULL);
+ GetTypedByRefData(pProcess,
+ m_valueHome.m_pHome->GetAddress(),
+ type,
+ m_appdomain->GetADToken(),
+ &m_info);
+ }
+ else
+ {
+ GetObjectData(pProcess, GetObjectAddress(localValue), type, m_appdomain->GetADToken(), &m_info);
+ }
+
+ // if we got (what we believe is probably) a good reference, we should update the type info
+ if (!m_info.objRefBad)
+ {
+ // we may have gotten back a more specific type than we had previously
+ UpdateTypeInfo();
+ }
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+} // CordbReferenceValue::InitRef
+
+/* ------------------------------------------------------------------------- *
+ * Object Value class
+ * ------------------------------------------------------------------------- */
+
+
+// validate a CordbObjectValue to ensure it hasn't been neutered
+#define COV_VALIDATE_OBJECT() do { \
+ BOOL bValid; \
+ HRESULT hr; \
+ if (FAILED(hr = IsValid(&bValid))) \
+ return hr; \
+ \
+ if (!bValid) \
+ { \
+ return CORDBG_E_INVALID_OBJECT; \
+ } \
+ }while(0)
+
+// constructor
+// Arguments:
+// input: pAppDomain - the appdomain to which the object belongs
+// pType - the type of the object
+// remoteValue - the LS address and size of the object
+// pObjectData - other information about the object, most importantly, the offset to the
+// fields of the object
+CordbObjectValue::CordbObjectValue(CordbAppDomain * pAppdomain,
+ CordbType * pType,
+ TargetBuffer remoteValue,
+ DebuggerIPCE_ObjectData *pObjectData )
+ : CordbValue(pAppdomain, pType, remoteValue.pAddress,
+ false, pAppdomain->GetProcess()->GetContinueNeuterList()),
+ m_info(*pObjectData),
+ m_pObjectCopy(NULL), m_objectLocalVars(NULL), m_stringBuffer(NULL),
+ m_valueHome(pAppdomain->GetProcess(), remoteValue),
+ m_fIsExceptionObject(FALSE), m_fIsRcw(FALSE)
+{
+ _ASSERTE(pAppdomain != NULL);
+
+ m_size = m_info.objSize;
+
+ HRESULT hr = S_FALSE;
+
+ ALLOW_DATATARGET_MISSING_MEMORY
+ (
+ hr = IsExceptionObject();
+ );
+
+ if (hr == S_OK)
+ m_fIsExceptionObject = TRUE;
+
+ hr = S_FALSE;
+ ALLOW_DATATARGET_MISSING_MEMORY
+ (
+ hr = IsRcw();
+ );
+
+ if (hr == S_OK)
+ m_fIsRcw = TRUE;
+} // CordbObjectValue::CordbObjectValue
+
+// destructor
+CordbObjectValue::~CordbObjectValue()
+{
+ DTOR_ENTRY(this);
+
+ _ASSERTE(IsNeutered());
+} // CordbObjectValue::~CordbObjectValue
+
+void CordbObjectValue::Neuter()
+{
+ // Destroy the copy of the object.
+ if (m_pObjectCopy != NULL)
+ {
+ delete [] m_pObjectCopy;
+ m_pObjectCopy = NULL;
+ }
+
+ CordbValue::Neuter();
+} // CordbObjectValue::Neuter
+
+HRESULT CordbObjectValue::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugValue)
+ {
+ *pInterface = static_cast<ICorDebugValue*>(static_cast<ICorDebugObjectValue*>(this));
+ }
+ else if (id == IID_ICorDebugValue2)
+ {
+ *pInterface = static_cast<ICorDebugValue2*>(this);
+ }
+ else if (id == IID_ICorDebugValue3)
+ {
+ *pInterface = static_cast<ICorDebugValue3*>(this);
+ }
+ else if (id == IID_ICorDebugObjectValue)
+ {
+ *pInterface = static_cast<ICorDebugObjectValue*>(this);
+ }
+ else if (id == IID_ICorDebugObjectValue2)
+ {
+ *pInterface = static_cast<ICorDebugObjectValue2*>(this);
+ }
+ else if (id == IID_ICorDebugGenericValue)
+ {
+ *pInterface = static_cast<ICorDebugGenericValue*>(this);
+ }
+ else if (id == IID_ICorDebugHeapValue)
+ {
+ *pInterface = static_cast<ICorDebugHeapValue*>(this);
+ }
+ else if (id == IID_ICorDebugHeapValue2)
+ {
+ *pInterface = static_cast<ICorDebugHeapValue2*>(this);
+ }
+ else if (id == IID_ICorDebugHeapValue3)
+ {
+ *pInterface = static_cast<ICorDebugHeapValue3*>(this);
+ }
+ else if ((id == IID_ICorDebugStringValue) &&
+ (m_info.objTypeData.elementType == ELEMENT_TYPE_STRING))
+ {
+ *pInterface = static_cast<ICorDebugStringValue*>(this);
+ }
+ else if (id == IID_ICorDebugExceptionObjectValue && m_fIsExceptionObject)
+ {
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugExceptionObjectValue*>(this));
+ }
+ else if (id == IID_ICorDebugComObjectValue && m_fIsRcw)
+ {
+ *pInterface = static_cast<ICorDebugComObjectValue*>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugObjectValue*>(this));
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+} // CordbObjectValue::QueryInterface
+
+// gets the type of the object
+// Arguments:
+// output: pType - the type of the value. The caller must guarantee that pType is non-null.
+// Return Value: S_OK on success, E_INVALIDARG on failure
+HRESULT CordbObjectValue::GetType(CorElementType *pType)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ return (CordbValue::GetType(pType));
+} // CordbObjectValue::GetType
+
+// gets the size of the object
+// Arguments:
+// output: pSize - the size of the value. The caller must guarantee that pSize is non-null.
+// Return Value: S_OK on success, E_INVALIDARG on failure
+HRESULT CordbObjectValue::GetSize(ULONG32 *pSize)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ return (CordbValue::GetSize(pSize));
+} // CordbObjectValue::GetSize
+
+// gets the size of the object
+// Arguments:
+// output: pSize - the size of the value. The caller must guarantee that pSize is non-null.
+// Return Value: S_OK on success, E_INVALIDARG on failure
+HRESULT CordbObjectValue::GetSize64(ULONG64 *pSize)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ return (CordbValue::GetSize64(pSize));
+} // CordbObjectValue::GetSize64
+
+
+// gets the remote (LS) address of the object. This may return NULL if the
+// object is a literal or resides in a register.
+// Arguments:
+// output: pAddress - the LS address (the contents should not be null since objects
+// aren't enregistered nor are they fields or elements of other
+// types). The caller must ensure that pAddress is not null.
+// Return Value: S_OK on success or E_INVALIDARG if pAddress is null
+HRESULT CordbObjectValue::GetAddress(CORDB_ADDRESS *pAddress)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ COV_VALIDATE_OBJECT();
+ VALIDATE_POINTER_TO_OBJECT(pAddress, CORDB_ADDRESS *);
+
+ *pAddress = m_valueHome.GetAddress();
+ return (S_OK);
+} // CordbObjectValue::GetAddress
+
+HRESULT CordbObjectValue::CreateBreakpoint(ICorDebugValueBreakpoint ** ppBreakpoint)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ COV_VALIDATE_OBJECT();
+
+ return (CordbValue::CreateBreakpoint(ppBreakpoint));
+}
+
+// determine if "this" is still valid (i.e., not neutered)
+// Arguments:
+// output: pfIsValid - true iff "this" is still not neutered
+// Return Value: S_OK or E_INVALIDARG
+HRESULT CordbObjectValue::IsValid(BOOL * pfIsValid)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(pfIsValid, BOOL *);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ // We're neutered on continue, so we're valid up until the time we're neutered
+ (*pfIsValid) = TRUE;
+ return S_OK;
+}
+
+HRESULT CordbObjectValue::CreateRelocBreakpoint(
+ ICorDebugValueBreakpoint **ppBreakpoint)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppBreakpoint, ICorDebugValueBreakpoint **);
+
+ COV_VALIDATE_OBJECT();
+
+ return E_NOTIMPL;
+}
+
+/*
+* Creates a handle of the given type for this heap value.
+*
+* Not Implemented In-Proc.
+*/
+HRESULT CordbObjectValue::CreateHandle(
+ CorDebugHandleType handleType,
+ ICorDebugHandleValue ** ppHandle)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ return CordbValue::InternalCreateHandle(handleType, ppHandle);
+} // CreateHandle
+
+// Get class information for this object
+// Arguments:
+// output: ppClass - ICDClass instance for this object
+// Return Value: S_OK if success, CORDBG_E_CLASS_NOT_LOADED, E_INVALIDARG, OOM on failure
+HRESULT CordbObjectValue::GetClass(ICorDebugClass **ppClass)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(ppClass, ICorDebugClass **);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ if (m_type->m_pClass == NULL)
+ {
+ if (FAILED(hr = m_type->Init(FALSE)))
+ return hr;
+ }
+
+ _ASSERTE(m_type->m_pClass);
+ *ppClass = (ICorDebugClass*) m_type->m_pClass;
+
+ if (*ppClass != NULL)
+ (*ppClass)->AddRef();
+
+ return hr;
+} // CordbObjectValue::GetClass
+
+
+
+
+
+//-----------------------------------------------------------------------------
+//
+// Public API to get instance field of the given type in the object and returns an ICDValue for it.
+//
+// Arguments:
+// pType - The type containing the field token.
+// fieldDef - The field's metadata def.
+// ppValue - OUT: the ICDValue for the field.
+//
+// Returns:
+// S_OK on success. E_INVALIDARG, CORDBG_E_ENC_HANGING_FIELD, CORDBG_E_FIELD_NOT_INSTANCE or OOM on
+// failure
+//
+// Notes:
+// This is for instance fields only.
+// Lookup on code:CordbType::GetStaticFieldValue to get static fields.
+// This is generics aware.
+HRESULT CordbObjectValue::GetFieldValueForType(ICorDebugType * pType,
+ mdFieldDef fieldDef,
+ ICorDebugValue ** ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(pType, ICorDebugType *);
+ VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **);
+
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ COV_VALIDATE_OBJECT();
+
+ CordbType * pCordbType = NULL;
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+ BOOL fSyncBlockField = FALSE;
+ SIZE_T fldOffset;
+
+ //
+ // <TODO>@todo: need to ensure that pType is really on the class
+ // hierarchy of m_class!!!</TODO>
+ //
+ if (pType == NULL)
+ {
+ pCordbType = m_type;
+ }
+ else
+ {
+ pCordbType = static_cast<CordbType *>(pType);
+ }
+
+ // Validate the token.
+ if (pCordbType->m_pClass == NULL)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+ IMetaDataImport * pImport = pCordbType->m_pClass->GetModule()->GetMetaDataImporter();
+
+ if (!pImport->IsValidToken(fieldDef))
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ FieldData * pFieldData;
+
+ #ifdef _DEBUG
+ pFieldData = NULL;
+ #endif
+
+ hr = pCordbType->GetFieldInfo(fieldDef, &pFieldData);
+
+ // If we couldn't get field info because the field was added with EnC
+ if (hr == CORDBG_E_ENC_HANGING_FIELD)
+ {
+ // The instance field hangs off the syncblock, get its address
+ hr = pCordbType->m_pClass->GetEnCHangingField(fieldDef, &pFieldData, this);
+
+ if (SUCCEEDED(hr))
+ {
+ fSyncBlockField = TRUE;
+ }
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ _ASSERTE(pFieldData != NULL);
+
+ if (pFieldData->m_fFldIsStatic)
+ {
+ ThrowHR(CORDBG_E_FIELD_NOT_INSTANCE);
+ }
+
+ // Compute the remote address, too, so that SetValue will work.
+ // Note that if pFieldData is a syncBlock field, fldOffset will have been cooked
+ // to produce the correct result here.
+ _ASSERTE(pFieldData->OkToGetOrSetInstanceOffset());
+ fldOffset = pFieldData->GetInstanceOffset();
+
+ CordbModule * pModule = pCordbType->m_pClass->GetModule();
+
+ SigParser sigParser;
+ IfFailThrow(pFieldData->GetFieldSignature(pModule, &sigParser));
+
+ CordbType * pFieldType;
+ IfFailThrow(CordbType::SigToType(pModule, &sigParser, &(pCordbType->m_inst), &pFieldType));
+
+ ULONG32 size = GetSizeForType(pFieldType, kUnboxed);
+
+ void * localAddr = NULL;
+ if (!fSyncBlockField)
+ {
+ // verify that the field starts and ends before the end of m_pObjectCopy
+ _ASSERTE(m_info.objOffsetToVars + fldOffset < m_size);
+ _ASSERTE(m_info.objOffsetToVars + fldOffset + size <= m_size);
+ localAddr = m_objectLocalVars + fldOffset;
+ }
+
+ // pass the computed local field address, but don't claim we have a local addr if the fldOffset
+ // has been cooked to point us to a sync block field.
+ m_valueHome.CreateInternalValue(pFieldType,
+ m_info.objOffsetToVars + fldOffset,
+ localAddr,
+ size,
+ ppValue); // throws
+ }
+
+ // If we can't get it b/c it's a constant, then say so.
+ hr = CordbClass::PostProcessUnavailableHRESULT(hr, pImport, fieldDef);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+} // CordbObjectValue::GetFieldValueForType
+
+// Public implementation of ICorDebugObjectValue::GetFieldValue
+// Arguments:
+// input: pClass - class information for this object
+// fieldDef - the field token for the requested field
+// output: ppValue - instance of ICDValue created to represent the field
+// Return Value: S_OK on success, E_INVALIDARG, CORDBG_E_ENC_HANGING_FIELD, CORDBG_E_FIELD_NOT_INSTANCE
+// or OOM on failure
+HRESULT CordbObjectValue::GetFieldValue(ICorDebugClass *pClass,
+ mdFieldDef fieldDef,
+ ICorDebugValue **ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ VALIDATE_POINTER_TO_OBJECT(pClass, ICorDebugClass *);
+ VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **);
+
+ COV_VALIDATE_OBJECT();
+
+ HRESULT hr;
+ _ASSERTE(m_type);
+
+ if (m_type->m_elementType != ELEMENT_TYPE_CLASS &&
+ m_type->m_elementType != ELEMENT_TYPE_VALUETYPE)
+ {
+ return E_INVALIDARG;
+ }
+
+ // mdFieldDef may specify a field within a base class. mdFieldDef tokens are unique throughout a module.
+ // So we still need a metadata scope to resolve the mdFieldDef. We can infer the scope from pClass.
+ // Beware that this Type may be derived from a type in another module, and so the incoming
+ // fieldDef has to be resolved in the metadata scope of pClass.
+
+ RSExtSmartPtr<CordbType> relevantType;
+
+ // This object has an ICorDebugType which has the type-parameters for generics.
+ // ICorDebugClass provided by the caller does not have type-parameters. So we resolve that
+ // by using the provided ICDClass with the type parameters from this object's ICDType.
+ if (FAILED (hr= m_type->GetParentType((CordbClass *) pClass, &relevantType)))
+ {
+ return hr;
+ }
+ // Upon exit relevantType will either be the appropriate type for the
+ // class we're looking for.
+
+ hr = GetFieldValueForType(relevantType, fieldDef, ppValue);
+ // GetParentType adds one reference to relevantType., Holder dtor releases
+ return hr;
+
+} // CordbObjectValue::GetFieldValue
+
+HRESULT CordbObjectValue::GetVirtualMethod(mdMemberRef memberRef,
+ ICorDebugFunction **ppFunction)
+{
+ VALIDATE_POINTER_TO_OBJECT(ppFunction, ICorDebugFunction **);
+ FAIL_IF_NEUTERED(this);
+ COV_VALIDATE_OBJECT();
+
+ return E_NOTIMPL;
+} // CordbObjectValue::GetVirtualMethod
+
+HRESULT CordbObjectValue::GetVirtualMethodAndType(mdMemberRef memberRef,
+ ICorDebugFunction **ppFunction,
+ ICorDebugType **ppType)
+{
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppFunction, ICorDebugFunction **);
+ VALIDATE_POINTER_TO_OBJECT(ppFunction, ICorDebugType **);
+
+ COV_VALIDATE_OBJECT();
+
+ return E_NOTIMPL;
+} // CordbObjectValue::GetVirtualMethodAndType
+
+HRESULT CordbObjectValue::GetContext(ICorDebugContext **ppContext)
+{
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppContext, ICorDebugContext **);
+
+ COV_VALIDATE_OBJECT();
+
+ return E_NOTIMPL;
+} // CordbObjectValue::GetContext
+
+// determines whether this represents a value class-- always returns false
+// Arguments:
+// output: pfIsValueClass - always false; CordbVCObjectValue is used to represent
+// value classes, so by definition, a CordbObjectValue instance
+// does not represent a value class
+// Return Value: S_OK
+//
+HRESULT CordbObjectValue::IsValueClass(BOOL * pfIsValueClass)
+{
+ FAIL_IF_NEUTERED(this);
+ COV_VALIDATE_OBJECT();
+
+ if (pfIsValueClass) // don't assign to a null pointer!
+ *pfIsValueClass = FALSE;
+
+ return S_OK;
+} // CordbObjectValue::IsValueClass
+
+HRESULT CordbObjectValue::GetManagedCopy(IUnknown **ppObject)
+{
+ // GetManagedCopy() is deprecated. In the case where the version of
+ // the debugger doesn't match the version of the debuggee, the two processes
+ // might have dangerously different notions of the layout of an object.
+
+ // This function is deprecated
+ return E_NOTIMPL;
+} // CordbObjectValue::GetManagedCopy
+
+HRESULT CordbObjectValue::SetFromManagedCopy(IUnknown *pObject)
+{
+ // Deprecated for the same reason as GetManagedCopy()
+ return E_NOTIMPL;
+} // CordbObjectValue::SetFromManagedCopy
+
+// gets a copy of the value
+// Arguments:
+// output: pTo - buffer to hold the object copy. The caller must guarantee that this
+// is non-null and the buffer is large enough to hold the object
+// Return Value: S_OK or CORDBG_E_INVALID_OBJECT, CORDBG_E_OBJECT_NEUTERED, or E_INVALIDARG on failure
+//
+HRESULT CordbObjectValue::GetValue(void *pTo)
+{
+ FAIL_IF_NEUTERED(this);
+ COV_VALIDATE_OBJECT();
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(pTo, BYTE, m_size, false, true);
+
+ // Copy out the value, which is the whole object.
+ memcpy(pTo, m_pObjectCopy, m_size);
+
+ return S_OK;
+} // CordbObjectValue::GetValue
+
+HRESULT CordbObjectValue::SetValue(void *pFrom)
+{
+ // You're not allowed to set a whole object at once.
+ return E_INVALIDARG;
+} // CordbObjectValue::SetValue
+
+// If this instance of CordbObjectValue is actually a string, get its length
+// Arguments:
+// output: pcchString - the count of characters in the string
+// Return Value: S_OK or CORDBG_E_INVALID_OBJECT, CORDBG_E_OBJECT_NEUTERED, or E_INVALIDARG on failure
+// Note: if the object is not really a string, the value in pcchString will be garbage on exit
+HRESULT CordbObjectValue::GetLength(ULONG32 *pcchString)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(pcchString, SIZE_T *);
+ FAIL_IF_NEUTERED(this);
+
+ _ASSERTE(m_info.objTypeData.elementType == ELEMENT_TYPE_STRING);
+
+ COV_VALIDATE_OBJECT();
+
+ *pcchString = (ULONG32)m_info.stringInfo.length;
+ return S_OK;
+} // CordbObjectValue::GetLength
+
+// If this instance of CordbObjectValue represents a string, extract the string and its length.
+// If cchString is less than the length of the string, we'll return only the first cchString characters
+// but pcchString will still hold the full length. If cchString is more than the string length, we'll
+// return only string length characters.
+// Arguments:
+// input: cchString - the maximum number of characters to return, including NULL terminator
+// output: pcchString - the actual length of the string, excluding NULL terminator (this may be greater than cchString)
+// szString - a buffer holding the string. The memory for this must be allocated and
+// managed by the caller and must have space for at least cchString characters
+// Return Value: S_OK or CORDBG_E_INVALID_OBJECT, CORDBG_E_OBJECT_NEUTERED, or E_INVALIDARG on failure
+HRESULT CordbObjectValue::GetString(ULONG32 cchString,
+ ULONG32 *pcchString,
+ __out_ecount_opt(cchString) WCHAR szString[])
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(szString, WCHAR, cchString, true, true);
+ VALIDATE_POINTER_TO_OBJECT(pcchString, SIZE_T *);
+
+ _ASSERTE(m_info.objTypeData.elementType == ELEMENT_TYPE_STRING);
+
+ COV_VALIDATE_OBJECT();
+
+ if ((szString == NULL) || (cchString == 0))
+ return E_INVALIDARG;
+
+ // Add 1 to include null terminator
+ SIZE_T len = m_info.stringInfo.length + 1;
+
+ // adjust length to the size of the buffer
+ if (cchString < len)
+ len = cchString;
+
+ memcpy(szString, m_stringBuffer, len * 2);
+ *pcchString = (ULONG32)m_info.stringInfo.length;
+
+ return S_OK;
+} // CordbObjectValue::GetString
+
+// Initialize an instance of CordbObjectValue, filling in the m_pObjectCopy field and, if appropriate,
+// string information.
+// Arguments: none
+// ReturnValue: S_OK on success or E_OUTOFMEMORY or read process memory errors on failure
+HRESULT CordbObjectValue::Init()
+{
+ INTERNAL_SYNC_API_ENTRY(this->GetProcess()); //
+ LOG((LF_CORDB,LL_INFO1000,"Invoking COV::Init\n"));
+
+ HRESULT hr = S_OK;
+
+ _ASSERTE (m_info.objTypeData.elementType != ELEMENT_TYPE_GENERICINST);
+ _ASSERTE (m_info.objTypeData.elementType != ELEMENT_TYPE_VAR);
+ _ASSERTE (m_info.objTypeData.elementType != ELEMENT_TYPE_MVAR);
+
+ // Copy the entire object over to this process.
+ m_pObjectCopy = new (nothrow) BYTE[m_size];
+
+ if (m_pObjectCopy == NULL)
+ return E_OUTOFMEMORY;
+
+ EX_TRY
+ {
+ m_valueHome.GetValue(MemoryRange(m_pObjectCopy, m_size)); // throws
+ }
+ EX_CATCH_HRESULT(hr);
+ IfFailRet(hr);
+
+ // Compute offsets in bytes to the locals and to a string if this is a
+ // string object.
+ m_objectLocalVars = m_pObjectCopy + m_info.objOffsetToVars;
+
+ if (m_info.objTypeData.elementType == ELEMENT_TYPE_STRING)
+ m_stringBuffer = m_pObjectCopy + m_info.stringInfo.offsetToStringBase;
+
+ return hr;
+} // CordbObjectValue::Init
+
+// CordbObjectValue::GetThreadOwningMonitorLock
+// If a managed thread owns the monitor lock on this object then *ppThread
+// will point to that thread and S_OK will be returned. The thread object is valid
+// until the thread exits. *pAcquisitionCount will indicate the number of times
+// this thread would need to release the lock before it returns to being
+// unowned.
+// If no managed thread owns the monitor lock on this object then *ppThread
+// and pAcquisitionCount will be unchanged and S_FALSE returned.
+// If ppThread or pAcquisitionCount is not a valid pointer the result is
+// undefined.
+// If any error occurs such that it cannot be determined which, if any, thread
+// owns the monitor lock on this object then a failing HRESULT will be returned
+HRESULT CordbObjectValue::GetThreadOwningMonitorLock(ICorDebugThread **ppThread, DWORD *pAcquisitionCount)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ return CordbHeapValue3Impl::GetThreadOwningMonitorLock(GetProcess(),
+ GetValueHome()->GetAddress(),
+ ppThread,
+ pAcquisitionCount);
+}
+
+// CordbObjectValue::GetMonitorEventWaitList
+// Provides an ordered list of threads which are queued on the event associated
+// with a monitor lock. The first thread in the list is the first thread which
+// will be released by the next call to Monitor.Pulse, the next thread in the list
+// will be released on the following call, and so on.
+// If this list is non-empty S_OK will be returned, if it is empty S_FALSE
+// will be returned (the enumeration is still valid, just empty).
+// In either case the enumeration interface is only usable for the duration
+// of the current synchronized state, however the threads interfaces dispensed
+// from it are valid until the thread exits.
+// If ppThread is not a valid pointer the result is undefined.
+// If any error occurs such that it cannot be determined which, if any, threads
+// are waiting for the monitor then a failing HRESULT will be returned
+HRESULT CordbObjectValue::GetMonitorEventWaitList(ICorDebugThreadEnum **ppThreadEnum)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ return CordbHeapValue3Impl::GetMonitorEventWaitList(GetProcess(),
+ GetValueHome()->GetAddress(),
+ ppThreadEnum);
+}
+
+HRESULT CordbObjectValue::EnumerateExceptionCallStack(ICorDebugExceptionObjectCallStackEnum** ppCallStackEnum)
+{
+ if (!ppCallStackEnum)
+ return E_INVALIDARG;
+
+ *ppCallStackEnum = NULL;
+
+ HRESULT hr = S_OK;
+ CorDebugExceptionObjectStackFrame* pStackFrames = NULL;
+
+ PUBLIC_API_BEGIN(this);
+
+ CORDB_ADDRESS objAddr = m_valueHome.GetAddress();
+
+ IDacDbiInterface* pDAC = GetProcess()->GetDAC();
+ VMPTR_Object vmObj = pDAC->GetObject(objAddr);
+
+ DacDbiArrayList<DacExceptionCallStackData> dacStackFrames;
+
+ pDAC->GetStackFramesFromException(vmObj, dacStackFrames);
+ int stackFramesLength = dacStackFrames.Count();
+
+ if (stackFramesLength > 0)
+ {
+ pStackFrames = new CorDebugExceptionObjectStackFrame[stackFramesLength];
+ for (int index = 0; index < stackFramesLength; ++index)
+ {
+ DacExceptionCallStackData& currentDacFrame = dacStackFrames[index];
+ CorDebugExceptionObjectStackFrame& currentStackFrame = pStackFrames[index];
+
+ CordbAppDomain* pAppDomain = GetProcess()->LookupOrCreateAppDomain(currentDacFrame.vmAppDomain);
+ CordbModule* pModule = pAppDomain->LookupOrCreateModule(currentDacFrame.vmDomainFile);
+
+ hr = pModule->QueryInterface(IID_ICorDebugModule, reinterpret_cast<void**>(&currentStackFrame.pModule));
+ _ASSERTE(SUCCEEDED(hr));
+
+ currentStackFrame.ip = currentDacFrame.ip;
+ currentStackFrame.methodDef = currentDacFrame.methodDef;
+ currentStackFrame.isLastForeignExceptionFrame = currentDacFrame.isLastForeignExceptionFrame;
+ }
+ }
+
+ CordbExceptionObjectCallStackEnumerator* callStackEnum = new CordbExceptionObjectCallStackEnumerator(GetProcess(), pStackFrames, stackFramesLength);
+ GetProcess()->GetContinueNeuterList()->Add(GetProcess(), callStackEnum);
+
+ hr = callStackEnum->QueryInterface(IID_ICorDebugExceptionObjectCallStackEnum, reinterpret_cast<void**>(ppCallStackEnum));
+ _ASSERTE(SUCCEEDED(hr));
+
+ PUBLIC_API_END(hr);
+
+ if (pStackFrames)
+ delete[] pStackFrames;
+
+ return hr;
+}
+
+HRESULT CordbObjectValue::IsExceptionObject()
+{
+ HRESULT hr = S_OK;
+
+ if (m_info.objTypeData.elementType != ELEMENT_TYPE_CLASS)
+ {
+ hr = S_FALSE;
+ }
+ else
+ {
+ CORDB_ADDRESS objAddr = m_valueHome.GetAddress();
+
+ if (objAddr == NULL)
+ {
+ // object is a literal
+ hr = S_FALSE;
+ }
+ else
+ {
+ IDacDbiInterface* pDAC = GetProcess()->GetDAC();
+
+ VMPTR_Object vmObj = pDAC->GetObject(objAddr);
+ BOOL fIsException = pDAC->IsExceptionObject(vmObj);
+
+ if (!fIsException)
+ hr = S_FALSE;
+ }
+ }
+
+ return hr;
+}
+
+HRESULT CordbObjectValue::IsRcw()
+{
+ HRESULT hr = S_OK;
+
+ if (m_info.objTypeData.elementType != ELEMENT_TYPE_CLASS)
+ {
+ hr = S_FALSE;
+ }
+ else
+ {
+ CORDB_ADDRESS objAddr = m_valueHome.GetAddress();
+
+ if (objAddr == NULL)
+ {
+ // object is a literal
+ hr = S_FALSE;
+ }
+ else
+ {
+ IDacDbiInterface* pDAC = GetProcess()->GetDAC();
+
+ VMPTR_Object vmObj = pDAC->GetObject(objAddr);
+ BOOL fIsRcw = pDAC->IsRcw(vmObj);
+
+ if (!fIsRcw)
+ hr = S_FALSE;
+ }
+ }
+
+ return hr;
+}
+
+HRESULT CordbObjectValue::GetCachedInterfaceTypes(
+ BOOL bIInspectableOnly,
+ ICorDebugTypeEnum * * ppInterfacesEnum)
+{
+#if !defined(FEATURE_COMINTEROP)
+
+ return E_NOTIMPL;
+
+#else
+
+ HRESULT hr = S_OK;
+
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ VALIDATE_POINTER_TO_OBJECT(ppInterfacesEnum, ICorDebugTypeEnum **);
+
+ _ASSERTE(m_fIsRcw);
+
+ EX_TRY
+ {
+ *ppInterfacesEnum = NULL;
+
+ NewArrayHolder<CordbType*> pItfs(NULL);
+
+ // retrieve interface types
+ DacDbiArrayList<DebuggerIPCE_ExpandedTypeData> dacInterfaces;
+
+ IDacDbiInterface* pDAC = GetProcess()->GetDAC();
+
+ CORDB_ADDRESS objAddr = m_valueHome.GetAddress();
+ VMPTR_Object vmObj = pDAC->GetObject(objAddr);
+
+ // retrieve type info from LS
+ pDAC->GetRcwCachedInterfaceTypes(vmObj, m_appdomain->GetADToken(),
+ bIInspectableOnly, &dacInterfaces);
+
+ // synthesize CordbType instances
+ int cItfs = dacInterfaces.Count();
+ if (cItfs > 0)
+ {
+ pItfs = new CordbType*[cItfs];
+ for (int n = 0; n < cItfs; ++n)
+ {
+ hr = CordbType::TypeDataToType(m_appdomain,
+ &(dacInterfaces[n]),
+ &pItfs[n]);
+ }
+ }
+
+ // build a type enumerator
+ CordbTypeEnum* pTypeEnum = CordbTypeEnum::Build(m_appdomain, GetProcess()->GetContinueNeuterList(), cItfs, pItfs);
+ if ( pTypeEnum == NULL )
+ {
+ IfFailThrow(E_OUTOFMEMORY);
+ }
+
+ (*ppInterfacesEnum) = static_cast<ICorDebugTypeEnum*> (pTypeEnum);
+ pTypeEnum->ExternalAddRef();
+
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+
+#endif
+}
+
+HRESULT CordbObjectValue::GetCachedInterfacePointers(
+ BOOL bIInspectableOnly,
+ ULONG32 celt,
+ ULONG32 *pcEltFetched,
+ CORDB_ADDRESS * ptrs)
+{
+#if !defined(FEATURE_COMINTEROP)
+
+ return E_NOTIMPL;
+
+#else
+
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ _ASSERTE(m_fIsRcw);
+
+ if (pcEltFetched == NULL && (ptrs == NULL || celt == 0))
+ return E_INVALIDARG;
+
+ HRESULT hr = S_OK;
+ ULONG32 cItfs = 0;
+
+ // retrieve interface types
+
+ CORDB_ADDRESS objAddr = m_valueHome.GetAddress();
+
+ DacDbiArrayList<CORDB_ADDRESS> dacItfPtrs;
+ EX_TRY
+ {
+ IDacDbiInterface* pDAC = GetProcess()->GetDAC();
+ VMPTR_Object vmObj = pDAC->GetObject(objAddr);
+
+ // retrieve type info from LS
+ pDAC->GetRcwCachedInterfacePointers(vmObj, bIInspectableOnly, &dacItfPtrs);
+ }
+ EX_CATCH_HRESULT(hr);
+ IfFailRet(hr);
+
+ // synthesize CordbType instances
+ cItfs = (ULONG32)dacItfPtrs.Count();
+
+ if (pcEltFetched != NULL && ptrs == NULL)
+ {
+ *pcEltFetched = cItfs;
+ return S_OK;
+ }
+
+ if (pcEltFetched != NULL)
+ {
+ *pcEltFetched = (cItfs <= celt ? cItfs : celt);
+ }
+
+ if (ptrs != NULL && *pcEltFetched > 0)
+ {
+ for (ULONG32 i = 0; i < *pcEltFetched; ++i)
+ ptrs[i] = dacItfPtrs[i];
+ }
+
+ return (*pcEltFetched == celt ? S_OK : S_FALSE);
+
+#endif
+}
+
+
+/* ------------------------------------------------------------------------- *
+ * Value Class Object
+ * ------------------------------------------------------------------------- */
+
+// constructor
+// Arguments:
+// input: pAppdomain - app domain to which the value belongs
+// pType - type information for the value
+// remoteValue - buffer describing the target location of the value
+// ppRemoteRegAddr - describes the register information if the value resides in a register
+// Note: May throw E_OUTOFMEMORY
+CordbVCObjectValue::CordbVCObjectValue(CordbAppDomain * pAppdomain,
+ CordbType * pType,
+ TargetBuffer remoteValue,
+ EnregisteredValueHomeHolder * ppRemoteRegAddr)
+
+ // We'd like to neuter this on Continue (not just exit), but it may be a breaking change,
+ // especially for ValueTypes that don't have any GC refs in them.
+ : CordbValue(pAppdomain,
+ pType,
+ remoteValue.pAddress,
+ false,
+ pAppdomain->GetSweepableExitNeuterList()),
+ m_pObjectCopy(NULL),
+ m_pValueHome(NULL)
+{
+ // instantiate the value home
+ NewHolder<ValueHome> pHome(NULL);
+
+ if (remoteValue.IsEmpty())
+ {
+ pHome = (new RegisterValueHome(pAppdomain->GetProcess(), ppRemoteRegAddr));
+ }
+ else
+ {
+ pHome = (new VCRemoteValueHome(pAppdomain->GetProcess(), remoteValue));
+ }
+ m_pValueHome = pHome.GetValue(); // throws
+ pHome.SuppressRelease();
+} // CordbVCObjectValue::CordbVCObjectValue
+
+// destructor
+CordbVCObjectValue::~CordbVCObjectValue()
+{
+ DTOR_ENTRY(this);
+
+ _ASSERTE(IsNeutered());
+
+ // Destroy the copy of the object.
+ if (m_pObjectCopy != NULL)
+ {
+ delete [] m_pObjectCopy;
+ m_pObjectCopy = NULL;
+ }
+
+ // destroy the value home
+ if (m_pValueHome != NULL)
+ {
+ delete m_pValueHome;
+ m_pValueHome = NULL;
+}
+} // CordbVCObjectValue::~CordbVCObjectValue
+
+HRESULT CordbVCObjectValue::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugValue)
+ {
+ *pInterface = static_cast<ICorDebugValue*>(static_cast<ICorDebugObjectValue*>(this));
+ }
+ else if (id == IID_ICorDebugValue2)
+ {
+ *pInterface = static_cast<ICorDebugValue2*>(this);
+ }
+ else if (id == IID_ICorDebugValue3)
+ {
+ *pInterface = static_cast<ICorDebugValue3*>(this);
+ }
+ else if (id == IID_ICorDebugObjectValue)
+ {
+ *pInterface = static_cast<ICorDebugObjectValue*>(this);
+ }
+ else if (id == IID_ICorDebugObjectValue2)
+
+ {
+ *pInterface = static_cast<ICorDebugObjectValue2*>(this);
+ }
+ else if (id == IID_ICorDebugGenericValue)
+ {
+ *pInterface = static_cast<ICorDebugGenericValue*>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugObjectValue*>(this));
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+} // CordbVCObjectValue::QueryInterface
+
+// returns the basic type of the ICDValue
+// Arguments:
+// output: pType - the type of the ICDValue (always E_T_VALUETYPE)
+// ReturnValue: S_OK on success or E_INVALIDARG if pType is NULL
+HRESULT CordbVCObjectValue::GetType(CorElementType *pType)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(pType, CorElementType *);
+
+ *pType = ELEMENT_TYPE_VALUETYPE;
+ return S_OK;
+} // CordbVCObjectValue::GetType
+
+// public API to get the CordbClass field
+// Arguments:
+// output: ppClass - holds a pointer to the ICDClass instance belonging to this
+// Return Value: S_OK on success, CORDBG_E_OBJECT_NEUTERED or synchronization errors on failure
+HRESULT CordbVCObjectValue::GetClass(ICorDebugClass **ppClass)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ *ppClass = (ICorDebugClass*) GetClass();
+
+ if (*ppClass != NULL)
+ (*ppClass)->AddRef();
+
+ return S_OK;
+} // CordbVCObjectValue::GetClass
+
+// internal method to get the CordbClass field
+// Arguments: none
+// ReturnValue: the instance of CordbClass belonging to this VC object
+CordbClass *CordbVCObjectValue::GetClass()
+{
+ CordbClass *tycon;
+ Instantiation inst;
+ m_type->DestConstructedType(&tycon, &inst);
+ return tycon;
+} // CordbVCObjectValue::GetClass
+
+//-----------------------------------------------------------------------------
+//
+// Finds the given field of the given type in the object and returns an ICDValue for it.
+//
+// Arguments:
+// pType - The type of the field
+// fieldDef - The field's metadata def.
+// ppValue - OUT: the ICDValue for the field.
+//
+// Returns:
+// S_OK on success, CORDBG_E_OBJECT_NEUTERED, E_INVALIDARG, CORDBG_E_ENC_HANGING_FIELD, or various other
+// failure codes
+HRESULT CordbVCObjectValue::GetFieldValueForType(ICorDebugType * pType,
+ mdFieldDef fieldDef,
+ ICorDebugValue ** ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // Validate the token.
+ if ((m_type->m_pClass == NULL) || !m_type->m_pClass->GetModule()->GetMetaDataImporter()->IsValidToken(fieldDef))
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+
+ CordbType * pCordbType;
+
+ //
+ // <TODO>@todo: need to ensure that pClass is really on the class
+ // hierarchy of m_class!!!</TODO>
+ //
+ if (pType == NULL)
+ {
+ pCordbType = m_type;
+ }
+ else
+ {
+ pCordbType = static_cast<CordbType *> (pType);
+ }
+
+ FieldData * pFieldData;
+
+ #ifdef _DEBUG
+ pFieldData = NULL;
+ #endif
+
+ hr = pCordbType->GetFieldInfo(fieldDef, &pFieldData);
+ _ASSERTE(hr != CORDBG_E_ENC_HANGING_FIELD);
+
+ // If we get back CORDBG_E_ENC_HANGING_FIELD we'll just fail -
+ // value classes should not be able to add fields once they're loaded,
+ // since the new fields _can't_ be contiguous with the old fields,
+ // and having all the fields contiguous is kinda the point of a V.C.
+ IfFailThrow(hr);
+
+ _ASSERTE(pFieldData != NULL);
+
+ CordbModule * pModule = pCordbType->m_pClass->GetModule();
+
+ SigParser sigParser;
+ IfFailThrow(pFieldData->GetFieldSignature(pModule, &sigParser));
+
+ // <TODO>
+ // How can I assert that I have exactly one field?
+ // </TODO>
+ CordbType * pFieldType;
+
+ IfFailThrow(CordbType::SigToType(pModule, &sigParser, &(pCordbType->m_inst), &pFieldType));
+
+ _ASSERTE(pFieldData->OkToGetOrSetInstanceOffset());
+ // Compute the address of the field contents in our local object cache
+ SIZE_T fieldOffset = pFieldData->GetInstanceOffset();
+ ULONG32 size = GetSizeForType(pFieldType, kUnboxed);
+
+ // verify that the field starts before the end of m_pObjectCopy
+ _ASSERTE(fieldOffset < m_size);
+ _ASSERTE(fieldOffset + size <= m_size);
+
+ m_pValueHome->CreateInternalValue(pFieldType,
+ fieldOffset,
+ m_pObjectCopy + fieldOffset,
+ size,
+ ppValue); // throws
+
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+} // CordbVCObjectValue::GetFieldValueForType
+
+// gets an ICDValue to represent a field of the VC object
+// Arguments:
+// input: pClass - the class information for this object (needed to get the parent class information)
+// fieldDef - field token for the desired field
+// output: ppValue - on success, the ICDValue representing the desired field
+// Return Value: S_OK on success, CORDBG_E_OBJECT_NEUTERED, CORDBG_E_CLASS_NOT_LOADED, E_INVALIDARG, OOM,
+// CORDBG_E_ENC_HANGING_FIELD, or various other failure codes
+HRESULT CordbVCObjectValue::GetFieldValue(ICorDebugClass *pClass,
+ mdFieldDef fieldDef,
+ ICorDebugValue **ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ VALIDATE_POINTER_TO_OBJECT(pClass, ICorDebugClass *);
+ VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **);
+
+ HRESULT hr;
+ _ASSERTE(m_type);
+
+ if (m_type->m_elementType != ELEMENT_TYPE_CLASS &&
+ m_type->m_elementType != ELEMENT_TYPE_VALUETYPE)
+ {
+ return E_INVALIDARG;
+ }
+
+ RSExtSmartPtr<CordbType> relevantType;
+
+ if (FAILED (hr= m_type->GetParentType((CordbClass *) pClass, &relevantType)))
+ {
+ return hr;
+ }
+ // Upon exit relevantType will either be the appropriate type for the
+ // class we're looking for.
+
+ hr = GetFieldValueForType(relevantType, fieldDef, ppValue);
+ // GetParentType ands one reference to relevantType, holder dtor releases that.
+ return hr;
+
+} // CordbVCObjectValue::GetFieldValue
+
+// get a copy of the VC object
+// Arguments:
+// output: pTo - a caller-allocated buffer to hold the copy
+// Return Value: S_OK on success, CORDBG_E_OBJECT_NEUTERED on failure
+// Note: The caller must ensure the buffer is large enough to hold the value (by a previous call to GetSize)
+// and is responsible for allocation and deallocation.
+HRESULT CordbVCObjectValue::GetValue(void *pTo)
+{
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(pTo, BYTE, m_size, false, true);
+ FAIL_IF_NEUTERED(this);
+
+ // Copy out the value, which is the whole object.
+ memcpy(pTo, m_pObjectCopy, m_size);
+
+ return S_OK;
+} // CordbVCObjectValue::GetValue
+
+// set the value of a VC object
+// Arguments:
+// input: pSrc - buffer containing the new value. Allocated and managed by the caller.
+// Return Value: S_OK on success, CORDBG_E_OBJECT_NEUTERED, synchronization errors, E_INVALIDARG, write
+// process memory errors, CORDBG_E_CLASS_NOT_LOADED or OOM on failure
+HRESULT CordbVCObjectValue::SetValue(void * pSrc)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(pSrc, BYTE, m_size, true, false);
+
+ // Can't change literals...
+ if (m_isLiteral)
+ return E_INVALIDARG;
+
+ if (m_type)
+ {
+ IfFailRet(m_type->Init(FALSE));
+ }
+
+ EX_TRY
+ {
+ m_pValueHome->SetValue(MemoryRange(pSrc, m_size), m_type); // throws
+ }
+ EX_CATCH_HRESULT(hr);
+ if (SUCCEEDED(hr))
+ {
+ // That worked, so update the copy of the value we have over here.
+ memcpy(m_pObjectCopy, pSrc, m_size);
+ }
+
+ return hr;
+} // CordbVCObjectValue::SetValue
+
+HRESULT CordbVCObjectValue::GetVirtualMethod(mdMemberRef memberRef,
+ ICorDebugFunction **ppFunction)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT CordbVCObjectValue::GetVirtualMethodAndType(mdMemberRef memberRef,
+ ICorDebugFunction **ppFunction,
+ ICorDebugType **ppType)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT CordbVCObjectValue::GetContext(ICorDebugContext **ppContext)
+{
+ return E_NOTIMPL;
+}
+
+// self-identifier--always returns true as long as pbIsValueClass is non-Null
+HRESULT CordbVCObjectValue::IsValueClass(BOOL *pbIsValueClass)
+{
+ if (pbIsValueClass)
+ *pbIsValueClass = TRUE;
+
+ return S_OK;
+} // CordbVCObjectValue::IsValueClass
+
+HRESULT CordbVCObjectValue::GetManagedCopy(IUnknown **ppObject)
+{
+ // This function is deprecated
+ return E_NOTIMPL;
+}
+
+HRESULT CordbVCObjectValue::SetFromManagedCopy(IUnknown *pObject)
+{
+ // This function is deprecated
+ return E_NOTIMPL;
+}
+
+ //
+// CordbVCObjectValue::Init
+//
+// Description
+// Initializes the Right-Side's representation of a Value Class object.
+// Parameters
+// input: localValue - buffer containing the value if this instance of CordbObjectValue
+// was a field or array element of an existing value, otherwise this
+// will have a start address equal to NULL
+// Returns
+// HRESULT
+// S_OK if the function completed normally
+// failing HR otherwise
+// Exceptions
+// None
+//
+HRESULT CordbVCObjectValue::Init(MemoryRange localValue)
+{
+ HRESULT hr = S_OK;
+
+ INTERNAL_SYNC_API_ENTRY(this->GetProcess()); //
+
+ // Get the object size from the class
+ ULONG32 size;
+ IfFailRet( m_type->GetUnboxedObjectSize(&size) );
+ m_size = size;
+
+ // Copy the entire object over to this process.
+ m_pObjectCopy = new (nothrow) BYTE[m_size];
+
+ if (m_pObjectCopy == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ if (localValue.StartAddress() != NULL)
+ {
+ // The data is already in the local address space. Go ahead and copy it
+ // from there.
+ // localValue.StartAddress points to:
+ // 1. A field from the local cached copy belonging to an instance of CordbVCObjectValue (different
+ // instance from "this") or CordbObjectValue
+ // 2. An element in the locally cached subrange of an array belonging to an instance of CordbArrayValue
+ // 3. The address of a particular register in the register display of an instance of CordbNativeFrame
+ // for an enregistered value type. In this case, it's possible that the size of the value is
+ // smaller than the size of a full register. For that reason, we can't just use localValue.Size()
+ // as the number of bytes to copy, because only enough space for the value has been allocated.
+ _ASSERTE(localValue.Size() >= m_size);
+ localCopy(m_pObjectCopy, MemoryRange(localValue.StartAddress(), m_size));
+ return S_OK;
+ }
+
+ EX_TRY
+ {
+ m_pValueHome->GetValue(MemoryRange(m_pObjectCopy, m_size)); // throws
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+} // CordbVCObjectValue::Init
+
+/* ------------------------------------------------------------------------- *
+ * Box Value class
+ * ------------------------------------------------------------------------- */
+
+// constructor
+// Arguments:
+// input: appdomain - app domain to which the value belongs
+// type - type information for the boxed value
+// remoteValue - buffer describing the remote location of the value
+// size - size of the value
+// offsetToVars - offset from the beginning of the value to the first field of the value
+CordbBoxValue::CordbBoxValue(CordbAppDomain *appdomain,
+ CordbType *type,
+ TargetBuffer remoteValue,
+ ULONG32 size,
+ SIZE_T offsetToVars)
+ : CordbValue(appdomain, type, remoteValue.pAddress, false, appdomain->GetProcess()->GetContinueNeuterList()),
+ m_offsetToVars(offsetToVars),
+ m_valueHome(appdomain->GetProcess(), remoteValue)
+{
+ m_size = size;
+} // CordbBoxValue::CordbBoxValue
+
+// destructor
+CordbBoxValue::~CordbBoxValue()
+{
+ DTOR_ENTRY(this);
+ _ASSERTE(IsNeutered());
+} // CordbBoxValue::~CordbBoxValue
+
+HRESULT CordbBoxValue::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugValue)
+ {
+ *pInterface = static_cast<ICorDebugValue*>(static_cast<ICorDebugBoxValue*>(this));
+ }
+ else if (id == IID_ICorDebugValue2)
+ {
+ *pInterface = static_cast<ICorDebugValue2*>(this);
+ }
+ else if (id == IID_ICorDebugValue3)
+ {
+ *pInterface = static_cast<ICorDebugValue3*>(this);
+ }
+ else if (id == IID_ICorDebugBoxValue)
+ {
+ *pInterface = static_cast<ICorDebugBoxValue*>(this);
+ }
+ else if (id == IID_ICorDebugGenericValue)
+ {
+ *pInterface = static_cast<ICorDebugGenericValue*>(this);
+ }
+ else if (id == IID_ICorDebugHeapValue)
+ {
+ *pInterface = static_cast<ICorDebugHeapValue*>(this);
+ }
+ else if (id == IID_ICorDebugHeapValue2)
+ {
+ *pInterface = static_cast<ICorDebugHeapValue2*>(this);
+ }
+ else if (id == IID_ICorDebugHeapValue3)
+ {
+ *pInterface = static_cast<ICorDebugHeapValue3*>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugBoxValue*>(this));
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+} // CordbBoxValue::QueryInterface
+
+// returns the basic type of the ICDValue
+// Arguments:
+// output: pType - the type of the ICDValue (always E_T_CLASS)
+// ReturnValue: S_OK on success or E_INVALIDARG if pType is NULL
+HRESULT CordbBoxValue::GetType(CorElementType *pType)
+{
+ VALIDATE_POINTER_TO_OBJECT(pType, CorElementType *);
+
+ *pType = ELEMENT_TYPE_CLASS;
+
+ return (S_OK);
+} // CordbBoxValue::GetType
+
+HRESULT CordbBoxValue::IsValid(BOOL *pbValid)
+{
+ VALIDATE_POINTER_TO_OBJECT(pbValid, BOOL *);
+
+ // <TODO>@todo: implement tracking of objects across collections.</TODO>
+
+ return E_NOTIMPL;
+}
+
+HRESULT CordbBoxValue::CreateRelocBreakpoint(ICorDebugValueBreakpoint **ppBreakpoint)
+{
+ VALIDATE_POINTER_TO_OBJECT(ppBreakpoint, ICorDebugValueBreakpoint **);
+
+ return E_NOTIMPL;
+}
+
+// Creates a handle of the given type for this heap value.
+// Not Implemented In-Proc.
+// Create a handle for a heap object.
+// @todo: How to prevent this being called by non-heap object?
+// Arguments:
+// input: handleType - type of the handle to be created
+// output: ppHandle - on success, the newly created handle
+// Return Value: S_OK on success or E_INVALIDARG, E_OUTOFMEMORY, or CORDB_E_HELPER_MAY_DEADLOCK
+HRESULT CordbBoxValue::CreateHandle(
+ CorDebugHandleType handleType,
+ ICorDebugHandleValue ** ppHandle)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ return CordbValue::InternalCreateHandle(handleType, ppHandle);
+} // CordbBoxValue::CreateHandle
+
+HRESULT CordbBoxValue::GetValue(void *pTo)
+{
+ // Can't get a whole copy of a box.
+ return E_INVALIDARG;
+}
+
+HRESULT CordbBoxValue::SetValue(void *pFrom)
+{
+ // You're not allowed to set a box value.
+ return E_INVALIDARG;
+}
+
+// gets the unboxed value from this boxed value
+// Arguments:
+// output: ppObject - pointer to an instance of ICDValue representing the unboxed value, unless ppObject
+// is NULL
+// Return Value: S_OK on success or a variety of possible failures: OOM, E_FAIL, errors from
+// ReadProcessMemory.
+HRESULT CordbBoxValue::GetObject(ICorDebugObjectValue **ppObject)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(ppObject, ICorDebugObjectValue **);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ ULONG32 size;
+ m_type->GetUnboxedObjectSize(&size);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ m_valueHome.CreateInternalValue(m_type,
+ m_offsetToVars,
+ NULL,
+ size,
+ reinterpret_cast<ICorDebugValue **>(ppObject)); // throws
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+} // CordbBoxValue::GetObject
+
+// If a managed thread owns the monitor lock on this object then *ppThread
+// will point to that thread and S_OK will be returned. The thread object is valid
+// until the thread exits. *pAcquisitionCount will indicate the number of times
+// this thread would need to release the lock before it returns to being
+// unowned.
+// If no managed thread owns the monitor lock on this object then *ppThread
+// and pAcquisitionCount will be unchanged and S_FALSE returned.
+// If ppThread or pAcquisitionCount is not a valid pointer the result is
+// undefined.
+// If any error occurs such that it cannot be determined which, if any, thread
+// owns the monitor lock on this object then a failing HRESULT will be returned
+HRESULT CordbBoxValue::GetThreadOwningMonitorLock(ICorDebugThread **ppThread, DWORD *pAcquisitionCount)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ return CordbHeapValue3Impl::GetThreadOwningMonitorLock(GetProcess(),
+ GetValueHome()->GetAddress(),
+ ppThread,
+ pAcquisitionCount);
+}
+
+// Provides an ordered list of threads which are queued on the event associated
+// with a monitor lock. The first thread in the list is the first thread which
+// will be released by the next call to Monitor.Pulse, the next thread in the list
+// will be released on the following call, and so on.
+// If this list is non-empty S_OK will be returned, if it is empty S_FALSE
+// will be returned (the enumeration is still valid, just empty).
+// In either case the enumeration interface is only usable for the duration
+// of the current synchronized state, however the threads interfaces dispensed
+// from it are valid until the thread exits.
+// If ppThread is not a valid pointer the result is undefined.
+// If any error occurs such that it cannot be determined which, if any, threads
+// are waiting for the monitor then a failing HRESULT will be returned
+HRESULT CordbBoxValue::GetMonitorEventWaitList(ICorDebugThreadEnum **ppThreadEnum)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ return CordbHeapValue3Impl::GetMonitorEventWaitList(GetProcess(),
+ GetValueHome()->GetAddress(),
+ ppThreadEnum);
+}
+
+
+/* ------------------------------------------------------------------------- *
+ * Array Value class
+ * ------------------------------------------------------------------------- */
+
+// The size of the buffer we allocate to hold array elements.
+// Note that since we must be able to hold at least one element, we may
+// allocate larger than the cache size here.
+// Also, this cache doesn't include a small header used to store the rank vectors
+#ifdef _DEBUG
+// For debug, use a small size to cause more churn
+ #define ARRAY_CACHE_SIZE (1000)
+#else
+// For release, guess 4 pages should be enough. Subtract some bytes to store
+// the header so that that doesn't push us onto another page. (We guess a reasonable
+// header size, but it's ok if it's larger).
+ #define ARRAY_CACHE_SIZE (4 * 4096 - 24)
+#endif
+
+// constructor
+// Arguments:
+// input:
+// pAppDomain - app domain to which the value belongs
+// pType - type information for the value
+// pObjectInfo - array specific type information
+// remoteValue - buffer describing the remote location of the value
+CordbArrayValue::CordbArrayValue(CordbAppDomain * pAppdomain,
+ CordbType * pType,
+ DebuggerIPCE_ObjectData * pObjectInfo,
+ TargetBuffer remoteValue)
+ : CordbValue(pAppdomain,
+ pType,
+ remoteValue.pAddress,
+ false,
+ pAppdomain->GetProcess()->GetContinueNeuterList()),
+ m_info(*pObjectInfo),
+ m_pObjectCopy(NULL),
+ m_valueHome(pAppdomain->GetProcess(), remoteValue)
+{
+ m_size = m_info.objSize;
+ pType->DestUnaryType(&m_elemtype);
+
+// Set range to illegal values to force a load on first access
+ m_idxLower = m_idxUpper = (SIZE_T) -1;
+} // CordbArrayValue::CordbArrayValue
+
+// destructor
+CordbArrayValue::~CordbArrayValue()
+{
+ DTOR_ENTRY(this);
+ _ASSERTE(IsNeutered());
+
+ // Destroy the copy of the object.
+ if (m_pObjectCopy != NULL)
+ delete [] m_pObjectCopy;
+} // CordbArrayValue::~CordbArrayValue
+
+HRESULT CordbArrayValue::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugValue)
+ {
+ *pInterface = static_cast<ICorDebugValue*>(static_cast<ICorDebugArrayValue*>(this));
+ }
+ else if (id == IID_ICorDebugValue2)
+ {
+ *pInterface = static_cast<ICorDebugValue2*>(this);
+ }
+ else if (id == IID_ICorDebugValue3)
+ {
+ *pInterface = static_cast<ICorDebugValue3*>(this);
+ }
+ else if (id == IID_ICorDebugArrayValue)
+ {
+ *pInterface = static_cast<ICorDebugArrayValue*>(this);
+ }
+ else if (id == IID_ICorDebugGenericValue)
+ {
+ *pInterface = static_cast<ICorDebugGenericValue*>(this);
+ }
+ else if (id == IID_ICorDebugHeapValue)
+ {
+ *pInterface = static_cast<ICorDebugHeapValue*>(this);
+ }
+ else if (id == IID_ICorDebugHeapValue2)
+ {
+ *pInterface = static_cast<ICorDebugHeapValue2*>(this);
+ }
+ else if (id == IID_ICorDebugHeapValue3)
+ {
+ *pInterface = static_cast<ICorDebugHeapValue3*>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugArrayValue*>(this));
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+} // CordbArrayValue::QueryInterface
+
+// gets the type of the array elements
+// Arguments:
+// output: pType - the element type unless pType is NULL
+// Return Value: S_OK on success or E_INVALIDARG if pType is null
+HRESULT CordbArrayValue::GetElementType(CorElementType *pType)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pType, CorElementType *);
+
+ *pType = m_elemtype->m_elementType;
+ return S_OK;
+} // CordbArrayValue::GetElementType
+
+
+// gets the rank of the array
+// Arguments:
+// output: pnRank - the rank of the array unless pnRank is null
+// Return Value: S_OK on success or E_INVALIDARG if pnRank is null
+HRESULT CordbArrayValue::GetRank(ULONG32 *pnRank)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pnRank, SIZE_T *);
+
+ // Rank info is duplicated for sanity checking - double check it here.
+ _ASSERTE(m_info.arrayInfo.rank == m_type->m_rank);
+ *pnRank = m_type->m_rank;
+ return S_OK;
+} // CordbArrayValue::GetRank
+
+// gets the number of elements in the array
+// Arguments:
+// output: pnCount - the number of dimensions for the array unless pnCount is null
+// Return Value: S_OK on success or E_INVALIDARG if pnCount is null
+HRESULT CordbArrayValue::GetCount(ULONG32 *pnCount)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pnCount, ULONG32 *);
+
+ *pnCount = (ULONG32)m_info.arrayInfo.componentCount;
+ return S_OK;
+} // CordbArrayValue::GetCount
+
+// get the size of each dimension of the array
+// Arguments:
+// input: cdim - the number of dimensions about which to get dimensions--this must be the same as the rank
+// output: dims - an array to hold the sizes of the dimensions of the array--this is allocated and
+// managed by the caller
+// Return Value: S_OK on success or E_INVALIDARG
+HRESULT CordbArrayValue::GetDimensions(ULONG32 cdim, ULONG32 dims[])
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(dims, SIZE_T, cdim, true, true);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ // Rank info is duplicated for sanity checking - double check it here.
+ _ASSERTE(m_info.arrayInfo.rank == m_type->m_rank);
+ if (cdim != m_type->m_rank)
+ return E_INVALIDARG;
+
+ // SDArrays don't have bounds info, so return the component count.
+ if (cdim == 1)
+ dims[0] = (ULONG32)m_info.arrayInfo.componentCount;
+ else
+ {
+ _ASSERTE(m_info.arrayInfo.offsetToUpperBounds != 0);
+ _ASSERTE(m_arrayUpperBase != NULL);
+
+ // The upper bounds info in the array is the true size of each
+ // dimension.
+ for (unsigned int i = 0; i < cdim; i++)
+ dims[i] = m_arrayUpperBase[i];
+ }
+
+ return S_OK;
+} // CordbArrayValue::GetDimensions
+
+//
+// indicates whether the array has base indices
+// Arguments:
+// output: pbHasBaseIndices - true iff the array has more than one dimension and pbHasBaseIndices is not null
+// Return Value: S_OK on success or E_INVALIDARG if pbHasBaseIndices is null
+HRESULT CordbArrayValue::HasBaseIndicies(BOOL *pbHasBaseIndices)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pbHasBaseIndices, BOOL *);
+
+ *pbHasBaseIndices = m_info.arrayInfo.offsetToLowerBounds != 0;
+ return S_OK;
+} // CordbArrayValue::HasBaseIndicies
+
+// gets the base indices for a multidimensional array
+// Arguments:
+// input: cdim - the number of dimensions (this must be the same as the actual rank of the array)
+// indices - an array to hold the base indices for the array dimensions (allocated and managed
+// by the caller, it must have space for cdim elements)
+// Return Value: S_OK on success or E_INVALIDARG if cdim is not equal to the array rank or indices is null
+HRESULT CordbArrayValue::GetBaseIndicies(ULONG32 cdim, ULONG32 indices[])
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(indices, SIZE_T, cdim, true, true);
+
+ // Rank info is duplicated for sanity checking - double check it here.
+ _ASSERTE(m_info.arrayInfo.rank == m_type->m_rank);
+ if ((cdim != m_type->m_rank) ||
+ (m_info.arrayInfo.offsetToLowerBounds == 0))
+ return E_INVALIDARG;
+
+ _ASSERTE(m_arrayLowerBase != NULL);
+
+ for (unsigned int i = 0; i < cdim; i++)
+ indices[i] = m_arrayLowerBase[i];
+
+ return S_OK;
+} // CordbArrayValue::GetBaseIndicies
+
+// Get an element at the position indicated by the values in indices (one index for each dimension)
+// Arguments:
+// input: cdim - the number of dimensions and thus the number of elements in indices. This must match
+// the actual rank of the array value.
+// indices - an array of indices to specify the position of the element. For example, to get a[2][1][0],
+// indices would contain 2, 1, and 0 in that order.
+// output: ppValue - an ICDValue representing the element, unless an error occurs
+// Return Value: S_OK on success or E_INVALIDARG if cdim != rank, indices is NULL or ppValue is NULL
+// or a variety of possible failures: OOM, E_FAIL, errors from
+// ReadProcessMemory.
+HRESULT CordbArrayValue::GetElement(ULONG32 cdim,
+ ULONG32 indices[],
+ ICorDebugValue **ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(indices, SIZE_T, cdim, true, true);
+ VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ *ppValue = NULL;
+
+ // Rank info is duplicated for sanity checking - double check it here.
+ _ASSERTE(m_info.arrayInfo.rank == m_type->m_rank);
+ if ((cdim != m_type->m_rank) || (indices == NULL))
+ return E_INVALIDARG;
+
+ // If the array has lower bounds, adjust the indices.
+ if (m_info.arrayInfo.offsetToLowerBounds != 0)
+ {
+ _ASSERTE(m_arrayLowerBase != NULL);
+
+ for (unsigned int i = 0; i < cdim; i++)
+ indices[i] -= m_arrayLowerBase[i];
+ }
+
+ SIZE_T offset = 0;
+
+ // SDArrays don't have upper bounds
+ if (cdim == 1)
+ {
+ offset = indices[0];
+
+ // Bounds check
+ if (offset >= m_info.arrayInfo.componentCount)
+ return E_INVALIDARG;
+ }
+ else
+ {
+ _ASSERTE(m_info.arrayInfo.offsetToUpperBounds != 0);
+ _ASSERTE(m_arrayUpperBase != NULL);
+
+ // Calculate the offset in bytes for all dimensions.
+ SIZE_T multiplier = 1;
+
+ for (int i = cdim - 1; i >= 0; i--)
+ {
+ // Bounds check
+ if (indices[i] >= m_arrayUpperBase[i])
+ return E_INVALIDARG;
+
+ offset += indices[i] * multiplier;
+ multiplier *= m_arrayUpperBase[i];
+ }
+
+ _ASSERTE(offset < m_info.arrayInfo.componentCount);
+ }
+
+ return GetElementAtPosition((ULONG32)offset, ppValue);
+} // CordbArrayValue::GetElement
+
+// get an ICDValue to represent the element at a given position
+// Arguments:
+// input: nPosition - the offset from the beginning of the array to the element
+// output: ppValue - the ICDValue representing the array element on success
+// Return Value: S_OK on success, E_INVALIDARG or a variety of possible failures: OOM, E_FAIL, errors from
+// ReadProcessMemory.
+HRESULT CordbArrayValue::GetElementAtPosition(ULONG32 nPosition,
+ ICorDebugValue **ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ if (nPosition >= m_info.arrayInfo.componentCount)
+ {
+ *ppValue = NULL;
+ return E_INVALIDARG;
+ }
+
+ // Rank info is duplicated for sanity checking - double check it here.
+ _ASSERTE(m_info.arrayInfo.rank == m_type->m_rank);
+
+ // The header consists of two DWORDs for each dimension, representing the upper and lower bound for that dimension. A
+ // vector of lower bounds comes first, followed by a vector of upper bounds. We want to copy a range of
+ // elements into m_pObjectCopy following these vectors, so we need to compute the address where the
+ // vectors end and the elements begin.
+ const int cbHeader = 2 * m_type->m_rank * sizeof(DWORD);
+ HRESULT hr = S_OK;
+
+ // Ensure that the proper subset is in the cache. m_idxLower and m_idxUpper are initialized to -1, so the
+ // first time we hit this condition check, it will evaluate to true. We will set these inside the
+ // consequent to the range starting at nPosition and ending at the last available cache position. Thus,
+ // after the first time we hit this, we are asking if nPosition lies outside the range we've cached.
+ if (nPosition < m_idxLower || nPosition >= m_idxUpper)
+ {
+ const SIZE_T cbElemSize = m_info.arrayInfo.elementSize;
+ SIZE_T len = 1;
+
+ if (cbElemSize != 0)
+ {
+ // the element size could be bigger than the cache, but we want len to be at least 1.
+ len = max(ARRAY_CACHE_SIZE / cbElemSize, len);
+ }
+ else _ASSERTE(cbElemSize != 0);
+
+ m_idxLower = nPosition;
+ m_idxUpper = min(m_idxLower + len, m_info.arrayInfo.componentCount);
+ _ASSERTE(m_idxLower < m_idxUpper);
+
+ SIZE_T cbOffsetFrom = m_info.arrayInfo.offsetToArrayBase + m_idxLower * cbElemSize;
+
+ SIZE_T cbSize = (m_idxUpper - m_idxLower) * cbElemSize; // we'll copy the largest range of ellements possible
+
+ _ASSERTE(cbSize <= m_info.objSize);
+ // Copy the proper subrange of the array over
+ EX_TRY
+ {
+ m_valueHome.GetInternalValue(MemoryRange(m_pObjectCopy + cbHeader, cbSize), cbOffsetFrom); // throws
+ }
+ EX_CATCH_HRESULT(hr);
+ IfFailRet(hr);
+ }
+
+ SIZE_T size = m_info.arrayInfo.elementSize;
+ _ASSERTE(size <= m_info.objSize);
+
+ SIZE_T offset = m_info.arrayInfo.offsetToArrayBase + (nPosition * size);
+ void * localAddress = m_pObjectCopy + cbHeader + ((nPosition - m_idxLower) * size);
+
+ EX_TRY
+ {
+ m_valueHome.CreateInternalValue(m_elemtype,
+ offset,
+ localAddress,
+ (ULONG32)size,
+ ppValue); // throws
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+
+} // CordbArrayValue::GetElementAtPosition
+
+HRESULT CordbArrayValue::IsValid(BOOL *pbValid)
+{
+ VALIDATE_POINTER_TO_OBJECT(pbValid, BOOL *);
+
+ // <TODO>@todo: implement tracking of objects across collections.</TODO>
+
+ return E_NOTIMPL;
+}
+
+HRESULT CordbArrayValue::CreateRelocBreakpoint(
+ ICorDebugValueBreakpoint **ppBreakpoint)
+{
+ VALIDATE_POINTER_TO_OBJECT(ppBreakpoint, ICorDebugValueBreakpoint **);
+
+ return E_NOTIMPL;
+}
+
+// Creates a handle of the given type for this heap value.
+// Not Implemented In-Proc.
+// Arguments:
+// input: handleType - type of the handle to be created
+// output: ppHandle - on success, the newly created handle
+// Return Value: S_OK on success or E_INVALIDARG, E_OUTOFMEMORY, or CORDB_E_HELPER_MAY_DEADLOCK
+HRESULT CordbArrayValue::CreateHandle(
+ CorDebugHandleType handleType,
+ ICorDebugHandleValue ** ppHandle)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ return CordbValue::InternalCreateHandle(handleType, ppHandle);
+} // CordbArrayValue::CreateHandle
+
+// get a copy of the array
+// Arguments
+// output: pTo - pointer to a caller-allocated and managed buffer to hold the copy. The caller must guarantee
+// that this is large enough to hold the entire array
+// Return Value: S_OK on success, E_INVALIDARG or read process memory errors on failure
+HRESULT CordbArrayValue::GetValue(void *pTo)
+{
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(pTo, void *, 1, false, true);
+ FAIL_IF_NEUTERED(this);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // Copy out the value, which is the whole array.
+ // There's no lazy-evaluation here, so this could be rather large
+ m_valueHome.GetValue(MemoryRange(pTo, m_size)); // throws
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+} // CordbArrayValue::GetValue
+
+HRESULT CordbArrayValue::SetValue(void *pFrom)
+{
+ // You're not allowed to set a whole array at once.
+ return E_INVALIDARG;
+}
+
+// initialize a new instance of CordbArrayValue
+// Arguments: none
+// Return Value: S_OK on success or E_OUTOFMEMORY or read process memory errors on failure
+// Note: we are only initializing information about the array (rank, sizes, dimensions, etc) here. We will not
+// attempt to read array contents until we receive a request to do so.
+HRESULT CordbArrayValue::Init()
+{
+ INTERNAL_SYNC_API_ENTRY(this->GetProcess()); //
+ HRESULT hr = S_OK;
+
+ SIZE_T cbVector = m_info.arrayInfo.rank * sizeof(DWORD);
+ _ASSERTE(cbVector <= m_info.objSize);
+
+ int cbHeader = 2 * (int)cbVector;
+
+ // Find largest data size that will fit in cache
+ SIZE_T cbData = m_info.arrayInfo.componentCount * m_info.arrayInfo.elementSize;
+ if (cbData > ARRAY_CACHE_SIZE)
+ {
+ cbData = (ARRAY_CACHE_SIZE / m_info.arrayInfo.elementSize)
+ * m_info.arrayInfo.elementSize;
+ }
+
+ if (cbData < m_info.arrayInfo.elementSize)
+ {
+ cbData = m_info.arrayInfo.elementSize;
+ }
+
+ // Allocate memory
+ m_pObjectCopy = new (nothrow) BYTE[cbHeader + cbData];
+ if (m_pObjectCopy == NULL)
+ return E_OUTOFMEMORY;
+
+
+ m_arrayLowerBase = NULL;
+ m_arrayUpperBase = NULL;
+
+ // Copy base vectors into header. (Offsets are 0 if the vectors aren't used)
+ if (m_info.arrayInfo.offsetToLowerBounds != 0)
+ {
+ m_arrayLowerBase = (DWORD*)(m_pObjectCopy);
+ EX_TRY
+ {
+ m_valueHome.GetInternalValue(MemoryRange(m_arrayLowerBase, cbVector),
+ m_info.arrayInfo.offsetToLowerBounds); // throws
+ }
+ EX_CATCH_HRESULT(hr);
+ IfFailRet(hr);
+ }
+
+
+ if (m_info.arrayInfo.offsetToUpperBounds != 0)
+ {
+ m_arrayUpperBase = (DWORD*)(m_pObjectCopy + cbVector);
+ EX_TRY
+ {
+ m_valueHome.GetInternalValue(MemoryRange(m_arrayUpperBase, cbVector),
+ m_info.arrayInfo.offsetToUpperBounds); // throws
+ }
+ EX_CATCH_HRESULT(hr);
+ IfFailRet(hr);
+ }
+
+ // That's all for now. We'll do lazy-evaluation for the array contents.
+
+ return hr;
+} // CordbArrayValue::Init
+
+// CordbArrayValue::GetThreadOwningMonitorLock
+// If a managed thread owns the monitor lock on this object then *ppThread
+// will point to that thread and S_OK will be returned. The thread object is valid
+// until the thread exits. *pAcquisitionCount will indicate the number of times
+// this thread would need to release the lock before it returns to being
+// unowned.
+// If no managed thread owns the monitor lock on this object then *ppThread
+// and pAcquisitionCount will be unchanged and S_FALSE returned.
+// If ppThread or pAcquisitionCount is not a valid pointer the result is
+// undefined.
+// If any error occurs such that it cannot be determined which, if any, thread
+// owns the monitor lock on this object then a failing HRESULT will be returned
+HRESULT CordbArrayValue::GetThreadOwningMonitorLock(ICorDebugThread **ppThread, DWORD *pAcquisitionCount)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ return CordbHeapValue3Impl::GetThreadOwningMonitorLock(GetProcess(),
+ GetValueHome()->GetAddress(), ppThread, pAcquisitionCount);
+}
+
+// CordbArrayValue::GetMonitorEventWaitList
+// Provides an ordered list of threads which are queued on the event associated
+// with a monitor lock. The first thread in the list is the first thread which
+// will be released by the next call to Monitor.Pulse, the next thread in the list
+// will be released on the following call, and so on.
+// If this list is non-empty S_OK will be returned, if it is empty S_FALSE
+// will be returned (the enumeration is still valid, just empty).
+// In either case the enumeration interface is only usable for the duration
+// of the current synchronized state, however the threads interfaces dispensed
+// from it are valid until the thread exits.
+// If ppThread is not a valid pointer the result is undefined.
+// If any error occurs such that it cannot be determined which, if any, threads
+// are waiting for the monitor then a failing HRESULT will be returned
+HRESULT CordbArrayValue::GetMonitorEventWaitList(ICorDebugThreadEnum **ppThreadEnum)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ return CordbHeapValue3Impl::GetMonitorEventWaitList(GetProcess(),
+ GetValueHome()->GetAddress(),
+ ppThreadEnum);
+}
+
+/* ------------------------------------------------------------------------- *
+ * Handle Value
+ * ------------------------------------------------------------------------- */
+// constructor
+// Arguments:
+// input:
+// pAppDomain - app domain to which the value belongs
+// pType - type information for the value
+// handleType - indicates whether we are constructing a strong or weak handle
+CordbHandleValue::CordbHandleValue(
+ CordbAppDomain * pAppdomain,
+ CordbType * pType, // The type of object that we create handle on
+ CorDebugHandleType handleType) // strong or weak handle
+ : CordbValue(pAppdomain, pType, NULL, false,
+ pAppdomain->GetSweepableExitNeuterList()
+ )
+{
+ m_vmHandle = VMPTR_OBJECTHANDLE::NullPtr();
+ m_fCanBeValid = TRUE;
+
+ m_handleType = handleType;
+ m_size = sizeof(void*);
+} // CordbHandleValue::CordbHandleValue
+
+//-----------------------------------------------------------------------------
+// Assign internal handle to the given value, and update pertinent counters
+//
+// Arguments:
+// handle - non-null CLR ObjectHandle that this CordbHandleValue will represent
+//
+// Notes:
+// Call code:CordbHandleValue::ClearHandle to clear the handle value.
+void CordbHandleValue::AssignHandle(VMPTR_OBJECTHANDLE handle)
+{
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+ _ASSERTE(m_vmHandle.IsNull());
+
+ // Use code:CordbHandleValue::ClearHandle to clear the handle value.
+ _ASSERTE(!handle.IsNull());
+
+ m_vmHandle = handle;
+ GetProcess()->IncrementOutstandingHandles();
+}
+
+//-----------------------------------------------------------------------------
+// Clear the handle value
+//
+// Assumptions:
+// Caller only clears if not already cleared.
+//
+// Notes:
+// This is the inverse of code:CordbHandleValue::AssignHandle
+void CordbHandleValue::ClearHandle()
+{
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+ _ASSERTE(!m_vmHandle.IsNull());
+
+ m_vmHandle = VMPTR_OBJECTHANDLE::NullPtr();
+ GetProcess()->DecrementOutstandingHandles();
+}
+
+// initialize a new instance of CordbHandleValue
+// Arguments:
+// input: pHandle - non-null CLR ObjectHandle that this CordbHandleValue will represent
+// Return Value: S_OK on success or CORDBG_E_TARGET_INCONSISTENT, E_INVALIDARG, read process memory errors.
+HRESULT CordbHandleValue::Init(VMPTR_OBJECTHANDLE pHandle)
+{
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+ HRESULT hr = S_OK;
+
+ {
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+ // If it is a strong handle, m_pHandle will not be NULL unless Dispose method is called.
+ // If it is a weak handle, m_pHandle can be NULL when Dispose is called.
+ AssignHandle(pHandle);
+ }
+
+ // This will init m_info.
+ IfFailRet(RefreshHandleValue());
+
+ // objRefBad is currently overloaded to mean that 1) the object ref is invalid, or 2) the object ref is NULL.
+ // NULL is clearly not a bad object reference, but in either case we have no more type data to work with,
+ // so don't attempt to assign more specific type information to the reference.
+ if (!m_info.objRefBad)
+ {
+ // We need to get the type info from the left side.
+ CordbType *newtype;
+
+ IfFailRet(CordbType::TypeDataToType(m_appdomain, &m_info.objTypeData, &newtype));
+
+ m_type.Assign(newtype);
+ }
+
+ return hr;
+} // CordbHandleValue::Init
+
+// destructor
+CordbHandleValue::~CordbHandleValue()
+{
+ DTOR_ENTRY(this);
+
+ _ASSERTE(IsNeutered());
+} // CordbHandleValue::~CordbHandleValue
+
+// Free left-side resources, mainly the GC handle keeping the object alive.
+void CordbHandleValue::NeuterLeftSideResources()
+{
+ Dispose();
+
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+ Neuter();
+} // CordbHandleValue::NeuterLeftSideResources
+
+// Neuter
+// Notes:
+// CordbHandleValue may hold Left-Side resources via the GC handle.
+// By the time we neuter it, those resources must have been freed,
+// either explicitly by calling code:CordbHandleValue::Dispose, or
+// implicitly by the left-side process exiting.
+void CordbHandleValue::Neuter()
+{
+ // CordbHandleValue is on the AppDomainExit neuter list.
+
+ // We should have cleaned up our Left-side resource by now (m_vmHandle
+ // should be null). If AppDomain / Process has already exited, then the LS
+ // already cleaned them up for us, and so we don't worry about them.
+ bool fAppDomainIsAlive = (m_appdomain != NULL && !m_appdomain->IsNeutered());
+ if (fAppDomainIsAlive)
+ {
+ BOOL fTargetIsDead = !GetProcess()->IsSafeToSendEvents() || GetProcess()->m_exiting;
+ if (!fTargetIsDead)
+ {
+ _ASSERTE(m_vmHandle.IsNull());
+ }
+ }
+
+ CordbValue::Neuter();
+} // CordbHandleValue::Neuter
+
+// Helper: Refresh the handle value object.
+// Gets information about the object to which the handle points.
+// Arguments: none
+// Return Value: S_OK on success, CORDBG_E_HANDLE_HAS_BEEN_DISPOSED, CORDBG_E_BAD_REFERENCE_VALUE,
+// errors from read process memory.
+HRESULT CordbHandleValue::RefreshHandleValue()
+{
+ INTERNAL_SYNC_API_ENTRY(this->GetProcess()); //
+ _ASSERTE(m_appdomain != NULL);
+ _ASSERTE(!m_appdomain->IsNeutered());
+
+ // If Dispose has been called, don't bother to refresh handle value.
+ if (m_vmHandle.IsNull())
+ {
+ return CORDBG_E_HANDLE_HAS_BEEN_DISPOSED;
+ }
+
+ // If weak handle and the object was dead, no point to refresh the handle value
+ if (m_fCanBeValid == FALSE)
+ {
+ return CORDBG_E_BAD_REFERENCE_VALUE;
+ }
+
+ HRESULT hr = S_OK;
+ CorElementType type = m_type->m_elementType;
+
+ _ASSERTE((m_pProcess != NULL));
+
+ _ASSERTE (type != ELEMENT_TYPE_GENERICINST);
+ _ASSERTE (type != ELEMENT_TYPE_VAR);
+ _ASSERTE (type != ELEMENT_TYPE_MVAR);
+
+ CordbProcess * pProcess = GetProcess();
+ void * objectAddress = NULL;
+ CORDB_ADDRESS objectHandle = 0;
+
+ EX_TRY
+ {
+ objectHandle = pProcess->GetDAC()->GetHandleAddressFromVmHandle(m_vmHandle);
+ if (type != ELEMENT_TYPE_TYPEDBYREF)
+ {
+ pProcess->SafeReadBuffer(TargetBuffer(objectHandle, sizeof(void *)), (BYTE *)&objectAddress);
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ IfFailRet(hr);
+ EX_TRY
+ {
+ if (type == ELEMENT_TYPE_TYPEDBYREF)
+ {
+ CordbReferenceValue::GetTypedByRefData(pProcess,
+ objectHandle,
+ type,
+ m_appdomain->GetADToken(),
+ &m_info);
+ }
+ else
+ {
+ CordbReferenceValue::GetObjectData(pProcess,
+ objectAddress,
+ type,
+ m_appdomain->GetADToken(),
+ &m_info);
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ IfFailRet(hr);
+
+ // If reference is already gone bad or reference is NULL,
+ // don't bother to refetch in the future.
+ //
+ if ((m_info.objRefBad) || (m_info.objRef == NULL))
+ {
+ m_fCanBeValid = FALSE;
+ }
+
+ return hr;
+}
+ // CordbHandleValue::RefreshHandleValue
+
+HRESULT CordbHandleValue::QueryInterface(REFIID id, void **pInterface)
+{
+ VALIDATE_POINTER_TO_OBJECT(pInterface, void **);
+
+ if (id == IID_ICorDebugValue)
+ {
+ *pInterface = static_cast<ICorDebugValue*>(this);
+ }
+ else if (id == IID_ICorDebugValue2)
+ {
+ *pInterface = static_cast<ICorDebugValue2*>(this);
+ }
+ else if (id == IID_ICorDebugValue3)
+ {
+ *pInterface = static_cast<ICorDebugValue3*>(this);
+ }
+ else if (id == IID_ICorDebugReferenceValue)
+ {
+ *pInterface = static_cast<ICorDebugReferenceValue*>(this);
+ }
+ else if (id == IID_ICorDebugHandleValue)
+ {
+ *pInterface = static_cast<ICorDebugHandleValue*>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugHandleValue*>(this));
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+} // CordbHandleValue::QueryInterface
+
+
+// return handle type. Currently we have strong and weak.
+// Arguments:
+// output: pType - the handle type unless pType is null
+// Return Value: S_OK on success or E_INVALIDARG or CORDBG_E_HANDLE_HAS_BEEN_DISPOSED on failure
+HRESULT CordbHandleValue::GetHandleType(CorDebugHandleType *pType)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(pType, CorDebugHandleType *);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ _ASSERTE(m_appdomain != NULL);
+ _ASSERTE(!m_appdomain->IsNeutered());
+
+ if (m_vmHandle.IsNull())
+ {
+ // handle has been disposed!
+ return CORDBG_E_HANDLE_HAS_BEEN_DISPOSED;
+ }
+ *pType = m_handleType;
+ return S_OK;
+} // CordbHandleValue::GetHandleType
+
+// Dispose will cause handle to be recycled.
+// Arguments: none
+// Return Value: S_OK on success, CORDBG_E_HANDLE_HAS_BEEN_DISPOSED or errors from the
+// DB_IPCE_DISPOSE_HANDLE event
+
+// @dbgtodo Microsoft inspection: remove the dispose handle hresults when the IPC events are eliminated
+HRESULT CordbHandleValue::Dispose()
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ _ASSERTE(m_appdomain != NULL);
+ _ASSERTE(!m_appdomain->IsNeutered());
+
+ HRESULT hr = S_OK;
+ DebuggerIPCEvent event;
+ CordbProcess *process;
+
+ process = GetProcess();
+
+ // Process should still be alive because it would have neutered us if it became invalid.
+ _ASSERTE(process != NULL);
+
+ VMPTR_OBJECTHANDLE vmObjHandle = VMPTR_OBJECTHANDLE::NullPtr();
+ {
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+ if (m_vmHandle.IsNull())
+ {
+ // handle has been disposed!
+ return CORDBG_E_HANDLE_HAS_BEEN_DISPOSED;
+ }
+
+ vmObjHandle = m_vmHandle;
+ ClearHandle(); // set m_pHandle to null.
+
+ if (process->m_exiting)
+ {
+ // process is exiting. Don't do anything
+ return S_OK;
+ }
+ }
+
+ // recycle the handle to EE
+ process->InitIPCEvent(&event,
+ DB_IPCE_DISPOSE_HANDLE,
+ false,
+ m_appdomain->GetADToken());
+
+ event.DisposeHandle.vmObjectHandle = vmObjHandle;
+ if (m_handleType == HANDLE_STRONG)
+ {
+ event.DisposeHandle.fStrong = TRUE;
+ }
+ else
+ {
+ event.DisposeHandle.fStrong = FALSE;
+ }
+
+ // Note: one-way event here...
+ hr = process->SendIPCEvent(&event, sizeof(DebuggerIPCEvent));
+
+ hr = WORST_HR(hr, event.hr);
+
+ return hr;
+} // CordbHandleValue::Dispose
+
+// get the type of the object to which the handle points
+// Arguments:
+// output: pType - the object type on success
+// Return Value: S_OK on success, CORDBG_E_HANDLE_HAS_BEEN_DISPOSED, CORDBG_E_CLASS_NOT_LOADED or synchronization errors on
+// failure
+HRESULT CordbHandleValue::GetType(CorElementType *pType)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(pType, CorElementType *);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ _ASSERTE(m_appdomain != NULL);
+ _ASSERTE(!m_appdomain->IsNeutered());
+
+ HRESULT hr = S_OK;
+
+ if (m_vmHandle.IsNull())
+ {
+ return CORDBG_E_HANDLE_HAS_BEEN_DISPOSED;
+ }
+
+ bool isBoxedVCObject = false;
+ if ((m_type->m_pClass != NULL) && (m_type->m_elementType != ELEMENT_TYPE_STRING))
+ {
+ EX_TRY
+ {
+ isBoxedVCObject = m_type->m_pClass->IsValueClass();
+ }
+ EX_CATCH_HRESULT(hr);
+ if (FAILED(hr))
+ return hr;
+ }
+
+ if (isBoxedVCObject)
+ {
+ // if we create the handle to a boxed value type, then the type is
+ // E_T_CLASS. m_type is the underlying value type. That is incorrect to
+ // return.
+ //
+ *pType = ELEMENT_TYPE_CLASS;
+ return S_OK;
+ }
+
+ return m_type->GetType(pType);
+} // CordbHandleValue::GetType
+
+// get the size of the handle-- this will always return the size of the handle itself (just pointer size), so
+// it's not particularly interesting.
+// Arguments:
+// output: pSize - the size of the handle (on success). This must be non-null. Memory management belongs
+// to the caller.
+// Return Value: S_OK on success, E_INVALIDARG (if pSize is null), or CORDBG_E_HANDLE_HAS_BEEN_DISPOSED on failure
+HRESULT CordbHandleValue::GetSize(ULONG32 *pSize)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(pSize, ULONG32 *);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ _ASSERTE(m_appdomain != NULL);
+ _ASSERTE(!m_appdomain->IsNeutered());
+
+ if (m_vmHandle.IsNull())
+ {
+ return CORDBG_E_HANDLE_HAS_BEEN_DISPOSED;
+ }
+
+ if (m_size > ULONG_MAX)
+ {
+ *pSize = ULONG_MAX;
+ return (COR_E_OVERFLOW);
+ }
+
+ //return the size of reference
+ *pSize = (ULONG)m_size;
+ return S_OK;
+} // CordbHandleValue::GetSize
+
+// get the size of the handle-- this will always return the size of the handle itself (just pointer size), so
+// it's not particularly interesting.
+// Arguments:
+// output: pSize - the size of the handle (on success). This must be non-null. Memory management belongs
+// to the caller.
+// Return Value: S_OK on success, E_INVALIDARG (if pSize is null), or CORDBG_E_HANDLE_HAS_BEEN_DISPOSED on failure
+HRESULT CordbHandleValue::GetSize64(ULONG64 *pSize)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(pSize, ULONG64 *);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ _ASSERTE(m_appdomain != NULL);
+ _ASSERTE(!m_appdomain->IsNeutered());
+
+ if (m_vmHandle.IsNull())
+ {
+ return CORDBG_E_HANDLE_HAS_BEEN_DISPOSED;
+ }
+
+ //return the size of reference
+ *pSize = m_size;
+ return S_OK;
+} // CordbHandleValue::GetSize
+
+// Get the target address of the handle
+// Arguments:
+// output: pAddress - handle address on success. This must be non-null and memory is managed by the caller
+// Return Value: S_OK on success or CORDBG_E_HANDLE_HAS_BEEN_DISPOSED or E_INVALIDARG on failure
+HRESULT CordbHandleValue::GetAddress(CORDB_ADDRESS *pAddress)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(pAddress, CORDB_ADDRESS *);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ _ASSERTE(m_appdomain != NULL);
+ _ASSERTE(!m_appdomain->IsNeutered());
+
+ if (m_vmHandle.IsNull())
+ {
+ return CORDBG_E_HANDLE_HAS_BEEN_DISPOSED;
+ }
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ *pAddress = GetProcess()->GetDAC()->GetHandleAddressFromVmHandle(m_vmHandle);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+} // CordbHandleValue::GetAddress
+
+HRESULT CordbHandleValue::CreateBreakpoint(ICorDebugValueBreakpoint **ppBreakpoint)
+{
+ return E_NOTIMPL;
+} // CreateBreakpoint
+
+// indicates whether a handle is null
+// Arguments:
+// output: pbNull - true iff the handle is null and pbNull is non-null.Memory is managed by the caller
+// Return Value: S_OK on success or CORDBG_E_HANDLE_HAS_BEEN_DISPOSED or E_INVALIDARG, CORDBG_E_BAD_REFERENCE_VALUE,
+// errors from read process memory.
+HRESULT CordbHandleValue::IsNull(BOOL *pbNull)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(pbNull, BOOL *);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ _ASSERTE(m_appdomain != NULL);
+ _ASSERTE(!m_appdomain->IsNeutered());
+
+ HRESULT hr = S_OK;
+
+ *pbNull = FALSE;
+
+ if (m_vmHandle.IsNull())
+ {
+ return CORDBG_E_HANDLE_HAS_BEEN_DISPOSED;
+ }
+
+
+ // Only return true if handle is long weak handle and is disposed.
+ if (m_handleType == HANDLE_WEAK_TRACK_RESURRECTION)
+ {
+ hr = RefreshHandleValue();
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ if (m_info.objRef == NULL)
+ {
+ *pbNull = TRUE;
+ }
+ }
+ else if (m_info.objRef == NULL)
+ {
+ *pbNull = TRUE;
+ }
+
+ // strong handle always return false for IsNull
+
+ return S_OK;
+} // CordbHandleValue::IsNull
+
+// gets a copy of the value of the handle
+// Arguments:
+// output: pValue - handle { on success. This must be non-null and memory is managed by the caller
+// Return Value: S_OK on success or CORDBG_E_HANDLE_HAS_BEEN_DISPOSED or E_INVALIDARG, CORDBG_E_BAD_REFERENCE_VALUE,
+// errors from read process memory.
+HRESULT CordbHandleValue::GetValue(CORDB_ADDRESS *pValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(pValue, CORDB_ADDRESS *);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ _ASSERTE(m_appdomain != NULL);
+ _ASSERTE(!m_appdomain->IsNeutered());
+
+ if (m_vmHandle.IsNull())
+ {
+ return CORDBG_E_HANDLE_HAS_BEEN_DISPOSED;
+ }
+
+ RefreshHandleValue();
+ *pValue = PTR_TO_CORDB_ADDRESS(m_info.objRef);
+ return S_OK;
+} // CordbHandleValue::GetValue
+
+HRESULT CordbHandleValue::SetValue(CORDB_ADDRESS value)
+{
+ // do not support SetValue on Handle
+ return E_FAIL;
+} // CordbHandleValue::GetValue
+
+// get an ICDValue to represent the object to which the handle refers
+// Arguments:
+// output: ppValue - pointer to the ICDValue for the handle referent as long as ppValue is non-null
+// Return Value: S_OK on success or CORDBG_E_HANDLE_HAS_BEEN_DISPOSED or E_INVALIDARG, CORDBG_E_BAD_REFERENCE_VALUE,
+// errors from read process memory.
+HRESULT CordbHandleValue::Dereference(ICorDebugValue **ppValue)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ _ASSERTE(m_appdomain != NULL);
+ _ASSERTE(!m_appdomain->IsNeutered());
+
+ *ppValue = NULL;
+
+ if (m_vmHandle.IsNull())
+ {
+ return CORDBG_E_HANDLE_HAS_BEEN_DISPOSED;
+ }
+
+ hr = RefreshHandleValue();
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ if ((m_info.objRefBad) || (m_info.objRef == NULL))
+ {
+ return CORDBG_E_BAD_REFERENCE_VALUE;
+ }
+
+ EX_TRY
+ {
+ hr = CordbReferenceValue::DereferenceCommon(m_appdomain,
+ m_type,
+ NULL, // don't support typed-by-refs
+ &m_info,
+ ppValue);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+} // CordbHandleValue::Dereference
+
+HRESULT CordbHandleValue::DereferenceStrong(ICorDebugValue **ppValue)
+{
+ return E_NOTIMPL;
+}
+
+// CordbHeapValue3Impl::GetThreadOwningMonitorLock
+// If a managed thread owns the monitor lock on this object then *ppThread
+// will point to that thread and S_OK will be returned. The thread object is valid
+// until the thread exits. *pAcquisitionCount will indicate the number of times
+// this thread would need to release the lock before it returns to being
+// unowned.
+// If no managed thread owns the monitor lock on this object then *ppThread
+// and pAcquisitionCount will be unchanged and S_FALSE returned.
+// If ppThread or pAcquisitionCount is not a valid pointer the result is
+// undefined.
+// If any error occurs such that it cannot be determined which, if any, thread
+// owns the monitor lock on this object then a failing HRESULT will be returned
+HRESULT CordbHeapValue3Impl::GetThreadOwningMonitorLock(CordbProcess* pProcess,
+ CORDB_ADDRESS remoteObjAddress,
+ ICorDebugThread **ppThread,
+ DWORD *pAcquisitionCount)
+{
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ IDacDbiInterface *pDac = pProcess->GetDAC();
+ VMPTR_Object vmObj = pDac->GetObject(remoteObjAddress);
+ MonitorLockInfo info = pDac->GetThreadOwningMonitorLock(vmObj);
+ if(info.acquisitionCount == 0)
+ {
+ // unowned
+ *ppThread = NULL;
+ *pAcquisitionCount = 0;
+ hr = S_FALSE;
+ }
+ else
+ {
+ RSLockHolder lockHolder(pProcess->GetProcessLock());
+ CordbThread* pThread = pProcess->LookupOrCreateThread(info.lockOwner);
+ pThread->QueryInterface(__uuidof(ICorDebugThread), (VOID**) ppThread);
+ *pAcquisitionCount = info.acquisitionCount;
+ hr = S_OK;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+// A small helper for CordbHeapValue3Impl::GetMonitorEventWaitList that adds each enumerated thread to an array
+// Arguments:
+// vmThread - The thread to add
+// puserData - the array to add it to
+VOID ThreadEnumerationCallback(VMPTR_Thread vmThread, VOID* pUserData)
+{
+ CQuickArrayList<VMPTR_Thread>* pThreadList = (CQuickArrayList<VMPTR_Thread>*) pUserData;
+ pThreadList->Push(vmThread);
+}
+
+// CordbHeapValue3Impl::GetMonitorEventWaitList
+// Provides an ordered list of threads which are queued on the event associated
+// with a monitor lock. The first thread in the list is the first thread which
+// will be released by the next call to Monitor.Pulse, the next thread in the list
+// will be released on the following call, and so on.
+// If this list is non-empty S_OK will be returned, if it is empty S_FALSE
+// will be returned (the enumeration is still valid, just empty).
+// In either case the enumeration interface is only usable for the duration
+// of the current synchronized state, however the threads interfaces dispensed
+// from it are valid until the thread exits.
+// If ppThread is not a valid pointer the result is undefined.
+// If any error occurs such that it cannot be determined which, if any, threads
+// are waiting for the monitor then a failing HRESULT will be returned
+HRESULT CordbHeapValue3Impl::GetMonitorEventWaitList(CordbProcess* pProcess,
+ CORDB_ADDRESS remoteObjAddress,
+ ICorDebugThreadEnum **ppThreadEnum)
+{
+ HRESULT hr = S_OK;
+ RSSmartPtr<CordbThread> *rsThreads = NULL;
+ EX_TRY
+ {
+ IDacDbiInterface *pDac = pProcess->GetDAC();
+ VMPTR_Object vmObj = pDac->GetObject(remoteObjAddress);
+ CQuickArrayList<VMPTR_Thread> threads;
+ pDac->EnumerateMonitorEventWaitList(vmObj,
+ (IDacDbiInterface::FP_THREAD_ENUMERATION_CALLBACK)ThreadEnumerationCallback, (VOID*)&threads);
+
+ rsThreads = new RSSmartPtr<CordbThread>[threads.Size()];
+ {
+ RSLockHolder lockHolder(pProcess->GetProcessLock());
+ for(DWORD i = 0; i < threads.Size(); i++)
+ {
+ rsThreads[i].Assign(pProcess->LookupOrCreateThread(threads[i]));
+ }
+ }
+
+ CordbThreadEnumerator* threadEnum =
+ new CordbThreadEnumerator(pProcess, rsThreads, (DWORD)threads.Size());
+ pProcess->GetContinueNeuterList()->Add(pProcess, threadEnum);
+ threadEnum->QueryInterface(__uuidof(ICorDebugThreadEnum), (VOID**)ppThreadEnum);
+ if(threads.Size() == 0)
+ {
+ hr = S_FALSE;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ delete [] rsThreads;
+ return hr;
+}
diff --git a/src/debug/di/eventchannel.h b/src/debug/di/eventchannel.h
new file mode 100644
index 0000000000..5a4ff23ea8
--- /dev/null
+++ b/src/debug/di/eventchannel.h
@@ -0,0 +1,264 @@
+// 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.
+//*****************************************************************************
+// EventChannel.h
+//
+
+//
+// This file contains the old-style event channel interface.
+//*****************************************************************************
+
+
+#ifndef _EVENT_CHANNEL_H_
+#define _EVENT_CHANNEL_H_
+
+//---------------------------------------------------------------------------------------
+//
+// This is the abstract base class for the old-style "IPC" event channel. (Despite the name, these events are
+// no longer transmitted in an IPC shared memory block.) The event channel owns the DebuggerIPCControlBlock.
+//
+// Assumptions:
+// This class is NOT thread-safe. Caller is assumed to have taken the appropriate measures for
+// synchronization.
+//
+// Notes:
+// In Whidbey, both LS-to-RS and RS-to-LS communication are done by IPC shared memory block. We allocate
+// a DebuggerIPCControlBlock (DCB) on the IPC shared memory block. The DCB contains both a send buffer
+// and a receive buffer (from the perspective of the LS, e.g. the send buffer is for LS-to-RS communication).
+//
+// In the new architecture, LS-to-RS communication is mostly done by raising an exception on the LS and
+// calling code:INativeEventPipeline::WaitForDebugEvent on the RS. This communication is handled by
+// code:INativeEventPipeline. RS-to-LS communication is mostly done by calling into the code:IDacDbiInterface,
+// which on Windows is just a structured way to do ReadProcessMemory().
+//
+// There are still cases where we are sending IPC events in not-yet-DACized code. There are two main
+// categories:
+//
+// 1) There are three types of events which the RS can send to the LS:
+// a) asynchronous: the RS can just send the event and continue
+// b) synchronous, but no reply: the RS must wait for an acknowledgement, but there is no reply
+// c) synchronous, reply required: the RS must wait for an acknowledgement before it can get the reply
+//
+// For (c), the RS sends a synchronous IPC event to the LS and wait for a reply. The reply is returned
+// in the same buffer space used to send the event, i.e. in the receive buffer.
+// - RS: code:CordbRCEventThread::SendIPCEvent
+// - LS: code:DebuggerRCThread::SendIPCReply
+//
+// 2) In the case where the information from the LS has a variable size (and so we are not sure if it will
+// fit in one event), the RS sends an asynchronous IPC event to the LS and wait for one or more
+// events from the LS. The events from the LS are actually sent using the native pipeline. This is
+// somewhat tricky because we need to make sure the event from the native pipeline is passed along to
+// the thread which is waiting for the IPC events from the LS. (For more information, see how we use
+// code:CordbProcess::m_leftSideEventAvailable and code:CordbProcess::m_leftSideEventRead). Currently,
+// the only place where we use send IPC events this way is in the inspection code used to check the
+// results from the DAC against the results from the IPC events.
+// - RS: code:Cordb::WaitForIPCEventFromProcess
+// - LS: code:DebuggerRCThread::SendIPCEvent
+//
+// In a sense, you can think of the LS and the RS sharing 3 channels: one for debug events (see
+// code:INativeEventPipeline), one for DDI calls (see code:IDacDbiInterface),
+// and one for "IPC" events. This is the interface for the "IPC" events.
+//
+
+class IEventChannel
+{
+public:
+
+ //
+ // Inititalize the event channel.
+ //
+ // Arguments:
+ // hTargetProc - the handle of the debuggee process
+ //
+ // Return Value:
+ // S_OK if successful
+ //
+ // Notes:
+ // For Mac debugging, the handle is not necessary.
+ //
+
+ virtual HRESULT Init(HANDLE hTargetProc) = 0;
+
+ //
+ // Called when the debugger is detaching. Depending on the implementation, this may be necessary to
+ // make sure the debuggee state is reset in case another debugger attaches to it.
+ //
+ // Notes:
+ // This is currently a nop on for Mac debugging.
+ //
+
+ virtual void Detach() = 0;
+
+ //
+ // Delete the event channel and clean up all the resources it owns. This function can only be called once.
+ //
+
+ virtual void Delete() = 0;
+
+ //
+ // Update a single field with a value stored in the RS copy of the DCB. We can't update the entire LS DCB
+ // because in some cases, the LS and RS are simultaneously initializing the DCB. If we initialize a field on
+ // the RS and write back the whole thing, we may overwrite something the LS has initialized in the interim.
+ //
+ // Arguments:
+ // rsFieldAddr - the address of the field in the RS copy of the DCB that we want to write back to
+ // the LS DCB. We use this to compute the offset of the field from the beginning of the
+ // DCB and then add this offset to the starting address of the LS DCB to get the LS
+ // address of the field we are updating
+ // size - the size of the field we're updating.
+ //
+ // Return Value:
+ // S_OK if successful, otherwise whatever failure HR returned by the actual write operation
+ //
+
+ virtual HRESULT UpdateLeftSideDCBField(void * rsFieldAddr, SIZE_T size) = 0;
+
+ //
+ // Update the entire RS copy of the debugger control block by reading the LS copy. The RS copy is treated as
+ // a throw-away temporary buffer, rather than a true cache. That is, we make no assumptions about the
+ // validity of the information over time. Thus, before using any of the values, we need to update it. We
+ // update everything for simplicity; any perf hit we take by doing this instead of updating the individual
+ // fields we want at any given point isn't significant, particularly if we are updating multiple fields.
+ //
+ // Return Value:
+ // S_OK if successful, otherwise whatever failure HR returned by the actual read operation
+ //
+
+ virtual HRESULT UpdateRightSideDCB() = 0;
+
+ //
+ // Get the pointer to the RS DCB. The LS copy isn't updated until UpdateLeftSideDCBField() is called.
+ // Note that the DCB is owned by the event channel.
+ //
+ // Return Value:
+ // Return a pointer to the RS DCB. The memory is owned by the event channel.
+ //
+
+ virtual DebuggerIPCControlBlock * GetDCB() = 0;
+
+ //
+ // Check whether we need to wait for an acknowledgement from the LS after sending an IPC event.
+ // If so, wait for GetRightSideEventAckHandle().
+ //
+ // Arguments:
+ // pEvent - the IPC event which has just been sent to the LS
+ //
+ // Return Value:
+ // TRUE if an acknowledgement is required (see the comment for this class for more information)
+ //
+
+ virtual BOOL NeedToWaitForAck(DebuggerIPCEvent * pEvent) = 0;
+
+ //
+ // Get a handle to wait on after sending an IPC event to the LS. The caller should call NeedToWaitForAck()
+ // first to see if it is necessary to wait for an acknowledgement.
+ //
+ // Return Value:
+ // a handle to a Win32 event which will be signaled when the LS acknowledges the receipt of the IPC event
+ //
+ // Assumptions:
+ // NeedToWaitForAck() returns true after sending an IPC event to the LS
+ //
+
+ virtual HANDLE GetRightSideEventAckHandle() = 0;
+
+ //
+ // After sending an event to the LS and determining that we need to wait for the LS's acknowledgement,
+ // if any failure occurs, the LS may not have reset the Win32 event which is signaled when an event is
+ // available on the RS (i.e. what's called the Right-Side-Event-Available (RSEA) event). This function
+ // should be called if any failure occurs to make sure our state is consistent.
+ //
+
+ virtual void ClearEventForLeftSide() = 0;
+
+ //
+ // Send an IPC event to the LS. The caller should call NeedToWaitForAck() to check if it needs to wait
+ // for an acknowledgement, and wait on GetRightSideEventAckHandle() if necessary.
+ //
+ // Arguments:
+ // pEvent - the IPC event to be sent over to the LS
+ // eventSize - the size of the IPC event; cannot be bigger than CorDBIPC_BUFFER_SIZE
+ //
+ // Return Value:
+ // S_OK if successful
+ //
+ // Notes:
+ // This function returns a failure HR for recoverable errors. It throws on unrecoverable errors.
+ //
+
+ virtual HRESULT SendEventToLeftSide(DebuggerIPCEvent * pEvent, SIZE_T eventSize) = 0;
+
+ //
+ // Get the reply from the LS for a previously sent IPC event. The caller must have waited on
+ // GetRightSdieEventAckHandle().
+ //
+ // Arguments:
+ // pReplyEvent - buffer for the replyl event
+ // eventSize - size of the buffer
+ //
+ // Return Value:
+ // S_OK if successful
+ //
+
+ virtual HRESULT GetReplyFromLeftSide(DebuggerIPCEvent * pReplyEvent, SIZE_T eventSize) = 0;
+
+ //
+ // This function and GetEventFromLeftSide() are for the second category of IPC events described in the
+ // class header above, i.e. for events which take more than one IPC event to reply. The event actually
+ // doesn't come from the IPC channel. Instead, it comes from the native pipeline. We need to save the
+ // event from the native pipeline and then wake up the thread which is waiting for this event. Then the
+ // thread can call GetEventFromLeftSide() to receive this event.
+ //
+ // Arguments:
+ // pEventFromLeftSide - IPC event from the LS
+ //
+ // Return Value:
+ // S_OK if successful, E_FAIL if an event has already been saved
+ //
+ // Assumptions:
+ // At any given time there should only be one event saved. The caller is responsible for the
+ // synchronization.
+ //
+
+ virtual HRESULT SaveEventFromLeftSide(DebuggerIPCEvent * pEventFromLeftSide) = 0;
+
+ //
+ // See the function header for SaveEventFromLeftSide.
+ //
+ // Arguments:
+ // pLocalManagedEvent - buffer to be filled with the IPC event from the LS
+ //
+ // Return Value:
+ // S_OK if successful
+ //
+ // Assumptions:
+ // At any given time there should only be one event saved. The caller is responsible for the
+ // synchronization.
+ //
+
+ virtual HRESULT GetEventFromLeftSide(DebuggerIPCEvent * pLocalManagedEvent) = 0;
+};
+
+//-----------------------------------------------------------------------------
+//
+// Allocate and return an old-style event channel object for this target platform.
+//
+// Arguments:
+// pLeftSideDCB - target address of the DCB on the LS
+// pMutableDataTarget - data target for reading from and writing to the target process's address space
+// dwProcessId - used for Mac debugging; specifies the target process ID
+// machineInfo - used for Mac debugging; specifies the machine and the port number of the proxy
+// ppEventChannel - out parament; returns the newly created event channel
+//
+// Return Value:
+// S_OK if successful
+//
+
+HRESULT NewEventChannelForThisPlatform(CORDB_ADDRESS pLeftSideDCB,
+ ICorDebugMutableDataTarget * pMutableDataTarget,
+ DWORD dwProcessId,
+ MachineInfo machineInfo,
+ IEventChannel ** ppEventChannel);
+
+#endif // _EVENT_CHANNEL_H_
diff --git a/src/debug/di/eventredirectionpipeline.cpp b/src/debug/di/eventredirectionpipeline.cpp
new file mode 100644
index 0000000000..23405d643a
--- /dev/null
+++ b/src/debug/di/eventredirectionpipeline.cpp
@@ -0,0 +1,350 @@
+// 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.
+//*****************************************************************************
+// File: EventRedirectionPipeline.cpp
+//
+
+//
+// Implement a native pipeline that redirects events.
+//*****************************************************************************
+
+#include "stdafx.h"
+#include "nativepipeline.h"
+#include "sstring.h"
+
+#if defined(ENABLE_EVENT_REDIRECTION_PIPELINE)
+#include "eventredirection.h"
+#include "eventredirectionpipeline.h"
+
+
+// Constructor
+EventRedirectionPipeline::EventRedirectionPipeline()
+{
+ m_pBlock = NULL;
+ m_dwProcessId = 0;
+
+ InitConfiguration();
+}
+
+// Dtor
+EventRedirectionPipeline::~EventRedirectionPipeline()
+{
+ CloseBlock();
+}
+
+// Call to free up the pipeline.
+void EventRedirectionPipeline::Delete()
+{
+ delete this;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Returns true if the Redirection is enabled.
+//
+// Arguments:
+// szOptions - specific Create/attach options to include in the overal format string
+// pidTarget - pid of real debuggeee.
+//
+// Return Value:
+// S_OK on success.
+//
+//
+// Notes:
+// This will spin up an auxillary debugger (windbg) and attach it to the existing
+// process. If this is a create case, then we're attaching to a create-suspended process.
+//
+//---------------------------------------------------------------------------------------
+void EventRedirectionPipeline::InitConfiguration()
+{
+ // We need some config strings. See header for possible values.
+ m_DebuggerCmd.Init_DontUse_(CLRConfig::EXTERNAL_DbgRedirectApplication);
+ m_AttachParams.Init_DontUse_(CLRConfig::EXTERNAL_DbgRedirectAttachCmd);
+ m_CreateParams.Init_DontUse_(CLRConfig::EXTERNAL_DbgRedirectCreateCmd);
+ m_CommonParams.Init_DontUse_(CLRConfig::EXTERNAL_DbgRedirectCommonCmd);
+}
+
+
+// Implement INativeEventPipeline::DebugSetProcessKillOnExit
+BOOL EventRedirectionPipeline::DebugSetProcessKillOnExit(bool fKillOnExit)
+{
+ // Not implemented for redirection pipeline. That's ok. Redirection pipeline doesn't care.
+ // Return success so caller can assert for other pipeline types.
+ return TRUE;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Attach a real debugger to the target.
+//
+// Arguments:
+// szOptions - specific Create/attach options to include in the overal format string
+// pidTarget - pid of real debuggeee.
+//
+// Return Value:
+// S_OK on success.
+//
+//
+// Notes:
+// This will spin up an auxillary debugger (windbg) and attach it to the existing
+// process. If this is a create case, then we're attaching to a create-suspended process.
+//
+//---------------------------------------------------------------------------------------
+HRESULT EventRedirectionPipeline::AttachDebuggerToTarget(LPCWSTR szOptions, DWORD pidTarget)
+{
+ SString s;
+
+ BOOL fRemap = false;
+
+ LPCWSTR lpApplicationName = NULL;
+ LPCWSTR lpCommandLine = NULL;
+
+ EX_TRY
+ {
+ m_pBlock = new (nothrow) RedirectionBlock(); // $$ make throwing
+
+ ZeroMemory(m_pBlock, sizeof(RedirectionBlock));
+
+ // Initialize
+ m_pBlock->m_versionCookie = EVENT_REDIRECTION_CURRENT_VERSION;
+
+ s.Printf(m_CommonParams.Value(), GetCurrentProcessId(), m_pBlock, szOptions, pidTarget);
+ lpCommandLine = s.GetUnicode();
+
+
+ lpApplicationName = m_DebuggerCmd.Value(); // eg, something like L"c:\\debuggers_amd64\\windbg.exe";
+
+ // Initialize events.
+ const BOOL kManualResetEvent = TRUE;
+ const BOOL kAutoResetEvent = FALSE;
+
+ m_pBlock->m_hEventAvailable = WszCreateEvent(NULL, kAutoResetEvent, FALSE, NULL);
+ m_pBlock->m_hEventConsumed = WszCreateEvent(NULL, kAutoResetEvent, FALSE, NULL);
+
+ m_pBlock->m_hDetachEvent = WszCreateEvent(NULL, kManualResetEvent, FALSE, NULL);
+
+ fRemap = true;
+ }EX_CATCH {}
+ EX_END_CATCH(RethrowTerminalExceptions);
+
+ if (!fRemap)
+ {
+ return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
+ }
+
+ STARTUPINFO startupInfo = {0};
+ startupInfo.cb = sizeof (STARTUPINFOW);
+
+ PROCESS_INFORMATION procInfo = {0};
+
+ // Now create the debugger
+ BOOL fStatus = WszCreateProcess(
+ lpApplicationName,
+ lpCommandLine,
+ NULL,
+ NULL,
+ FALSE,
+ 0, // flags
+ NULL,
+ NULL,
+ &startupInfo,
+ &procInfo);
+
+ if (!fStatus)
+ {
+ return HRESULT_FROM_GetLastError();
+ }
+
+ CloseHandle(procInfo.hProcess);
+ CloseHandle(procInfo.hThread);
+
+ return S_OK;
+
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Close the event block
+//
+// Notes:
+// This can be called multiple times.
+//---------------------------------------------------------------------------------------
+void EventRedirectionPipeline::CloseBlock()
+{
+ if (m_pBlock == NULL)
+ {
+ return;
+ }
+
+ // Close our handle to the IPC events. When server closes its handles, OS will free the events.
+
+ // Setting the detach event signals the Server that this block is closing.
+ if (m_pBlock->m_hDetachEvent != NULL)
+ {
+ SetEvent(m_pBlock->m_hDetachEvent);
+ CloseHandle(m_pBlock->m_hDetachEvent);
+ }
+
+ if (m_pBlock->m_hEventAvailable != NULL)
+ {
+ CloseHandle(m_pBlock->m_hEventAvailable);
+ }
+
+ if (m_pBlock->m_hEventConsumed != NULL)
+ {
+ CloseHandle(m_pBlock->m_hEventConsumed);
+ }
+
+ delete m_pBlock;
+ m_pBlock = NULL;
+}
+
+
+// Wait for a debug event
+BOOL EventRedirectionPipeline::WaitForDebugEvent(DEBUG_EVENT * pEvent, DWORD dwTimeout, CordbProcess * pProcess)
+{
+ // Get debug event via Redirection from control block
+ DWORD res = WaitForSingleObject(m_pBlock->m_hEventAvailable, dwTimeout);
+ if (res == WAIT_TIMEOUT)
+ {
+ // No event is available.
+ return FALSE;
+ }
+
+
+ pEvent->dwDebugEventCode = EXCEPTION_DEBUG_EVENT;
+ pEvent->dwProcessId = m_pBlock->m_dwProcessId;
+ pEvent->dwThreadId = m_pBlock->m_dwThreadId;
+ pEvent->u.Exception.dwFirstChance = m_pBlock->m_dwFirstChance;
+
+ _ASSERTE(sizeof(m_pBlock->m_record) == sizeof(pEvent->u.Exception.ExceptionRecord));
+ memcpy(&pEvent->u.Exception.ExceptionRecord, &m_pBlock->m_record, sizeof(m_pBlock->m_record));
+
+ // We've got an event!
+ return TRUE;
+}
+
+// Continue a debug event
+BOOL EventRedirectionPipeline::ContinueDebugEvent(
+ DWORD dwProcessId,
+ DWORD dwThreadId,
+ DWORD dwContinueStatus
+)
+{
+ m_pBlock->m_ContinuationStatus = dwContinueStatus;
+ m_pBlock->m_counterConsumed++;
+
+ // Sanity check the block. If these checks fail, then the block is corrupted (perhaps a issue in the
+ // extension dll feeding us the events?).
+
+
+ _ASSERTE(dwProcessId == m_pBlock->m_dwProcessId);
+ _ASSERTE(dwThreadId == m_pBlock->m_dwThreadId);
+ _ASSERTE(m_pBlock->m_counterAvailable == m_pBlock->m_counterConsumed);
+
+ SetEvent(m_pBlock->m_hEventConsumed);
+
+ return TRUE;
+}
+
+// Create
+HRESULT EventRedirectionPipeline::CreateProcessUnderDebugger(
+ MachineInfo machineInfo,
+ LPCWSTR lpApplicationName,
+ LPCWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ LPVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation)
+{
+ DWORD dwRealCreationFlags = dwCreationFlags;
+ dwRealCreationFlags |= CREATE_SUSPENDED;
+ dwRealCreationFlags &= ~(DEBUG_ONLY_THIS_PROCESS | DEBUG_PROCESS);
+
+ // We must create the real process so that startup info and process information are correct.
+ BOOL fStatus = WszCreateProcess(
+ lpApplicationName,
+ lpCommandLine,
+ lpProcessAttributes,
+ lpThreadAttributes,
+ bInheritHandles,
+ dwRealCreationFlags,
+ lpEnvironment,
+ lpCurrentDirectory,
+ lpStartupInfo,
+ lpProcessInformation);
+ if (!fStatus)
+ {
+ return HRESULT_FROM_GetLastError();
+ }
+
+ // Attach the real debugger.
+ AttachDebuggerToTarget(m_CreateParams.Value(), lpProcessInformation->dwProcessId);
+
+ m_dwProcessId = lpProcessInformation->dwProcessId;
+
+ return S_OK;
+}
+
+
+// Attach
+HRESULT EventRedirectionPipeline::DebugActiveProcess(MachineInfo machineInfo, DWORD processId)
+{
+ m_dwProcessId = processId;
+
+ // Use redirected pipeline
+ // Spin up debugger to attach to target.
+ return AttachDebuggerToTarget(m_AttachParams.Value(), processId);
+}
+
+// Detach
+HRESULT EventRedirectionPipeline::DebugActiveProcessStop(DWORD processId)
+{
+ // Use redirected pipeline
+ SetEvent(m_pBlock->m_hDetachEvent);
+ CloseBlock();
+
+ // Assume detach can't fail (true on WinXP and above)
+ return S_OK;
+}
+
+// Return a handle for the debuggee process.
+HANDLE EventRedirectionPipeline::GetProcessHandle()
+{
+ _ASSERTE(m_dwProcessId != 0);
+
+ return ::OpenProcess(PROCESS_DUP_HANDLE |
+ PROCESS_QUERY_INFORMATION |
+ PROCESS_TERMINATE |
+ PROCESS_VM_OPERATION |
+ PROCESS_VM_READ |
+ PROCESS_VM_WRITE |
+ SYNCHRONIZE,
+ FALSE,
+ m_dwProcessId);
+}
+
+// Terminate the debuggee process.
+BOOL EventRedirectionPipeline::TerminateProcess(UINT32 exitCode)
+{
+ _ASSERTE(m_dwProcessId != 0);
+
+ // Get a process handle for the process ID.
+ HANDLE hProc = OpenProcess(PROCESS_TERMINATE, FALSE, m_dwProcessId);
+
+ if (hProc == NULL)
+ {
+ return FALSE;
+ }
+
+ return ::TerminateProcess(hProc, exitCode);
+}
+
+#endif // ENABLE_EVENT_REDIRECTION_PIPELINE
+
+
diff --git a/src/debug/di/eventredirectionpipeline.h b/src/debug/di/eventredirectionpipeline.h
new file mode 100644
index 0000000000..87549c1150
--- /dev/null
+++ b/src/debug/di/eventredirectionpipeline.h
@@ -0,0 +1,145 @@
+// 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.
+//*****************************************************************************
+// EventRedirectionPipeline.h
+//
+
+//
+// defines native pipeline abstraction for debug-support
+// for event redirection.
+//*****************************************************************************
+
+#ifndef _EVENTREDIRECTION_PIPELINE_
+#define _EVENTREDIRECTION_PIPELINE_
+
+#include "nativepipeline.h"
+
+struct RedirectionBlock;
+//-----------------------------------------------------------------------------
+// For debugging purposes, helper class to allow native debug events to get
+// redirected through StrikeRS debugger extension. Only 1 OS debugger can be
+// attached to a process. This allows a debugger (such as Windbg) to attach directly
+// to the Left-side (and thus be used to debug the left-side). ICorDebug then does a
+// "virtual attach" through this pipeline.
+//
+// If this is a raw native attach, all calls go right through to the native pipeline.
+//-----------------------------------------------------------------------------
+class EventRedirectionPipeline :
+ public INativeEventPipeline
+{
+public:
+ EventRedirectionPipeline();
+ ~EventRedirectionPipeline();
+
+ // Returns null if redirection is not enabled, else returns a new redirection pipeline.
+
+ //
+ // Implementation of INativeEventPipeline
+ //
+
+ // Call to free up the pipeline.
+ virtual void Delete();
+
+ // Mark what to do with outstanding debuggees when event thread is killed.
+ virtual BOOL DebugSetProcessKillOnExit(bool fKillOnExit);
+
+ // Create
+ virtual HRESULT CreateProcessUnderDebugger(
+ MachineInfo machineInfo,
+ LPCWSTR lpApplicationName,
+ LPCWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ LPVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation);
+
+ // Attach
+ virtual HRESULT DebugActiveProcess(MachineInfo machineInfo, DWORD processId);
+
+ // Detach
+ virtual HRESULT DebugActiveProcessStop(DWORD processId);
+
+ // GEt a debug event
+ virtual BOOL WaitForDebugEvent(DEBUG_EVENT * pEvent, DWORD dwTimeout, CordbProcess * pProcess);
+
+ // Continue a debug event received from WaitForDebugEvent
+ virtual BOOL ContinueDebugEvent(
+ DWORD dwProcessId,
+ DWORD dwThreadId,
+ DWORD dwContinueStatus
+ );
+
+ // Return a handle for the debuggee process.
+ virtual HANDLE GetProcessHandle();
+
+ // Terminate the debuggee process.
+ virtual BOOL TerminateProcess(UINT32 exitCode);
+
+protected:
+
+ //
+ // Following us support for event-redirection.
+ //
+
+
+ // Rediretion block, or NULL if we're using the native pipeline.
+ RedirectionBlock * m_pBlock;
+
+ // Initialize configuration values.
+ void InitConfiguration();
+
+ HRESULT AttachDebuggerToTarget(LPCWSTR szOptions, DWORD pid);
+ void CloseBlock();
+
+ //
+ // Configuration information to launch the debugger.
+ // These are retrieved via the standard Config helpers.
+ //
+
+ // The debugger application to launch. eg:
+ // c:\debuggers_amd64\windbg.exe
+ ConfigStringHolder m_DebuggerCmd;
+
+ // The common format string for the command line.
+ // This will get the following printf args:
+ // int (%d or %x): this process's pid (the ICD Client)
+ // pointer (%p): the address of the control block (m_pBlock). The launched debugger will
+ // then use this to communicate with this process.
+ // extra format string (%s): args specific for either launch or attach
+ // target debuggee (%d or %x): pid of the debuggee.
+ // eg (for windbg):
+ // -c ".load C:\vbl\ClrDbg\ndp\clr\src\Tools\strikeRS\objc\amd64\strikeRS.dll; !watch %x %p" %s -p %d
+ ConfigStringHolder m_CommonParams;
+
+ // Command parameters for create case.
+ // Note that we must always physically call CreateProcess on the debuggee so that we get the proper out-parameters
+ // from create-processs (eg, target's handle, startup info, etc). So we always attach the auxillary debugger
+ // even in the create case. Use "-pr -pb" in Windbg to attach to a create-suspended process.
+ //
+ // Common Windbg options:
+ // -WX disable automatic workspace loading. This gaurantees the newly created windbg has a clean
+ // environment and is not tainted with settings that will break the extension dll.
+ // -pr option will tell real Debugger to resume main thread. This goes with the CREATE_SUSPENDED flag we passed to CreateProcess.
+ // -pb option is required when attaching to newly created suspended process. It tells the debugger
+ // to not create the break-in thread (which it can't do on a pre-initialized process).
+ // eg:
+ // "-WX -pb -pr"
+ ConfigStringHolder m_CreateParams;
+
+ // command parameters for attach. The WFDE server will send a loader breakpoint.
+ // eg:
+ // "-WX"
+ ConfigStringHolder m_AttachParams;
+
+ DWORD m_dwProcessId;
+};
+
+
+
+#endif // _EVENTREDIRECTION_PIPELINE_
+
diff --git a/src/debug/di/hash.cpp b/src/debug/di/hash.cpp
new file mode 100644
index 0000000000..554edaa308
--- /dev/null
+++ b/src/debug/di/hash.cpp
@@ -0,0 +1,638 @@
+// 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.
+//*****************************************************************************
+// File: hash.cpp
+//
+
+//
+//*****************************************************************************
+#include "stdafx.h"
+
+/* ------------------------------------------------------------------------- *
+ * Hash Table class
+ * ------------------------------------------------------------------------- */
+
+CordbHashTable::~CordbHashTable()
+{
+ HASHFIND find;
+
+ for (CordbHashEntry *entry = (CordbHashEntry *) FindFirstEntry(&find);
+ entry != NULL;
+ entry = (CordbHashEntry *) FindNextEntry(&find))
+ entry->pBase->InternalRelease();
+}
+
+HRESULT CordbHashTable::UnsafeAddBase(CordbBase *pBase)
+{
+ AssertIsProtected();
+ DbgIncChangeCount();
+
+ HRESULT hr = S_OK;
+
+ if (!m_initialized)
+ {
+ HRESULT res = NewInit(m_iBuckets, sizeof(CordbHashEntry), 0xffff);
+
+ if (res != S_OK)
+ {
+ return res;
+ }
+
+ m_initialized = true;
+ }
+
+ CordbHashEntry *entry = (CordbHashEntry *) Add(HASH((ULONG_PTR)pBase->m_id));
+
+ if (entry == NULL)
+ {
+ hr = E_FAIL;
+ }
+ else
+ {
+ entry->pBase = pBase;
+ m_count++;
+ pBase->InternalAddRef();
+ }
+ return hr;
+}
+bool CordbHashTable::IsInitialized()
+{
+ return m_initialized;
+}
+
+CordbBase *CordbHashTable::UnsafeGetBase(ULONG_PTR id, BOOL fFab)
+{
+ AssertIsProtected();
+
+ CordbHashEntry *entry = NULL;
+
+ if (!m_initialized)
+ return (NULL);
+
+ entry = (CordbHashEntry *) Find(HASH((ULONG_PTR)id), KEY(id));
+ return (entry ? entry->pBase : NULL);
+}
+
+HRESULT CordbHashTable::UnsafeSwapBase(CordbBase *pOldBase, CordbBase *pNewBase)
+{
+ if (!m_initialized)
+ return E_FAIL;
+
+ AssertIsProtected();
+ DbgIncChangeCount();
+
+ ULONG_PTR id = (ULONG_PTR)pOldBase->m_id;
+
+ CordbHashEntry *entry
+ = (CordbHashEntry *) Find(HASH((ULONG_PTR)id), KEY(id));
+
+ if (entry == NULL)
+ {
+ return E_FAIL;
+ }
+
+ _ASSERTE(entry->pBase == pOldBase);
+ entry->pBase = pNewBase;
+
+ // release the hash table's reference to the old base and transfer it
+ // to the new one.
+ pOldBase->InternalRelease();
+ pNewBase->InternalAddRef();
+
+ return S_OK;
+}
+
+
+CordbBase *CordbHashTable::UnsafeRemoveBase(ULONG_PTR id)
+{
+ AssertIsProtected();
+
+ if (!m_initialized)
+ return NULL;
+
+ DbgIncChangeCount();
+
+
+ CordbHashEntry *entry
+ = (CordbHashEntry *) Find(HASH((ULONG_PTR)id), KEY(id));
+
+ if (entry == NULL)
+ {
+ return NULL;
+ }
+
+ CordbBase *base = entry->pBase;
+
+ Delete(HASH((ULONG_PTR)id), (HASHENTRY *) entry);
+ m_count--;
+ base->InternalRelease();
+
+ return base;
+}
+
+CordbBase *CordbHashTable::UnsafeFindFirst(HASHFIND *find)
+{
+ AssertIsProtected();
+ return UnsafeUnlockedFindFirst(find);
+}
+
+CordbBase *CordbHashTable::UnsafeUnlockedFindFirst(HASHFIND *find)
+{
+ CordbHashEntry *entry = (CordbHashEntry *) FindFirstEntry(find);
+
+ if (entry == NULL)
+ return NULL;
+ else
+ return entry->pBase;
+}
+
+CordbBase *CordbHashTable::UnsafeFindNext(HASHFIND *find)
+{
+ AssertIsProtected();
+ return UnsafeUnlockedFindNext(find);
+}
+
+CordbBase *CordbHashTable::UnsafeUnlockedFindNext(HASHFIND *find)
+{
+ CordbHashEntry *entry = (CordbHashEntry *) FindNextEntry(find);
+
+ if (entry == NULL)
+ return NULL;
+ else
+ return entry->pBase;
+}
+
+/* ------------------------------------------------------------------------- *
+ * Hash Table Enumerator class
+ * ------------------------------------------------------------------------- */
+
+// This constructor is part of 2 phase construction.
+// Use the BuildOrThrow method to instantiate.
+CordbHashTableEnum::CordbHashTableEnum(
+ CordbBase * pOwnerObj, NeuterList * pOwnerList,
+ CordbHashTable *table,
+ REFIID guid
+)
+ : CordbBase(pOwnerObj->GetProcess(), 0, enumCordbHashTableEnum),
+ m_pOwnerObj(pOwnerObj),
+ m_pOwnerNeuterList(pOwnerList),
+ m_table(table),
+ m_started(false),
+ m_done(false),
+ m_guid(guid),
+ m_iCurElt(0),
+ m_count(0),
+ m_fCountInit(FALSE)
+{
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Build a new Hash enumerator or throw
+//
+// Arguments:
+// pOwnerObj - owner
+// pOwnerList - neuter list to add to
+// table - hash table to enumerate.
+// id - guid of objects to enumerate
+// pHolder - holder to get ownership.
+//
+void CordbHashTableEnum::BuildOrThrow(
+ CordbBase * pOwnerObj,
+ NeuterList * pOwnerList,
+ CordbHashTable *pTable,
+ const _GUID &id,
+ RSInitHolder<CordbHashTableEnum> * pHolder)
+{
+ CordbHashTableEnum * pEnum = new CordbHashTableEnum(pOwnerObj, pOwnerList, pTable, id);
+ pHolder->Assign(pEnum);
+
+ // If no neuter-list supplied, then our owner is manually managing us.
+ // It also means we can't be cloned.
+ if (pOwnerList != NULL)
+ {
+ pOwnerList->Add(pOwnerObj->GetProcess(), pEnum);
+ }
+
+#ifdef _DEBUG
+ pEnum->m_DbgChangeCount = pEnum->m_table->GetChangeCount();
+#endif
+}
+
+// Only for cloning.
+// Copy constructor makes life easy & fun!
+CordbHashTableEnum::CordbHashTableEnum(CordbHashTableEnum *cloneSrc)
+ : CordbBase(cloneSrc->m_pOwnerObj->GetProcess(), 0, enumCordbHashTableEnum),
+ m_pOwnerObj(cloneSrc->m_pOwnerObj),
+ m_pOwnerNeuterList(cloneSrc->m_pOwnerNeuterList),
+ m_started(cloneSrc->m_started),
+ m_done(cloneSrc->m_done),
+ m_hashfind(cloneSrc->m_hashfind),
+ m_guid(cloneSrc->m_guid),
+ m_iCurElt(cloneSrc->m_iCurElt),
+ m_count(cloneSrc->m_count),
+ m_fCountInit(cloneSrc->m_fCountInit)
+{
+ // We can get cloned at any time, so our owner can't manually control us,
+ // so we need explicit access to a neuter list.
+ _ASSERTE(m_pOwnerNeuterList != NULL);
+
+ m_table = cloneSrc->m_table;
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // Add to neuter list
+ if (m_pOwnerObj->GetProcess() != NULL)
+ {
+ // Normal case. For things enumerating stuff within a CordbProcess tree.
+ m_pOwnerNeuterList->Add(m_pOwnerObj->GetProcess(), this);
+ }
+ else
+ {
+ // For Process-list enums that have broken neuter semantics.
+ // @dbgtodo: this goes away once we remove the top-level ICorDebug interface,
+ // and thus no longer have a Process enumerator.
+ m_pOwnerNeuterList->UnsafeAdd(NULL, this);
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ SetUnrecoverableIfFailed(GetProcess(), hr);
+
+#ifdef _DEBUG
+ m_DbgChangeCount = cloneSrc->m_DbgChangeCount;
+#endif
+}
+
+CordbHashTableEnum::~CordbHashTableEnum()
+{
+ _ASSERTE(this->IsNeutered());
+
+ _ASSERTE(m_table == NULL);
+ _ASSERTE(m_pOwnerObj == NULL);
+ _ASSERTE(m_pOwnerNeuterList == NULL);
+}
+
+void CordbHashTableEnum::Neuter()
+{
+ m_table = NULL;
+ m_pOwnerObj = NULL;
+ m_pOwnerNeuterList = NULL;
+
+ CordbBase::Neuter();
+}
+
+
+HRESULT CordbHashTableEnum::Reset()
+{
+ HRESULT hr = S_OK;
+
+ m_started = false;
+ m_done = false;
+
+ return hr;
+}
+
+HRESULT CordbHashTableEnum::Clone(ICorDebugEnum **ppEnum)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ AssertValid();
+
+ VALIDATE_POINTER_TO_OBJECT(ppEnum, ICorDebugEnum **);
+
+ HRESULT hr;
+ hr = S_OK;
+
+ CordbHashTableEnum *e = NULL;
+
+ CordbProcess * pProc = GetProcess();
+
+ if (pProc != NULL)
+ {
+ // @todo - this is really ugly. This macro sets up stack-based dtor cleanup
+ // objects, and so it has to be in the same block of code as the code
+ // it protectes. Eg, we couldn't say: 'if (...) { ATT_ } { common code }'
+ // because the ATT_ stack based object would get destructed before the 'common code'
+ // was executed.
+ ATT_REQUIRE_STOPPED_MAY_FAIL(pProc);
+ e = new (nothrow) CordbHashTableEnum(this);
+ }
+ else
+ {
+ e = new (nothrow) CordbHashTableEnum(this);
+
+ }
+
+ if (e == NULL)
+ {
+ (*ppEnum) = NULL;
+ hr = E_OUTOFMEMORY;
+ goto LExit;
+ }
+
+ e->QueryInterface(m_guid, (void **) ppEnum);
+
+LExit:
+ return hr;
+}
+
+HRESULT CordbHashTableEnum::GetCount(ULONG *pcelt)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ AssertValid();
+
+ VALIDATE_POINTER_TO_OBJECT(pcelt, ULONG *);
+
+ *pcelt = m_table->GetCount();
+
+ return S_OK;
+}
+
+HRESULT CordbHashTableEnum::PrepForEnum(CordbBase **pBase)
+{
+ HRESULT hr = S_OK;
+
+ if (!m_started)
+ {
+ (*pBase) = m_table->UnsafeUnlockedFindFirst(&m_hashfind);
+ m_started = true;
+ }
+ else
+ {
+ (*pBase) = m_table->UnsafeUnlockedFindNext(&m_hashfind);
+ }
+
+ return hr;
+}
+
+HRESULT CordbHashTableEnum::SetupModuleEnum(void)
+{
+ return S_OK;
+}
+
+
+HRESULT CordbHashTableEnum::AdvancePreAssign(CordbBase **pBase)
+{
+ HRESULT hr = S_OK;
+ return hr;
+}
+
+HRESULT CordbHashTableEnum::AdvancePostAssign(CordbBase **pBase,
+ CordbBase **b,
+ CordbBase **bEnd)
+{
+ CordbBase *base;
+
+ if (pBase == NULL)
+ pBase = &base;
+
+ // If we're looping like normal, or we're in skip
+ if ( ((b < bEnd) || ((b ==bEnd)&&(b==NULL)))
+ )
+ {
+ (*pBase) = m_table->UnsafeUnlockedFindNext(&m_hashfind);
+ if (*pBase == NULL)
+ m_done = true;
+ }
+
+ return S_OK;
+}
+
+// This is an public function implementing all of the ICorDebugXXXEnum interfaces.
+HRESULT CordbHashTableEnum::Next(ULONG celt,
+ CordbBase *bases[],
+ ULONG *pceltFetched)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ AssertValid();
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(bases,
+ CordbBase *,
+ celt,
+ true,
+ true);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pceltFetched,
+ ULONG *);
+
+ if ((pceltFetched == NULL) && (celt != 1))
+ {
+ return E_INVALIDARG;
+ }
+
+ if (celt == 0)
+ {
+ if (pceltFetched != NULL)
+ {
+ *pceltFetched = 0;
+ }
+ return S_OK;
+ }
+
+ HRESULT hr = S_OK;
+ CordbBase *base = NULL;
+ CordbBase **b = bases;
+ CordbBase **bEnd = bases + celt;
+
+ hr = PrepForEnum(&base);
+ if (FAILED(hr))
+ {
+ goto LError;
+ }
+
+ while (b < bEnd && !m_done)
+ {
+ hr = AdvancePreAssign(&base);
+ if (FAILED(hr))
+ {
+ goto LError;
+ }
+
+ if (base == NULL)
+ {
+ m_done = true;
+ }
+ else
+ {
+ if (m_guid == IID_ICorDebugProcessEnum)
+ {
+ *b = (CordbBase*)(ICorDebugProcess*)(CordbProcess*)base;
+ }
+ else if (m_guid == IID_ICorDebugBreakpointEnum)
+ {
+ *b = (CordbBase*)(ICorDebugBreakpoint*)(CordbBreakpoint*)base;
+ }
+ else if (m_guid == IID_ICorDebugStepperEnum)
+ {
+ *b = (CordbBase*)(ICorDebugStepper*)(CordbStepper*)base;
+ }
+ else if (m_guid == IID_ICorDebugModuleEnum)
+ {
+ *b = (CordbBase*)(ICorDebugModule*)(CordbModule*)base;
+ }
+ else if (m_guid == IID_ICorDebugThreadEnum)
+ {
+ *b = (CordbBase*)(ICorDebugThread*)(CordbThread*)base;
+ }
+ else if (m_guid == IID_ICorDebugAppDomainEnum)
+ {
+ *b = (CordbBase*)(ICorDebugAppDomain*)(CordbAppDomain*)base;
+ }
+ else if (m_guid == IID_ICorDebugAssemblyEnum)
+ {
+ *b = (CordbBase*)(ICorDebugAssembly*)(CordbAssembly*)base;
+ }
+ else
+ {
+ *b = (CordbBase*)(IUnknown*)base;
+ }
+
+ if (*b)
+ {
+ // 'b' is not a valid CordbBase ptr.
+ base->ExternalAddRef();
+ b++;
+ }
+
+ hr = AdvancePostAssign(&base, b, bEnd);
+ if (FAILED(hr))
+ {
+ goto LError;
+ }
+ }
+ }
+
+LError:
+ //
+ // If celt == 1, then the pceltFetched parameter is optional.
+ //
+ if (pceltFetched != NULL)
+ {
+ *pceltFetched = (ULONG)(b - bases);
+ }
+
+ //
+ // If we reached the end of the enumeration, but not the end
+ // of the number of requested items, we return S_FALSE.
+ //
+ if (!FAILED(hr) && m_done && (b != bEnd))
+ {
+ hr = S_FALSE;
+ }
+
+ return hr;
+}
+
+HRESULT CordbHashTableEnum::Skip(ULONG celt)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ AssertValid();
+
+ HRESULT hr = S_OK;
+
+ CordbBase *base;
+
+ if (celt > 0)
+ {
+ if (!m_started)
+ {
+ base = m_table->UnsafeUnlockedFindFirst(&m_hashfind);
+
+ if (base == NULL)
+ m_done = true;
+ else
+ celt--;
+
+ m_started = true;
+ }
+
+ while (celt > 0 && !m_done)
+ {
+ base = m_table->UnsafeUnlockedFindNext(&m_hashfind);
+
+ if (base == NULL)
+ m_done = true;
+ else
+ celt--;
+ }
+ }
+
+ return hr;
+}
+
+HRESULT CordbHashTableEnum::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugEnum)
+ {
+ ExternalAddRef();
+ *pInterface = static_cast<ICorDebugEnum *>(static_cast<ICorDebugProcessEnum *>(this));
+
+ return S_OK;
+ }
+ if (id == IID_IUnknown)
+ {
+ ExternalAddRef();
+ *pInterface = static_cast<IUnknown *>(static_cast<ICorDebugProcessEnum *>(this));
+
+ return S_OK;
+ }
+ if (id == m_guid)
+ {
+ ExternalAddRef();
+
+ if (id == IID_ICorDebugProcessEnum)
+ *pInterface = static_cast<ICorDebugProcessEnum *>(this);
+ else if (id == IID_ICorDebugBreakpointEnum)
+ *pInterface = static_cast<ICorDebugBreakpointEnum *>(this);
+ else if (id == IID_ICorDebugStepperEnum)
+ *pInterface = static_cast<ICorDebugStepperEnum *>(this);
+ else if (id == IID_ICorDebugModuleEnum)
+ *pInterface = static_cast<ICorDebugModuleEnum *>(this);
+ else if (id == IID_ICorDebugThreadEnum)
+ *pInterface = static_cast<ICorDebugThreadEnum *>(this);
+ else if (id == IID_ICorDebugAppDomainEnum)
+ *pInterface = static_cast<ICorDebugAppDomainEnum *>(this);
+ else if (id == IID_ICorDebugAssemblyEnum)
+ *pInterface = static_cast<ICorDebugAssemblyEnum *>(this);
+
+ return S_OK;
+ }
+
+ return E_NOINTERFACE;
+}
+
+#ifdef _DEBUG
+void CordbHashTableEnum::AssertValid()
+{
+ // @todo - Our behavior is undefined when enumerating a collection that changes underneath us.
+ // We'd love to just call this situatation illegal, but clients could very reasonably taken a depedency
+ // on it. Various APIs (eg, ICDStepper::Deactivate) may remove items from the hash.
+ // So we need to figure out what the behavior is here, spec it, and then enforce that and enable the
+ // strongest asserts possible there.
+ //
+ // Specifically, we cannot check that the hash hasn't change from underneath us:
+ // CONSISTENCY_CHECK_MSGF(m_DbgChangeCount == m_table->GetChangeCount(),
+ // ("Underlying hashtable has changed while enumerating.\nOriginal stamp=%d\nNew stamp=%d\nThis enum=0x%p",
+ // m_DbgChangeCount, m_table->GetChangeCount(), this));
+}
+
+
+//
+void CordbHashTable::AssertIsProtected()
+{
+#ifdef RSCONTRACTS
+ if (m_pDbgLock != NULL)
+ {
+ DbgRSThread * pThread = DbgRSThread::GetThread();
+ if (pThread->IsInRS())
+ {
+ CONSISTENCY_CHECK_MSGF(m_pDbgLock->HasLock(), ("Hash table being accessed w/o holding '%s'", m_pDbgLock->Name()));
+ }
+ }
+#endif
+}
+#endif
diff --git a/src/debug/di/helpers.h b/src/debug/di/helpers.h
new file mode 100644
index 0000000000..3c6b73cdd7
--- /dev/null
+++ b/src/debug/di/helpers.h
@@ -0,0 +1,210 @@
+// 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.
+//*****************************************************************************
+// helpers.h
+//
+
+//
+// public helpers for debugger.
+//*****************************************************************************
+
+#ifndef _HELPERS_H
+#define _HELPERS_H
+
+//-----------------------------------------------------------------------------
+// Smartpointer for internal Addref/Release
+// Using Wrapper / Holder infrastructure from src\inc\Holder.h
+//-----------------------------------------------------------------------------
+template <typename TYPE>
+inline void HolderRSRelease(TYPE *value)
+{
+ _ASSERTE(value != NULL);
+ value->InternalRelease();
+}
+
+template <typename TYPE>
+inline void HolderRSAddRef(TYPE *value)
+{
+ _ASSERTE(value != NULL);
+ value->InternalAddRef();
+}
+
+// Smart ptrs for external refs. External refs are important
+// b/c they may keep an object alive.
+template <typename TYPE>
+inline void HolderRSReleaseExternal(TYPE *value)
+{
+ _ASSERTE(value != NULL);
+ value->Release();
+}
+
+template <typename TYPE>
+inline void HolderRSAddRefExternal(TYPE *value)
+{
+ _ASSERTE(value != NULL);
+ value->AddRef();
+}
+
+// The CordbBase::m_pProcess backpointer needs to adjust the external reference count, but manipulate it from
+// within the RS. This means we need to skip debugging checks that ensure
+// that the external count is only manipulated from outside the RS. Since we're
+// skipping these checks, we call this an "Unsafe" pointer.
+template <typename TYPE>
+inline void HolderRSUnsafeExtRelease(TYPE *value)
+{
+ _ASSERTE(value != NULL);
+ value->BaseRelease();
+}
+template <typename TYPE>
+inline void HolderRSUnsafeExtAddRef(TYPE *value)
+{
+ _ASSERTE(value != NULL);
+ value->BaseAddRef();
+}
+
+//-----------------------------------------------------------------------------
+// Base Smart pointer implementation.
+// This abstracts out the AddRef + Release methods.
+//-----------------------------------------------------------------------------
+template <typename TYPE, void (*ACQUIREF)(TYPE*), void (*RELEASEF)(TYPE*)>
+class BaseSmartPtr
+{
+public:
+ BaseSmartPtr () {
+ // Ensure that these smart-ptrs are really ptr-sized.
+ static_assert_no_msg(sizeof(*this) == sizeof(void*));
+ m_ptr = NULL;
+ }
+ explicit BaseSmartPtr (TYPE * ptr) : m_ptr(NULL) {
+ if (ptr != NULL)
+ {
+ RawAcquire(ptr);
+ }
+ }
+
+ ~BaseSmartPtr() {
+ Clear();
+ }
+
+ FORCEINLINE void Assign(TYPE * ptr)
+ {
+ // Do the AddRef before the release to avoid the release pinging 0 if we assign to ourself.
+ if (ptr != NULL)
+ {
+ ACQUIREF(ptr);
+ }
+ if (m_ptr != NULL)
+ {
+ RELEASEF(m_ptr);
+ }
+ m_ptr = ptr;
+ };
+
+ FORCEINLINE void Clear()
+ {
+ if (m_ptr != NULL)
+ {
+ RawRelease();
+ }
+ }
+
+ FORCEINLINE operator TYPE*() const
+ {
+ return m_ptr;
+ }
+
+ FORCEINLINE TYPE* GetValue() const
+ {
+ return m_ptr;
+ }
+
+ FORCEINLINE TYPE** operator & ()
+ {
+ // We allow getting the address so we can pass it in as an outparam.
+ // BTW/@TODO: this is a subtle and dangerous thing to do, since it easily leads to situations
+ // when pointer gets assigned without the ref counter being incremented.
+ // This can cause premature freeing of the object after the pointer dtor was called.
+
+ // But if we have a non-null m_Ptr, then it may get silently overwritten,
+ // and thus we'll lose the chance to call release on it.
+ // So we'll just avoid that pattern and assert to enforce it.
+ _ASSERTE(m_ptr == NULL);
+ return &m_ptr;
+ }
+
+ // For legacy purposes, some pre smart-pointer code needs to be able to get the
+ // address of the pointer. This is needed for RSPtrArray::GetAddrOfIndex.
+ FORCEINLINE TYPE** UnsafeGetAddr()
+ {
+ return &m_ptr;
+ }
+
+ FORCEINLINE TYPE* operator->()
+ {
+ return m_ptr;
+ }
+
+ FORCEINLINE int operator==(TYPE* p)
+ {
+ return (m_ptr == p);
+ }
+
+ FORCEINLINE int operator!= (TYPE* p)
+ {
+ return (m_ptr != p);
+ }
+
+private:
+ TYPE * m_ptr;
+
+ // Don't allow copy ctor. Explicitly don't define body to force linker errors if they're called.
+ BaseSmartPtr(BaseSmartPtr<TYPE,ACQUIREF,RELEASEF> & other);
+ void operator=(BaseSmartPtr<TYPE,ACQUIREF,RELEASEF> & other);
+
+ void RawAcquire(TYPE * p)
+ {
+ _ASSERTE(m_ptr == NULL);
+ m_ptr= p;
+ ACQUIREF(m_ptr);
+ }
+ void RawRelease()
+ {
+ _ASSERTE(m_ptr != NULL);
+ RELEASEF(m_ptr);
+ m_ptr = NULL;
+ }
+
+};
+
+//-----------------------------------------------------------------------------
+// Helper to make it easy to declare new SmartPtrs
+//-----------------------------------------------------------------------------
+#define DECLARE_MY_NEW_HOLDER(NAME, ADDREF, RELEASE) \
+template<typename TYPE> \
+class NAME : public BaseSmartPtr<TYPE, ADDREF, RELEASE> { \
+public: \
+ NAME() { }; \
+ NAME(NAME & other) { this->Assign(other.GetValue()); } \
+ explicit NAME(TYPE * p) : BaseSmartPtr<TYPE, ADDREF, RELEASE>(p) { }; \
+ FORCEINLINE NAME * GetAddr() { return this; } \
+ void operator=(NAME & other) { this->Assign(other.GetValue()); } \
+}; \
+
+//-----------------------------------------------------------------------------
+// Declare the various smart ptrs.
+//-----------------------------------------------------------------------------
+DECLARE_MY_NEW_HOLDER(RSSmartPtr, HolderRSAddRef, HolderRSRelease);
+DECLARE_MY_NEW_HOLDER(RSExtSmartPtr, HolderRSAddRefExternal, HolderRSReleaseExternal);
+
+// The CordbBase::m_pProcess backpointer needs to adjust the external reference count, but manipulate it from
+// within the RS. This means we need to skip debugging checks that ensure
+// that the external count is only manipulated from outside the RS. Since we're
+// skipping these checks, we call this an "Unsafe" pointer.
+// This is purely used by CordbBase::m_pProcess.
+DECLARE_MY_NEW_HOLDER(RSUnsafeExternalSmartPtr, HolderRSUnsafeExtAddRef, HolderRSUnsafeExtRelease);
+
+
+
+#endif // _HELPERS_H
+
diff --git a/src/debug/di/i386/.gitmirror b/src/debug/di/i386/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/debug/di/i386/.gitmirror
@@ -0,0 +1 @@
+Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror. \ No newline at end of file
diff --git a/src/debug/di/i386/cordbregisterset.cpp b/src/debug/di/i386/cordbregisterset.cpp
new file mode 100644
index 0000000000..3418e3ca60
--- /dev/null
+++ b/src/debug/di/i386/cordbregisterset.cpp
@@ -0,0 +1,222 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+//*****************************************************************************
+// File: CordbRegisterSet.cpp
+//
+
+//
+//*****************************************************************************
+#include "primitives.h"
+
+
+HRESULT CordbRegisterSet::GetRegistersAvailable(ULONG64 *pAvailable)
+{
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pAvailable, ULONG64 *);
+
+ (*pAvailable) = SETBITULONG64( REGISTER_INSTRUCTION_POINTER )
+ | SETBITULONG64( REGISTER_STACK_POINTER )
+ | SETBITULONG64( REGISTER_FRAME_POINTER );
+
+ if (!m_quickUnwind || m_active)
+ (*pAvailable) |= SETBITULONG64( REGISTER_X86_EAX )
+ | SETBITULONG64( REGISTER_X86_ECX )
+ | SETBITULONG64( REGISTER_X86_EDX )
+ | SETBITULONG64( REGISTER_X86_EBX )
+ | SETBITULONG64( REGISTER_X86_ESI )
+ | SETBITULONG64( REGISTER_X86_EDI );
+
+ if (m_active)
+ (*pAvailable) |= SETBITULONG64( REGISTER_X86_FPSTACK_0 )
+ | SETBITULONG64( REGISTER_X86_FPSTACK_1 )
+ | SETBITULONG64( REGISTER_X86_FPSTACK_2 )
+ | SETBITULONG64( REGISTER_X86_FPSTACK_3 )
+ | SETBITULONG64( REGISTER_X86_FPSTACK_4 )
+ | SETBITULONG64( REGISTER_X86_FPSTACK_5 )
+ | SETBITULONG64( REGISTER_X86_FPSTACK_6 )
+ | SETBITULONG64( REGISTER_X86_FPSTACK_7 );
+
+ return S_OK;
+}
+
+
+#define FPSTACK_FROM_INDEX( _index ) (m_thread->m_floatValues[m_thread->m_floatStackTop -( (REGISTER_X86_FPSTACK_##_index)-REGISTER_X86_FPSTACK_0 ) ] )
+
+HRESULT CordbRegisterSet::GetRegisters(ULONG64 mask, ULONG32 regCount,
+ CORDB_REGISTER regBuffer[])
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ UINT iRegister = 0;
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(regBuffer, CORDB_REGISTER, regCount, true, true);
+
+ //If we need some floating point value, tell the thread to get it
+ if ( mask & ( SETBITULONG64(REGISTER_X86_FPSTACK_0)
+ | SETBITULONG64(REGISTER_X86_FPSTACK_1)
+ | SETBITULONG64(REGISTER_X86_FPSTACK_2)
+ | SETBITULONG64(REGISTER_X86_FPSTACK_3)
+ | SETBITULONG64(REGISTER_X86_FPSTACK_4)
+ | SETBITULONG64(REGISTER_X86_FPSTACK_5)
+ | SETBITULONG64(REGISTER_X86_FPSTACK_6)
+ | SETBITULONG64(REGISTER_X86_FPSTACK_7 ) ) )
+ {
+ HRESULT hr = S_OK;
+
+ if (!m_active)
+ return E_INVALIDARG;
+
+ if (!m_thread->m_fFloatStateValid)
+ {
+ EX_TRY
+ {
+ m_thread->LoadFloatState();
+ }
+ EX_CATCH_HRESULT(hr);
+ if ( !SUCCEEDED(hr) )
+ {
+ return hr;
+ }
+ LOG( ( LF_CORDB, LL_INFO1000, "CRS::GR: Loaded float state\n" ) );
+ }
+ }
+
+ // Make sure that the registers are really available
+ if ( mask & ( SETBITULONG64( REGISTER_X86_EAX )
+ | SETBITULONG64( REGISTER_X86_ECX )
+ | SETBITULONG64( REGISTER_X86_EDX )
+ | SETBITULONG64( REGISTER_X86_EBX )
+ | SETBITULONG64( REGISTER_X86_ESI )
+ | SETBITULONG64( REGISTER_X86_EDI ) ) )
+ {
+ if (!m_active && m_quickUnwind)
+ return E_INVALIDARG;
+ }
+
+ for ( int i = REGISTER_INSTRUCTION_POINTER
+ ; i<=REGISTER_X86_FPSTACK_7 && iRegister < regCount
+ ; i++)
+ {
+ if( mask & SETBITULONG64(i) )
+ {
+ switch( i )
+ {
+ case REGISTER_INSTRUCTION_POINTER:
+ regBuffer[iRegister++] = m_rd->PC; break;
+ case REGISTER_STACK_POINTER:
+ regBuffer[iRegister++] = m_rd->SP; break;
+ case REGISTER_FRAME_POINTER:
+ regBuffer[iRegister++] = m_rd->FP; break;
+ case REGISTER_X86_EAX:
+ regBuffer[iRegister++] = m_rd->Eax; break;
+ case REGISTER_X86_EBX:
+ regBuffer[iRegister++] = m_rd->Ebx; break;
+ case REGISTER_X86_ECX:
+ regBuffer[iRegister++] = m_rd->Ecx; break;
+ case REGISTER_X86_EDX:
+ regBuffer[iRegister++] = m_rd->Edx; break;
+ case REGISTER_X86_ESI:
+ regBuffer[iRegister++] = m_rd->Esi; break;
+ case REGISTER_X86_EDI:
+ regBuffer[iRegister++] = m_rd->Edi; break;
+
+ //for floats, copy the bits, not the integer part of
+ //the value, into the register
+ case REGISTER_X86_FPSTACK_0:
+ memcpy(&regBuffer[iRegister++],
+ &(FPSTACK_FROM_INDEX(0)),
+ sizeof(CORDB_REGISTER));
+ break;
+ case REGISTER_X86_FPSTACK_1:
+ memcpy( &regBuffer[iRegister++],
+ & (FPSTACK_FROM_INDEX( 1 ) ),
+ sizeof(CORDB_REGISTER) );
+ break;
+ case REGISTER_X86_FPSTACK_2:
+ memcpy( &regBuffer[iRegister++],
+ & (FPSTACK_FROM_INDEX( 2 ) ),
+ sizeof(CORDB_REGISTER) ); break;
+ case REGISTER_X86_FPSTACK_3:
+ memcpy( &regBuffer[iRegister++],
+ & (FPSTACK_FROM_INDEX( 3 ) ),
+ sizeof(CORDB_REGISTER) ); break;
+ case REGISTER_X86_FPSTACK_4:
+ memcpy( &regBuffer[iRegister++],
+ & (FPSTACK_FROM_INDEX( 4 ) ),
+ sizeof(CORDB_REGISTER) ); break;
+ case REGISTER_X86_FPSTACK_5:
+ memcpy( &regBuffer[iRegister++],
+ & (FPSTACK_FROM_INDEX( 5 ) ),
+ sizeof(CORDB_REGISTER) ); break;
+ case REGISTER_X86_FPSTACK_6:
+ memcpy( &regBuffer[iRegister++],
+ & (FPSTACK_FROM_INDEX( 6 ) ),
+ sizeof(CORDB_REGISTER) ); break;
+ case REGISTER_X86_FPSTACK_7:
+ memcpy( &regBuffer[iRegister++],
+ & (FPSTACK_FROM_INDEX( 7 ) ),
+ sizeof(CORDB_REGISTER) ); break;
+ }
+ }
+ }
+
+ _ASSERTE( iRegister <= regCount );
+ return S_OK;
+}
+
+
+HRESULT CordbRegisterSet::GetRegistersAvailable(ULONG32 regCount,
+ BYTE pAvailable[])
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(pAvailable, CORDB_REGISTER, regCount, true, true);
+
+ // Defer to adapter for v1.0 interface
+ return GetRegistersAvailableAdapter(regCount, pAvailable);
+}
+
+
+HRESULT CordbRegisterSet::GetRegisters(ULONG32 maskCount, BYTE mask[],
+ ULONG32 regCount, CORDB_REGISTER regBuffer[])
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(regBuffer, CORDB_REGISTER, regCount, true, true);
+
+ // Defer to adapter for v1.0 interface
+ return GetRegistersAdapter(maskCount, mask, regCount, regBuffer);
+}
+
+
+// This is just a convenience function to convert a regdisplay into a Context.
+// Since a context has more info than a regdisplay, the conversion isn't perfect
+// and the context can't be fully accurate.
+void CordbRegisterSet::InternalCopyRDToContext(DT_CONTEXT * pInputContext)
+{
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+ _ASSERTE(pInputContext);
+
+ //now update the registers based on the current frame
+ if((pInputContext->ContextFlags & DT_CONTEXT_INTEGER)==DT_CONTEXT_INTEGER)
+ {
+ pInputContext->Eax = m_rd->Eax;
+ pInputContext->Ebx = m_rd->Ebx;
+ pInputContext->Ecx = m_rd->Ecx;
+ pInputContext->Edx = m_rd->Edx;
+ pInputContext->Esi = m_rd->Esi;
+ pInputContext->Edi = m_rd->Edi;
+ }
+
+
+ if((pInputContext->ContextFlags & DT_CONTEXT_CONTROL)==DT_CONTEXT_CONTROL)
+ {
+ pInputContext->Eip = m_rd->PC;
+ pInputContext->Esp = m_rd->SP;
+ pInputContext->Ebp = m_rd->FP;
+ }
+}
+
diff --git a/src/debug/di/i386/primitives.cpp b/src/debug/di/i386/primitives.cpp
new file mode 100644
index 0000000000..1e173327e3
--- /dev/null
+++ b/src/debug/di/i386/primitives.cpp
@@ -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.
+
+//
+
+
+#include "../../shared/i386/primitives.cpp"
+
+
diff --git a/src/debug/di/localeventchannel.cpp b/src/debug/di/localeventchannel.cpp
new file mode 100644
index 0000000000..f31c46f7bb
--- /dev/null
+++ b/src/debug/di/localeventchannel.cpp
@@ -0,0 +1,499 @@
+// 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.
+//*****************************************************************************
+// File: LocalEventChannel.cpp
+//
+
+//
+// Implements the old-style event channel between two processes on a local Windows machine.
+//*****************************************************************************
+
+#include "stdafx.h"
+#include "eventchannel.h"
+
+
+//---------------------------------------------------------------------------------------
+//
+// This is the implementation of the event channel for the normal case, where both the debugger and the
+// debuggee are on the same Windows machine. See code:IEventChannel for more information.
+//
+
+class LocalEventChannel : public IEventChannel
+{
+public:
+ LocalEventChannel(CORDB_ADDRESS pLeftSideDCB,
+ DebuggerIPCControlBlock * pDCBBuffer,
+ ICorDebugMutableDataTarget * pMutableDataTarget);
+
+ // Inititalize the event channel.
+ virtual HRESULT Init(HANDLE hTargetProc);
+
+ // Called when the debugger is detaching.
+ virtual void Detach();
+
+ // Delete the event channel and clean up all the resources it owns. This function can only be called once.
+ virtual void Delete();
+
+
+
+ // Update a single field with a value stored in the RS copy of the DCB.
+ virtual HRESULT UpdateLeftSideDCBField(void *rsFieldAddr, SIZE_T size);
+
+ // Update the entire RS copy of the debugger control block by reading the LS copy.
+ virtual HRESULT UpdateRightSideDCB();
+
+ // Get the pointer to the RS DCB.
+ virtual DebuggerIPCControlBlock * GetDCB();
+
+
+
+ // Check whether we need to wait for an acknowledgement from the LS after sending an IPC event.
+ virtual BOOL NeedToWaitForAck(DebuggerIPCEvent * pEvent);
+
+ // Get a handle to wait on after sending an IPC event to the LS. The caller should call NeedToWaitForAck()
+ virtual HANDLE GetRightSideEventAckHandle();
+
+ // Clean up the state if the wait for an acknowledgement is unsuccessful.
+ virtual void ClearEventForLeftSide();
+
+
+
+ // Send an IPC event to the LS.
+ virtual HRESULT SendEventToLeftSide(DebuggerIPCEvent * pEvent, SIZE_T eventSize);
+
+ // Get the reply from the LS for a previously sent IPC event.
+ virtual HRESULT GetReplyFromLeftSide(DebuggerIPCEvent * pReplyEvent, SIZE_T eventSize);
+
+
+
+ // Save an IPC event from the LS.
+ // Used for transferring an IPC event from the native pipeline to the IPC event channel.
+ virtual HRESULT SaveEventFromLeftSide(DebuggerIPCEvent * pEventFromLeftSide);
+
+ // Get a saved IPC event from the LS.
+ // Used for transferring an IPC event from the native pipeline to the IPC event channel.
+ virtual HRESULT GetEventFromLeftSide(DebuggerIPCEvent * pLocalManagedEvent);
+
+private:
+ // Get a target buffer representing the area of the DebuggerIPCControlBlock on the helper thread that
+ // holds information received from the LS as the result of an IPC event.
+ TargetBuffer RemoteReceiveBuffer(SIZE_T size);
+
+ // Get a target buffer representing the area of the DebuggerIPCControlBlock on the helper thread that
+ // holds information sent to the LS with an IPC event.
+ TargetBuffer RemoteSendBuffer(SIZE_T size);
+
+ // write memory to the LS using the data target
+ HRESULT SafeWriteBuffer(TargetBuffer tb, const BYTE * pLocalBuffer);
+
+ // read memory from the LS using the data target
+ HRESULT SafeReadBuffer(TargetBuffer tb, BYTE * pLocalBuffer);
+
+ // duplicate a remote handle into the local process
+ HRESULT DuplicateHandleToLocalProcess(HANDLE * pLocalHandle, RemoteHANDLE * pRemoteHandle);
+
+ // target address of the DCB on the LS
+ CORDB_ADDRESS m_pLeftSideDCB;
+
+ // used to signal the to the LS that an event is available
+ HANDLE m_rightSideEventAvailable;
+
+ // used by the LS to signal that the event is read
+ HANDLE m_rightSideEventRead;
+
+ // handle of the debuggee process
+ HANDLE m_hTargetProc;
+
+ // local buffer for the DCB on the RS
+ DebuggerIPCControlBlock * m_pDCBBuffer;
+
+ // data target used for cross-process memory reads and writes
+ RSExtSmartPtr<ICorDebugMutableDataTarget> m_pMutableDataTarget;
+};
+
+// Allocate and return an old-style event channel object for this target platform.
+HRESULT NewEventChannelForThisPlatform(CORDB_ADDRESS pLeftSideDCB,
+ ICorDebugMutableDataTarget * pMutableDataTarget,
+ DWORD dwProcessId,
+ MachineInfo machineInfo,
+ IEventChannel ** ppEventChannel)
+{
+ _ASSERTE(ppEventChannel != NULL);
+
+ LocalEventChannel * pEventChannel = NULL;
+ DebuggerIPCControlBlock * pDCBBuffer = NULL;
+
+ pDCBBuffer = new (nothrow) DebuggerIPCControlBlock;
+ if (pDCBBuffer == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ pEventChannel = new (nothrow) LocalEventChannel(pLeftSideDCB, pDCBBuffer, pMutableDataTarget);
+ if (pEventChannel == NULL)
+ {
+ delete pDCBBuffer;
+ return E_OUTOFMEMORY;
+ }
+
+ *ppEventChannel = pEventChannel;
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+//
+// This is the constructor.
+//
+// Arguments:
+// pLeftSideDCB - target address of the DCB on the LS
+// pDCBBuffer - local buffer for storing the DCB on the RS; the memory is owned by this class
+// pMutableDataTarget - data target for reading from and writing to the target process's address space
+//
+
+LocalEventChannel::LocalEventChannel(CORDB_ADDRESS pLeftSideDCB,
+ DebuggerIPCControlBlock * pDCBBuffer,
+ ICorDebugMutableDataTarget * pMutableDataTarget)
+{
+ m_pLeftSideDCB = pLeftSideDCB;
+ m_pDCBBuffer = pDCBBuffer;
+
+ m_rightSideEventAvailable = NULL;
+ m_rightSideEventRead = NULL;
+
+ m_pMutableDataTarget.Assign(pMutableDataTarget);
+}
+
+// Inititalize the event channel.
+//
+// virtual
+HRESULT LocalEventChannel::Init(HANDLE hTargetProc)
+{
+ HRESULT hr = E_FAIL;
+
+ m_hTargetProc = hTargetProc;
+
+ // Duplicate the handle of the RS process (i.e. the debugger) to the LS process's address space.
+ BOOL fSuccess =
+ m_pDCBBuffer->m_rightSideProcessHandle.DuplicateToRemoteProcess(m_hTargetProc, GetCurrentProcess());
+ if (!fSuccess)
+ {
+ return HRESULT_FROM_GetLastError();
+ }
+
+ IfFailRet(UpdateLeftSideDCBField(&(m_pDCBBuffer->m_rightSideProcessHandle),
+ sizeof(m_pDCBBuffer->m_rightSideProcessHandle)));
+
+ // Dup RSEA and RSER into this process if we don't already have them.
+ // On Launch, we don't have them yet, but on attach we do.
+ IfFailRet(DuplicateHandleToLocalProcess(&m_rightSideEventAvailable,
+ &m_pDCBBuffer->m_rightSideEventAvailable));
+ IfFailRet(DuplicateHandleToLocalProcess(&m_rightSideEventRead,
+ &m_pDCBBuffer->m_rightSideEventRead));
+
+ return S_OK;
+}
+
+// Called when the debugger is detaching.
+//
+// virtual
+void LocalEventChannel::Detach()
+{
+ // This averts a race condition wherein we'll detach, then reattach,
+ // and find these events in the still-signalled state.
+ if (m_rightSideEventAvailable != NULL)
+ {
+ ResetEvent(m_rightSideEventAvailable);
+ }
+ if (m_rightSideEventRead != NULL)
+ {
+ ResetEvent(m_rightSideEventRead);
+ }
+}
+
+// Delete the event channel and clean up all the resources it owns. This function can only be called once.
+//
+// virtual
+void LocalEventChannel::Delete()
+{
+ if (m_hTargetProc != NULL)
+ {
+ m_pDCBBuffer->m_rightSideProcessHandle.CloseInRemoteProcess(m_hTargetProc);
+ UpdateLeftSideDCBField(&(m_pDCBBuffer->m_rightSideProcessHandle), sizeof(m_pDCBBuffer->m_rightSideProcessHandle));
+ m_hTargetProc = NULL;
+ }
+
+ if (m_rightSideEventAvailable != NULL)
+ {
+ CloseHandle(m_rightSideEventAvailable);
+ m_rightSideEventAvailable = NULL;
+ }
+
+ if (m_rightSideEventRead!= NULL)
+ {
+ CloseHandle(m_rightSideEventRead);
+ m_rightSideEventRead = NULL;
+ }
+
+ if (m_pDCBBuffer != NULL)
+ {
+ delete m_pDCBBuffer;
+ m_pDCBBuffer = NULL;
+ }
+
+ if (m_pMutableDataTarget != NULL)
+ {
+ m_pMutableDataTarget.Clear();
+ }
+
+ delete this;
+}
+
+// Update a single field with a value stored in the RS copy of the DCB.
+//
+// virtual
+HRESULT LocalEventChannel::UpdateLeftSideDCBField(void * rsFieldAddr, SIZE_T size)
+{
+ _ASSERTE(m_pDCBBuffer != NULL);
+ _ASSERTE(m_pLeftSideDCB != NULL);
+
+ BYTE * pbRSFieldAddr = reinterpret_cast<BYTE *>(rsFieldAddr);
+ CORDB_ADDRESS lsFieldAddr = m_pLeftSideDCB + (pbRSFieldAddr - reinterpret_cast<BYTE *>(m_pDCBBuffer));
+ return SafeWriteBuffer(TargetBuffer(lsFieldAddr, (ULONG)size), const_cast<const BYTE *>(pbRSFieldAddr));
+}
+
+// Update the entire RS copy of the debugger control block by reading the LS copy.
+//
+// virtual
+HRESULT LocalEventChannel::UpdateRightSideDCB()
+{
+ _ASSERTE(m_pDCBBuffer != NULL);
+ _ASSERTE(m_pLeftSideDCB != NULL);
+
+ return SafeReadBuffer(TargetBuffer(m_pLeftSideDCB, sizeof(DebuggerIPCControlBlock)),
+ reinterpret_cast<BYTE *>(m_pDCBBuffer));
+}
+
+// Get the pointer to the RS DCB.
+//
+// virtual
+DebuggerIPCControlBlock * LocalEventChannel::GetDCB()
+{
+ return m_pDCBBuffer;
+}
+
+
+// Check whether we need to wait for an acknowledgement from the LS after sending an IPC event.
+//
+// virtual
+BOOL LocalEventChannel::NeedToWaitForAck(DebuggerIPCEvent * pEvent)
+{
+ // On Windows, we need to wait for acknowledgement for every synchronous event.
+ return !pEvent->asyncSend;
+}
+
+// Get a handle to wait on after sending an IPC event to the LS. The caller should call NeedToWaitForAck()
+//
+// virtual
+HANDLE LocalEventChannel::GetRightSideEventAckHandle()
+{
+ return m_rightSideEventRead;
+}
+
+// Clean up the state if the wait for an acknowledgement is unsuccessful.
+//
+// virtual
+void LocalEventChannel::ClearEventForLeftSide()
+{
+ ResetEvent(m_rightSideEventAvailable);
+}
+
+// Send an IPC event to the LS.
+//
+// virtual
+HRESULT LocalEventChannel::SendEventToLeftSide(DebuggerIPCEvent * pEvent, SIZE_T eventSize)
+{
+ _ASSERTE(eventSize <= CorDBIPC_BUFFER_SIZE);
+
+ HRESULT hr = E_FAIL;
+ BOOL fSuccess = FALSE;
+
+ // Copy the event into the shared memory segment.
+ hr = SafeWriteBuffer(RemoteReceiveBuffer(eventSize), reinterpret_cast<BYTE *>(pEvent));
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ // Do some safety-checks for sending an Async-Event.
+#if defined(_DEBUG)
+ {
+ // We can only send 1 event from RS-->LS at a time.
+ // For non-async events, this is obviously enforced. (since the events are blocking & serialized)
+ // If this is an AsyncSend, then our caller was responsible for making sure it
+ // was safe to send.
+ // There should be no other IPC event in the pipeline. This, both RSEA & RSER
+ // should be non-signaled. check that now.
+ // It's ok if these fail - we detect that below.
+ int res2 = ::WaitForSingleObject(m_rightSideEventAvailable, 0);
+ CONSISTENCY_CHECK_MSGF(res2 != WAIT_OBJECT_0, ("RSEA:%d", res2));
+
+ int res3 = ::WaitForSingleObject(m_rightSideEventRead, 0);
+ CONSISTENCY_CHECK_MSGF(res3 != WAIT_OBJECT_0, ("RSER:%d", res3));
+ }
+#endif // _DEBUG
+
+ // Tell the runtime controller there is an event ready.
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "Set RSEA\n");
+ fSuccess = SetEvent(m_rightSideEventAvailable);
+
+ if (!fSuccess)
+ {
+ ThrowHR(HRESULT_FROM_GetLastError());
+ }
+
+ return S_OK;
+}
+
+// Get the reply from the LS for a previously sent IPC event.
+//
+// virtual
+HRESULT LocalEventChannel::GetReplyFromLeftSide(DebuggerIPCEvent * pReplyEvent, SIZE_T eventSize)
+{
+ // Simply read the IPC event reply directly from the receive buffer on the LS.
+ return SafeReadBuffer(RemoteReceiveBuffer(eventSize), reinterpret_cast<BYTE *>(pReplyEvent));
+}
+
+// Save an IPC event from the LS.
+// Used for transferring an IPC event from the native pipeline to the IPC event channel.
+//
+// virtual
+HRESULT LocalEventChannel::SaveEventFromLeftSide(DebuggerIPCEvent * pEventFromLeftSide)
+{
+ // On Windows, when a thread raises a debug event through the native pipeline, the process is suspended.
+ // Thus, the LS IPC event will still be in the send buffer in the debuggee's address space.
+ // Since there is no chance the send buffer can be altered, we don't need to save the event.
+ // We can simply read from it.
+ return S_OK;
+}
+
+// Get a saved IPC event from the LS.
+// Used for transferring an IPC event from the native pipeline to the IPC event channel.
+//
+// virtual
+HRESULT LocalEventChannel::GetEventFromLeftSide(DebuggerIPCEvent * pLocalManagedEvent)
+{
+ // See code:LocalEventChannel::SaveEventFromLeftSide.
+ // Make sure we are reading form the send buffer, not the receive buffer.
+ return SafeReadBuffer(RemoteSendBuffer(CorDBIPC_BUFFER_SIZE), reinterpret_cast<BYTE *>(pLocalManagedEvent));
+}
+
+//-----------------------------------------------------------------------------
+//
+// Get a target buffer representing the area of the DebuggerIPCControlBlock on the helper thread that
+// holds information received from the LS as the result of an IPC event.
+//
+// Arguments:
+// size - size of the receive buffer
+//
+// Return Value:
+// a TargetBuffer representing the receive buffer on the LS
+//
+
+TargetBuffer LocalEventChannel::RemoteReceiveBuffer(SIZE_T size)
+{
+ return TargetBuffer(m_pLeftSideDCB + offsetof(DebuggerIPCControlBlock, m_receiveBuffer), (ULONG)size);
+}
+
+//-----------------------------------------------------------------------------
+//
+// Get a target buffer representing the area of the DebuggerIPCControlBlock on the helper thread that
+// holds information sent to the LS with an IPC event.
+//
+// Arguments:
+// size - size of the send buffer
+//
+// Return Value:
+// a TargetBuffer representing the send buffer on the LS
+//
+
+TargetBuffer LocalEventChannel::RemoteSendBuffer(SIZE_T size)
+{
+ return TargetBuffer(m_pLeftSideDCB + offsetof(DebuggerIPCControlBlock, m_sendBuffer), (ULONG)size);
+}
+
+//-----------------------------------------------------------------------------
+//
+// Write memory to the LS using the data target.
+//
+// Arguments:
+// tb - target address and size to be written to
+// pLocalBuffer - data to write
+//
+// Return Value:
+// S_OK if successful
+//
+
+HRESULT LocalEventChannel::SafeWriteBuffer(TargetBuffer tb, const BYTE * pLocalBuffer)
+{
+ return m_pMutableDataTarget->WriteVirtual(tb.pAddress, pLocalBuffer, tb.cbSize);
+}
+
+//-----------------------------------------------------------------------------
+//
+// Read memory from the LS using the data target.
+//
+// Arguments:
+// tb - target address and size to be read from
+// pLocalBuffer - buffer for storing the data read from the LS
+//
+// Return Value:
+// S_OK if the entire specified range is read successful
+//
+
+HRESULT LocalEventChannel::SafeReadBuffer(TargetBuffer tb, BYTE * pLocalBuffer)
+{
+ ULONG32 cbRead;
+ HRESULT hr = m_pMutableDataTarget->ReadVirtual(tb.pAddress, pLocalBuffer, tb.cbSize, &cbRead);
+ if (FAILED(hr))
+ {
+ return CORDBG_E_READVIRTUAL_FAILURE;
+ }
+
+ if (cbRead != tb.cbSize)
+ {
+ return HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY);
+ }
+
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+//
+// Duplicate a remote handle into the local process.
+//
+// Arguments:
+// pLocalHandle - out parameter; return the duplicated handle
+// pRemoteHandle - remote handle to be duplicated
+//
+// Return Value:
+// S_OK if successful
+//
+// Notes:
+// nop if pLocalHandle is already initialized
+//
+
+HRESULT LocalEventChannel::DuplicateHandleToLocalProcess(HANDLE * pLocalHandle, RemoteHANDLE * pRemoteHandle)
+{
+ // Dup RSEA and RSER into this process if we don't already have them.
+ // On Launch, we don't have them yet, but on attach we do.
+ if (*pLocalHandle == NULL)
+ {
+ BOOL fSuccess = pRemoteHandle->DuplicateToLocalProcess(m_hTargetProc, pLocalHandle);
+ if (!fSuccess)
+ {
+ return HRESULT_FROM_GetLastError();
+ }
+ }
+ return S_OK;
+}
diff --git a/src/debug/di/module.cpp b/src/debug/di/module.cpp
new file mode 100644
index 0000000000..6717e8575f
--- /dev/null
+++ b/src/debug/di/module.cpp
@@ -0,0 +1,4925 @@
+// 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.
+
+//*****************************************************************************
+// File: module.cpp
+//
+
+//
+//*****************************************************************************
+#include "stdafx.h"
+#include "winbase.h"
+
+#include "metadataexports.h"
+
+#include "winbase.h"
+#include "corpriv.h"
+#include "corsym.h"
+#include "ildbsymlib.h"
+
+#include "pedecoder.h"
+
+//---------------------------------------------------------------------------------------
+// Update an existing metadata importer with a buffer
+//
+// Arguments:
+// pUnk - IUnknoown of importer to update.
+// pData - local buffer containing new metadata
+// cbData - size of buffer in bytes.
+// dwReOpenFlags - metadata flags to pass for reopening.
+//
+// Returns:
+// S_OK on success. Else failure.
+//
+// Notes:
+// This will call code:MDReOpenMetaDataWithMemoryEx from the metadata engine.
+STDAPI ReOpenMetaDataWithMemoryEx(
+ void *pUnk,
+ LPCVOID pData,
+ ULONG cbData,
+ DWORD dwReOpenFlags)
+{
+ HRESULT hr = MDReOpenMetaDataWithMemoryEx(pUnk,pData, cbData, dwReOpenFlags);
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+// Initialize a new CordbModule around a Module in the target.
+//
+// Arguments:
+// pProcess - process that this module lives in
+// vmDomainFile - CLR cookie for module.
+CordbModule::CordbModule(
+ CordbProcess * pProcess,
+ VMPTR_Module vmModule,
+ VMPTR_DomainFile vmDomainFile)
+: CordbBase(pProcess, vmDomainFile.IsNull() ? VmPtrToCookie(vmModule) : VmPtrToCookie(vmDomainFile), enumCordbModule),
+ m_pAssembly(0),
+ m_pAppDomain(0),
+ m_classes(11),
+ m_functions(101),
+ m_vmDomainFile(vmDomainFile),
+ m_vmModule(vmModule),
+ m_EnCCount(0),
+ m_isIlWinMD(Uninitialized),
+ m_fForceMetaDataSerialize(FALSE),
+ m_nativeCodeTable(101)
+{
+ _ASSERTE(pProcess->GetProcessLock()->HasLock());
+
+ _ASSERTE(!vmModule.IsNull());
+
+ m_nLoadEventContinueCounter = 0;
+#ifdef _DEBUG
+ m_classes.DebugSetRSLock(pProcess->GetProcessLock());
+ m_functions.DebugSetRSLock(pProcess->GetProcessLock());
+#endif
+
+ // Fill out properties via DAC.
+ ModuleInfo modInfo;
+ pProcess->GetDAC()->GetModuleData(vmModule, &modInfo); // throws
+
+ m_PEBuffer.Init(modInfo.pPEBaseAddress, modInfo.nPESize);
+
+ m_fDynamic = modInfo.fIsDynamic;
+ m_fInMemory = modInfo.fInMemory;
+ m_vmPEFile = modInfo.vmPEFile;
+
+ if (!vmDomainFile.IsNull())
+ {
+ DomainFileInfo dfInfo;
+
+ pProcess->GetDAC()->GetDomainFileData(vmDomainFile, &dfInfo); // throws
+
+ m_pAppDomain = pProcess->LookupOrCreateAppDomain(dfInfo.vmAppDomain);
+ m_pAssembly = m_pAppDomain->LookupOrCreateAssembly(dfInfo.vmDomainAssembly);
+ }
+ else
+ {
+ // Not yet implemented
+ m_pAppDomain = pProcess->GetSharedAppDomain();
+ m_pAssembly = m_pAppDomain->LookupOrCreateAssembly(modInfo.vmAssembly);
+ }
+#ifdef _DEBUG
+ m_nativeCodeTable.DebugSetRSLock(GetProcess()->GetProcessLock());
+#endif
+
+ // MetaData is initialized lazily (via code:CordbModule::GetMetaDataImporter).
+ // Getting the metadata may be very expensive (especially if we go through the metadata locator, which
+ // invokes back to the data-target), so don't do it until asked.
+ // m_pIMImport, m_pInternalMetaDataImport are smart pointers that already initialize to NULL.
+}
+
+
+#ifdef _DEBUG
+//---------------------------------------------------------------------------------------
+// Callback helper for code:CordbModule::DbgAssertModuleDeleted
+//
+// Arguments
+// vmDomainFile - domain file in the enumeration
+// pUserData - pointer to the CordbModule that we just got an exit event for.
+//
+void DbgAssertModuleDeletedCallback(VMPTR_DomainFile vmDomainFile, void * pUserData)
+{
+ CordbModule * pThis = reinterpret_cast<CordbModule *>(pUserData);
+ INTERNAL_DAC_CALLBACK(pThis->GetProcess());
+
+ if (!pThis->m_vmDomainFile.IsNull())
+ {
+ VMPTR_DomainFile vmDomainFileDeleted = pThis->m_vmDomainFile;
+
+ CONSISTENCY_CHECK_MSGF((vmDomainFileDeleted != vmDomainFile),
+ ("A Module Unload event was sent for a module, but it still shows up in the enumeration.\n vmDomainFileDeleted=%p\n",
+ VmPtrToCookie(vmDomainFileDeleted)));
+ }
+}
+
+//---------------------------------------------------------------------------------------
+// Assert that a module is no longer discoverable via enumeration.
+//
+// Notes:
+// See code:IDacDbiInterface#Enumeration for rules that we're asserting.
+// This is a debug only method. It's conceptually similar to
+// code:CordbProcess::DbgAssertAppDomainDeleted.
+//
+void CordbModule::DbgAssertModuleDeleted()
+{
+ GetProcess()->GetDAC()->EnumerateModulesInAssembly(
+ m_pAssembly->GetDomainAssemblyPtr(),
+ DbgAssertModuleDeletedCallback,
+ this);
+}
+#endif // _DEBUG
+
+CordbModule::~CordbModule()
+{
+ // We should have been explicitly neutered before our internal ref went to 0.
+ _ASSERTE(IsNeutered());
+
+ _ASSERTE(m_pIMImport == NULL);
+}
+
+// Neutered by CordbAppDomain
+void CordbModule::Neuter()
+{
+ // m_pAppDomain, m_pAssembly assigned w/o AddRef()
+ m_classes.NeuterAndClear(GetProcess()->GetProcessLock());
+ m_functions.NeuterAndClear(GetProcess()->GetProcessLock());
+
+ m_nativeCodeTable.NeuterAndClear(GetProcess()->GetProcessLock());
+ m_pClass.Clear();
+
+ // This is very important because it also releases the metadata's potential file locks.
+ m_pInternalMetaDataImport.Clear();
+ m_pIMImport.Clear();
+
+ CordbBase::Neuter();
+}
+
+//
+// Creates an IStream based off the memory described by the TargetBuffer.
+//
+// Arguments:
+// pProcess - process that buffer is valid in.
+// buffer - memory range in target
+// ppStream - out parameter to receive the new stream. *ppStream == NULL on input.
+// caller owns the new object and must call Release.
+//
+// Returns:
+// Throws on error.
+// Common errors include if memory is missing in the target.
+//
+// Notes:
+// This will copy the memory over from the TargetBuffer, and then create a new IStream
+// object around it.
+//
+void GetStreamFromTargetBuffer(CordbProcess * pProcess, TargetBuffer buffer, IStream ** ppStream)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(ppStream != NULL);
+ _ASSERTE(*ppStream == NULL);
+
+ int cbSize = buffer.cbSize;
+ NewArrayHolder<BYTE> localBuffer(new BYTE[cbSize]);
+
+ pProcess->SafeReadBuffer(buffer, localBuffer);
+
+ HRESULT hr = E_FAIL;
+ hr = CInMemoryStream::CreateStreamOnMemoryCopy(localBuffer, cbSize, ppStream);
+ IfFailThrow(hr);
+ _ASSERTE(*ppStream != NULL);
+}
+
+//
+// Helper API to get in-memory symbols from the target into a host stream object.
+//
+// Arguments:
+// ppStream - out parameter to receive the new stream. *ppStream == NULL on input.
+// caller owns the new object and must call Release.
+//
+// Returns:
+// kSymbolFormatNone if no PDB stream is present. This is a common case for
+// file-based modules, and also for dynamic modules that just aren't tracking
+// debug information.
+// The format of the symbols stored into ppStream. This is common:
+// - Ref.Emit modules if the debuggee generated debug symbols,
+// - in-memory modules (such as Load(Byte[], Byte[])
+// - hosted modules.
+// Throws on error
+//
+IDacDbiInterface::SymbolFormat CordbModule::GetInMemorySymbolStream(IStream ** ppStream)
+{
+ // @dbgtodo : add a PUBLIC_REENTRANT_API_ENTRY_FOR_SHIM contract
+ // This function is mainly called internally in dbi, and also by the shim to emulate the
+ // UpdateModuleSymbols callback on attach.
+
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(ppStream != NULL);
+ _ASSERTE(*ppStream == NULL);
+ *ppStream = NULL;
+
+ TargetBuffer bufferPdb;
+ IDacDbiInterface::SymbolFormat symFormat;
+ GetProcess()->GetDAC()->GetSymbolsBuffer(m_vmModule, &bufferPdb, &symFormat);
+ if (bufferPdb.IsEmpty())
+ {
+ // No in-memory PDB. Common case.
+ _ASSERTE(symFormat == IDacDbiInterface::kSymbolFormatNone);
+ return IDacDbiInterface::kSymbolFormatNone;
+ }
+ else
+ {
+ _ASSERTE(symFormat != IDacDbiInterface::kSymbolFormatNone);
+ GetStreamFromTargetBuffer(GetProcess(), bufferPdb, ppStream);
+ return symFormat;
+ }
+}
+
+//---------------------------------------------------------------------------------------
+// Accessor for PE file.
+//
+// Returns:
+// VMPTR_PEFile for this module. Should always be non-null
+//
+// Notes:
+// A main usage of this is to find the proper internal MetaData importer.
+// DACized code needs to map from PEFile --> IMDInternalImport.
+//
+VMPTR_PEFile CordbModule::GetPEFile()
+{
+ return m_vmPEFile;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Top-level getter for the public metadata importer for this module
+//
+// Returns:
+// metadata importer.
+// Never returns NULL. Will throw some hr (likely CORDBG_E_MISSING_METADATA) instead.
+//
+// Notes:
+// This will lazily create the metadata, possibly invoking back into the data-target.
+IMetaDataImport * CordbModule::GetMetaDataImporter()
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+
+ // If we already have it, then we're done.
+ // This is critical to do at the top of this function to avoid potential recursion.
+ if (m_pIMImport != NULL)
+ {
+ return m_pIMImport;
+ }
+
+ // Lazily initialize
+
+
+ // Fetch metadata from target
+ LOG((LF_CORDB,LL_INFO1000, "CM::GMI Lazy init refreshing metadata\n"));
+
+ ALLOW_DATATARGET_MISSING_MEMORY(
+ RefreshMetaData();
+ );
+
+ // If lookup failed from the Module & target memory, try the metadata locator interface
+ // from debugger, if we have one.
+ if (m_pIMImport == NULL)
+ {
+ bool isILMetaDataForNGENImage; // Not currently used for anything.
+
+ // The process's LookupMetaData will ping the debugger's ICorDebugMetaDataLocator iface.
+ CordbProcess * pProcess = GetProcess();
+ RSLockHolder processLockHolder(pProcess->GetProcessLock());
+ m_pInternalMetaDataImport.Clear();
+
+ // Do not call code:CordbProcess::LookupMetaData from this function. It will try to load
+ // through the CordbModule again which will end up back here, and on failure you'll fill the stack.
+ // Since we've already done everything possible from the Module anyhow, just call the
+ // stuff that talks to the debugger.
+ // Don't do anything with the ptr returned here, since it's really m_pInternalMetaDataImport.
+ pProcess->LookupMetaDataFromDebugger(m_vmPEFile, isILMetaDataForNGENImage, this);
+ }
+
+ // If we still can't get it, throw.
+ if (m_pIMImport == NULL)
+ {
+ ThrowHR(CORDBG_E_MISSING_METADATA);
+ }
+
+ return m_pIMImport;
+}
+
+// Refresh the metadata cache if a profiler added new rows.
+//
+// Arguments:
+// token - token that we want to ensure is in the metadata cache.
+//
+// Notes:
+// In profiler case, this may be referred to new rows and we may need to update the metadata
+// This only supports StandAloneSigs.
+//
+void CordbModule::UpdateMetaDataCacheIfNeeded(mdToken token)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB,LL_INFO10000, "CM::UMCIN token=0x%x\n", token));
+
+ // If we aren't trying to keep parity with our legacy profiler metadata update behavior
+ // then we should avoid this temporary update mechanism entirely
+ if(GetProcess()->GetWriteableMetadataUpdateMode() != LegacyCompatPolicy)
+ {
+ return;
+ }
+
+ // the metadata in WinMD is currently static since there's no
+ // support for profilers or EnC so we can simply exit early.
+ if (IsWinMD())
+ {
+ LOG((LF_CORDB,LL_INFO10000, "CM::UMCIN token is in WinMD, exiting\n"));
+ return;
+ }
+
+ //
+ // 1) Check if in-range? Compare against tables, etc.
+ //
+ if(CheckIfTokenInMetaData(token))
+ {
+ LOG((LF_CORDB,LL_INFO10000, "CM::UMCIN token was present\n"));
+ return;
+ }
+
+ //
+ // 2) Copy over new MetaData. From now on we assume that the profiler is
+ // modifying module metadata and that we need to serialize in process
+ // at each refresh
+ //
+ LOG((LF_CORDB,LL_INFO10000, "CM::UMCIN token was not present, refreshing\n"));
+ m_fForceMetaDataSerialize = TRUE;
+ RefreshMetaData();
+
+ // If we are dump debugging, we may still not have it. Nothing to be done.
+}
+
+// Returns TRUE if the token is present, FALSE if not.
+BOOL CordbModule::CheckIfTokenInMetaData(mdToken token)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+ LOG((LF_CORDB,LL_INFO10000, "CM::CITIM token=0x%x\n", token));
+ _ASSERTE(TypeFromToken(token) == mdtSignature);
+ // we shouldn't be doing this on WinMD modules since they don't implement IID_IMetaDataTables
+ _ASSERTE(!IsWinMD());
+ RSExtSmartPtr<IMetaDataTables> pTable;
+
+ HRESULT hr = GetMetaDataImporter()->QueryInterface(IID_IMetaDataTables, (void**) &pTable);
+
+ _ASSERTE(SUCCEEDED(hr));
+ if (FAILED(hr))
+ {
+ ThrowHR(hr);
+ }
+
+ ULONG cbRowsAvailable; // number of rows in the table
+
+ hr = pTable->GetTableInfo(
+ mdtSignature >> 24, // [IN] Which table.
+ NULL, // [OUT] Size of a row, bytes.
+ &cbRowsAvailable, // [OUT] Number of rows.
+ NULL, // [OUT] Number of columns in each row.
+ NULL, // [OUT] Key column, or -1 if none.
+ NULL); // [OUT] Name of the table.
+
+ _ASSERTE(SUCCEEDED(hr));
+ if (FAILED(hr))
+ {
+ ThrowHR(hr);
+ }
+
+
+ // Rows start counting with number 1.
+ ULONG rowRequested = RidFromToken(token);
+ LOG((LF_CORDB,LL_INFO10000, "CM::UMCIN requested=0x%x available=0x%x\n", rowRequested, cbRowsAvailable));
+ return (rowRequested <= cbRowsAvailable);
+}
+
+// This helper class ensures the remote serailzied buffer gets deleted in the RefreshMetaData
+// function below
+class CleanupRemoteBuffer
+{
+public:
+ CordbProcess* pProcess;
+ CordbModule* pModule;
+ TargetBuffer bufferMetaData;
+ BOOL fDoCleanup;
+
+ CleanupRemoteBuffer() :
+ fDoCleanup(FALSE) { }
+
+ ~CleanupRemoteBuffer()
+ {
+ if(fDoCleanup)
+ {
+ //
+ // Send 2nd event to free buffer.
+ //
+ DebuggerIPCEvent event;
+ pProcess->InitIPCEvent(&event,
+ DB_IPCE_RESOLVE_UPDATE_METADATA_2,
+ true,
+ pModule->GetAppDomain()->GetADToken());
+
+ event.MetadataUpdateRequest.pMetadataStart = CORDB_ADDRESS_TO_PTR(bufferMetaData.pAddress);
+
+ // Note: two-way event here...
+ IfFailThrow(pProcess->SendIPCEvent(&event, sizeof(DebuggerIPCEvent)));
+ _ASSERTE(event.type == DB_IPCE_RESOLVE_UPDATE_METADATA_2_RESULT);
+ }
+ }
+
+};
+
+// Called to refetch metadata. This occurs when a dynamic module grows or the profiler
+// has edited the metadata
+void CordbModule::RefreshMetaData()
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB,LL_INFO1000, "CM::RM\n"));
+
+ // There are several different ways we can get the metadata
+ // 1) [Most common] Module is loaded into VM and never changed. The importer
+ // will be constructed refering to the file on disk. This is a significant
+ // working set win because the VM and debugger share the image. If there is
+ // an error reading by file we can fall back to case #2 for these modules
+ // 2) Most modules have a buffer in target memory that represents their
+ // metadata. We copy that data over the RS and construct an in-memory
+ // importer on top of it.
+ // 3) The only modules that don't have a suitable buffer (case #2) are those
+ // modified in memory via the profiling API (or ENC). A message can be sent from
+ // the debugger to the debuggee instructing it to allocate a buffer and
+ // serialize the metadata into it. Then we copy that data to the RS and
+ // construct an in-memory importer on top of it.
+ // We don't need to send this message in the ENC case because the debugger
+ // has the same changes applied as the debuggee.
+ // 4) Case #3 won't work when dump debugging because we can't send IPC events.
+ // Instead we can locate chunks of the metadata pointed to in the implementation
+ // details of a remote MDInternalRW object, marshal that memory over to the
+ // debugger process, and then put a metadata reader on top of it.
+ // In time this DAC'ized metadata could be used in almost any scenario,
+ // although its probably worth keeping the file mapping technique in case
+ // #1 around for its performance wins.
+
+ CordbProcess * pProcess = GetProcess();
+ TargetBuffer bufferMetaData;
+ CleanupRemoteBuffer cleanup; // this local has a destructor to do some finally work
+
+
+ // check for scenarios we might want to handle with case #4
+ if (GetProcess()->GetShim() == NULL &&
+ GetProcess()->GetWriteableMetadataUpdateMode() == AlwaysShowUpdates &&
+ !m_fDynamic)
+ {
+ //None of the above requirements are particularly hard to change in the future as needed...
+ // a) dump-debugging mode - If we do this on a process that can move forward we need a mechanism to determine
+ // when to refetch the metadata.
+ // b) AlwaysShowUpdates - this is purely a risk mitigation choice, there aren't any known back-compat issues
+ // using DAC'ized metadata. If you want back-compat with the in-proc debugging behavior
+ // you need to figure out how to ReOpen the same public MD interface with new data.
+ // c) !m_fDynamic - A risk mitigation choice. Initial testing suggests it would work fine.
+
+
+ // So far we've only got a reader for in-memory-writable metadata (MDInternalRW implementation)
+ // We could make a reader for MDInternalRO, but no need yet. This also ensures we don't encroach into common
+ // scenario where we can map a file on disk.
+ TADDR remoteMDInternalRWAddr = NULL;
+ GetProcess()->GetDAC()->GetPEFileMDInternalRW(m_vmPEFile, &remoteMDInternalRWAddr);
+ if (remoteMDInternalRWAddr != NULL)
+ {
+ // we should only be doing this once to initialize, we don't support reopen with this technique
+ _ASSERTE(m_pIMImport == NULL);
+ ULONG32 mdStructuresVersion;
+ HRESULT hr = GetProcess()->GetDAC()->GetMDStructuresVersion(&mdStructuresVersion);
+ IfFailThrow(hr);
+ ULONG32 mdStructuresDefines;
+ hr = GetProcess()->GetDAC()->GetDefinesBitField(&mdStructuresDefines);
+ IfFailThrow(hr);
+ IMetaDataDispenserCustom* pDispCustom = NULL;
+ hr = GetProcess()->GetDispenser()->QueryInterface(IID_IMetaDataDispenserCustom, (void**)&pDispCustom);
+ IfFailThrow(hr);
+ IMDCustomDataSource* pDataSource = NULL;
+ hr = CreateRemoteMDInternalRWSource(remoteMDInternalRWAddr, GetProcess()->GetDataTarget(), mdStructuresDefines, mdStructuresVersion, &pDataSource);
+ IfFailThrow(hr);
+ IMetaDataImport* pImport = NULL;
+ hr = pDispCustom->OpenScopeOnCustomDataSource(pDataSource, 0, IID_IMetaDataImport, (IUnknown**)&m_pIMImport);
+ IfFailThrow(hr);
+ UpdateInternalMetaData();
+ return;
+ }
+ }
+
+ if(!m_fForceMetaDataSerialize) // case 1 and 2
+ {
+ LOG((LF_CORDB,LL_INFO10000, "CM::RM !m_fForceMetaDataSerialize case\n"));
+ GetProcess()->GetDAC()->GetMetadata(m_vmModule, &bufferMetaData); // throws
+ }
+ else if (GetProcess()->GetShim() == NULL) // case 3 won't work on a dump so don't try
+ {
+ return;
+ }
+ else // case 3 on a live process
+ {
+ LOG((LF_CORDB,LL_INFO10000, "CM::RM m_fForceMetaDataSerialize case\n"));
+ //
+ // Send 1 event to get metadata. This allocates a buffer
+ //
+ DebuggerIPCEvent event;
+ pProcess->InitIPCEvent(&event,
+ DB_IPCE_RESOLVE_UPDATE_METADATA_1,
+ true,
+ GetAppDomain()->GetADToken());
+
+ event.MetadataUpdateRequest.vmModule = m_vmModule;
+
+ // Note: two-way event here...
+ IfFailThrow(pProcess->SendIPCEvent(&event, sizeof(DebuggerIPCEvent)));
+
+ _ASSERTE(event.type == DB_IPCE_RESOLVE_UPDATE_METADATA_1_RESULT);
+
+ //
+ // Update it on the RS
+ //
+ bufferMetaData.Init(PTR_TO_CORDB_ADDRESS(event.MetadataUpdateRequest.pMetadataStart), (ULONG) event.MetadataUpdateRequest.nMetadataSize);
+
+ // init the cleanup object to ensure the buffer gets destroyed later
+ cleanup.bufferMetaData = bufferMetaData;
+ cleanup.pProcess = pProcess;
+ cleanup.pModule = this;
+ cleanup.fDoCleanup = TRUE;
+ }
+
+ InitMetaData(bufferMetaData, IsFileMetaDataValid()); // throws
+}
+
+// Determines whether the on-disk metadata for this module is usable as the
+// current metadata
+BOOL CordbModule::IsFileMetaDataValid()
+{
+ bool fOpenFromFile = true;
+
+ // Dynamic, In-memory, modules must be OpenScopeOnMemory.
+ // For modules that require the metadata to be serialized in memory, we must also OpenScopeOnMemory
+ // For Enc, we'll can use OpenScope(onFile) and it will get converted to Memory when we get an emitter.
+ // We're called from before the ModuleLoad callback, so EnC status hasn't been set yet, so
+ // EnC will be false.
+ if (m_fDynamic || m_fInMemory || m_fForceMetaDataSerialize)
+ {
+ LOG((LF_CORDB,LL_INFO10000, "CM::IFMV: m_fDynamic=0x%x m_fInMemory=0x%x m_fForceMetaDataSerialize=0x%x\n",
+ m_fDynamic, m_fInMemory, m_fForceMetaDataSerialize));
+ fOpenFromFile = false;
+ }
+
+#ifdef _DEBUG
+ // Reg key override to force us to use Open-by-memory. This can let us run perf tests to
+ // compare the Open-by-mem vs. Open-by-file.
+ static DWORD openFromFile = 99;
+ if (openFromFile == 99)
+ openFromFile = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgNoOpenMDByFile);
+
+ if (openFromFile)
+ {
+ LOG((LF_CORDB,LL_INFO10000, "CM::IFMV: INTERNAL_DbgNoOpenMDByFile is set\n"));
+ fOpenFromFile = false;
+ }
+#endif
+
+ LOG((LF_CORDB,LL_INFO10000, "CM::IFMV: returns 0x%x\n", fOpenFromFile));
+ return fOpenFromFile;
+}
+
+//---------------------------------------------------------------------------------------
+// Accessor for Internal MetaData importer. This is lazily initialized.
+//
+// Returns:
+// Internal MetaDataImporter, which can be handed off to DAC. Not AddRef().
+// Should be non-null. Throws on error.
+//
+// Notes:
+// An internal metadata importer is used extensively by DAC-ized code (And Edit-and-continue).
+// This should not be handed out through ICorDebug.
+IMDInternalImport * CordbModule::GetInternalMD()
+{
+ if (m_pInternalMetaDataImport == NULL)
+ {
+ UpdateInternalMetaData(); // throws
+ }
+ return m_pInternalMetaDataImport;
+}
+
+//---------------------------------------------------------------------------------------
+// The one-stop top-level initialization function the metadata (both public and private) for this module.
+//
+// Arguments:
+// buffer - valid buffer into target containing the metadata.
+// useFileMappingOptimization - if true this allows us to attempt just opening the importer
+// by using the metadata in the module on disk. if false or
+// if the attempt fails we open the metadata import on memory in
+// target buffer
+//
+// Notes:
+// This will initialize both the internal and public metadata from the buffer in the target.
+// Only called as a helper from RefreshMetaData()
+//
+// This may throw (eg, target buffer is missing).
+//
+void CordbModule::InitMetaData(TargetBuffer buffer, BOOL allowFileMappingOptimization)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB,LL_INFO100000, "CM::IM: initing with remote buffer 0x%p length 0x%x\n",
+ CORDB_ADDRESS_TO_PTR(buffer.pAddress), buffer.cbSize));
+
+ // clear all the metadata
+ m_pInternalMetaDataImport.Clear();
+
+ if (m_pIMImport == NULL)
+ {
+ // The optimization we're going for here is that the OS will use the same physical memory to
+ // back multiple ReadOnly opens of the same file. Thus since we expect the target process in
+ // live debugging, or the debugger in dump debugging, has already opened the file we would
+ // like to not create a local buffer and spend time copying in metadata from the target when
+ // the OS will happily do address lookup magic against the same physical memory for everyone.
+
+
+ // Try getting the data from the file if allowed, and fall back to using the buffer
+ // if required
+ HRESULT hr = S_OK;
+ if (allowFileMappingOptimization)
+ {
+ hr = InitPublicMetaDataFromFile();
+ if(FAILED(hr))
+ {
+ LOG((LF_CORDB,LL_INFO1000000, "CM::IPM: File mapping failed with hr=0x%x\n", hr));
+ }
+ }
+
+ if(!allowFileMappingOptimization || FAILED(hr))
+ {
+ // This is where the expensive copy of all metadata content from target memory
+ // that we would like to try and avoid happens.
+ InitPublicMetaData(buffer);
+ }
+ }
+ else
+ {
+ // We've already handed out an Import object, and so we can't create a new pointer instance.
+ // Instead, we update the existing instance with new data.
+ UpdatePublicMetaDataFromRemote(buffer);
+ }
+
+ // if we haven't set it by this point UpdateInternalMetaData below is going to get us
+ // in an infinite loop of refreshing public metadata
+ _ASSERTE(m_pIMImport != NULL);
+
+ // Now that public metadata has changed, force internal metadata to update too.
+ // Public and internal metadata expose different access interfaces to the same underlying storage.
+ UpdateInternalMetaData();
+}
+
+//---------------------------------------------------------------------------------------
+// Updates the Internal MetaData object from the public importer. Lazily fetch public importer if needed.
+//
+// Assumptions:
+// Caller has cleared Internal metadata before even updating public metadata.
+// This way, if the caller fails halfway through updating the public metadata, we don't have
+// stale internal MetaData.
+void CordbModule::UpdateInternalMetaData()
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ // Caller should have already cleared it.
+ _ASSERTE(m_pInternalMetaDataImport == NULL);
+
+ // Get the importer. If it's currently null, this will go fetch it.
+ IMetaDataImport * pImport = GetMetaDataImporter(); // throws
+
+ // If both the public and the private interfaces are NULL on entry to this function, the call above will
+ // recursively call this function. This can happen if the caller calls GetInternalMD() directly
+ // instead of InitMetaData(). In this case, the above function call will have initialized the internal
+ // interface as well, so we need to check for it here.
+
+ if (m_pInternalMetaDataImport == NULL)
+ {
+ HRESULT hr = GetMDInternalInterfaceFromPublic(
+ pImport,
+ IID_IMDInternalImport,
+ reinterpret_cast<void**> (&m_pInternalMetaDataImport));
+
+ if (m_pInternalMetaDataImport == NULL)
+ {
+ ThrowHR(hr);
+ }
+ }
+
+ _ASSERTE(m_pInternalMetaDataImport != NULL);
+}
+
+// Initialize the public metadata.
+//
+// The debuggee already has a copy of the metadata in its process.
+// If we OpenScope on file as read-only, the OS file-system will share our metadata with the
+// copy in the debuggee. This can be a major perf win. FX metadata can be over 8 MB+.
+// OpenScopeOnMemory can't be shared b/c we allocate a buffer.
+HRESULT CordbModule::InitPublicMetaDataFromFile()
+{
+ INTERNAL_API_ENTRY(this->GetProcess());
+
+ // @dbgtodo metadata - In v3, we can't assume we have the same path namespace as the target (i.e. it could be
+ // a dump or remote), so we can't just try and open the file. Instead we have to rely on interfaces
+ // on the datatarget to map the metadata here. Note that this must also work for minidumps where the
+ // metadata isn't necessarily in the dump image.
+
+ // Get filename. There are 2 filenames to choose from:
+ // - ngen (if applicable).
+ // - non-ngen (aka "normal").
+ // By loading metadata out of the same OS file as loaded into the debuggee space, the OS can share those pages.
+ const WCHAR * szFullPathName = NULL;
+ bool fDebuggerLoadingNgen = false;
+ bool fDebuggeeLoadedNgen = false;
+ szFullPathName = GetNGenImagePath();
+
+ if(szFullPathName != NULL)
+ {
+ fDebuggeeLoadedNgen = true;
+ fDebuggerLoadingNgen = true;
+
+#ifndef FEATURE_PAL
+ // NGEN images are large and we shouldn't load them if they won't be shared, therefore fail the NGEN mapping and
+ // fallback to IL image if the debugger doesn't have the image loaded already.
+ // Its possible that the debugger would still load the NGEN image sometime in the future and we will miss a sharing
+ // opportunity. Its an acceptable loss from an imperfect heuristic.
+ if (NULL == WszGetModuleHandle(szFullPathName))
+#endif
+ {
+ szFullPathName = NULL;
+ fDebuggerLoadingNgen = false;
+ }
+
+ }
+
+ // If we don't have or decided not to load the NGEN image, check to see if IL image is available
+ if (!fDebuggerLoadingNgen)
+ {
+ szFullPathName = GetModulePath();
+ }
+
+ // If we are doing live debugging we shouldn't use metadata from an IL image because it doesn't match closely enough.
+ // In particular the RVAs for IL code headers are different between the two images which will cause all IL code and
+ // local var signature lookups to fail. With further work we could compensate for the RVAs by computing
+ // the image layout differences and adjusting the returned RVAs, but there may be other differences that need to be accounted
+ // for as well. If we did go that route we should do a binary diff across a variety of NGEN/IL image metadata blobs to
+ // get a concrete understanding of the format differences.
+ //
+ // This check should really be 'Are we OK with only getting the functionality level of mini-dump debugging?' but since we
+ // don't know the debugger's intent we guess whether or not we are doing dump debugging by checking if we are shimmed. Once
+ // the shim supports live debugging we should probably just stop automatically falling back to IL image and let the debugger
+ // decide via the ICorDebugMetadataLocator interface.
+ if(fDebuggeeLoadedNgen && !fDebuggerLoadingNgen && GetProcess()->GetShim()!=NULL)
+ {
+ // The IL image might be there, but we shouldn't use it for live debugging
+ return CORDBG_E_MISSING_METADATA;
+ }
+
+
+ // @dbgtodo metadata - This is really a CreateFile() call which we can't do. We must offload this to
+ // the data target for the dump-debugging scenarios.
+ //
+ // We're opening it as "read". If we QI for an IEmit interface (which we need for EnC),
+ // then the metadata engine will convert it to a "write" underneath us.
+ // We want "read" so that we can let the OS share the pages.
+ DWORD dwOpenFlags = 0;
+
+ // If we know we're never going to need to write (i.e. never do EnC), then we should indicate
+ // that to metadata by telling it this interface will always be read-only. By passing read-only,
+ // the metadata library will then also share the VM space for the image when the same image is
+ // opened multiple times for multiple AppDomains.
+ // We don't currently have a way to tell absolutely whether this module will support EnC, but we
+ // know that NGen modules NEVER support EnC, and NGen is the common case that eats up a lot of VM.
+ // So we'll use the heuristic of opening the metadata for all ngen images as read-only. Ideally
+ // we'd go even further here (perhaps even changing metadata to map only the region of the file it
+ // needs).
+ if (fDebuggerLoadingNgen)
+ {
+ dwOpenFlags = ofReadOnly | ofTrustedImage;
+ }
+
+ // This is the only place we ever validate that the file matches, because we're potentially
+ // loading the file from disk ourselves. We're doing this without giving the debugger a chance
+ // to do anything. We should never load a file that isn't an exact match.
+ return InitPublicMetaDataFromFile(szFullPathName, dwOpenFlags, true);
+}
+
+// We should only ever validate we have the correct file if it's a file we found ourselves.
+// We allow the debugger to choose their own policy with regard to using metadata from the IL image
+// when debugging an NI, or even intentionally using mismatched metadata if they like.
+HRESULT CordbModule::InitPublicMetaDataFromFile(const WCHAR * pszFullPathName,
+ DWORD dwOpenFlags,
+ bool validateFileInfo)
+{
+#ifdef FEATURE_PAL
+ // UNIXTODO: Some intricate details of file mapping don't work on Linux as on Windows.
+ // We have to revisit this and try to fix it for POSIX system.
+ return E_FAIL;
+#else
+ if (validateFileInfo)
+ {
+ // Check that we've got the right file to target.
+ // There's nothing to prevent some other file being copied in for live, and with
+ // dump debugging there's nothing to say that we're not on another machine where a different
+ // file is at the same path.
+ // If we can't validate we have a hold of the correct file, we should not open it.
+ // We will fall back on asking the debugger to get us the correct file, or copying
+ // target memory back to the debugger.
+ DWORD dwImageTimeStamp = 0;
+ DWORD dwImageSize = 0;
+ bool isNGEN = false; // unused
+ StringCopyHolder filePath;
+
+
+ _ASSERTE(!m_vmPEFile.IsNull());
+ // MetaData lookup favors the NGEN image, which is what we want here.
+ if (!this->GetProcess()->GetDAC()->GetMetaDataFileInfoFromPEFile(m_vmPEFile,
+ dwImageTimeStamp,
+ dwImageSize,
+ isNGEN,
+ &filePath))
+ {
+ LOG((LF_CORDB,LL_WARNING, "CM::IM: Couldn't get metadata info for file \"%s\"\n", pszFullPathName));
+ return CORDBG_E_MISSING_METADATA;
+ }
+
+ // If the timestamp and size don't match, then this is the wrong file!
+ // Map the file and check them.
+ HandleHolder hMDFile = WszCreateFile(pszFullPathName,
+ GENERIC_READ,
+ FILE_SHARE_READ,
+ NULL, // default security descriptor
+ OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL,
+ NULL);
+
+ if (hMDFile == INVALID_HANDLE_VALUE)
+ {
+ LOG((LF_CORDB,LL_WARNING, "CM::IM: Couldn't open file \"%s\" (GLE=%x)\n", pszFullPathName, GetLastError()));
+ return CORDBG_E_MISSING_METADATA;
+ }
+
+ DWORD dwFileHigh = 0;
+ DWORD dwFileLow = GetFileSize(hMDFile, &dwFileHigh);
+ if (dwFileLow == INVALID_FILE_SIZE)
+ {
+ LOG((LF_CORDB,LL_WARNING, "CM::IM: File \"%s\" had invalid size.\n", pszFullPathName));
+ return CORDBG_E_MISSING_METADATA;
+ }
+
+ _ASSERTE(dwFileHigh == 0);
+
+ HandleHolder hMap = WszCreateFileMapping(hMDFile, NULL, PAGE_READONLY, dwFileHigh, dwFileLow, NULL);
+ if (hMap == NULL)
+ {
+ LOG((LF_CORDB,LL_WARNING, "CM::IM: Couldn't create mapping of file \"%s\" (GLE=%x)\n", pszFullPathName, GetLastError()));
+ return CORDBG_E_MISSING_METADATA;
+ }
+
+ MapViewHolder hMapView = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);
+ if (hMapView == NULL)
+ {
+ LOG((LF_CORDB,LL_WARNING, "CM::IM: Couldn't map view of file \"%s\" (GLE=%x)\n", pszFullPathName, GetLastError()));
+ return CORDBG_E_MISSING_METADATA;
+ }
+
+ // Mapped as flat file, have PEDecoder go find what we want.
+ PEDecoder pedecoder(hMapView, (COUNT_T)dwFileLow);
+
+ if (!pedecoder.HasNTHeaders())
+ {
+ LOG((LF_CORDB,LL_WARNING, "CM::IM: \"%s\" did not have PE headers!\n", pszFullPathName));
+ return CORDBG_E_MISSING_METADATA;
+ }
+
+ if ((dwImageSize != pedecoder.GetVirtualSize()) ||
+ (dwImageTimeStamp != pedecoder.GetTimeDateStamp()))
+ {
+ LOG((LF_CORDB,LL_WARNING, "CM::IM: Validation of \"%s\" failed. "
+ "Expected size=%x, Expected timestamp=%x, Actual size=%x, Actual timestamp=%x\n",
+ pszFullPathName,
+ pedecoder.GetVirtualSize(),
+ pedecoder.GetTimeDateStamp(),
+ dwImageSize,
+ dwImageTimeStamp));
+ return CORDBG_E_MISSING_METADATA;
+ }
+
+ // All checks passed, go ahead and load this file for real.
+ }
+
+ // Get metadata Dispenser.
+ IMetaDataDispenserEx * pDisp = GetProcess()->GetDispenser();
+
+ HRESULT hr = pDisp->OpenScope(pszFullPathName, dwOpenFlags, IID_IMetaDataImport, (IUnknown**)&m_pIMImport);
+ _ASSERTE(SUCCEEDED(hr) == (m_pIMImport != NULL));
+
+ if (FAILED(hr))
+ {
+ // This should never happen in normal scenarios. It could happen if someone has renamed
+ // the assembly after it was opened by the debugee process, but this should be rare enough
+ // that we don't mind taking the perf. hit and loading from memory.
+ // @dbgtodo metadata - would this happen in the shadow-copy scenario?
+ LOG((LF_CORDB,LL_WARNING, "CM::IM: Couldn't open metadata in file \"%s\" (hr=%x)\n", pszFullPathName, hr));
+ }
+
+ return hr;
+#endif // FEATURE_PAL
+}
+
+//---------------------------------------------------------------------------------------
+// Initialize the public metadata.
+//
+// Arguments:
+// buffer - valid buffer into target containing the metadata.
+//
+// Assumptions:
+// This is an internal function which should only be called once to initialize the
+// metadata. Future attempts to re-initialize (in dynamic cases) should call code:CordbModule::UpdatePublicMetaDataFromRemote
+// After the public metadata is initialized, initialize private metadata via code:CordbModule::UpdateInternalMetaData
+//
+void CordbModule::InitPublicMetaData(TargetBuffer buffer)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ INTERNAL_API_ENTRY(this->GetProcess());
+ LOG((LF_CORDB,LL_INFO100000, "CM::IPM: initing with remote buffer 0x%p length 0x%x\n",
+ CORDB_ADDRESS_TO_PTR(buffer.pAddress), buffer.cbSize));
+ ULONG nMetaDataSize = buffer.cbSize;
+
+ if (nMetaDataSize == 0)
+ {
+ // We should always have metadata, and if we don't, we want to know.
+ // @dbgtodo metadata - we know metadata from dynamic modules doesn't work in V3
+ // (non-shim) cases yet.
+ // But our caller should already have handled that case.
+ SIMPLIFYING_ASSUMPTION(!"Error: missing the metadata");
+ return;
+ }
+
+ HRESULT hr = S_OK;
+
+ // Get metadata Dispenser.
+ IMetaDataDispenserEx * pDisp = GetProcess()->GetDispenser();
+
+ // copy it over from the remote process
+
+ CoTaskMemHolder<VOID> pMetaDataCopy;
+ CopyRemoteMetaData(buffer, pMetaDataCopy.GetAddr());
+
+
+ //
+ // Setup our metadata import object, m_pIMImport
+ //
+
+ // Save the old mode for restoration
+ VARIANT valueOld;
+ hr = pDisp->GetOption(MetaDataSetUpdate, &valueOld);
+ SIMPLIFYING_ASSUMPTION(!FAILED(hr));
+
+ // Set R/W mode so that we can update the metadata when
+ // we do EnC operations.
+ VARIANT valueRW;
+ V_VT(&valueRW) = VT_UI4;
+ V_I4(&valueRW) = MDUpdateFull;
+ hr = pDisp->SetOption(MetaDataSetUpdate, &valueRW);
+ SIMPLIFYING_ASSUMPTION(!FAILED(hr));
+
+ hr = pDisp->OpenScopeOnMemory(pMetaDataCopy,
+ nMetaDataSize,
+ ofTakeOwnership,
+ IID_IMetaDataImport,
+ reinterpret_cast<IUnknown**>( &m_pIMImport ));
+
+ // MetaData has taken ownership -don't free the memory
+ pMetaDataCopy.SuppressRelease();
+
+ // Immediately restore the old setting.
+ HRESULT hrRestore = pDisp->SetOption(MetaDataSetUpdate, &valueOld);
+ SIMPLIFYING_ASSUMPTION(!FAILED(hrRestore));
+
+ // Throw on errors.
+ IfFailThrow(hr);
+ IfFailThrow(hrRestore);
+
+ // Done!
+}
+
+//---------------------------------------------------------------------------------------
+// Update public MetaData by copying it from the target and updating our IMetaDataImport object.
+//
+// Arguments:
+// buffer - buffer into target space containing metadata blob
+//
+// Notes:
+// Useful for additional class-loads into a dynamic module. A new class means new metadata
+// and so we need to update the RS metadata to stay in sync with the left-side.
+//
+// This will call code:CordbModule::CopyRemoteMetaData to copy the remote buffer locally, and then
+// it can OpenScopeOnMemory().
+//
+void CordbModule::UpdatePublicMetaDataFromRemote(TargetBuffer bufferRemoteMetaData)
+{
+ CONTRACTL
+ {
+ // @dbgtodo metadata - think about the error semantics here. These fails during dispatching an event; so
+ // address this during event pipeline.
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ if (bufferRemoteMetaData.IsEmpty())
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ INTERNAL_API_ENTRY(this->GetProcess()); //
+ LOG((LF_CORDB,LL_INFO100000, "CM::UPMFR: updating with remote buffer 0x%p length 0x%x\n",
+ CORDB_ADDRESS_TO_PTR(bufferRemoteMetaData.pAddress), bufferRemoteMetaData.cbSize));
+ // We're re-initializing existing metadata.
+ _ASSERTE(m_pIMImport != NULL);
+
+
+ HRESULT hr = S_OK;
+
+ ULONG dwMetaDataSize = bufferRemoteMetaData.cbSize;
+
+ // First copy it from the remote process
+ CoTaskMemHolder<VOID> pLocalMetaDataPtr;
+ CopyRemoteMetaData(bufferRemoteMetaData, pLocalMetaDataPtr.GetAddr());
+
+ IMetaDataDispenserEx * pDisp = GetProcess()->GetDispenser();
+ _ASSERTE(pDisp != NULL); // throws on error.
+
+ LOG((LF_CORDB,LL_INFO100000, "CM::RI: converting to new metadata\n"));
+
+ // now verify that the metadata is valid by opening a temporary scope on the memory
+ {
+ ReleaseHolder<IMetaDataImport> pIMImport;
+ hr = pDisp->OpenScopeOnMemory(pLocalMetaDataPtr,
+ dwMetaDataSize,
+ 0,
+ IID_IMetaDataImport,
+ (IUnknown**)&pIMImport);
+ IfFailThrow(hr);
+ }
+
+ // We reopen on an existing instance, not create a new instance.
+ _ASSERTE(m_pIMImport != NULL); //
+
+ // Now tell our current IMetaDataImport object to re-initialize by swapping in the new memory block.
+ // This allows us to keep manipulating metadata objects on other threads without crashing.
+ // This will also invalidate an existing associated Internal MetaData.
+ hr = ReOpenMetaDataWithMemoryEx(m_pIMImport, pLocalMetaDataPtr, dwMetaDataSize, ofTakeOwnership );
+ IfFailThrow(hr);
+
+ // Success. MetaData now owns the metadata memory
+ pLocalMetaDataPtr.SuppressRelease();
+}
+
+//---------------------------------------------------------------------------------------
+// Copy metadata memory from the remote process into a newly allocated local buffer.
+//
+// Arguments:
+// pRemoteMetaDataPtr - pointer to remote buffer
+// dwMetaDataSize - size of buffer.
+// pLocalBuffer - holder to get local buffer.
+//
+// Returns:
+// pLocalBuffer may be allocated.
+// Throws on error (pLocalBuffer may contain garbage).
+// Else if successful, pLocalBuffer contains local copy of metadata.
+//
+// Notes:
+// This can copy metadata out for the dynamic case or the normal case.
+// Uses an allocator (CoTaskMemHolder) that lets us hand off the memory to the metadata.
+void CordbModule::CopyRemoteMetaData(
+ TargetBuffer buffer,
+ CoTaskMemHolder<VOID> * pLocalBuffer)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(pLocalBuffer != NULL);
+ _ASSERTE(!buffer.IsEmpty());
+
+ // Allocate space for the local copy of the metadata
+ // No need to zero out the memory since we'll fill it all here.
+ LPVOID pRawBuffer = CoTaskMemAlloc(buffer.cbSize);
+ if (pRawBuffer == NULL)
+ {
+ ThrowOutOfMemory();
+ }
+
+ pLocalBuffer->Assign(pRawBuffer);
+
+
+
+ // Copy the metadata from the left side
+ GetProcess()->SafeReadBuffer(buffer, (BYTE *)pRawBuffer);
+
+ return;
+}
+
+HRESULT CordbModule::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugModule)
+ {
+ *pInterface = static_cast<ICorDebugModule*>(this);
+ }
+ else if (id == IID_ICorDebugModule2)
+ {
+ *pInterface = static_cast<ICorDebugModule2*>(this);
+ }
+ else if (id == IID_ICorDebugModule3)
+ {
+ *pInterface = static_cast<ICorDebugModule3*>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugModule*>(this));
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+HRESULT CordbModule::GetProcess(ICorDebugProcess **ppProcess)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppProcess, ICorDebugProcess **);
+
+ *ppProcess = static_cast<ICorDebugProcess*> (GetProcess());
+ GetProcess()->ExternalAddRef();
+
+ return S_OK;
+}
+
+HRESULT CordbModule::GetBaseAddress(CORDB_ADDRESS *pAddress)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pAddress, CORDB_ADDRESS *);
+
+ *pAddress = m_PEBuffer.pAddress;
+ return S_OK;
+}
+
+HRESULT CordbModule::GetAssembly(ICorDebugAssembly **ppAssembly)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppAssembly, ICorDebugAssembly **);
+
+ *ppAssembly = static_cast<ICorDebugAssembly *> (m_pAssembly);
+ if (m_pAssembly != NULL)
+ {
+ m_pAssembly->ExternalAddRef();
+ }
+
+ return S_OK;
+}
+
+// Public implementation of ICorDebugModule::GetName,
+// wrapper around code:GetNameWorker (which throws).
+HRESULT CordbModule::GetName(ULONG32 cchName, ULONG32 *pcchName, __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[])
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this)
+ {
+ EX_TRY
+ {
+ hr = GetNameWorker(cchName, pcchName, szName);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ // GetNameWorker can use metadata. If it fails due to missing metadata, or if we fail to find expected
+ // target memory (dump debugging) then we should fall back to getting the file name without metadata.
+ if ((hr == CORDBG_E_MISSING_METADATA) ||
+ (hr == CORDBG_E_READVIRTUAL_FAILURE) ||
+ (hr == HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY)))
+ {
+ DWORD dwImageTimeStamp = 0; // unused
+ DWORD dwImageSize = 0; // unused
+ bool isNGEN = false;
+ StringCopyHolder filePath;
+
+ _ASSERTE(!m_vmPEFile.IsNull());
+ if (this->GetProcess()->GetDAC()->GetMetaDataFileInfoFromPEFile(m_vmPEFile,
+ dwImageTimeStamp,
+ dwImageSize,
+ isNGEN,
+ &filePath))
+ {
+ _ASSERTE(filePath.IsSet());
+
+ // Unfortunately, metadata lookup preferentially takes the ngen image - so in this case,
+ // we need to go back and get the IL image's name instead.
+ if ((isNGEN) &&
+ (this->GetProcess()->GetDAC()->GetILImageInfoFromNgenPEFile(m_vmPEFile,
+ dwImageTimeStamp,
+ dwImageSize,
+ &filePath)))
+ {
+ _ASSERTE(filePath.IsSet());
+ }
+
+ hr = CopyOutString(filePath, cchName, pcchName, szName);
+ }
+ }
+ }
+ PUBLIC_API_END(hr);
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+// Gets the module pretty name (may be filename or faked up name)
+//
+// Arguments:
+// cchName - count of characters in the szName buffer on input.
+// *pcchName - Optional Out parameter, which gets set to the fully requested size
+// (not just how many characters are written).
+// szName - buffer to get name.
+//
+// Returns:
+// S_OK on success.
+// S_FALSE if we fabricate the name.
+// Return failing HR (on common errors) or Throw on exceptional errors.
+//
+// Note:
+// Filename isn't necessarily the same as the module name in the metadata.
+//
+HRESULT CordbModule::GetNameWorker(ULONG32 cchName, ULONG32 *pcchName, __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[])
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+ HRESULT hr = S_OK;
+ const WCHAR * szTempName = NULL;
+
+ ALLOW_DATATARGET_MISSING_MEMORY(
+ szTempName = GetModulePath();
+ );
+
+#if defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ // To support VS when debugging remotely we act like the Compact Framework and return the assembly name
+ // when asked for the name of an in-memory module.
+ if (szTempName == NULL)
+ {
+ IMetaDataAssemblyImport *pAssemblyImport = NULL;
+ if (SUCCEEDED(hr = GetMetaDataImporter()->QueryInterface(IID_IMetaDataAssemblyImport, (void**)&pAssemblyImport)))
+ {
+ mdAssembly mda = TokenFromRid(1, mdtAssembly);
+ hr = pAssemblyImport->GetAssemblyProps(mda, // [IN] The Assembly for which to get the properties.
+ NULL, // [OUT] Pointer to the Originator blob.
+ NULL, // [OUT] Count of bytes in the Originator Blob.
+ NULL, // [OUT] Hash Algorithm.
+ szName, // [OUT] Buffer to fill with name.
+ cchName, // [IN] Size of buffer in wide chars.
+ (ULONG*)pcchName, // [OUT] Actual # of wide chars in name.
+ NULL, // [OUT] Assembly MetaData.
+ NULL); // [OUT] Flags.
+
+ pAssemblyImport->Release();
+
+ return hr;
+ }
+
+ // reset hr
+ hr = S_OK;
+ }
+
+
+#endif // FEATURE_DBGIPC_TRANSPORT_DI
+
+
+ EX_TRY_ALLOW_DATATARGET_MISSING_MEMORY
+ {
+ StringCopyHolder buffer;
+ // If the module has no file name, then we'll fabricate a fake name
+ if (!szTempName)
+ {
+ // On MiniDumpNormal, if the debugger can't find the module then there's no way we will
+ // find metadata.
+ hr = HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY);
+
+ // Tempting to use the metadata-scope name, but that's a regression from Whidbey. For manifest modules,
+ // the metadata scope name is not initialized with the string the user supplied to create the
+ // dynamic assembly. So we call into the runtime to use CLR heuristics to get a more accurate name.
+ m_pProcess->GetDAC()->GetModuleSimpleName(m_vmModule, &buffer);
+ _ASSERTE(buffer.IsSet());
+ szTempName = buffer;
+ // Note that we considered returning S_FALSE for fabricated names like this, but that's a breaking
+ // change from Whidbey that is known to trigger bugs in vS. If a debugger wants to differentiate
+ // real path names from fake simple names, we'll just have to add a new API with the right semantics.
+ }
+
+ hr = CopyOutString(szTempName, cchName, pcchName, szName);
+ }
+ EX_END_CATCH_ALLOW_DATATARGET_MISSING_MEMORY
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+// Gets actual name of loaded module. (no faked names)
+//
+// Returns:
+// string for full path to module name. This is a file that can be opened.
+// NULL if name is not available (such as in some dynamic module cases)
+// Throws if failed accessing target
+//
+// Notes:
+// We avoid using the method name "GetModuleFileName" because winbase.h #defines that
+// token (along with many others) to have an A or W suffix.
+const WCHAR * CordbModule::GetModulePath()
+{
+ // Lazily initialize. Module filenames cannot change, and so once
+ // we've retrieved this successfully, it's stored for good.
+ if (!m_strModulePath.IsSet())
+ {
+ IDacDbiInterface * pDac = m_pProcess->GetDAC(); // throws
+ pDac->GetModulePath(m_vmModule, &m_strModulePath); // throws
+ _ASSERTE(m_strModulePath.IsSet());
+ }
+
+ if (m_strModulePath.IsEmpty())
+ {
+ return NULL; // module has no filename
+ }
+ return m_strModulePath;
+}
+
+//---------------------------------------------------------------------------------------
+// Get and caches ngen image path.
+//
+// Returns:
+// Null-terminated string to ngen image path.
+// NULL if there is no ngen filename (eg, file is not ngenned).
+// Throws on error (such as inability to read the path from the target).
+//
+// Notes:
+// This can be used to get the path to find metadata. For ngenned images,
+// the IL (and associated metadata) may not be loaded, so we may want to get the
+// metadata out of the ngen image.
+const WCHAR * CordbModule::GetNGenImagePath()
+{
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // Lazily initialize. Module filenames cannot change, and so once
+ // we've retrieved this successfully, it's stored for good.
+ if (!m_strNGenImagePath.IsSet())
+ {
+ IDacDbiInterface * pDac = m_pProcess->GetDAC(); // throws
+ BOOL fNonEmpty = pDac->GetModuleNGenPath(m_vmModule, &m_strNGenImagePath); // throws
+ (void)fNonEmpty; //prevent "unused variable" error from GCC
+ _ASSERTE(m_strNGenImagePath.IsSet() && (m_strNGenImagePath.IsEmpty() == !fNonEmpty));
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ if (FAILED(hr) ||
+ m_strNGenImagePath == NULL ||
+ m_strNGenImagePath.IsEmpty())
+ {
+ return NULL; // module has no ngen filename
+ }
+ return m_strNGenImagePath;
+}
+
+// Implementation of ICorDebugModule::EnableJITDebugging
+// See also code:CordbModule::SetJITCompilerFlags
+HRESULT CordbModule::EnableJITDebugging(BOOL bTrackJITInfo, BOOL bAllowJitOpts)
+{
+ // Leftside will enforce that this is a valid time to change jit flags.
+ // V1.0 behavior allowed setting these in the middle of a module's lifetime, which meant
+ // that different methods throughout the module may have been jitted differently.
+ // Since V2, this has to be set when the module is first loaded, before anything is jitted.
+
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ DWORD dwFlags = CORDEBUG_JIT_DEFAULT;
+
+ // Since V2, bTrackJITInfo is the default and cannot be turned off.
+ if (!bAllowJitOpts)
+ {
+ dwFlags |= CORDEBUG_JIT_DISABLE_OPTIMIZATION;
+ }
+ return SetJITCompilerFlags(dwFlags);
+}
+
+HRESULT CordbModule::EnableClassLoadCallbacks(BOOL bClassLoadCallbacks)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_ALLOW_LIVE_DO_STOPGO(GetProcess());
+
+ // You must receive ClassLoad callbacks for dynamic modules so that we can keep the metadata up-to-date on the Right
+ // Side. Therefore, we refuse to turn them off for all dynamic modules (they were forced on when the module was
+ // loaded on the Left Side.)
+ if (m_fDynamic && !bClassLoadCallbacks)
+ return E_INVALIDARG;
+
+ if (m_vmDomainFile.IsNull())
+ return E_UNEXPECTED;
+
+ // Send a Set Class Load Flag event to the left side. There is no need to wait for a response, and this can be
+ // called whether or not the process is synchronized.
+ CordbProcess *pProcess = GetProcess();
+
+ DebuggerIPCEvent event;
+ pProcess->InitIPCEvent(&event,
+ DB_IPCE_SET_CLASS_LOAD_FLAG,
+ false,
+ (GetAppDomain()->GetADToken()));
+ event.SetClassLoad.vmDomainFile = this->m_vmDomainFile;
+ event.SetClassLoad.flag = (bClassLoadCallbacks == TRUE);
+
+ HRESULT hr = pProcess->m_cordb->SendIPCEvent(pProcess, &event,
+ sizeof(DebuggerIPCEvent));
+ hr = WORST_HR(hr, event.hr);
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// Public implementation of ICorDebugModule::GetFunctionFromToken
+// Get the CordbFunction matches this token / module pair.
+// Each time a function is Enc-ed, it gets its own CordbFunction object.
+// This will return the latest EnC version of the function for this Module,Token pair.
+HRESULT CordbModule::GetFunctionFromToken(mdMethodDef token,
+ ICorDebugFunction **ppFunction)
+{
+ // This is not reentrant. DBI should call code:CordbModule::LookupOrCreateFunctionLatestVersion instead.
+ PUBLIC_API_ENTRY(this);
+ ATT_ALLOW_LIVE_DO_STOPGO(GetProcess()); // @todo - can this be RequiredStop?
+
+
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppFunction, ICorDebugFunction **);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+
+ // Check token is valid.
+ if ((token == mdMethodDefNil) ||
+ (!GetMetaDataImporter()->IsValidToken(token)))
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ CordbFunction * pFunction = LookupOrCreateFunctionLatestVersion(token);
+
+ *ppFunction = static_cast<ICorDebugFunction*> (pFunction);
+ pFunction->ExternalAddRef();
+
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+HRESULT CordbModule::GetFunctionFromRVA(CORDB_ADDRESS rva,
+ ICorDebugFunction **ppFunction)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppFunction, ICorDebugFunction **);
+
+ return E_NOTIMPL;
+}
+
+HRESULT CordbModule::LookupClassByToken(mdTypeDef token,
+ CordbClass **ppClass)
+{
+ INTERNAL_API_ENTRY(this->GetProcess()); //
+ FAIL_IF_NEUTERED(this);
+
+ HRESULT hr = S_OK;
+ EX_TRY // @dbgtodo exceptions - push this up
+ {
+ *ppClass = NULL;
+
+ if ((token == mdTypeDefNil) || (TypeFromToken(token) != mdtTypeDef))
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock()); // @dbgtodo synchronization - Push this up
+
+ CordbClass *pClass = m_classes.GetBase(token);
+ if (pClass == NULL)
+ {
+ // Validate the token.
+ if (!GetMetaDataImporter()->IsValidToken(token))
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ RSInitHolder<CordbClass> pClassInit(new CordbClass(this, token));
+ pClass = pClassInit.TransferOwnershipToHash(&m_classes);
+ }
+
+ *ppClass = pClass;
+
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+HRESULT CordbModule::GetClassFromToken(mdTypeDef token,
+ ICorDebugClass **ppClass)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_ALLOW_LIVE_DO_STOPGO(this->GetProcess()); // @todo - could this be RequiredStopped?
+ VALIDATE_POINTER_TO_OBJECT(ppClass, ICorDebugClass **);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ CordbClass *pClass = NULL;
+ *ppClass = NULL;
+
+ // Validate the token.
+ if (!GetMetaDataImporter()->IsValidToken(token))
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ hr = LookupClassByToken(token, &pClass);
+ IfFailThrow(hr);
+
+ *ppClass = static_cast<ICorDebugClass*> (pClass);
+ pClass->ExternalAddRef();
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+HRESULT CordbModule::CreateBreakpoint(ICorDebugModuleBreakpoint **ppBreakpoint)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppBreakpoint, ICorDebugModuleBreakpoint **);
+
+ return E_NOTIMPL;
+}
+
+//
+// Return the token for the Module table entry for this object. The token
+// may then be passed to the meta data import api's.
+//
+HRESULT CordbModule::GetToken(mdModule *pToken)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pToken, mdModule *);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ hr = GetMetaDataImporter()->GetModuleFromScope(pToken);
+ IfFailThrow(hr);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+
+// public implementation for ICorDebugModule::GetMetaDataInterface
+// Return a meta data interface pointer that can be used to examine the
+// meta data for this module.
+HRESULT CordbModule::GetMetaDataInterface(REFIID riid, IUnknown **ppObj)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppObj, IUnknown **);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // QI the importer that we already have and return the result.
+ hr = GetMetaDataImporter()->QueryInterface(riid, (void**)ppObj);
+ IfFailThrow(hr);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// LookupFunctionLatestVersion finds the latest cached version of an existing CordbFunction
+// in the given module. If the function doesn't exist, it returns NULL.
+//
+// Arguments:
+// funcMetaDataToken - methoddef token for function to lookup
+//
+//
+// Notes:
+// If no CordbFunction instance was cached, then this returns NULL.
+// use code:CordbModule::LookupOrCreateFunctionLatestVersion to do a lookup that will
+// populate the cache if needed.
+CordbFunction* CordbModule::LookupFunctionLatestVersion(mdMethodDef funcMetaDataToken)
+{
+ INTERNAL_API_ENTRY(this);
+ return m_functions.GetBase(funcMetaDataToken);
+}
+
+
+//-----------------------------------------------------------------------------
+// Lookup (or create) the CordbFunction for the latest EnC version.
+//
+// Arguments:
+// funcMetaDataToken - methoddef token for function to lookup
+//
+// Returns:
+// CordbFunction instance for that token. This will create an instance if needed, and so never returns null.
+// Throws on critical error.
+//
+// Notes:
+// This creates the latest EnC version. Use code:CordbModule::LookupOrCreateFunction to do an
+// enc-version aware function lookup.
+//
+CordbFunction* CordbModule::LookupOrCreateFunctionLatestVersion(mdMethodDef funcMetaDataToken)
+{
+ INTERNAL_API_ENTRY(this);
+ CordbFunction * pFunction = m_functions.GetBase(funcMetaDataToken);
+ if (pFunction != NULL)
+ {
+ return pFunction;
+ }
+
+ // EnC adds each version to the hash. So if the hash lookup fails, then it must not be an EnC case,
+ // and so we can use the default version number.
+ return CreateFunction(funcMetaDataToken, CorDB_DEFAULT_ENC_FUNCTION_VERSION);
+}
+
+//-----------------------------------------------------------------------------
+// LookupOrCreateFunction finds an existing version of CordbFunction in the given module.
+// If the function doesn't exist, it creates it.
+//
+// The outgoing function is not yet fully inititalized. For eg, the Class field is not set.
+// However, ICorDebugFunction::GetClass() will check that and lazily initialize the field.
+//
+// Throws on error.
+//
+CordbFunction * CordbModule::LookupOrCreateFunction(mdMethodDef funcMetaDataToken, SIZE_T enCVersion)
+{
+ INTERNAL_API_ENTRY(this);
+
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+
+ CordbFunction * pFunction = m_functions.GetBase(funcMetaDataToken);
+
+ // special case non-existance as need to add to the hash table too
+ if (pFunction == NULL)
+ {
+ // EnC adds each version to the hash. So if the hash lookup fails,
+ // then it must not be an EnC case.
+ return CreateFunction(funcMetaDataToken, enCVersion);
+ }
+
+ // linked list sorted with most recent version at front. Version numbers correspond
+ // to actual edit count against the module, so version numbers not necessarily contiguous.
+ // Any valid EnC version must already exist as we would have created it on the ApplyChanges
+ for (CordbFunction *pf=pFunction; pf != NULL; pf = pf->GetPrevVersion())
+ {
+ if (pf->GetEnCVersionNumber() == enCVersion)
+ {
+ return pf;
+ }
+ }
+
+ _ASSERTE(!"Couldn't find EnC version of function\n");
+ ThrowHR(E_FAIL);
+}
+
+HRESULT CordbModule::IsDynamic(BOOL *pDynamic)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pDynamic, BOOL *);
+
+ (*pDynamic) = m_fDynamic;
+
+ return S_OK;
+}
+
+BOOL CordbModule::IsDynamic()
+{
+ return m_fDynamic;
+}
+
+
+HRESULT CordbModule::IsInMemory(BOOL *pInMemory)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pInMemory, BOOL *);
+
+ (*pInMemory) = m_fInMemory;
+
+ return S_OK;
+}
+
+HRESULT CordbModule::GetGlobalVariableValue(mdFieldDef fieldDef,
+ ICorDebugValue **ppValue)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this->GetProcess());
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+
+ if (m_pClass == NULL)
+ {
+ CordbClass * pGlobalClass = NULL;
+ hr = LookupClassByToken(COR_GLOBAL_PARENT_TOKEN, &pGlobalClass);
+ IfFailThrow(hr);
+
+ m_pClass.Assign(pGlobalClass);
+ _ASSERTE(m_pClass != NULL);
+ }
+
+ hr = m_pClass->GetStaticFieldValue(fieldDef, NULL, ppValue);
+ IfFailThrow(hr);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+
+
+//
+// CreateFunction creates a new function from the given information and
+// adds it to the module.
+//
+CordbFunction * CordbModule::CreateFunction(mdMethodDef funcMetaDataToken, SIZE_T enCVersion)
+{
+ INTERNAL_API_ENTRY(this);
+
+ // In EnC cases, the token may not yet be valid. We may be caching the CordbFunction
+ // for a token for an added method before the metadata is updated on the RS.
+ // We rely that our caller has done token validation.
+
+ // Create a new CordbFunction object or throw.
+ RSInitHolder<CordbFunction> pFunction(new CordbFunction(this, funcMetaDataToken, enCVersion)); // throws
+ CordbFunction * pCopy = pFunction.TransferOwnershipToHash(&m_functions);
+ return pCopy;
+}
+
+#ifdef EnC_SUPPORTED
+//---------------------------------------------------------------------------------------
+//
+// Creates a new CordbFunction object to represent this new version of a function and
+// updates the module's function collection to mark this as the latest version.
+//
+// Arguments:
+// funcMetaDataToken - the functions methodDef token in this module
+// enCVerison - The new version number of this function
+// ppFunction - Output param for the new instance - optional
+//
+// Assumptions:
+// Assumes the specified version of this function doesn't already exist (i.e. enCVersion
+// is newer than all existing versions).
+//
+HRESULT CordbModule::UpdateFunction(mdMethodDef funcMetaDataToken,
+ SIZE_T enCVersion,
+ CordbFunction** ppFunction)
+{
+ INTERNAL_API_ENTRY(this);
+ if (ppFunction)
+ *ppFunction = NULL;
+
+ _ASSERTE(funcMetaDataToken);
+
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+
+ // pOldVersion is the 2nd newest version
+ CordbFunction* pOldVersion = LookupFunctionLatestVersion(funcMetaDataToken);
+
+ // if don't have an old version, then create a default versioned one as will most likely
+ // go looking for it later and easier to put it in now than have code to insert it later.
+ if (!pOldVersion)
+ {
+ LOG((LF_ENC, LL_INFO10000, "CM::UF: adding %8.8x with version %d\n", funcMetaDataToken, enCVersion));
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ pOldVersion = CreateFunction(funcMetaDataToken, CorDB_DEFAULT_ENC_FUNCTION_VERSION);
+ }
+ EX_CATCH_HRESULT(hr);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+ }
+
+ // This method should not be called for versions that already exist
+ _ASSERTE( enCVersion > pOldVersion->GetEnCVersionNumber());
+
+ LOG((LF_ENC, LL_INFO10000, "CM::UF: updating %8.8x with version %d\n", funcMetaDataToken, enCVersion));
+ // Create a new function object.
+ CordbFunction * pNewVersion = new (nothrow) CordbFunction(this, funcMetaDataToken, enCVersion);
+
+ if (pNewVersion == NULL)
+ return E_OUTOFMEMORY;
+
+ // Chain the 2nd most recent version onto this instance (this will internal addref).
+ pNewVersion->SetPrevVersion(pOldVersion);
+
+ // Add the function to the Module's hash of all functions.
+ HRESULT hr = m_functions.SwapBase(pOldVersion, pNewVersion);
+
+ if (FAILED(hr))
+ {
+ delete pNewVersion;
+ return hr;
+ }
+
+ // Do cleanup for function which is no longer the latest version
+ pNewVersion->GetPrevVersion()->MakeOld();
+
+ if (ppFunction)
+ *ppFunction = pNewVersion;
+
+ return hr;
+}
+#endif // EnC_SUPPORTED
+
+
+HRESULT CordbModule::LookupOrCreateClass(mdTypeDef classMetaDataToken,CordbClass** ppClass)
+{
+ INTERNAL_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock()); // @dbgtodo exceptions synchronization-
+ // Push this lock up, convert to exceptions.
+
+ HRESULT hr = S_OK;
+ *ppClass = LookupClass(classMetaDataToken);
+ if (*ppClass == NULL)
+ {
+ hr = CreateClass(classMetaDataToken,ppClass);
+ if (!SUCCEEDED(hr))
+ {
+ return hr;
+ }
+ _ASSERTE(*ppClass != NULL);
+ }
+ return hr;
+}
+
+//
+// LookupClass finds an existing CordbClass in the given module.
+// If the class doesn't exist, it returns NULL.
+//
+CordbClass* CordbModule::LookupClass(mdTypeDef classMetaDataToken)
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+ return m_classes.GetBase(classMetaDataToken);
+}
+
+//
+// CreateClass creates a new class from the given information and
+// adds it to the module.
+//
+HRESULT CordbModule::CreateClass(mdTypeDef classMetaDataToken,
+ CordbClass** ppClass)
+{
+ INTERNAL_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+
+ CordbClass* pClass = new (nothrow) CordbClass(this, classMetaDataToken);
+
+ if (pClass == NULL)
+ return E_OUTOFMEMORY;
+
+ HRESULT hr = m_classes.AddBase(pClass);
+
+ if (SUCCEEDED(hr))
+ *ppClass = pClass;
+ else
+ delete pClass;
+
+ if (classMetaDataToken == COR_GLOBAL_PARENT_TOKEN)
+ {
+ _ASSERTE( m_pClass == NULL ); //redundant create
+ m_pClass.Assign(pClass);
+ }
+
+ return hr;
+}
+
+
+// Resolve a type-ref from this module to a CordbClass
+//
+// Arguments:
+// token - a Type Ref in this module's scope.
+// ppClass - out parameter to get the class we resolve to.
+//
+// Returns:
+// S_OK on success.
+// CORDBG_E_CLASS_NOT_LOADED is the TypeRef is not yet resolved because the type it will refer
+// to is not yet loaded.
+//
+// Notes:
+// In general, a TypeRef refers to a type in another module. (Although as a corner case, it could
+// refer to this module too). This resolves a TypeRef within the current module's scope to a
+// (TypeDef, metadata scope), which is in turn encapsulated as a CordbClass.
+//
+// A TypeRef has a resolution scope (ModuleRef or AssemblyRef) and string name for the type
+// within that scope. Resolving means:
+// 1. Determining the actual metadata scope loaded for the resolution scope.
+// See also code:CordbModule::ResolveAssemblyInternal
+// If the resolved module hasn't been loaded yet, the resolution will fail.
+// 2. Doing a string lookup of the TypeRef's name within that resolved scope to find the TypeDef.
+// 3. Returning the (resolved scope, TypeDef) pair.
+//
+HRESULT CordbModule::ResolveTypeRef(mdTypeRef token, CordbClass **ppClass)
+{
+ FAIL_IF_NEUTERED(this);
+ INTERNAL_SYNC_API_ENTRY(GetProcess()); //
+
+ CordbProcess * pProcess = GetProcess();
+
+ _ASSERTE((pProcess->GetShim() == NULL) || pProcess->GetSynchronized());
+
+
+ if ((token == mdTypeRefNil) || (TypeFromToken(token) != mdtTypeRef))
+ {
+ return E_INVALIDARG;
+ }
+
+ if (m_vmDomainFile.IsNull() || m_pAppDomain == NULL)
+ {
+ return E_UNEXPECTED;
+ }
+
+ HRESULT hr = S_OK;
+ *ppClass = NULL;
+ EX_TRY
+ {
+ TypeRefData inData = {m_vmDomainFile, token};
+ TypeRefData outData;
+
+ {
+ RSLockHolder lockHolder(pProcess->GetProcessLock());
+ pProcess->GetDAC()->ResolveTypeReference(&inData, &outData);
+ }
+
+ CordbModule * pModule = m_pAppDomain->LookupOrCreateModule(outData.vmDomainFile);
+ IfFailThrow(pModule->LookupClassByToken(outData.typeToken, ppClass));
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+
+} // CordbModule::ResolveTypeRef
+
+// Resolve a type ref or def to a CordbClass
+//
+// Arguments:
+// token - a mdTypeDef or mdTypeRef in this module's scope to be resolved
+// ppClass - out parameter to get the CordbClass for this type
+//
+// Notes:
+// See code:CordbModule::ResolveTypeRef for more details.
+HRESULT CordbModule::ResolveTypeRefOrDef(mdToken token, CordbClass **ppClass)
+{
+ FAIL_IF_NEUTERED(this);
+ INTERNAL_SYNC_API_ENTRY(this->GetProcess()); //
+
+ if ((token == mdTypeRefNil) ||
+ (TypeFromToken(token) != mdtTypeRef && TypeFromToken(token) != mdtTypeDef))
+ return E_INVALIDARG;
+
+ if (TypeFromToken(token)==mdtTypeRef)
+ {
+ // It's a type-ref. That means the type is defined in another module.
+ // That other module is determined at runtime by Fusion / Loader policy. So we need to
+ // ultimately ask the runtime which module was actually loaded.
+ return ( ResolveTypeRef(token, ppClass) );
+ }
+ else
+ {
+ // It's a type-def. This is the easy case because the type is defined in this same module.
+ return ( LookupClassByToken(token, ppClass) );
+ }
+
+}
+
+//
+// GetSize returns the size of the module.
+//
+HRESULT CordbModule::GetSize(ULONG32 *pcBytes)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pcBytes, ULONG32 *);
+
+ *pcBytes = m_PEBuffer.cbSize;
+
+ return S_OK;
+}
+
+CordbAssembly *CordbModule::GetCordbAssembly()
+{
+ INTERNAL_API_ENTRY(this);
+ return m_pAssembly;
+}
+
+
+// This is legacy from the aborted V1 EnC attempt - not used in V2 EnC support
+HRESULT CordbModule::GetEditAndContinueSnapshot(
+ ICorDebugEditAndContinueSnapshot **ppEditAndContinueSnapshot)
+{
+ return E_NOTIMPL;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Requests that an edit be applied to the module for edit and continue and updates
+// the right-side state and metadata.
+//
+// Arguments:
+// cbMetaData - number of bytes in pbMetaData
+// pbMetaData - a delta metadata blob describing the metadata edits to be made
+// cbIL - number of bytes in pbIL
+// pbIL - a new method body stream containing all of the method body information
+// (IL, EH info, etc) for edited and added methods.
+//
+// Return Value:
+// S_OK on success, various errors on failure
+//
+// Notes:
+//
+//
+// This applies the same changes to the RS's copy of the metadata that the left-side will apply to
+// it's copy of the metadata. see code:EditAndContinueModule::ApplyEditAndContinue
+//
+HRESULT CordbModule::ApplyChanges(ULONG cbMetaData,
+ BYTE pbMetaData[],
+ ULONG cbIL,
+ BYTE pbIL[])
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+#ifdef EnC_SUPPORTED
+ // We enable EnC back in code:CordbModule::SetJITCompilerFlags.
+ // If EnC isn't enabled, then we'll fail in the LS when we try to ApplyChanges.
+ // We'd expect a well-behaved debugger to never actually land here.
+
+
+ LOG((LF_CORDB,LL_INFO10000, "CP::AC: applying changes"));
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(pbMetaData,
+ BYTE,
+ cbMetaData,
+ true,
+ true);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(pbIL,
+ BYTE,
+ cbIL,
+ true,
+ true);
+
+ HRESULT hr;
+ RSExtSmartPtr<IUnknown> pUnk;
+ RSExtSmartPtr<IMDInternalImport> pMDImport;
+ RSExtSmartPtr<IMDInternalImport> pMDImport2;
+
+ //
+ // Edit was successful - update the right-side state to reflect the edit
+ //
+
+ ++m_EnCCount;
+
+ // apply the changes to our copy of the metadata
+
+ _ASSERTE(m_pIMImport != NULL); // must have metadata at this point in EnC
+ IfFailGo(m_pIMImport->QueryInterface(IID_IUnknown, (void**)&pUnk));
+
+ IfFailGo(GetMDInternalInterfaceFromPublic(pUnk, IID_IMDInternalImport,
+ (void **)&pMDImport));
+
+ // The left-side will call this same method on its copy of the metadata.
+ hr = pMDImport->ApplyEditAndContinue(pbMetaData, cbMetaData, &pMDImport2);
+ if (pMDImport2 != NULL)
+ {
+ // ApplyEditAndContinue() expects IMDInternalImport**, but we give it RSExtSmartPtr<IMDInternalImport>
+ // Silent cast of RSExtSmartPtr to IMDInternalImport* leads to assignment of a raw pointer
+ // without calling AddRef(), thus we need to do it manually.
+
+ // @todo - ApplyEditAndContinue should probably AddRef the out parameter.
+ pMDImport2->AddRef();
+ }
+ IfFailGo(hr);
+
+
+ // We're about to get a new importer object, so release the old one.
+ m_pIMImport.Clear();
+ IfFailGo(GetMDPublicInterfaceFromInternal(pMDImport2, IID_IMetaDataImport, (void **)&m_pIMImport));
+ // set the new RVA value
+
+ // Send the delta over to the debugee and request that it apply the edit
+ IfFailGo( ApplyChangesInternal(cbMetaData, pbMetaData, cbIL, pbIL) );
+
+ EX_TRY
+ {
+
+ m_pInternalMetaDataImport.Clear();
+ UpdateInternalMetaData();
+ }
+ EX_CATCH_HRESULT(hr);
+ _ASSERTE(SUCCEEDED(hr));
+
+ErrExit:
+ // MetaData interface pointers will be automatically released via SmartPtr dtors.
+
+ // @todo : prevent further execution of program
+ return hr;
+#else
+ return E_NOTIMPL;
+#endif
+}
+
+
+
+
+//---------------------------------------------------------------------------------------
+//
+// Requests that an edit be applied to the module for edit and continue and updates
+// some right-side state, but does not update our copy of the metadata.
+//
+// Arguments:
+// cbMetaData - number of bytes in pbMetaData
+// pbMetaData - a delta metadata blob describing the metadata edits to be made
+// cbIL - number of bytes in pbIL
+// pbIL - a new method body stream containing all of the method body information
+// (IL, EH info, etc) for edited and added methods.
+//
+// Return Value:
+// S_OK on success, various errors on failure
+//
+HRESULT CordbModule::ApplyChangesInternal(ULONG cbMetaData,
+ BYTE pbMetaData[],
+ ULONG cbIL,
+ BYTE pbIL[])
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_ENC,LL_INFO100, "CordbProcess::ApplyChangesInternal\n"));
+
+ FAIL_IF_NEUTERED(this);
+ INTERNAL_SYNC_API_ENTRY(this->GetProcess()); //
+
+ if (m_vmDomainFile.IsNull())
+ return E_UNEXPECTED;
+
+#ifdef EnC_SUPPORTED
+ HRESULT hr;
+
+ void * pRemoteBuf = NULL;
+
+ EX_TRY
+ {
+
+ // Create and initialize the event as synchronous
+ // We'll be sending a NULL appdomain pointer since the individual modules
+ // will contains pointers to their respective A.D.s
+ DebuggerIPCEvent event;
+ GetProcess()->InitIPCEvent(&event, DB_IPCE_APPLY_CHANGES, false, VMPTR_AppDomain::NullPtr());
+
+ event.ApplyChanges.vmDomainFile = this->m_vmDomainFile;
+
+ // Have the left-side create a buffer for us to store the delta into
+ ULONG cbSize = cbMetaData+cbIL;
+ TargetBuffer tbFull = GetProcess()->GetRemoteBuffer(cbSize);
+ pRemoteBuf = CORDB_ADDRESS_TO_PTR(tbFull.pAddress);
+
+ TargetBuffer tbMetaData = tbFull.SubBuffer(0, cbMetaData); // 1st half
+ TargetBuffer tbIL = tbFull.SubBuffer(cbMetaData); // 2nd half
+
+ // Copy the delta metadata over to the debugee
+
+ GetProcess()->SafeWriteBuffer(tbMetaData, pbMetaData); // throws
+ GetProcess()->SafeWriteBuffer(tbIL, pbIL); // throws
+
+ // Send a synchronous event requesting the debugee apply the edit
+ event.ApplyChanges.pDeltaMetadata = tbMetaData.pAddress;
+ event.ApplyChanges.cbDeltaMetadata = tbMetaData.cbSize;
+ event.ApplyChanges.pDeltaIL = tbIL.pAddress;
+ event.ApplyChanges.cbDeltaIL = tbIL.cbSize;
+
+ LOG((LF_ENC,LL_INFO100, "CordbProcess::ApplyChangesInternal sending event\n"));
+ hr = GetProcess()->SendIPCEvent(&event, sizeof(event));
+ hr = WORST_HR(hr, event.hr);
+ IfFailThrow(hr);
+
+ // Allocate space for the return event.
+ // We always copy over the whole buffer size which is bigger than sizeof(DebuggerIPCEvent)
+ // This seems ugly, in this case we know the exact size of the event we want to read
+ // why copy over all the extra data?
+ DebuggerIPCEvent *retEvent = (DebuggerIPCEvent *) _alloca(CorDBIPC_BUFFER_SIZE);
+
+ {
+ //
+ // Wait for events to return from the RC. We expect zero or more add field,
+ // add function or update function events and one completion event.
+ //
+ while (TRUE)
+ {
+ hr = GetProcess()->m_cordb->WaitForIPCEventFromProcess(GetProcess(),
+ GetAppDomain(),
+ retEvent);
+ IfFailThrow(hr);
+
+ if (retEvent->type == DB_IPCE_APPLY_CHANGES_RESULT)
+ {
+ // Done receiving update events
+ hr = retEvent->ApplyChangesResult.hr;
+ LOG((LF_CORDB, LL_INFO1000, "[%x] RCET::DRCE: EnC apply changes result %8.8x.\n", hr));
+ break;
+ }
+
+ _ASSERTE(retEvent->type == DB_IPCE_ENC_UPDATE_FUNCTION ||
+ retEvent->type == DB_IPCE_ENC_ADD_FUNCTION ||
+ retEvent->type == DB_IPCE_ENC_ADD_FIELD);
+ LOG((LF_CORDB, LL_INFO1000, "[%x] RCET::DRCE: EnC %s %8.8x to version %d.\n",
+ GetCurrentThreadId(),
+ retEvent->type == DB_IPCE_ENC_UPDATE_FUNCTION ? "Update function" :
+ retEvent->type == DB_IPCE_ENC_ADD_FUNCTION ? "Add function" : "Add field",
+ retEvent->EnCUpdate.memberMetadataToken, retEvent->EnCUpdate.newVersionNumber));
+
+ CordbAppDomain *pAppDomain = GetAppDomain();
+ _ASSERTE(NULL != pAppDomain);
+ CordbModule* pModule = NULL;
+
+
+ pModule = pAppDomain->LookupOrCreateModule(retEvent->EnCUpdate.vmDomainFile); // throws
+ _ASSERTE(pModule != NULL);
+
+ // update to the newest version
+
+ if (retEvent->type == DB_IPCE_ENC_UPDATE_FUNCTION ||
+ retEvent->type == DB_IPCE_ENC_ADD_FUNCTION)
+ {
+ // Update the function collection to reflect this edit
+ hr = pModule->UpdateFunction(retEvent->EnCUpdate.memberMetadataToken, retEvent->EnCUpdate.newVersionNumber, NULL);
+
+ }
+ // mark the class and relevant type as old so we update it next time we try to query it
+ if (retEvent->type == DB_IPCE_ENC_ADD_FUNCTION ||
+ retEvent->type == DB_IPCE_ENC_ADD_FIELD)
+ {
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock()); // @dbgtodo synchronization - push this up
+ CordbClass* pClass = pModule->LookupClass(retEvent->EnCUpdate.classMetadataToken);
+ // if don't find class, that is fine because it hasn't been loaded yet so doesn't
+ // need to be updated
+ if (pClass)
+ {
+ pClass->MakeOld();
+ }
+ }
+ }
+ }
+
+ LOG((LF_ENC,LL_INFO100, "CordbProcess::ApplyChangesInternal complete.\n"));
+ }
+ EX_CATCH_HRESULT(hr);
+
+ // process may have gone away by the time we get here so don't assume is there.
+ CordbProcess *pProcess = GetProcess();
+ if (pProcess)
+ {
+ HRESULT hr2 = pProcess->ReleaseRemoteBuffer(&pRemoteBuf);
+ TESTANDRETURNHR(hr2);
+ }
+ return hr;
+#else // EnC_SUPPORTED
+ return E_NOTIMPL;
+#endif // EnC_SUPPORTED
+
+}
+
+// Set the JMC status for the entire module.
+// All methods specified in others[] will have jmc status !fIsUserCode
+// All other methods will have jmc status fIsUserCode.
+HRESULT CordbModule::SetJMCStatus(
+ BOOL fIsUserCode,
+ ULONG32 cOthers,
+ mdToken others[])
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ if (m_vmDomainFile.IsNull())
+ return E_UNEXPECTED;
+
+ // @todo -allow the other parameters. These are functions that have default status
+ // opposite of fIsUserCode.
+ if (cOthers != 0)
+ {
+ _ASSERTE(!"not yet impl for cOthers != 0");
+ return E_NOTIMPL;
+ }
+
+ // Send event to the LS.
+ CordbProcess* pProcess = this->GetProcess();
+ _ASSERTE(pProcess != NULL);
+
+
+ // Tell the LS that this module is/is not user code
+ DebuggerIPCEvent event;
+ pProcess->InitIPCEvent(&event, DB_IPCE_SET_MODULE_JMC_STATUS, true, this->GetAppDomain()->GetADToken());
+ event.SetJMCFunctionStatus.vmDomainFile = m_vmDomainFile;
+ event.SetJMCFunctionStatus.dwStatus = fIsUserCode;
+
+
+ // Note: two-way event here...
+ HRESULT hr = pProcess->m_cordb->SendIPCEvent(pProcess, &event, sizeof(DebuggerIPCEvent));
+
+ // Stop now if we can't even send the event.
+ if (!SUCCEEDED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO10, "CordbModule::SetJMCStatus failed 0x%08x...\n", hr));
+
+ return hr;
+ }
+
+ _ASSERTE(event.type == DB_IPCE_SET_MODULE_JMC_STATUS_RESULT);
+
+ LOG((LF_CORDB, LL_INFO10, "returning from CordbModule::SetJMCStatus 0x%08x...\n", hr));
+
+ return event.hr;
+}
+
+
+//
+// Resolve an assembly given an AssemblyRef token. Note that
+// this will not trigger the loading of assembly. If assembly is not yet loaded,
+// this will return an CORDBG_E_CANNOT_RESOLVE_ASSEMBLY error
+//
+HRESULT CordbModule::ResolveAssembly(mdToken tkAssemblyRef,
+ ICorDebugAssembly **ppAssembly)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this->GetProcess());
+
+ if(ppAssembly)
+ {
+ *ppAssembly = NULL;
+ }
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ CordbAssembly *pCordbAsm = ResolveAssemblyInternal(tkAssemblyRef);
+ if (pCordbAsm == NULL)
+ {
+ // Don't throw here. It's a common-case failure path and not exceptional.
+ hr = CORDBG_E_CANNOT_RESOLVE_ASSEMBLY;
+ }
+ else if(ppAssembly)
+ {
+ _ASSERTE(pCordbAsm != NULL);
+ *ppAssembly = pCordbAsm;
+ pCordbAsm->ExternalAddRef();
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+// Worker to resolve an assembly ref.
+//
+// Arguments:
+// tkAssemblyRef - token of assembly ref to resolve
+//
+// Returns:
+// Assembly that this token resolves to.
+// NULL if it's a valid token but the assembly has not yet been resolved.
+// (This is a non-exceptional error case).
+//
+// Notes:
+// MetaData has tokens to represent a reference to another assembly.
+// But Loader/Fusion policy ultimately decides which specific assembly is actually loaded
+// for that token.
+// This does the lookup of actual assembly and reports back to the debugger.
+
+CordbAssembly * CordbModule::ResolveAssemblyInternal(mdToken tkAssemblyRef)
+{
+ INTERNAL_SYNC_API_ENTRY(GetProcess()); //
+
+ if (TypeFromToken(tkAssemblyRef) != mdtAssemblyRef || tkAssemblyRef == mdAssemblyRefNil)
+ {
+ // Not a valid token
+ ThrowHR(E_INVALIDARG);
+ }
+
+ CordbAssembly * pAssembly = NULL;
+
+ if (!m_vmDomainFile.IsNull())
+ {
+ // Get DAC to do the real work to resolve the assembly
+ VMPTR_DomainAssembly vmDomainAssembly = GetProcess()->GetDAC()->ResolveAssembly(m_vmDomainFile, tkAssemblyRef);
+
+ // now find the ICorDebugAssembly corresponding to it
+ if (!vmDomainAssembly.IsNull() && m_pAppDomain != NULL)
+ {
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+ // Don't throw here because if the lookup fails, we want to throw CORDBG_E_CANNOT_RESOLVE_ASSEMBLY.
+ pAssembly = m_pAppDomain->LookupOrCreateAssembly(vmDomainAssembly);
+ }
+ }
+
+ return pAssembly;
+}
+
+//
+// CreateReaderForInMemorySymbols - create an ISymUnmanagedReader object for symbols
+// which are loaded into memory in the CLR. See interface definition in cordebug.idl for
+// details.
+//
+HRESULT CordbModule::CreateReaderForInMemorySymbols(REFIID riid, void** ppObj)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ CordbProcess *pProcess = GetProcess();
+ ATT_REQUIRE_STOPPED_MAY_FAIL(pProcess);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // Get the symbol memory in a stream to give to the reader.
+ ReleaseHolder<IStream> pStream;
+ IDacDbiInterface::SymbolFormat symFormat = GetInMemorySymbolStream(&pStream);
+
+ // First create the symbol binder corresponding to the format of the stream
+ ReleaseHolder<ISymUnmanagedBinder> pBinder;
+ if (symFormat == IDacDbiInterface::kSymbolFormatPDB)
+ {
+#ifndef FEATURE_PAL
+ // PDB format - use diasymreader.dll with COM activation
+ InlineSString<_MAX_PATH> ssBuf;
+ IfFailThrow(GetHModuleDirectory(GetModuleInst(), ssBuf));
+ IfFailThrow(FakeCoCreateInstanceEx(CLSID_CorSymBinder_SxS,
+ ssBuf.GetUnicode(),
+ IID_ISymUnmanagedBinder,
+ (void**)&pBinder,
+ NULL));
+#else
+ IfFailThrow(FakeCoCreateInstance(CLSID_CorSymBinder_SxS,
+ IID_ISymUnmanagedBinder,
+ (void**)&pBinder));
+#endif
+ }
+ else if (symFormat == IDacDbiInterface::kSymbolFormatILDB)
+ {
+ // ILDB format - use statically linked-in ildbsymlib
+ IfFailThrow(IldbSymbolsCreateInstance(CLSID_CorSymBinder_SxS,
+ IID_ISymUnmanagedBinder,
+ (void**)&pBinder));
+ }
+ else
+ {
+ // No in-memory symbols, return the appropriate error
+ _ASSERTE(symFormat == IDacDbiInterface::kSymbolFormatNone);
+ if (m_fDynamic || m_fInMemory)
+ {
+ // This is indeed an in-memory or dynamic module, we just don't have any symbols for it.
+ // This means the application didn't supply any, or they are not yet available. Symbols
+ // first become available at LoadClass time for dynamic modules and UpdateModuleSymbols
+ // time for non-dynamic in-memory modules.
+ ThrowHR(CORDBG_E_SYMBOLS_NOT_AVAILABLE);
+ }
+
+ // This module is on disk - the debugger should use it's normal symbol-loading logic.
+ ThrowHR(CORDBG_E_MODULE_LOADED_FROM_DISK);
+ }
+
+ // In the attach or dump case, if we attach or take the dump after we have defined a dynamic module, we may
+ // have already set the symbol format to "PDB" by the time we call CreateReaderForInMemorySymbols during initialization
+ // for loaded modules. (In the launch case, we do this initialization when the module is actually loaded, and before we
+ // set the symbol format.) When we call CreateReaderForInMemorySymbols, we can't assume the initialization was already
+ // performed or specifically, that we already have m_pIMImport initialized. We can't call into diasymreader with a NULL
+ // pointer as the value for m_pIMImport, so we need to check that here.
+ if (m_pIMImport == NULL)
+ {
+ ThrowHR(CORDBG_E_SYMBOLS_NOT_AVAILABLE);
+ }
+
+ // Now create the symbol reader from the data
+ ReleaseHolder<ISymUnmanagedReader> pReader;
+ IfFailThrow(pBinder->GetReaderFromStream(m_pIMImport, pStream, &pReader));
+
+ // Attempt to return the interface requested
+ // Note that this does an AddRef for our return value ppObj, so we don't suppress the release
+ // of the pReader holder.
+ IfFailThrow(pReader->QueryInterface(riid, ppObj));
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+/* ------------------------------------------------------------------------- *
+ * Class class
+ * ------------------------------------------------------------------------- */
+
+//---------------------------------------------------------------------------------------
+// Set the continue counter that marks when the module is in its Load event
+//
+// Notes:
+// Jit flags can only be changed in the real module Load event. We may
+// have multiple module load events on different threads coming at the
+// same time. So each module load tracks its continue counter.
+//
+// This can be used by code:CordbModule::EnsureModuleIsInLoadCallback to
+// properly return CORDBG_E_MUST_BE_IN_LOAD_MODULE
+void CordbModule::SetLoadEventContinueMarker()
+{
+ // Well behaved targets should only set this once.
+ GetProcess()->TargetConsistencyCheck(m_nLoadEventContinueCounter == 0);
+
+ m_nLoadEventContinueCounter = GetProcess()->m_continueCounter;
+}
+
+//---------------------------------------------------------------------------------------
+// Return CORDBG_E_MUST_BE_IN_LOAD_MODULE if the module is not in the load module callback.
+//
+// Notes:
+// The comparison is done via continue counters. The counter of the load
+// event is cached via code:CordbModule::SetLoadEventContinueMarker.
+//
+// This state is currently stored on the RS. Alternatively, it could likely be retreived from the LS state as
+// well. One disadvantage of the current model is that if we detach during the load-module callback and
+// then reattach, the RS state is flushed and we lose the fact that we can toggle the jit flags.
+HRESULT CordbModule::EnsureModuleIsInLoadCallback()
+{
+ if (this->m_nLoadEventContinueCounter < GetProcess()->m_continueCounter)
+ {
+ return CORDBG_E_MUST_BE_IN_LOAD_MODULE;
+ }
+ else
+ {
+ return S_OK;
+ }
+}
+
+// Implementation of ICorDebugModule2::SetJITCompilerFlags
+// See also code:CordbModule::EnableJITDebugging
+HRESULT CordbModule::SetJITCompilerFlags(DWORD dwFlags)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ CordbProcess *pProcess = GetProcess();
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(pProcess);
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+ // can't have a subset of these, eg 0x101, so make sure we have an exact match
+ if ((dwFlags != CORDEBUG_JIT_DEFAULT) &&
+ (dwFlags != CORDEBUG_JIT_DISABLE_OPTIMIZATION) &&
+ (dwFlags != CORDEBUG_JIT_ENABLE_ENC))
+ {
+ hr = E_INVALIDARG;
+ }
+ else
+ {
+ BOOL fAllowJitOpts = ((dwFlags & CORDEBUG_JIT_DISABLE_OPTIMIZATION) != CORDEBUG_JIT_DISABLE_OPTIMIZATION);
+ BOOL fEnableEnC = ((dwFlags & CORDEBUG_JIT_ENABLE_ENC) == CORDEBUG_JIT_ENABLE_ENC);
+
+ // Can only change jit flags when module is first loaded and before there's any jitted code.
+ // This ensures all code in the module is jitted the same way.
+ hr = EnsureModuleIsInLoadCallback();
+
+ if (SUCCEEDED(hr))
+ {
+ // DD interface will check if it's a valid time to change the flags.
+ hr = pProcess->GetDAC()->SetCompilerFlags(GetRuntimeDomainFile(), fAllowJitOpts, fEnableEnC);
+ }
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ // emulate v2 hresults
+ if (GetProcess()->GetShim() != NULL)
+ {
+ // Emulate Whidbey error hresults
+ hr = GetProcess()->GetShim()->FilterSetJitFlagsHresult(hr);
+ }
+ return hr;
+
+}
+
+// Implementation of ICorDebugModule2::GetJitCompilerFlags
+HRESULT CordbModule::GetJITCompilerFlags(DWORD *pdwFlags )
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pdwFlags, DWORD*);
+ *pdwFlags = CORDEBUG_JIT_DEFAULT;;
+
+ CordbProcess *pProcess = GetProcess();
+
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(pProcess);
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+ BOOL fAllowJitOpts;
+ BOOL fEnableEnC;
+
+ pProcess->GetDAC()->GetCompilerFlags (
+ GetRuntimeDomainFile(),
+ &fAllowJitOpts,
+ &fEnableEnC);
+
+ if (fEnableEnC)
+ {
+ *pdwFlags = CORDEBUG_JIT_ENABLE_ENC;
+ }
+ else if (! fAllowJitOpts)
+ {
+ *pdwFlags = CORDEBUG_JIT_DISABLE_OPTIMIZATION;
+ }
+
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+BOOL CordbModule::IsWinMD()
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ if (m_isIlWinMD == Uninitialized)
+ {
+ BOOL isWinRT;
+ HRESULT hr = E_FAIL;
+
+ {
+ RSLockHolder processLockHolder(GetProcess()->GetProcessLock());
+ hr = GetProcess()->GetDAC()->IsWinRTModule(m_vmModule, isWinRT);
+ }
+
+ _ASSERTE(SUCCEEDED(hr));
+ if (FAILED(hr))
+ ThrowHR(hr);
+
+ if (isWinRT)
+ m_isIlWinMD = True;
+ else
+ m_isIlWinMD = False;
+ }
+
+ return m_isIlWinMD == True;
+}
+
+/* ------------------------------------------------------------------------- *
+ * CordbCode class
+ * ------------------------------------------------------------------------- */
+//-----------------------------------------------------------------------------
+// CordbCode constructor
+// Arguments:
+// Input:
+// pFunction - CordbFunction instance for this function
+// encVersion - Edit and Continue version number for this code chunk
+// fIsIL - indicates whether the instance is a CordbILCode (as
+// opposed to a CordbNativeCode)
+// id - This is the hashtable key for CordbCode objects
+// - for native code, the code start address
+// - for IL code, 0
+// - for ReJit IL code, the remote pointer to the ReJitSharedInfo
+// Output:
+// fields of the CordbCode instance have been initialized
+//-----------------------------------------------------------------------------
+
+CordbCode::CordbCode(CordbFunction * pFunction, UINT_PTR id, SIZE_T encVersion, BOOL fIsIL)
+ : CordbBase(pFunction->GetProcess(), id, enumCordbCode),
+ m_fIsIL(fIsIL),
+ m_nVersion(encVersion),
+ m_rgbCode(NULL),
+ m_continueCounterLastSync(0),
+ m_pFunction(pFunction)
+{
+ _ASSERTE(pFunction != NULL);
+ _ASSERTE(m_nVersion >= CorDB_DEFAULT_ENC_FUNCTION_VERSION);
+} // CordbCode::CordbCode
+
+//-----------------------------------------------------------------------------
+// Destructor for CordbCode object
+//-----------------------------------------------------------------------------
+CordbCode::~CordbCode()
+{
+ _ASSERTE(IsNeutered());
+}
+
+//-----------------------------------------------------------------------------
+// Neutered by CordbFunction
+// See CordbBase::Neuter for neuter semantics.
+//-----------------------------------------------------------------------------
+void CordbCode::Neuter()
+{
+ m_pFunction = NULL;
+
+ delete [] m_rgbCode;
+ m_rgbCode = NULL;
+
+ CordbBase::Neuter();
+}
+
+//-----------------------------------------------------------------------------
+// Public method for IUnknown::QueryInterface.
+// Has standard QI semantics.
+//-----------------------------------------------------------------------------
+HRESULT CordbCode::QueryInterface(REFIID id, void ** pInterface)
+{
+ if (id == IID_ICorDebugCode)
+ {
+ *pInterface = static_cast<ICorDebugCode*>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown *>(static_cast<ICorDebugCode *>(this));
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// NOT IMPLEMENTED. Remap sequence points are entirely private to the LS,
+// and ICorDebug will dispatch a RemapOpportunity callback to notify the
+// debugger instead of letting the debugger query for the points.
+//
+// Returns: E_NOTIMPL
+//-----------------------------------------------------------------------------
+HRESULT CordbCode::GetEnCRemapSequencePoints(ULONG32 cMap, ULONG32 * pcMap, ULONG32 offsets[])
+{
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pcMap, ULONG32*);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY_OR_NULL(offsets, ULONG32*, cMap, true, true);
+
+ //
+ // Old EnC interface - deprecated
+ //
+ return E_NOTIMPL;
+} // CordbCode::GetEnCRemapSequencePoints
+
+
+//-----------------------------------------------------------------------------
+// CordbCode::IsIL
+// Public method to determine if this Code object represents IL or native code.
+//
+// Parameters:
+// pbIL - OUT: on return, set to True if IL code, else False.
+//
+// Returns:
+// S_OK on success.
+//-----------------------------------------------------------------------------
+HRESULT CordbCode::IsIL(BOOL *pbIL)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pbIL, BOOL *);
+
+ *pbIL = IsIL();
+
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// CordbCode::GetFunction
+// Public method to get the Function object associated with this Code object.
+// Function:Code = 1:1 for IL, and 1:n for Native. So there is always a single
+// unique Function object to return.
+//
+// Parameters:
+// ppFunction - OUT: returns the Function object for this Code.
+//
+// Returns:
+// S_OK - on success.
+//-----------------------------------------------------------------------------
+HRESULT CordbCode::GetFunction(ICorDebugFunction **ppFunction)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppFunction, ICorDebugFunction **);
+
+ *ppFunction = static_cast<ICorDebugFunction*> (m_pFunction);
+ m_pFunction->ExternalAddRef();
+
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// CordbCode::GetSize
+// Get the size of the code in bytes. If this is IL code, it will be bytes of IL.
+// If this is native code, it will be bytes of native code.
+//
+// Parameters:
+// pcBytes - OUT: on return, set to the size of the code in bytes.
+//
+// Returns:
+// S_OK on success.
+//-----------------------------------------------------------------------------
+HRESULT CordbCode::GetSize(ULONG32 *pcBytes)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pcBytes, ULONG32 *);
+
+ *pcBytes = GetSize();
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// CordbCode::CreateBreakpoint
+// public method to create a breakpoint in the code.
+//
+// Parameters:
+// offset - offset in bytes to set the breakpoint at. If this is a Native
+// code object (IsIl == false), then units are bytes of native code. If
+// this is an IL code object, then units are bytes of IL code.
+// ppBreakpoint- out-parameter to hold newly created breakpoint object.
+//
+// Return value:
+// S_OK iff *ppBreakpoint is set. Else some error.
+//-----------------------------------------------------------------------------
+HRESULT CordbCode::CreateBreakpoint(ULONG32 offset,
+ ICorDebugFunctionBreakpoint **ppBreakpoint)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppBreakpoint, ICorDebugFunctionBreakpoint **);
+
+ HRESULT hr;
+ ULONG32 size = GetSize();
+ LOG((LF_CORDB, LL_INFO10000, "CCode::CreateBreakpoint, offset=%d, size=%d, IsIl=%d, this=0x%p\n",
+ offset, size, m_fIsIL, this));
+
+ // Make sure the offset is within range of the method.
+ // If we're native code, then both offset & total code size are bytes of native code,
+ // else they're both bytes of IL.
+ if (offset >= size)
+ {
+ return CORDBG_E_UNABLE_TO_SET_BREAKPOINT;
+ }
+
+ CordbFunctionBreakpoint *bp = new (nothrow) CordbFunctionBreakpoint(this, offset);
+
+ if (bp == NULL)
+ return E_OUTOFMEMORY;
+
+ hr = bp->Activate(TRUE);
+ if (SUCCEEDED(hr))
+ {
+ *ppBreakpoint = static_cast<ICorDebugFunctionBreakpoint*> (bp);
+ bp->ExternalAddRef();
+ return S_OK;
+ }
+ else
+ {
+ delete bp;
+ return hr;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// CordbCode::GetCode
+// Public method to get the code-bytes for this Code object. For an IL-code
+// object, this will be bytes of IL. For a native-code object, this will be
+// bytes of native opcodes.
+// The units of the offsets are the same as the units on the CordbCode object.
+// (eg, IL offsets for an IL code object, and native offsets for a native code object)
+// This will glue together hot + cold regions into a single blob.
+//
+// Units are also logical (aka linear) values, which
+// Parameters:
+// startOffset - linear offset in Code to start copying from.
+// endOffset - linear offset in Code to end copying from. Total bytes copied would be (endOffset - startOffset)
+// cBufferAlloc - number of bytes in the buffer supplied by the buffer[] parameter.
+// buffer - caller allocated storage to copy bytes into.
+// pcBufferSize - required out-parameter, holds number of bytes copied into buffer.
+//
+// Returns:
+// S_OK if copy successful. Else error.
+//-----------------------------------------------------------------------------
+HRESULT CordbCode::GetCode(ULONG32 startOffset,
+ ULONG32 endOffset,
+ ULONG32 cBufferAlloc,
+ BYTE buffer[],
+ ULONG32 *pcBufferSize)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(buffer, BYTE, cBufferAlloc, true, true);
+ VALIDATE_POINTER_TO_OBJECT(pcBufferSize, ULONG32 *);
+
+ LOG((LF_CORDB,LL_EVERYTHING, "CC::GC: for token:0x%x\n", m_pFunction->GetMetadataToken()));
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ *pcBufferSize = 0;
+
+ // Check ranges.
+ ULONG32 totalSize = GetSize();
+
+ if (cBufferAlloc < endOffset - startOffset)
+ endOffset = startOffset + cBufferAlloc;
+
+ if (endOffset > totalSize)
+ endOffset = totalSize;
+
+ if (startOffset > totalSize)
+ startOffset = totalSize;
+
+ // Check the continue counter since WriteMemory bumps it up.
+ if ((m_rgbCode == NULL) ||
+ (m_continueCounterLastSync < GetProcess()->m_continueCounter))
+ {
+ ReadCodeBytes();
+ m_continueCounterLastSync = GetProcess()->m_continueCounter;
+ }
+
+ // if we just got the code, we'll have to copy it over
+ if (*pcBufferSize == 0 && m_rgbCode != NULL)
+ {
+ memcpy(buffer,
+ m_rgbCode+startOffset,
+ endOffset - startOffset);
+ *pcBufferSize = endOffset - startOffset;
+ }
+ return hr;
+
+} // CordbCode::GetCode
+
+#include "dbgipcevents.h"
+
+//-----------------------------------------------------------------------------
+// CordbCode::GetVersionNumber
+// Public method to get the EnC version number of the code.
+//
+// Parameters:
+// nVersion - OUT: on return, set to the version number.
+//
+// Returns:
+// S_OK on success.
+//-----------------------------------------------------------------------------
+HRESULT CordbCode::GetVersionNumber( ULONG32 *nVersion)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(nVersion, ULONG32 *);
+
+ LOG((LF_CORDB,LL_INFO10000,"R:CC:GVN:Returning 0x%x "
+ "as version\n",m_nVersion));
+
+ *nVersion = (ULONG32)m_nVersion;
+
+#ifndef EnC_SUPPORTED
+ _ASSERTE(*nVersion == 1);
+#endif // EnC_SUPPORTED
+
+ return S_OK;
+}
+
+// get the CordbFunction instance for this code object
+CordbFunction * CordbCode::GetFunction()
+{
+ _ASSERTE(m_pFunction != NULL);
+ return m_pFunction;
+}
+
+/* ------------------------------------------------------------------------- *
+ * CordbILCode class
+ * ------------------------------------------------------------------------- */
+
+//-----------------------------------------------------------------------------
+// CordbILCode ctor to make IL code.
+// Arguments:
+// Input:
+// pFunction - pointer to the CordbFunction instance for this function
+// codeRegionInfo - starting address and size in bytes of IL code blob
+// nVersion - EnC version number for this IL code blob
+// localVarSigToken - LocalVarSig for this IL blob
+// id - the key when using ILCode in a CordbHashTable
+// Output:
+// fields of this instance of CordbILCode have been initialized
+//-----------------------------------------------------------------------------
+CordbILCode::CordbILCode(CordbFunction * pFunction,
+ TargetBuffer codeRegionInfo,
+ SIZE_T nVersion,
+ mdSignature localVarSigToken,
+ UINT_PTR id)
+ : CordbCode(pFunction, id, nVersion, TRUE),
+#ifdef EnC_SUPPORTED
+ m_fIsOld(FALSE),
+#endif
+ m_codeRegionInfo(codeRegionInfo),
+ m_localVarSigToken(localVarSigToken)
+{
+} // CordbILCode::CordbILCode
+
+
+#ifdef EnC_SUPPORTED
+//-----------------------------------------------------------------------------
+// CordbILCode::MakeOld
+// Internal method to perform any cleanup necessary when a code blob is no longer
+// the most current.
+//-----------------------------------------------------------------------------
+void CordbILCode::MakeOld()
+{
+ m_fIsOld = TRUE;
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// CordbILCode::GetAddress
+// Public method to get the Entry address for the code. This is the address
+// where the method first starts executing.
+//
+// Parameters:
+// pStart - out-parameter to hold start address.
+//
+// Returns:
+// S_OK if *pStart is properly updated.
+//-----------------------------------------------------------------------------
+HRESULT CordbILCode::GetAddress(CORDB_ADDRESS * pStart)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pStart, CORDB_ADDRESS *);
+
+
+ _ASSERTE(this != NULL);
+ _ASSERTE(this->GetFunction() != NULL);
+ _ASSERTE(this->GetFunction()->GetModule() != NULL);
+ _ASSERTE(this->GetFunction()->GetModule()->GetProcess() == GetProcess());
+
+ *pStart = (m_codeRegionInfo.pAddress);
+
+ return S_OK;
+} // CordbILCode::GetAddress
+
+//-----------------------------------------------------------------------------
+// CordbILCode::ReadCodeBytes
+// Reads the actual bytes of IL code into the data member m_rgbCode
+// Arguments:
+// none (uses data members)
+// Return value:
+// standard HRESULT values
+// also allocates and initializes m_rgbCode
+// Notes: assumes that the caller has checked to ensure that m_rgbCode doesn't
+// hold valid data
+//-----------------------------------------------------------------------------
+HRESULT CordbILCode::ReadCodeBytes()
+{
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // We have an address & size, so we'll just call ReadMemory.
+ // This will conveniently strip out any patches too.
+ CORDB_ADDRESS pStart = m_codeRegionInfo.pAddress;
+ ULONG32 cbSize = (ULONG32) m_codeRegionInfo.cbSize;
+
+ delete [] m_rgbCode;
+ m_rgbCode = new BYTE[cbSize]; // throws
+
+ SIZE_T cbRead;
+ hr = GetProcess()->ReadMemory(pStart, cbSize, m_rgbCode, &cbRead);
+ IfFailThrow(hr);
+
+ SIMPLIFYING_ASSUMPTION(cbRead == cbSize);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+} // CordbILCode::ReadCodeBytes
+
+//-----------------------------------------------------------------------------
+// CordbILCode::GetILToNativeMapping
+// Public method (implements ICorDebugCode) to get the IL-->{ Native Start, Native End} mapping.
+// Since 1 CordbILCode can map to multiple CordbNativeCode due to generics, we cannot reliably return the
+// mapping information in all cases. So we always fail with CORDBG_E_NON_NATIVE_FRAME. The caller should
+// call code:CordbNativeCode::GetILToNativeMapping instead.
+//
+// Parameters:
+// cMap - size of incoming map[] array (in elements).
+// pcMap - OUT: full size of IL-->Native map (in elements).
+// map - caller allocated array to be filled in.
+//
+// Returns:
+// CORDBG_E_NON_NATIVE_FRAME in all cases
+//-----------------------------------------------------------------------------
+HRESULT CordbILCode::GetILToNativeMapping(ULONG32 cMap,
+ ULONG32 * pcMap,
+ COR_DEBUG_IL_TO_NATIVE_MAP map[])
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pcMap, ULONG32 *);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY_OR_NULL(map, COR_DEBUG_IL_TO_NATIVE_MAP *, cMap, true, true);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ return CORDBG_E_NON_NATIVE_FRAME;
+} // CordbILCode::GetILToNativeMapping
+
+
+/*
+* CordbILCode::GetLocalVarSig
+*
+* Get the method's local variable metadata signature. This may be cached, but for dynamic modules we'll always
+* read it from the metadata. This function also returns the count of local variables in the method.
+*
+* Parameters:
+* pLocalSigParser - OUT: the local variable signature for the method.
+* pLocalCount - OUT: the number of locals the method has.
+*
+* Returns:
+* HRESULT for success or failure.
+*
+*/
+HRESULT CordbILCode::GetLocalVarSig(SigParser *pLocalSigParser,
+ ULONG *pLocalVarCount)
+{
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+
+ CONTRACTL // @dbgtodo exceptions - convert to throws...
+ {
+ NOTHROW;
+ }
+ CONTRACTL_END;
+
+ FAIL_IF_NEUTERED(this);
+ HRESULT hr = S_OK;
+
+ // A function will not have a local var sig if it has no locals!
+ if (m_localVarSigToken != mdSignatureNil)
+ {
+ PCCOR_SIGNATURE localSignature;
+ ULONG size;
+ ULONG localCount;
+
+ EX_TRY // // @dbgtodo exceptions - push this up
+ {
+ GetFunction()->GetModule()->UpdateMetaDataCacheIfNeeded(m_localVarSigToken);
+ hr = GetFunction()->GetModule()->GetMetaDataImporter()->GetSigFromToken(m_localVarSigToken,
+ &localSignature,
+ &size);
+ }
+ EX_CATCH_HRESULT(hr);
+ if (FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_WARNING, "CICF::GLVS caught hr=0x%x\n", hr));
+ }
+ IfFailRet(hr);
+
+ LOG((LF_CORDB, LL_INFO100000, "CIC::GLVS creating sig parser sig=0x%x size=0x%x\n", localSignature, size));
+ SigParser sigParser = SigParser(localSignature, size);
+
+ ULONG data;
+
+ IfFailRet(sigParser.GetCallingConvInfo(&data));
+
+ _ASSERTE(data == IMAGE_CEE_CS_CALLCONV_LOCAL_SIG);
+
+ // Snagg the count of locals in the sig.
+ IfFailRet(sigParser.GetData(&localCount));
+ LOG((LF_CORDB, LL_INFO100000, "CIC::GLVS localCount=0x%x\n", localCount));
+ if (pLocalSigParser != NULL)
+ {
+ *pLocalSigParser = sigParser;
+ }
+ if (pLocalVarCount != NULL)
+ {
+ *pLocalVarCount = localCount;
+ }
+ }
+ else
+ {
+ //
+ // Signature is Nil, so fill in everything with NULLs and zeros
+ //
+ if (pLocalSigParser != NULL)
+ {
+ *pLocalSigParser = SigParser(NULL, 0);
+ }
+
+ if (pLocalVarCount != NULL)
+ {
+ *pLocalVarCount = 0;
+ }
+ }
+ LOG((LF_CORDB, LL_INFO100000, "CIC::GLVS returning hr=0x%x\n", hr));
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// CordbILCode::GetLocalVariableType
+// Internal method. Return the type of an IL local, specified by 0-based index.
+//
+// Parameters:
+// dwIndex - 0-based index for IL local number.
+// inst - instantiation information if this is a generic function. Eg,
+// if function is List<T>, inst describes T.
+// res - out parameter, yields to CordbType of the local.
+//
+// Return:
+// S_OK on success.
+//
+HRESULT CordbILCode::GetLocalVariableType(DWORD dwIndex,
+ const Instantiation * pInst,
+ CordbType ** ppResultType)
+{
+ ATT_ALLOW_LIVE_DO_STOPGO(GetProcess());
+ LOG((LF_CORDB, LL_INFO10000, "CIC::GLVT dwIndex=0x%x pInst=0x%p\n", dwIndex, pInst));
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+ // Get the local variable signature.
+ SigParser sigParser;
+ ULONG cLocals;
+
+ IfFailThrow(GetLocalVarSig(&sigParser, &cLocals));
+
+ // Check the index.
+ if (dwIndex >= cLocals)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ // Run the signature and find the required argument.
+ for (unsigned int i = 0; i < dwIndex; i++)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "CIC::GLVT scanning index 0x%x\n", dwIndex));
+ IfFailThrow(sigParser.SkipExactlyOne());
+ }
+
+ hr = CordbType::SigToType(GetFunction()->GetModule(), &sigParser, pInst, ppResultType);
+ LOG((LF_CORDB, LL_INFO10000, "CIC::GLVT CT::SigToType returned hr=0x%x\n", hr));
+ IfFailThrow(hr);
+
+ } EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+mdSignature CordbILCode::GetLocalVarSigToken()
+{
+ return m_localVarSigToken;
+}
+
+CordbReJitILCode::CordbReJitILCode(CordbFunction *pFunction, SIZE_T encVersion, VMPTR_SharedReJitInfo vmSharedReJitInfo) :
+CordbILCode(pFunction, TargetBuffer(), encVersion, mdSignatureNil, VmPtrToCookie(vmSharedReJitInfo)),
+m_cClauses(0),
+m_cbLocalIL(0),
+m_cILMap(0)
+{
+ _ASSERTE(!vmSharedReJitInfo.IsNull());
+ DacSharedReJitInfo data = { 0 };
+ IfFailThrow(GetProcess()->GetDAC()->GetSharedReJitInfoData(vmSharedReJitInfo, &data));
+ IfFailThrow(Init(&data));
+}
+
+//-----------------------------------------------------------------------------
+// CordbReJitILCode::Init
+//
+// Returns:
+// S_OK if all fields are inited. Else error.
+HRESULT CordbReJitILCode::Init(DacSharedReJitInfo* pSharedReJitInfo)
+{
+ HRESULT hr = S_OK;
+
+ // Instrumented IL map
+ if (pSharedReJitInfo->m_cInstrumentedMapEntries)
+ {
+ if (pSharedReJitInfo->m_cInstrumentedMapEntries > 100000)
+ return CORDBG_E_TARGET_INCONSISTENT;
+ m_cILMap = pSharedReJitInfo->m_cInstrumentedMapEntries;
+ m_pILMap = new (nothrow)COR_IL_MAP[m_cILMap];
+ TargetBuffer mapBuffer(pSharedReJitInfo->m_rgInstrumentedMapEntries, m_cILMap*sizeof(COR_IL_MAP));
+ IfFailRet(GetProcess()->SafeReadBuffer(mapBuffer, (BYTE*)m_pILMap.GetValue(), FALSE /* bThrowOnError */));
+ }
+
+ // Read the method's IL header
+ CORDB_ADDRESS pIlHeader = pSharedReJitInfo->m_pbIL;
+ IMAGE_COR_ILMETHOD_FAT header = { 0 };
+ bool headerMustBeTiny = false;
+ ULONG32 headerSize = 0;
+ hr = GetProcess()->SafeReadStruct(pIlHeader, &header);
+ if (hr != S_OK)
+ {
+ // Its possible the header is tiny and there isn't enough memory to read a complete
+ // FAT header
+ headerMustBeTiny = true;
+ IfFailRet(GetProcess()->SafeReadStruct(pIlHeader, (IMAGE_COR_ILMETHOD_TINY *)&header));
+ }
+
+ // Read the ILCodeSize and LocalVarSigTok from header
+ ULONG32 ilCodeSize = 0;
+ IMAGE_COR_ILMETHOD_TINY *pMethodTinyHeader = (IMAGE_COR_ILMETHOD_TINY *)&header;
+ bool isTinyHeader = ((pMethodTinyHeader->Flags_CodeSize & (CorILMethod_FormatMask >> 1)) == CorILMethod_TinyFormat);
+ if (isTinyHeader)
+ {
+ ilCodeSize = (((unsigned)pMethodTinyHeader->Flags_CodeSize) >> (CorILMethod_FormatShift - 1));
+ headerSize = sizeof(IMAGE_COR_ILMETHOD_TINY);
+ m_localVarSigToken = mdSignatureNil;
+ }
+ else if (headerMustBeTiny)
+ {
+ // header was not CorILMethod_TinyFormat
+ // this is not possible, must be an error when reading from data target
+ return CORDBG_E_READVIRTUAL_FAILURE;
+ }
+ else
+ {
+ ilCodeSize = header.CodeSize;
+ headerSize = header.Size * 4;
+ m_localVarSigToken = header.LocalVarSigTok;
+ }
+ if (ilCodeSize == 0 || ilCodeSize > 100000)
+ {
+ return CORDBG_E_TARGET_INCONSISTENT;
+ }
+
+ m_codeRegionInfo.Init(pIlHeader + headerSize, ilCodeSize);
+ m_pLocalIL = new (nothrow) BYTE[ilCodeSize];
+ if (m_pLocalIL == NULL)
+ return E_OUTOFMEMORY;
+ m_cbLocalIL = ilCodeSize;
+ IfFailRet(GetProcess()->SafeReadBuffer(m_codeRegionInfo, m_pLocalIL, FALSE /*throwOnError*/));
+
+ // Check if this il code has exception clauses
+ if ((pMethodTinyHeader->Flags_CodeSize & CorILMethod_MoreSects) == 0)
+ {
+ return S_OK; // no EH, done initing
+ }
+
+ // EH section starts at the 4 byte aligned address after the code
+ CORDB_ADDRESS ehClauseHeader = ((pIlHeader + headerSize + ilCodeSize - 1) & ~3) + 4;
+ BYTE kind = 0;
+ IfFailRet(GetProcess()->SafeReadStruct(ehClauseHeader, &kind));
+ if ((kind & CorILMethod_Sect_KindMask) != CorILMethod_Sect_EHTable)
+ {
+ return S_OK;
+ }
+ if (kind & CorILMethod_Sect_FatFormat)
+ {
+ // Read the section header to see how many clauses there are
+ IMAGE_COR_ILMETHOD_SECT_FAT sectionHeader = { 0 };
+ IfFailRet(GetProcess()->SafeReadStruct(ehClauseHeader, &sectionHeader));
+ m_cClauses = (sectionHeader.DataSize - 4) / sizeof(IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_FAT);
+ if (m_cClauses > 10000) // sanity check the data before allocating
+ {
+ return CORDBG_E_TARGET_INCONSISTENT;
+ }
+
+ // Read in the clauses
+ TargetBuffer buffer(ehClauseHeader + sizeof(IMAGE_COR_ILMETHOD_SECT_FAT), m_cClauses*sizeof(IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_FAT));
+ NewArrayHolder<IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_FAT> pClauses = new (nothrow)IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_FAT[m_cClauses];
+ if (pClauses == NULL)
+ return E_OUTOFMEMORY;
+ IfFailRet(GetProcess()->SafeReadBuffer(buffer, (BYTE*)pClauses.GetValue(), FALSE /*throwOnError*/));
+
+ // convert clauses
+ m_pClauses = new (nothrow)CorDebugEHClause[m_cClauses];
+ if (m_pClauses == NULL)
+ return E_OUTOFMEMORY;
+ for (ULONG32 i = 0; i < m_cClauses; i++)
+ {
+ BOOL isFilter = ((pClauses[i].Flags & COR_ILEXCEPTION_CLAUSE_FILTER) != 0);
+ m_pClauses[i].Flags = pClauses[i].Flags;
+ m_pClauses[i].TryOffset = pClauses[i].TryOffset;
+ m_pClauses[i].TryLength = pClauses[i].TryLength;
+ m_pClauses[i].HandlerOffset = pClauses[i].HandlerOffset;
+ m_pClauses[i].HandlerLength = pClauses[i].HandlerLength;
+ // these two fields are a union in the image, but are seperate in the struct ICorDebug returns
+ m_pClauses[i].ClassToken = isFilter ? 0 : pClauses[i].ClassToken;
+ m_pClauses[i].FilterOffset = isFilter ? pClauses[i].FilterOffset : 0;
+ }
+ }
+ else
+ {
+ // Read in the section header to see how many small clauses there are
+ IMAGE_COR_ILMETHOD_SECT_SMALL sectionHeader = { 0 };
+ IfFailRet(GetProcess()->SafeReadStruct(ehClauseHeader, &sectionHeader));
+ ULONG32 m_cClauses = (sectionHeader.DataSize - 4) / sizeof(IMAGE_COR_ILMETHOD_SECT_SMALL);
+ if (m_cClauses > 10000) // sanity check the data before allocating
+ {
+ return CORDBG_E_TARGET_INCONSISTENT;
+ }
+
+ // Read in the clauses
+ TargetBuffer buffer(ehClauseHeader + sizeof(IMAGE_COR_ILMETHOD_SECT_SMALL), m_cClauses*sizeof(IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_SMALL));
+ NewArrayHolder<IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_SMALL> pClauses = new (nothrow)IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_SMALL[m_cClauses];
+ if (pClauses == NULL)
+ return E_OUTOFMEMORY;
+ IfFailRet(GetProcess()->SafeReadBuffer(buffer, (BYTE*)pClauses.GetValue(), FALSE /*throwOnError*/));
+
+ // convert clauses
+ m_pClauses = new (nothrow)CorDebugEHClause[m_cClauses];
+ if (m_pClauses == NULL)
+ return E_OUTOFMEMORY;
+ for (ULONG32 i = 0; i < m_cClauses; i++)
+ {
+ BOOL isFilter = ((pClauses[i].Flags & COR_ILEXCEPTION_CLAUSE_FILTER) != 0);
+ m_pClauses[i].Flags = pClauses[i].Flags;
+ m_pClauses[i].TryOffset = pClauses[i].TryOffset;
+ m_pClauses[i].TryLength = pClauses[i].TryLength;
+ m_pClauses[i].HandlerOffset = pClauses[i].HandlerOffset;
+ m_pClauses[i].HandlerLength = pClauses[i].HandlerLength;
+ // these two fields are a union in the image, but are seperate in the struct ICorDebug returns
+ m_pClauses[i].ClassToken = isFilter ? 0 : pClauses[i].ClassToken;
+ m_pClauses[i].FilterOffset = isFilter ? pClauses[i].FilterOffset : 0;
+ }
+ }
+ return S_OK;
+}
+
+#ifndef MIN
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+#endif
+
+//-----------------------------------------------------------------------------
+// CordbReJitILCode::GetEHClauses
+// Public method to get the EH clauses for IL code
+//
+// Parameters:
+// cClauses - size of incoming clauses array (in elements).
+// pcClauses - OUT param: cClauses>0 -> the number of elements written to in the clauses array.
+// cClauses=0 -> the number of EH clauses this IL code has
+// clauses - caller allocated storage to hold the EH clauses.
+//
+// Returns:
+// S_OK if successfully copied elements to clauses array.
+HRESULT CordbReJitILCode::GetEHClauses(ULONG32 cClauses, ULONG32 * pcClauses, CorDebugEHClause clauses[])
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pcClauses, ULONG32 *);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY_OR_NULL(clauses, CorDebugEHClause *, cClauses, true, true);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ if (cClauses != 0 && clauses == NULL)
+ {
+ return E_INVALIDARG;
+ }
+
+ if (pcClauses != NULL)
+ {
+ if (cClauses == 0)
+ {
+ *pcClauses = m_cClauses;
+ }
+ else
+ {
+ *pcClauses = MIN(cClauses, m_cClauses);
+ }
+ }
+
+ if (clauses != NULL)
+ {
+ memcpy_s(clauses, sizeof(CorDebugEHClause)*cClauses, m_pClauses, sizeof(CorDebugEHClause)*MIN(cClauses, m_cClauses));
+ }
+ return S_OK;
+}
+
+ULONG CordbReJitILCode::AddRef()
+{
+ return CordbCode::AddRef();
+}
+ULONG CordbReJitILCode::Release()
+{
+ return CordbCode::Release();
+}
+
+HRESULT CordbReJitILCode::QueryInterface(REFIID riid, void** ppInterface)
+{
+ if (riid == IID_ICorDebugILCode)
+ {
+ *ppInterface = static_cast<ICorDebugILCode*>(this);
+ }
+ else if (riid == IID_ICorDebugILCode2)
+ {
+ *ppInterface = static_cast<ICorDebugILCode2*>(this);
+ }
+ else
+ {
+ return CordbILCode::QueryInterface(riid, ppInterface);
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+HRESULT CordbReJitILCode::GetLocalVarSigToken(mdSignature *pmdSig)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pmdSig, mdSignature *);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ *pmdSig = m_localVarSigToken;
+ return S_OK;
+}
+
+HRESULT CordbReJitILCode::GetInstrumentedILMap(ULONG32 cMap, ULONG32 *pcMap, COR_IL_MAP map[])
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pcClauses, ULONG32 *);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY_OR_NULL(map, COR_IL_MAP *, cMap, true, true);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ if (cMap != 0 && map == NULL)
+ {
+ return E_INVALIDARG;
+ }
+
+ if (pcMap != NULL)
+ {
+ if (cMap == 0)
+ {
+ *pcMap = m_cILMap;
+ }
+ else
+ {
+ *pcMap = MIN(cMap, m_cILMap);
+ }
+ }
+
+ if (map != NULL)
+ {
+ memcpy_s(map, sizeof(COR_IL_MAP)*cMap, m_pILMap, sizeof(COR_IL_MAP)*MIN(cMap, m_cILMap));
+ }
+ return S_OK;
+}
+
+// FindNativeInfoInILVariableArray
+// Linear search through an array of NativeVarInfos, to find the variable of index dwIndex, valid
+// at the given ip. Returns CORDBG_E_IL_VAR_NOT_AVAILABLE if the variable isn't valid at the given ip.
+// Arguments:
+// input: dwIndex - variable number
+// ip - IP
+// nativeInfoList - list of instances of NativeVarInfo
+// output: ppNativeInfo - the element of nativeInfoList that corresponds to the IP and variable number
+// if we find such an element or NULL otherwise
+// Return value: HRESULT: returns S_OK or CORDBG_E_IL_VAR_NOT_AVAILABLE if the variable isn't found
+//
+HRESULT FindNativeInfoInILVariableArray(DWORD dwIndex,
+ SIZE_T ip,
+ const DacDbiArrayList<ICorDebugInfo::NativeVarInfo> * nativeInfoList,
+ const ICorDebugInfo::NativeVarInfo ** ppNativeInfo)
+{
+ _ASSERTE(ppNativeInfo != NULL);
+ *ppNativeInfo = NULL;
+
+ // A few words about this search: it must be linear, and the
+ // comparison of startOffset and endOffset to ip must be
+ // <=/>. startOffset points to the first instruction that will
+ // make the variable's home valid. endOffset points to the first
+ // instruction at which the variable's home invalid.
+ int lastGoodOne = -1;
+ for (unsigned int i = 0; i < (unsigned)nativeInfoList->Count(); i++)
+ {
+ if ((*nativeInfoList)[i].varNumber == dwIndex)
+ {
+ if ( (lastGoodOne == -1) ||
+ ((*nativeInfoList)[lastGoodOne].startOffset < (*nativeInfoList)[i].startOffset) )
+ {
+ lastGoodOne = i;
+ }
+
+ if (((*nativeInfoList)[i].startOffset <= ip) &&
+ ((*nativeInfoList)[i].endOffset > ip))
+ {
+ *ppNativeInfo = &((*nativeInfoList)[i]);
+
+ return S_OK;
+ }
+ }
+ }
+
+ // workaround:
+ //
+ // We didn't find the variable. Was the endOffset of the last range for this variable
+ // equal to the current IP? If so, go ahead and "lie" and report that as the
+ // variable's home for now.
+ //
+ // Rationale:
+ //
+ // * See TODO comment in code:Compiler::siUpdate (jit\scopeinfo.cpp). In optimized
+ // code, the JIT can report var lifetimes as being one instruction too short.
+ // This workaround makes up for that. Example code:
+ //
+ // static void foo(int x)
+ // {
+ // int b = x; // Value of "x" would not be reported in optimized code without the workaround
+ // bar(ref b);
+ // }
+ //
+ // * Since this is the first instruction after the last range a variable was alive,
+ // we're essentially assuming that since that instruction hasn't been executed
+ // yet, and since there isn't a new home for the variable, that the last home is
+ // still good. This actually turns out to be true 99.9% of the time, so we'll go
+ // with it for now.
+ // * We've been lying like this since 1999, so surely it's safe.
+ if ((lastGoodOne > -1) && ((*nativeInfoList)[lastGoodOne].endOffset == ip))
+ {
+ *ppNativeInfo = &((*nativeInfoList)[lastGoodOne]);
+ return S_OK;
+ }
+
+ return CORDBG_E_IL_VAR_NOT_AVAILABLE;
+} // FindNativeInfoInILVariableArray
+
+//* ------------------------------------------------------------------------- *
+// * Native Code class
+// * ------------------------------------------------------------------------- */
+
+
+//-----------------------------------------------------------------------------
+// CordbNativeCode ctor to make Native code.
+// Arguments:
+// Input:
+// pFunction - the function for which this is the native code object
+// pJitData - the information about this code object retrieved from the DAC
+// fIsInstantiatedGeneric - indicates whether this code object is an instantiated
+// generic
+// Output:
+// fields of this instance of CordbNativeCode have been initialized
+//-----------------------------------------------------------------------------
+CordbNativeCode::CordbNativeCode(CordbFunction * pFunction,
+ const NativeCodeFunctionData * pJitData,
+ BOOL fIsInstantiatedGeneric)
+ : CordbCode(pFunction, (UINT_PTR)pJitData->m_rgCodeRegions[kHot].pAddress, pJitData->encVersion, FALSE),
+ m_vmNativeCodeMethodDescToken(pJitData->vmNativeCodeMethodDescToken),
+ m_fCodeAvailable(TRUE),
+ m_fIsInstantiatedGeneric(fIsInstantiatedGeneric != FALSE)
+{
+ _ASSERTE(GetVersion() >= CorDB_DEFAULT_ENC_FUNCTION_VERSION);
+
+ for (CodeBlobRegion region = kHot; region < MAX_REGIONS; ++region)
+ {
+ m_rgCodeRegions[region] = pJitData->m_rgCodeRegions[region];
+ }
+} //CordbNativeCode::CordbNativeCode
+
+//-----------------------------------------------------------------------------
+// Public method for IUnknown::QueryInterface.
+// Has standard QI semantics.
+//-----------------------------------------------------------------------------
+HRESULT CordbNativeCode::QueryInterface(REFIID id, void ** pInterface)
+{
+ if (id == IID_ICorDebugCode)
+ {
+ *pInterface = static_cast<ICorDebugCode *>(this);
+ }
+ else if (id == IID_ICorDebugCode2)
+ {
+ *pInterface = static_cast<ICorDebugCode2 *>(this);
+ }
+ else if (id == IID_ICorDebugCode3)
+ {
+ *pInterface = static_cast<ICorDebugCode3 *>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown *>(static_cast<ICorDebugCode *>(this));
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// CordbNativeCode::GetAddress
+// Public method to get the Entry address for the code. This is the address
+// where the method first starts executing.
+//
+// Parameters:
+// pStart - out-parameter to hold start address.
+//
+// Returns:
+// S_OK if *pStart is properly updated.
+//-----------------------------------------------------------------------------
+HRESULT CordbNativeCode::GetAddress(CORDB_ADDRESS * pStart)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pStart, CORDB_ADDRESS *);
+
+
+ _ASSERTE(this != NULL);
+ _ASSERTE(this->GetFunction() != NULL);
+ _ASSERTE(this->GetFunction()->GetModule() != NULL);
+ _ASSERTE(this->GetFunction()->GetModule()->GetProcess() == GetProcess());
+
+ // Since we don't do code-pitching, the address points directly to the code.
+ *pStart = (m_rgCodeRegions[kHot].pAddress);
+
+ if (*pStart == NULL)
+ {
+ return CORDBG_E_CODE_NOT_AVAILABLE;
+ }
+ return S_OK;
+} // CordbNativeCode::GetAddress
+
+//-----------------------------------------------------------------------------
+// CordbNativeCode::ReadCodeBytes
+// Reads the actual bytes of native code from both the hot and cold regions
+// into the data member m_rgbCode
+// Arguments:
+// none (uses data members)
+// Return value:
+// standard HRESULT values
+// also allocates and initializes m_rgbCode
+// Notes: assumes that the caller has checked to ensure that m_rgbCode doesn't
+// hold valid data
+//-----------------------------------------------------------------------------
+HRESULT CordbNativeCode::ReadCodeBytes()
+{
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+ // We have an address & size, so we'll just call ReadMemory.
+ // This will conveniently strip out any patches too.
+ CORDB_ADDRESS pHotStart = m_rgCodeRegions[kHot].pAddress;
+ CORDB_ADDRESS pColdStart = m_rgCodeRegions[kCold].pAddress;
+ ULONG32 cbHotSize = (ULONG32) m_rgCodeRegions[kHot].cbSize;
+ ULONG32 cbColdSize = GetColdSize();
+
+ delete [] m_rgbCode;
+ m_rgbCode = new BYTE[cbHotSize + cbColdSize];
+
+ SIZE_T cbRead;
+ hr = GetProcess()->ReadMemory(pHotStart, cbHotSize, m_rgbCode, &cbRead);
+ IfFailThrow(hr);
+
+ SIMPLIFYING_ASSUMPTION(cbRead == cbHotSize);
+
+ if (HasColdRegion())
+ {
+ hr = GetProcess()->ReadMemory(pColdStart, cbColdSize, (BYTE *) m_rgbCode + cbHotSize, &cbRead);
+ IfFailThrow(hr);
+
+ SIMPLIFYING_ASSUMPTION(cbRead == cbColdSize);
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+
+} // CordbNativeCode::ReadCodeBytes
+
+//-----------------------------------------------------------------------------
+// CordbNativeCode::GetColdSize
+// Get the size of the cold regions in bytes.
+//
+// Parameters:
+// none--uses data member m_rgCodeRegions to compute total size.
+//
+// Returns:
+// the size of the code in bytes.
+//-----------------------------------------------------------------------------
+ULONG32 CordbNativeCode::GetColdSize()
+{
+ ULONG32 pcBytes = 0;
+ for (CodeBlobRegion index = kCold; index < MAX_REGIONS; ++index)
+ {
+ pcBytes += m_rgCodeRegions[index].cbSize;
+ }
+ return pcBytes;
+} // CordbNativeCode::GetColdSize
+
+//-----------------------------------------------------------------------------
+// CordbNativeCode::GetSize
+// Get the size of the code in bytes.
+//
+// Parameters:
+// none--uses data member m_rgCodeRegions to compute total size.
+//
+// Returns:
+// the size of the code in bytes.
+//-----------------------------------------------------------------------------
+ULONG32 CordbNativeCode::GetSize()
+{
+ ULONG32 pcBytes = 0;
+ for (CodeBlobRegion index = kHot; index < MAX_REGIONS; ++index)
+ {
+ pcBytes += m_rgCodeRegions[index].cbSize;
+ }
+ return pcBytes;
+} // CordbNativeCode::GetSize
+
+//-----------------------------------------------------------------------------
+// CordbNativeCode::GetILToNativeMapping
+// Public method (implements ICorDebugCode) to get the IL-->{ Native Start, Native End} mapping.
+// This can only be retrieved for native code.
+// This will copy as much of the map as can fit in the incoming buffer.
+//
+// Parameters:
+// cMap - size of incoming map[] array (in elements).
+// pcMap - OUT: full size of IL-->Native map (in elements).
+// map - caller allocated array to be filled in.
+//
+// Returns:
+// S_OK on successful copying.
+//-----------------------------------------------------------------------------
+HRESULT CordbNativeCode::GetILToNativeMapping(ULONG32 cMap,
+ ULONG32 * pcMap,
+ COR_DEBUG_IL_TO_NATIVE_MAP map[])
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pcMap, ULONG32 *);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY_OR_NULL(map, COR_DEBUG_IL_TO_NATIVE_MAP *,cMap,true,true);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ LoadNativeInfo();
+
+ SequencePoints * pSeqPts = GetSequencePoints();
+ DebuggerILToNativeMap * rgMapInt = pSeqPts->GetMapAddr();
+ ULONG32 cMapIntCount = pSeqPts->GetEntryCount();
+
+ // If they gave us space to copy into...
+ if (map != NULL)
+ {
+ // Only copy as much as either they gave us or we have to copy.
+ ULONG32 cMapToCopy = min(cMap, cMapIntCount);
+
+ // Remember that we need to translate between our internal DebuggerILToNativeMap and the external
+ // COR_DEBUG_IL_TO_NATIVE_MAP!
+ ULONG32 size = GetSize();
+ ExportILToNativeMap(cMapToCopy, map, rgMapInt, size);
+ }
+
+ // return the full count of map entries
+ if (pcMap)
+ {
+ *pcMap = cMapIntCount;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+} // CordbNativeCode::GetILToNativeMapping
+
+//-----------------------------------------------------------------------------
+// CordbNativeCode::GetCodeChunks
+// Public method to get the code regions of code. If the code
+// is broken into discontinuous regions (hot + cold), this lets a debugger
+// find the number of regions, and (start,size) of each.
+//
+// Parameters:
+// cbufSize - size of incoming chunks array (in elements).
+// pcnumChunks - OUT param: the number of elements written to in the chunk array.//
+// chunks - caller allocated storage to hold the code chunks.
+//
+// Returns:
+// S_OK if successfully copied elements to Chunk array.
+//-----------------------------------------------------------------------------
+HRESULT CordbNativeCode::GetCodeChunks(
+ ULONG32 cbufSize,
+ ULONG32 * pcnumChunks,
+ CodeChunkInfo chunks[]
+)
+{
+ PUBLIC_API_ENTRY(this);
+
+ if (pcnumChunks == NULL)
+ {
+ return E_INVALIDARG;
+ }
+ if ((chunks == NULL) != (cbufSize == 0))
+ {
+ return E_INVALIDARG;
+ }
+
+ // Current V2.0 implementation has at most 2 possible chunks right now (1 hot, and 1 cold).
+ ULONG32 cActualChunks = HasColdRegion() ? 2 : 1;
+
+ // If no buf size, then we're querying the total number of chunks.
+ if (cbufSize == 0)
+ {
+ *pcnumChunks = cActualChunks;
+ return S_OK;
+ }
+
+ // Else give them as many as they asked for.
+ for (CodeBlobRegion index = kHot; (index < MAX_REGIONS) && ((int)cbufSize > index); ++index)
+ {
+ // Fill in the region information
+ chunks[index].startAddr = m_rgCodeRegions[index].pAddress;
+ chunks[index].length = (ULONG32) (m_rgCodeRegions[index].cbSize);
+ *pcnumChunks = cbufSize;
+ }
+
+ return S_OK;
+} // CordbNativeCode::GetCodeChunks
+
+//-----------------------------------------------------------------------------
+// CordbNativeCode::GetCompilerFlags
+// Public entry point to get code flags for this Code object.
+// Originally, ICDCode had this method implemented independently from the
+// ICDModule method GetJitCompilerFlags. This was because it was considered that
+// the flags would be per function, rather than per module.
+// In addition, GetCompilerFlags did two different things depending on whether
+// the code had a native image. It turned out that was the wrong thing to do
+// .
+//
+// Parameters:
+// pdwFlags - OUT: code gen flags (see CorDebugJITCompilerFlags)
+//
+// Return value:
+// S_OK if pdwFlags is set properly.
+//-----------------------------------------------------------------------------
+HRESULT CordbNativeCode::GetCompilerFlags(DWORD * pdwFlags)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pdwFlags, DWORD *);
+ *pdwFlags = 0;
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ return GetFunction()->GetModule()->GetJITCompilerFlags(pdwFlags);
+
+} // CordbNativeCode::GetCompilerFlags
+
+//-----------------------------------------------------------------------------
+// Given an IL local variable number and a native IP offset, return the
+// location of the variable in jitted code.
+//-----------------------------------------------------------------------------
+HRESULT CordbNativeCode::ILVariableToNative(DWORD dwIndex,
+ SIZE_T ip,
+ const ICorDebugInfo::NativeVarInfo ** ppNativeInfo)
+{
+ _ASSERTE(m_nativeVarData.IsInitialized());
+
+ return FindNativeInfoInILVariableArray(dwIndex,
+ ip,
+ m_nativeVarData.GetOffsetInfoList(),
+ ppNativeInfo);
+} // CordbNativeCode::ILVariableToNative
+
+
+HRESULT CordbNativeCode::GetReturnValueLiveOffset(ULONG32 ILoffset, ULONG32 bufferSize, ULONG32 *pFetched, ULONG32 *pOffsets)
+{
+ HRESULT hr = S_OK;
+
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ VALIDATE_POINTER_TO_OBJECT(pFetched, ULONG32 *);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ EX_TRY
+ {
+ hr = GetReturnValueLiveOffsetImpl(NULL, ILoffset, bufferSize, pFetched, pOffsets);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+int CordbNativeCode::GetCallInstructionLength(BYTE *ip, ULONG32 count)
+{
+#if defined(DBG_TARGET_ARM)
+ return MAX_INSTRUCTION_LENGTH;
+#elif defined(DBG_TARGET_ARM64)
+ return MAX_INSTRUCTION_LENGTH;
+#elif defined(DBG_TARGET_X86)
+ if (count < 2)
+ return -1;
+
+ // Skip instruction prefixes
+ do
+ {
+ switch (*ip)
+ {
+ // Segment overrides
+ case 0x26: // ES
+ case 0x2E: // CS
+ case 0x36: // SS
+ case 0x3E: // DS
+ case 0x64: // FS
+ case 0x65: // GS
+
+ // Size overrides
+ case 0x66: // Operand-Size
+ case 0x67: // Address-Size
+
+ // Lock
+ case 0xf0:
+
+ // String REP prefixes
+ case 0xf1:
+ case 0xf2: // REPNE/REPNZ
+ case 0xf3:
+ ip++;
+ count--;
+ continue;
+
+ default:
+ break;
+ }
+ } while (0);
+
+ // Read the opcode
+ BYTE opcode = *ip++;
+ if (opcode == 0xcc)
+ {
+ // todo: Can we actually get this result? Doesn't ICorDebug hand out un-patched assembly?
+ _ASSERTE(!"Hit break opcode!");
+ return -1;
+ }
+
+ // Analyze what we can of the opcode
+ switch (opcode)
+ {
+ case 0xff:
+ {
+ // Count may have been decremented by prefixes.
+ if (count < 2)
+ return -1;
+
+ BYTE modrm = *ip++;
+ BYTE mod = (modrm & 0xC0) >> 6;
+ BYTE reg = (modrm & 0x38) >> 3;
+ BYTE rm = (modrm & 0x07);
+
+ int displace = -1;
+
+ if ((reg != 2) && (reg != 3) && (reg != 4) && (reg != 5))
+ {
+ //
+ // This is not a CALL or JMP instruction, return, unknown.
+ //
+ _ASSERTE(!"Unhandled opcode!");
+ return -1;
+ }
+
+
+ // Only try to decode registers if we actually have reg sets.
+ switch (mod)
+ {
+ case 0:
+ case 1:
+ case 2:
+
+ if (rm == 4)
+ {
+ if (count < 3)
+ return -1;
+
+ //
+ // Get values from the SIB byte
+ //
+ BYTE ss = (*ip & 0xC0) >> 6;
+ BYTE index = (*ip & 0x38) >> 3;
+ BYTE base = (*ip & 0x7);
+
+ //
+ // Finally add in the offset
+ //
+ if (mod == 0)
+ {
+ if (base == 5)
+ displace = 7;
+ else
+ displace = 3;
+ }
+ else if (mod == 1)
+ {
+ displace = 4;
+ }
+ else
+ {
+ displace = 7;
+ }
+ }
+ else
+ {
+ if (mod == 0)
+ {
+ if (rm == 5)
+ displace = 6;
+ else
+ displace = 2;
+ }
+ else if (mod == 1)
+ {
+ displace = 3;
+ }
+ else
+ {
+ displace = 6;
+ }
+ }
+ break;
+
+ case 3:
+ default:
+ displace = 2;
+ break;
+ }
+
+ return displace;
+ } // end of 0xFF case
+
+ case 0xe8:
+ return 5;
+
+
+ default:
+ break;
+ }
+
+
+ _ASSERTE(!"Unhandled opcode!");
+ return -1;
+
+#elif defined(DBG_TARGET_AMD64)
+ BYTE rex = NULL;
+ BYTE prefix = *ip;
+ BOOL fContainsPrefix = FALSE;
+
+ // Should not happen.
+ if (prefix == 0xcc)
+ return -1;
+
+ // Skip instruction prefixes
+ //@TODO by euzem:
+ //This "loop" can't be really executed more than once so if CALL can really have more than one prefix we'll crash.
+ //Some of these prefixes are not allowed for CALL instruction and we should treat them as invalid code.
+ //It appears that this code was mostly copy/pasted from \NDP\clr\src\Debug\EE\amd64\amd64walker.cpp
+ //with very minimum fixes.
+ do
+ {
+ switch (prefix)
+ {
+ // Segment overrides
+ case 0x26: // ES
+ case 0x2E: // CS
+ case 0x36: // SS
+ case 0x3E: // DS
+ case 0x64: // FS
+ case 0x65: // GS
+
+ // Size overrides
+ case 0x66: // Operand-Size
+ case 0x67: // Address-Size
+
+ // Lock
+ case 0xf0:
+
+ // String REP prefixes
+ case 0xf2: // REPNE/REPNZ
+ case 0xf3:
+ ip++;
+ fContainsPrefix = TRUE;
+ continue;
+
+ // REX register extension prefixes
+ case 0x40:
+ case 0x41:
+ case 0x42:
+ case 0x43:
+ case 0x44:
+ case 0x45:
+ case 0x46:
+ case 0x47:
+ case 0x48:
+ case 0x49:
+ case 0x4a:
+ case 0x4b:
+ case 0x4c:
+ case 0x4d:
+ case 0x4e:
+ case 0x4f:
+ // make sure to set rex to prefix, not *ip because *ip still represents the
+ // codestream which has a 0xcc in it.
+ rex = prefix;
+ ip++;
+ fContainsPrefix = TRUE;
+ continue;
+
+ default:
+ break;
+ }
+ } while (0);
+
+ // Read the opcode
+ BYTE opcode = *ip++;
+
+ // Should not happen.
+ if (opcode == 0xcc)
+ return -1;
+
+
+ // Setup rex bits if needed
+ BYTE rex_b = 0;
+ BYTE rex_x = 0;
+ BYTE rex_r = 0;
+
+ if (rex != NULL)
+ {
+ rex_b = (rex & 0x1); // high bit to modrm r/m field or SIB base field or OPCODE reg field -- Hmm, when which?
+ rex_x = (rex & 0x2) >> 1; // high bit to sib index field
+ rex_r = (rex & 0x4) >> 2; // high bit to modrm reg field
+ }
+
+ // Analyze what we can of the opcode
+ switch (opcode)
+ {
+ case 0xff:
+ {
+ BYTE modrm = *ip++;
+
+ _ASSERT(modrm != NULL);
+
+ BYTE mod = (modrm & 0xC0) >> 6;
+ BYTE reg = (modrm & 0x38) >> 3;
+ BYTE rm = (modrm & 0x07);
+
+ reg |= (rex_r << 3);
+ rm |= (rex_b << 3);
+
+ if ((reg < 2) || (reg > 5 && reg < 8) || (reg > 15)) {
+ // not a valid register for a CALL or BRANCH
+ _ASSERTE(!"Invalid opcode!");
+ return -1;
+ }
+
+ BYTE *result;
+ WORD displace = -1;
+
+ // See: Tables A-15,16,17 in AMD Dev Manual 3 for information
+ // about how the ModRM/SIB/REX bytes interact.
+
+ switch (mod)
+ {
+ case 0:
+ case 1:
+ case 2:
+ if ((rm & 0x07) == 4) // we have an SIB byte following
+ {
+ //
+ // Get values from the SIB byte
+ //
+ BYTE sib = *ip;
+ _ASSERT(sib != NULL);
+
+ BYTE base = (sib & 0x07);
+ base |= (rex_b << 3);
+
+ ip++;
+
+ //
+ // Finally add in the offset
+ //
+ if (mod == 0)
+ {
+ if ((base & 0x07) == 5)
+ displace = 7;
+ else
+ displace = 3;
+ }
+ else if (mod == 1)
+ {
+ displace = 4;
+ }
+ else // mod == 2
+ {
+ displace = 7;
+ }
+ }
+ else
+ {
+ //
+ // Get the value we need from the register.
+ //
+
+ // Check for RIP-relative addressing mode.
+ if ((mod == 0) && ((rm & 0x07) == 5))
+ {
+ displace = 6; // 1 byte opcode + 1 byte modrm + 4 byte displacement (signed)
+ }
+ else
+ {
+ if (mod == 0)
+ displace = 2;
+ else if (mod == 1)
+ displace = 3;
+ else // mod == 2
+ displace = 6;
+ }
+ }
+
+ break;
+
+ case 3:
+ default:
+ displace = 2;
+ }
+
+ // Displace should be set by one of the cases above
+ if (displace == -1)
+ {
+ _ASSERTE(!"GetCallInstructionLength() encountered unexpected call instruction");
+ return -1;
+ }
+
+ // Account for the 1 byte prefix (REX or otherwise)
+ if (fContainsPrefix)
+ displace++;
+
+ // reg == 4 or 5 means that it is not a CALL, but JMP instruction
+ // so we will fall back to ASSERT after break
+ if ((reg != 4) && (reg != 5))
+ return displace;
+ break;
+ }
+ case 0xe8:
+ {
+ //Near call with the target specified by a 32-bit relative displacement.
+ //[maybe 1 byte prefix] + [1 byte opcode E8h] + [4 bytes offset]
+ return 5 + (fContainsPrefix ? 1 : 0);
+ }
+ default:
+ break;
+ }
+
+ _ASSERTE(!"Invalid opcode!");
+ return -1;
+#else
+#error Platform not implemented
+#endif
+}
+
+HRESULT CordbNativeCode::GetSigParserFromFunction(mdToken mdFunction, mdToken *pClass, SigParser &parser, SigParser &methodGenerics)
+{
+ // mdFunction may be a MemberRef, a MethodDef, or a MethodSpec. We must handle all three cases.
+ HRESULT hr = S_OK;
+ IMetaDataImport* pImport = m_pFunction->GetModule()->GetMetaDataImporter();
+ RSExtSmartPtr<IMetaDataImport2> pImport2;
+ IfFailRet(pImport->QueryInterface(IID_IMetaDataImport2, (void**)&pImport2));
+
+ if (TypeFromToken(mdFunction) == mdtMemberRef)
+ {
+ PCCOR_SIGNATURE sig = 0;
+ ULONG sigSize = 0;
+ IfFailRet(pImport->GetMemberRefProps(mdFunction, pClass, NULL, 0, 0, &sig, &sigSize));
+ parser = SigParser(sig, sigSize);
+ }
+ else if (TypeFromToken(mdFunction) == mdtMethodDef)
+ {
+ PCCOR_SIGNATURE sig = 0;
+ ULONG sigSize = 0;
+ IfFailRet(pImport->GetMethodProps(mdFunction, pClass, NULL, 0, NULL, NULL, &sig, &sigSize, NULL, NULL));
+ parser = SigParser(sig, sigSize);
+ }
+ else if (TypeFromToken(mdFunction) == mdtMethodSpec)
+ {
+ // For a method spec, we use GetMethodSpecProps to get the generic singature and the parent token
+ // (which is a MethodDef token). We'll recurse to get the other properties from the parent token.
+
+ PCCOR_SIGNATURE sig = 0;
+ ULONG sigSize = 0;
+ mdToken parentToken = 0;
+ IfFailRet(pImport2->GetMethodSpecProps(mdFunction, &parentToken, &sig, &sigSize));
+ methodGenerics = SigParser(sig, sigSize);
+
+ if (pClass)
+ *pClass = parentToken;
+
+ return GetSigParserFromFunction(parentToken, pClass, parser, methodGenerics);
+ }
+ else
+ {
+ // According to ECMA III.3.19, this can never happen.
+ return E_UNEXPECTED;
+ }
+
+ return S_OK;
+}
+
+HRESULT CordbNativeCode::EnsureReturnValueAllowed(Instantiation *currentInstantiation, mdToken targetClass, SigParser &parser, SigParser &methodGenerics)
+{
+ HRESULT hr = S_OK;
+ ULONG genCount = 0;
+ IfFailRet(SkipToReturn(parser, &genCount));
+
+ return EnsureReturnValueAllowedWorker(currentInstantiation, targetClass, parser, methodGenerics, genCount);
+}
+
+HRESULT CordbNativeCode::EnsureReturnValueAllowedWorker(Instantiation *currentInstantiation, mdToken targetClass, SigParser &parser, SigParser &methodGenerics, ULONG genCount)
+{
+ // There are a few considerations here:
+ // 1. Generic instantiations. This is a "Foo<T>", and we need to check if that "Foo"
+ // fits one of the categories we disallow (such as a struct).
+ // 2. Void return.
+ // 3. ValueType - Unsupported this release.
+ // 4. MVAR - Method generics. We need to get the actual generic type and recursively
+ // check if we allow that.
+ // 5. VAR - Class generics. We need to get the actual generic type and recurse.
+
+ SigParser original(parser);
+ HRESULT hr = S_OK;
+ CorElementType returnType;
+ IfFailRet(parser.GetElemType(&returnType));
+ if (returnType == ELEMENT_TYPE_GENERICINST)
+ {
+ IfFailRet(parser.GetElemType(&returnType));
+
+ if (returnType == ELEMENT_TYPE_CLASS)
+ return S_OK;
+
+ if (returnType != ELEMENT_TYPE_VALUETYPE)
+ return META_E_BAD_SIGNATURE;
+
+ if (currentInstantiation == NULL)
+ return S_OK; // We will check again when we have the instantiation.
+
+ NewArrayHolder<CordbType*> types;
+ Instantiation inst;
+ IfFailRet(CordbJITILFrame::BuildInstantiationForCallsite(GetModule(), types, inst, currentInstantiation, targetClass, SigParser(methodGenerics)));
+
+ CordbType *pType = 0;
+ IfFailRet(CordbType::SigToType(GetModule(), &original, &inst, &pType));
+
+
+ IfFailRet(hr = pType->ReturnedByValue());
+ if (hr == S_OK) // not S_FALSE
+ return S_OK;
+
+ return CORDBG_E_UNSUPPORTED;
+ }
+
+ if (returnType == ELEMENT_TYPE_VALUETYPE)
+ {
+ Instantiation inst;
+ CordbType *pType = 0;
+ IfFailRet(CordbType::SigToType(GetModule(), &original, &inst, &pType));
+
+ IfFailRet(hr = pType->ReturnedByValue());
+ if (hr == S_OK) // not S_FALSE
+ return S_OK;
+
+ return CORDBG_E_UNSUPPORTED;
+ }
+
+ if (returnType == ELEMENT_TYPE_TYPEDBYREF)
+ return CORDBG_E_UNSUPPORTED;
+
+ if (returnType == ELEMENT_TYPE_VOID)
+ return E_UNEXPECTED;
+
+ if (returnType == ELEMENT_TYPE_MVAR)
+ {
+ // Get which generic parameter is referenced.
+ ULONG genParam = 0;
+ IfFailRet(parser.GetData(&genParam));
+
+ // Grab the calling convention of the method, ensure it's GENERICINST.
+ ULONG callingConv = 0;
+ IfFailRet(methodGenerics.GetCallingConvInfo(&callingConv));
+ if (callingConv != IMAGE_CEE_CS_CALLCONV_GENERICINST)
+ return META_E_BAD_SIGNATURE;
+
+ // Ensure sensible bounds.
+ SigParser generics(methodGenerics); // Make a copy since operations are destructive.
+ ULONG maxCount = 0;
+ IfFailRet(generics.GetData(&maxCount));
+ if (maxCount <= genParam || genParam > 1024)
+ return META_E_BAD_SIGNATURE;
+
+ // Walk to the parameter referenced.
+ while (genParam--)
+ IfFailRet(generics.SkipExactlyOne());
+
+ // Now recurse with "generics" at the location to continue parsing.
+ return EnsureReturnValueAllowedWorker(currentInstantiation, targetClass, generics, methodGenerics, genCount);
+ }
+
+
+ if (returnType == ELEMENT_TYPE_VAR)
+ {
+ // Get which type parameter is reference.
+ ULONG typeParam = 0;
+ parser.GetData(&typeParam);
+
+ // Ensure something reasonable.
+ if (typeParam > 1024)
+ return META_E_BAD_SIGNATURE;
+
+ // Lookup the containing class's signature so we can get the referenced generic parameter.
+ IMetaDataImport *pImport = m_pFunction->GetModule()->GetMetaDataImporter();
+ PCCOR_SIGNATURE sig;
+ ULONG countSig;
+ IfFailRet(pImport->GetTypeSpecFromToken(targetClass, &sig, &countSig));
+
+ // Enusre the type's typespec is GENERICINST.
+ SigParser typeParser(sig, countSig);
+ CorElementType et;
+ IfFailRet(typeParser.GetElemType(&et));
+ if (et != ELEMENT_TYPE_GENERICINST)
+ return META_E_BAD_SIGNATURE;
+
+ // Move to the correct location.
+ IfFailRet(typeParser.GetElemType(&et));
+ if (et != ELEMENT_TYPE_VALUETYPE && et != ELEMENT_TYPE_CLASS)
+ return META_E_BAD_SIGNATURE;
+
+ IfFailRet(typeParser.GetToken(NULL));
+
+ ULONG totalTypeCount = 0;
+ IfFailRet(typeParser.GetData(&totalTypeCount));
+ if (totalTypeCount < typeParam)
+ return META_E_BAD_SIGNATURE;
+
+ while (typeParam--)
+ IfFailRet(typeParser.SkipExactlyOne());
+
+ // This is a temporary workaround for an infinite recursion here. ALL of this code will
+ // go away when we allow struct return values, but in the mean time this avoids a corner
+ // case in the type system we haven't solved yet.
+ IfFailRet(typeParser.PeekElemType(&et));
+ if (et == ELEMENT_TYPE_VAR)
+ return E_FAIL;
+
+ // Now that typeParser is at the location of the correct generic parameter, recurse.
+ return EnsureReturnValueAllowedWorker(currentInstantiation, targetClass, typeParser, methodGenerics, genCount);
+ }
+
+ // Everything else supported
+ return S_OK;
+}
+
+HRESULT CordbNativeCode::SkipToReturn(SigParser &parser, ULONG *genCount)
+{
+ // Takes a method signature parser (at the beginning of a signature) and skips to the
+ // return value.
+ HRESULT hr = S_OK;
+
+ // Skip calling convention
+ ULONG uCallConv;
+ IfFailRet(parser.GetCallingConvInfo(&uCallConv));
+ if ((uCallConv == IMAGE_CEE_CS_CALLCONV_FIELD) || (uCallConv == IMAGE_CEE_CS_CALLCONV_LOCAL_SIG))
+ return META_E_BAD_SIGNATURE;
+
+ // Skip type parameter count if function is generic
+ if (uCallConv & IMAGE_CEE_CS_CALLCONV_GENERIC)
+ IfFailRet(parser.GetData(genCount));
+
+ // Skip argument count
+ IfFailRet(parser.GetData(NULL));
+
+ return S_OK;
+}
+
+HRESULT CordbNativeCode::GetCallSignature(ULONG32 ILoffset, mdToken *pClass, mdToken *pFunction, SigParser &parser, SigParser &generics)
+{
+ // check if specified IL offset is at a call instruction
+ CordbILCode *pCode = this->m_pFunction->GetILCode();
+ BYTE buffer[3];
+ ULONG32 fetched = 0;
+ HRESULT hr = pCode->GetCode(ILoffset, ILoffset+_countof(buffer), _countof(buffer), buffer, &fetched);
+
+ if (FAILED(hr))
+ return hr;
+ else if (fetched != _countof(buffer))
+ return CORDBG_E_INVALID_OPCODE;
+
+ // tail. - fe 14 (ECMA III.2.4)
+ BYTE instruction = buffer[0];
+ if (buffer[0] == 0xfe && buffer[1] == 0x14)
+ {
+ // tail call case. We don't allow managed return values for tailcalls.
+ return CORDBG_E_INVALID_OPCODE;
+ }
+
+ // call - 28 (ECMA III.3.19)
+ // callvirt - 6f (ECMA III.4.2)
+ if (instruction != 0x28 && instruction != 0x6f)
+ return CORDBG_E_INVALID_OPCODE;
+
+ // Now grab the MD token of the call
+ mdToken mdFunction = 0;
+ const ULONG32 offset = ILoffset + 1;
+ hr = pCode->GetCode(offset, offset+sizeof(mdToken), sizeof(mdToken), (BYTE*)&mdFunction, &fetched);
+ if (FAILED(hr) || fetched != sizeof(mdToken))
+ return CORDBG_E_INVALID_OPCODE;
+
+ if (pFunction)
+ *pFunction = mdFunction;
+
+ // Convert to a signature parser
+ return GetSigParserFromFunction(mdFunction, pClass, parser, generics);
+}
+
+HRESULT CordbNativeCode::GetReturnValueLiveOffsetImpl(Instantiation *currentInstantiation, ULONG32 ILoffset, ULONG32 bufferSize, ULONG32 *pFetched, ULONG32 *pOffsets)
+{
+ if (pFetched == NULL)
+ return E_INVALIDARG;
+
+ HRESULT hr = S_OK;
+ ULONG32 found = 0;
+
+ // verify that the call target actually returns something we allow
+ SigParser signature, generics;
+ mdToken mdClass = 0;
+ IfFailRet(GetCallSignature(ILoffset, &mdClass, NULL, signature, generics));
+ IfFailRet(EnsureReturnValueAllowed(currentInstantiation, mdClass, signature, generics));
+
+ // now find the native offset
+ SequencePoints *pSP = GetSequencePoints();
+ DebuggerILToNativeMap *pMap = pSP->GetCallsiteMapAddr();
+
+ for (ULONG32 i = 0; i < pSP->GetCallsiteEntryCount() && pMap; ++i, pMap++)
+ {
+ if (pMap->ilOffset == ILoffset && (pMap->source & ICorDebugInfo::CALL_INSTRUCTION) == ICorDebugInfo::CALL_INSTRUCTION)
+ {
+ // if we have a buffer, fill it in.
+ if (pOffsets && found < bufferSize)
+ {
+ // Fetch the actual assembly instructions
+ BYTE nativeBuffer[8];
+
+ ULONG32 fetched = 0;
+ IfFailRet(GetCode(pMap->nativeStartOffset, pMap->nativeStartOffset+_countof(nativeBuffer), _countof(nativeBuffer), nativeBuffer, &fetched));
+
+ int skipBytes = 0;
+
+#if defined(DBG_TARGET_X86) && defined(FEATURE_CORESYSTEM)
+ // Skip nop sleds on x86 coresystem. The JIT adds these instructions as a security measure,
+ // and incorrectly reports to us the wrong offset of the call instruction.
+ const BYTE nop_opcode = 0x90;
+ while (fetched && nativeBuffer[0] == nop_opcode)
+ {
+ skipBytes++;
+
+ for (int j = 1; j < _countof(nativeBuffer) && nativeBuffer[j] == nop_opcode; ++j)
+ skipBytes++;
+
+ // We must have at least one skip byte since the outer while ensures it. Thus we always need to reread
+ // the buffer at the end of this loop.
+ IfFailRet(GetCode(pMap->nativeStartOffset+skipBytes, pMap->nativeStartOffset+skipBytes+_countof(nativeBuffer), _countof(nativeBuffer), nativeBuffer, &fetched));
+ }
+#endif
+
+ // Get the length of the call instruction.
+ int offset = GetCallInstructionLength(nativeBuffer, fetched);
+ if (offset == -1)
+ return E_UNEXPECTED; // Could not decode instruction, this should never happen.
+
+ pOffsets[found] = pMap->nativeStartOffset + offset + skipBytes;
+ }
+
+ found++;
+ }
+ }
+
+ if (pOffsets)
+ *pFetched = found < bufferSize ? found : bufferSize;
+ else
+ *pFetched = found;
+
+ if (found == 0)
+ return E_FAIL;
+
+ if (pOffsets && found > bufferSize)
+ return S_FALSE;
+
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Creates a CordbNativeCode (if it's not already created) and adds it to the
+// hash table of CordbNativeCode instances belonging to this module.
+// Used by CordbFunction::InitNativeCodeInfo.
+//
+// Arguments:
+// Input:
+// methodToken - the methodDef token of the function this native code belongs to
+// methodDesc - the methodDesc for the jitted method
+// startAddress - the hot code startAddress for this method
+
+// Return value:
+// found or created CordbNativeCode pointer
+// Assumptions: methodToken is in the metadata for this module
+// methodDesc and startAddress should be consistent for
+// a jitted instance of methodToken's method
+//-----------------------------------------------------------------------------
+CordbNativeCode * CordbModule::LookupOrCreateNativeCode(mdMethodDef methodToken,
+ VMPTR_MethodDesc methodDesc,
+ CORDB_ADDRESS startAddress)
+{
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+ _ASSERTE(startAddress != NULL);
+ _ASSERTE(methodDesc != VMPTR_MethodDesc::NullPtr());
+
+ CordbNativeCode * pNativeCode = NULL;
+ NativeCodeFunctionData codeInfo;
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+
+ // see if we already have this--if not, we'll make an instance, otherwise we'll just return the one we have.
+ pNativeCode = m_nativeCodeTable.GetBase((UINT_PTR) startAddress);
+
+ if (pNativeCode == NULL)
+ {
+ GetProcess()->GetDAC()->GetNativeCodeInfoForAddr(methodDesc, startAddress, &codeInfo);
+
+ // We didn't have an instance, so we'll build one and add it to the hash table
+ LOG((LF_CORDB,
+ LL_INFO10000,
+ "R:CT::RSCreating code w/ ver:0x%x, md:0x%x, nativeStart=0x%08x, nativeSize=0x%08x\n",
+ codeInfo.encVersion,
+ VmPtrToCookie(codeInfo.vmNativeCodeMethodDescToken),
+ codeInfo.m_rgCodeRegions[kHot].pAddress,
+ codeInfo.m_rgCodeRegions[kHot].cbSize));
+
+ // Lookup the function object that this code should be bound to
+ CordbFunction* pFunction = CordbModule::LookupOrCreateFunction(methodToken, codeInfo.encVersion);
+ _ASSERTE(pFunction != NULL);
+
+ // There are bugs with the on-demand class load performed by CordbFunction in some cases. The old stack
+ // tracing code avoided them by eagerly loading the parent class so I am following suit
+ pFunction->InitParentClassOfFunction();
+
+ // First, create a new CordbNativeCode instance--we'll need this to make the CordbJITInfo instance
+ pNativeCode = new (nothrow)CordbNativeCode(pFunction, &codeInfo, codeInfo.isInstantiatedGeneric != 0);
+ _ASSERTE(pNativeCode != NULL);
+
+ m_nativeCodeTable.AddBaseOrThrow(pNativeCode);
+ }
+
+ return pNativeCode;
+} // CordbNativeCode::LookupOrCreateFromJITData
+
+// LoadNativeInfo loads from the left side any native variable info
+// from the JIT.
+//
+void CordbNativeCode::LoadNativeInfo()
+{
+ THROW_IF_NEUTERED(this);
+ INTERNAL_API_ENTRY(this->GetProcess());
+
+
+ // If we've either never done this before (no info), or we have, but the version number has increased, we
+ // should try and get a newer version of our JIT info.
+ if(m_nativeVarData.IsInitialized())
+ {
+ return;
+ }
+
+ // You can't do this if the function is implemented as part of the Runtime.
+ if (GetFunction()->IsNativeImpl() == CordbFunction::kNativeOnly)
+ {
+ ThrowHR(CORDBG_E_FUNCTION_NOT_IL);
+ }
+ CordbProcess *pProcess = GetProcess();
+ // Get everything via the DAC
+ if (m_fCodeAvailable)
+ {
+ RSLockHolder lockHolder(pProcess->GetProcessLock());
+ pProcess->GetDAC()->GetNativeCodeSequencePointsAndVarInfo(GetVMNativeCodeMethodDescToken(),
+ GetAddress(),
+ m_fCodeAvailable,
+ &m_nativeVarData,
+ &m_sequencePoints);
+ }
+
+} // CordbNativeCode::LoadNativeInfo
+
+
+
diff --git a/src/debug/di/nativepipeline.cpp b/src/debug/di/nativepipeline.cpp
new file mode 100644
index 0000000000..21105f359a
--- /dev/null
+++ b/src/debug/di/nativepipeline.cpp
@@ -0,0 +1,64 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+//*****************************************************************************
+// File: NativePipeline.cpp
+//
+
+//
+//*****************************************************************************
+
+#include "stdafx.h"
+#include "nativepipeline.h"
+
+#if defined(ENABLE_EVENT_REDIRECTION_PIPELINE)
+#include "eventredirection.h"
+#include "eventredirectionpipeline.h"
+#endif
+
+#include "sstring.h"
+
+//-----------------------------------------------------------------------------
+// Returns null if redirection is not enabled, else returns a new redirection pipeline.
+INativeEventPipeline * CreateEventRedirectionPipelineIfEnabled()
+{
+#if !defined(ENABLE_EVENT_REDIRECTION_PIPELINE)
+ return NULL;
+#else
+
+ BOOL fEnabled = CLRConfig::GetConfigValue(CLRConfig::UNSUPPORTED_DbgRedirect) != 0;
+ if (!fEnabled)
+ {
+ return NULL;
+ }
+
+ return new (nothrow) EventRedirectionPipeline();
+#endif
+}
+
+
+//-----------------------------------------------------------------------------
+// Allocate and return a pipeline object for this platform
+// Has debug checks (such as for event redirection)
+//
+// Returns:
+// newly allocated pipeline object. Caller must call delete on it.
+INativeEventPipeline * NewPipelineWithDebugChecks()
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ }
+ CONTRACTL_END;
+ INativeEventPipeline * pRedirection = CreateEventRedirectionPipelineIfEnabled();
+ if (pRedirection != NULL)
+ {
+ return pRedirection;
+ }
+
+ return NewPipelineForThisPlatform();
+}
+
+
+
+
diff --git a/src/debug/di/nativepipeline.h b/src/debug/di/nativepipeline.h
new file mode 100644
index 0000000000..c9560bfe65
--- /dev/null
+++ b/src/debug/di/nativepipeline.h
@@ -0,0 +1,228 @@
+// 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.
+//*****************************************************************************
+// NativePipeline.h
+//
+
+//
+// defines native pipeline abstraction, which includes debug-support
+// for event redirection.
+//*****************************************************************************
+
+
+#ifndef _NATIVE_PIPELINE_H
+#define _NATIVE_PIPELINE_H
+
+//-----------------------------------------------------------------------------
+// Interface for native-debugging pipeline associated with a single process
+// that is being debugged.
+//
+// On windows, this is a wrapper around the win32 debugging API
+// (eg, kernel32!WaitForDebugEvent). On rotor, it has an alternative implementation.
+// . See code:IEventChannel for more information.
+// @dbgtodo : All of the APIs that return BOOL should probably be changed to
+// return HRESULTS so we don't have to rely on some implicit GetLastError protocol.
+//-----------------------------------------------------------------------------
+class INativeEventPipeline
+{
+public:
+ // Call to delete the pipeline. This can only be called once.
+ virtual void Delete() = 0;
+
+
+ //
+ // set whether to kill outstanding debuggees when the debugger exits.
+ //
+ // Arguments:
+ // fKillOnExit - When the debugger thread (this thread) exits, outstanding debuggees will be
+ // terminated (if true), else detached (if false)
+ //
+ // Returns:
+ // True on success, False on failure.
+ //
+ // Notes:
+ // This is a cross-platform wrapper around Kernel32!DebugSetProcessKillOnExit.
+ // This affects all debuggees handled by this thread.
+ // This is not supported or necessary for Mac debugging. The only reason we need this on Windows is to
+ // ask the OS not to terminate the debuggee when the debugger exits. The Mac debugging pipeline
+ // doesn't automatically kill the debuggee when the debugger exits.
+ //
+
+ virtual BOOL DebugSetProcessKillOnExit(bool fKillOnExit) = 0;
+
+ // Create
+ virtual HRESULT CreateProcessUnderDebugger(
+ MachineInfo machineInfo,
+ LPCWSTR lpApplicationName,
+ LPCWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ LPVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation) = 0;
+
+ // Attach
+ virtual HRESULT DebugActiveProcess(MachineInfo machineInfo, DWORD processId) = 0;
+
+ // Detach
+ virtual HRESULT DebugActiveProcessStop(DWORD processId) =0;
+
+ //
+ // Block and wait for the next debug event from the debuggee process.
+ //
+ // Arguments:
+ // pEvent - buffer for the debug event to be returned
+ // dwTimeout - number of milliseconds to wait before timing out
+ // pProcess - the CordbProcess associated with this pipeline; used to look up a thread ID if necessary
+ //
+ // Return Value:
+ // TRUE if a debug event is available
+ //
+ // Notes:
+ // Once a debug event is returned, it is consumed from the pipeline and will not be accessible in the
+ // future. Caller is responsible for saving the debug event if necessary.
+ //
+
+ virtual BOOL WaitForDebugEvent(DEBUG_EVENT * pEvent, DWORD dwTimeout, CordbProcess * pProcess) =0;
+
+ //
+ // This is specific to Windows. When a debug event is sent to the debugger, the debuggee process is
+ // suspended. The debugger must call this function to resume the debuggee process.
+ //
+ // Arguments:
+ // dwProcessId - process ID of the debuggee
+ // dwThreadId - thread ID of the thread which has triggered a debug event before
+ // dwContinueStatus - whether to handle the exception (if any) reported on the specified thread
+ //
+ // Return Value:
+ // TRUE if successful
+ //
+ // Notes:
+ // For Mac debugging, the process isn't actually suspended when a debug event is raised. As such,
+ // this function is a nop for Mac debugging. See code:Debugger::SendRawEvent.
+ //
+ // Of course, this is a semantic difference from Windows. However, in most cases, the LS suspends
+ // all managed threads by calling code:Debugger::TrapAllRuntimeThreads immediately after raising a
+ // debug event. The only case where this is not true is code:Debugger::SendCreateProcess, but that
+ // doesn't seem to be a problem at this point.
+ //
+
+ virtual BOOL ContinueDebugEvent(
+ DWORD dwProcessId,
+ DWORD dwThreadId,
+ DWORD dwContinueStatus
+ ) =0;
+
+ //
+ // Return a handle for the debuggee process.
+ //
+ // Return Value:
+ // handle for the debuggee process (see below)
+ //
+ // Notes:
+ // Handles are a Windows-specific concept. For Mac debugging, the handle returned by this function is
+ // only valid for waiting on process termination. This is ok for now because the only cases where a
+ // real process handle is needed are related to interop-debugging, which isn't supported on the Mac.
+ //
+
+ virtual HANDLE GetProcessHandle() = 0;
+
+ //
+ // Terminate the debuggee process.
+ //
+ // Arguments:
+ // exitCode - the exit code for the debuggee process
+ //
+ // Return Value:
+ // TRUE if successful
+ //
+ // Notes:
+ // The exit code is ignored for Mac debugging.
+ //
+
+ virtual BOOL TerminateProcess(UINT32 exitCode) = 0;
+
+ //
+ // Resume any suspended threads in the currend process.
+ // This decreases the suspend count of each thread by at most 1.
+ // Call multiple times until it returns S_FALSE if you want to really ensure
+ // all threads are running.
+ //
+ // Notes:
+ // On Windows the OS may suspend threads when continuing a 2nd-chance exception.
+ // Call this to get them resumed again. On other platforms this
+ // will typically be a no-op, so I provide a default implementation to avoid
+ // everyone having to override this.
+ //
+ // Return Value:
+ // S_OK if at least one thread was resumed from a suspended state
+ // S_FALSE if nothing was done
+ // An error code indicating why we were not able to attempt this
+
+ virtual HRESULT EnsureThreadsRunning()
+ {
+ return S_FALSE;
+ }
+
+#ifdef FEATURE_PAL
+ // Used by debugger side (RS) to cleanup the target (LS) named pipes
+ // and semaphores when the debugger detects the debuggee process exited.
+ virtual void CleanupTargetProcess()
+ {
+ }
+#endif
+};
+
+//
+// Helper accessors for manipulating native pipeline.
+// These also provide some platform abstractions for DEBUG_EVENT.
+//
+
+// Returns process ID that the debug event is on.
+DWORD GetProcessId(const DEBUG_EVENT * pEvent);
+
+// Returns Thread ID of the thread that fired the debug event.
+DWORD GetThreadId(const DEBUG_EVENT * pEvent);
+
+//
+// Determines if this is an exception event.
+//
+// Arguments:
+// pEvent - [required, in]: debug event to inspect
+// pfFirstChance - [required, out]: set if this is an 1st-chance exception.
+// ppRecord - [required, out]: if this is an exception, pointer into to the exception record.
+// this pointer has the same lifetime semantics as the DEBUG_EVENT (it may
+// likely be a pointer into the debug-event).
+//
+// Returns:
+// True if this is an exception. Sets outparameters to exception values.
+// Else false.
+//
+// Notes:
+// Exceptions are spceial because they need to be sent to the CLR for filtering.
+BOOL IsExceptionEvent(const DEBUG_EVENT * pEvent, BOOL * pfFirstChance, const EXCEPTION_RECORD ** ppRecord);
+
+
+//-----------------------------------------------------------------------------
+// Allocate and return a pipeline object for this platform
+//
+// Returns:
+// newly allocated pipeline object. Caller must call Dispose() on it.
+INativeEventPipeline * NewPipelineForThisPlatform();
+
+//-----------------------------------------------------------------------------
+// Allocate and return a pipeline object for this platform
+// Has debug checks (such as for event redirection)
+//
+// Returns:
+// newly allocated pipeline object. Caller must call Dispose() on it.
+INativeEventPipeline * NewPipelineWithDebugChecks();
+
+
+
+#endif // _NATIVE_PIPELINE_H
+
diff --git a/src/debug/di/platformspecific.cpp b/src/debug/di/platformspecific.cpp
new file mode 100644
index 0000000000..9df22b1728
--- /dev/null
+++ b/src/debug/di/platformspecific.cpp
@@ -0,0 +1,40 @@
+// 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 "stdafx.h"
+
+//
+// This file exists just to pull in platform specific source files. More precisely we're switching on the
+// platform being targetted for debugging (not the platform we're currently building debugger executables
+// for). We do this instead of using build rules to overcome limitations with build when it comes including
+// different source files based on build macros.
+//
+
+#if FEATURE_DBGIPC_TRANSPORT_DI
+#include "dbgtransportpipeline.cpp"
+#include "shimremotedatatarget.cpp"
+#include "remoteeventchannel.cpp"
+#else
+#include "WindowsPipeline.cpp"
+#include "EventRedirectionPipeline.cpp"
+#include "ShimLocalDataTarget.cpp"
+#include "LocalEventChannel.cpp"
+#endif
+
+#if DBG_TARGET_X86
+#include "i386/cordbregisterset.cpp"
+#include "i386/primitives.cpp"
+#elif DBG_TARGET_AMD64
+#include "amd64/cordbregisterset.cpp"
+#include "amd64/primitives.cpp"
+#elif DBG_TARGET_ARM
+#include "arm/cordbregisterset.cpp"
+#include "arm/primitives.cpp"
+#elif DBG_TARGET_ARM64
+#include "arm64/cordbregisterset.cpp"
+#include "arm64/primitives.cpp"
+#else
+#error Unsupported platform
+#endif
diff --git a/src/debug/di/process.cpp b/src/debug/di/process.cpp
new file mode 100644
index 0000000000..44e4a0c667
--- /dev/null
+++ b/src/debug/di/process.cpp
@@ -0,0 +1,15235 @@
+// 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.
+//*****************************************************************************
+// File: process.cpp
+//
+
+//
+//*****************************************************************************
+#include "stdafx.h"
+#include "primitives.h"
+#include "safewrap.h"
+
+#include "check.h"
+
+#ifndef SM_REMOTESESSION
+#define SM_REMOTESESSION 0x1000
+#endif
+
+#include "corpriv.h"
+#include "corexcep.h"
+#include "../../dlls/mscorrc/resource.h"
+#include <limits.h>
+
+#include <sstring.h>
+
+// @dbgtodo shim: process has some private hooks into the shim.
+#include "shimpriv.h"
+
+#include "metadataexports.h"
+#include "readonlydatatargetfacade.h"
+#include "metahost.h"
+
+// Keep this around for retail debugging. It's very very useful because
+// it's global state that we can always find, regardless of how many locals the compiler
+// optimizes away ;)
+struct RSDebuggingInfo;
+extern RSDebuggingInfo * g_pRSDebuggingInfo;
+
+//---------------------------------------------------------------------------------------
+//
+// OpenVirtualProcessImpl method called by the shim to get an ICorDebugProcess4 instance
+//
+// Arguments:
+// clrInstanceId - target pointer identifying which CLR in the Target to debug.
+// pDataTarget - data target abstraction.
+// hDacModule - the handle of the appropriate DAC dll for this runtime
+// riid - interface ID to query for.
+// ppProcessOut - new object for target, interface ID matches riid.
+// ppFlagsOut - currently only has 1 bit to indicate whether or not this runtime
+// instance will send a managed event after attach
+//
+// Return Value:
+// S_OK on success. Else failure
+//
+// Assumptions:
+//
+// Notes:
+// The outgoing process object can be cleaned up by calling Detach (which
+// will reset the Attach bit.)
+// @dbgtodo attach-bit: need to determine fate of attach bit.
+//
+//---------------------------------------------------------------------------------------
+STDAPI OpenVirtualProcessImpl(
+ ULONG64 clrInstanceId,
+ IUnknown * pDataTarget,
+ HMODULE hDacModule,
+ CLR_DEBUGGING_VERSION * pMaxDebuggerSupportedVersion,
+ REFIID riid,
+ IUnknown ** ppInstance,
+ CLR_DEBUGGING_PROCESS_FLAGS* pFlagsOut)
+{
+ HRESULT hr = S_OK;
+ RSExtSmartPtr<CordbProcess> pProcess;
+ PUBLIC_API_ENTRY(NULL);
+ EX_TRY
+ {
+
+ if ( (pDataTarget == NULL) || (clrInstanceId == 0) || (pMaxDebuggerSupportedVersion == NULL) ||
+ ((pFlagsOut == NULL) && (ppInstance == NULL))
+ )
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ // We consider the top 8 bits of the struct version to be the only part that represents
+ // a breaking change. This gives us some freedom in the future to have the debugger
+ // opt into getting more data.
+ const WORD kMajorMask = 0xff00;
+ const WORD kMaxStructMajor = 0;
+ if ((pMaxDebuggerSupportedVersion->wStructVersion & kMajorMask) > kMaxStructMajor)
+ {
+ // Don't know how to interpret the version structure
+ ThrowHR(CORDBG_E_UNSUPPORTED_VERSION_STRUCT);
+ }
+
+ // This process object is intended to be used for the V3 pipeline, and so
+ // much of the process from V2 is not being used. For example,
+ // - there is no ShimProcess object
+ // - there is no w32et thread (all threads are effectively an event thread)
+ // - the stop state is 'live', which corresponds to CordbProcess not knowing what
+ // its stop state really is (because that is now controlled by the shim).
+ IfFailThrow(CordbProcess::OpenVirtualProcess(
+ clrInstanceId,
+ pDataTarget, // takes a reference
+ hDacModule,
+ NULL, // Cordb
+ (DWORD) 0, // 0 for V3 cases (pShim == NULL).
+ NULL, // no Shim in V3 cases
+ &pProcess));
+
+ // CordbProcess::OpenVirtualProcess already did the external addref to pProcess.
+ // Since pProcess is a smart ptr, it will external release in this function.
+ // Living reference will be the one from the QI.
+
+ // get the managed debug event pending flag
+ if(pFlagsOut != NULL)
+ {
+ hr = pProcess->GetAttachStateFlags(pFlagsOut);
+ if(FAILED(hr))
+ {
+ ThrowHR(hr);
+ }
+ }
+
+ //
+ // Check to make sure the debugger supports debugging this version
+ // Note that it's important that we still store the flags (above) in this case
+ //
+ if (!CordbProcess::IsCompatibleWith(pMaxDebuggerSupportedVersion->wMajor))
+ {
+ // Not compatible - don't keep the process instance, and return this specific error-code
+ ThrowHR(CORDBG_E_UNSUPPORTED_FORWARD_COMPAT);
+ }
+
+ //
+ // Now Query for the requested interface
+ //
+ if(ppInstance != NULL)
+ {
+ IfFailThrow(pProcess->QueryInterface(riid, reinterpret_cast<void**> (ppInstance)));
+ }
+
+ // if you have to add code here that could fail make sure ppInstance gets released and NULL'ed at exit
+ }
+ EX_CATCH_HRESULT(hr);
+
+ if((FAILED(hr) || ppInstance == NULL) && pProcess != NULL)
+ {
+ // The process has a strong reference to itself which is only released by neutering it.
+ // Since we aren't handing out the ref then we need to clean it up
+ _ASSERTE(ppInstance == NULL || *ppInstance == NULL);
+ pProcess->Neuter();
+ }
+ return hr;
+};
+
+//---------------------------------------------------------------------------------------
+// DEPRECATED - use OpenVirtualProcessImpl
+// OpenVirtualProcess method used by the shim in CLR v4 Beta1
+// We'd like a beta1 shim/VS to still be able to open dumps using a CLR v4 Beta2+ mscordbi.dll,
+// so we'll leave this in place (at least until after Beta2 is in wide use).
+//---------------------------------------------------------------------------------------
+STDAPI OpenVirtualProcess2(
+ ULONG64 clrInstanceId,
+ IUnknown * pDataTarget,
+ HMODULE hDacModule,
+ REFIID riid,
+ IUnknown ** ppInstance,
+ CLR_DEBUGGING_PROCESS_FLAGS* pFlagsOut)
+{
+ CLR_DEBUGGING_VERSION maxVersion = {0};
+ maxVersion.wMajor = 4;
+ return OpenVirtualProcessImpl(clrInstanceId, pDataTarget, hDacModule, &maxVersion, riid, ppInstance, pFlagsOut);
+}
+
+//---------------------------------------------------------------------------------------
+// DEPRECATED - use OpenVirtualProcessImpl
+// Public OpenVirtualProcess method to get an ICorDebugProcess4 instance
+// Used directly in CLR v4 pre Beta1 - can probably be safely removed now
+//---------------------------------------------------------------------------------------
+STDAPI OpenVirtualProcess(
+ ULONG64 clrInstanceId,
+ IUnknown * pDataTarget,
+ REFIID riid,
+ IUnknown ** ppInstance)
+{
+ return OpenVirtualProcess2(clrInstanceId, pDataTarget, NULL, riid, ppInstance, NULL);
+};
+
+//-----------------------------------------------------------------------------
+// Most Hresults to Unrecoverable error indicate an internal error
+// in the Right-Side.
+// However, a few are legal (eg, "could actually happen in a retail scenario and
+// not indicate an issue in mscorbi"). Track that here.
+//-----------------------------------------------------------------------------
+
+bool IsLegalFatalError(HRESULT hr)
+{
+ return
+ (hr == CORDBG_E_INCOMPATIBLE_PROTOCOL) ||
+ (hr == CORDBG_E_CANNOT_DEBUG_FIBER_PROCESS) ||
+ (hr == CORDBG_E_UNCOMPATIBLE_PLATFORMS) ||
+ (hr == CORDBG_E_MISMATCHED_CORWKS_AND_DACWKS_DLLS) ||
+ // This should only happen in the case of a security attack on us.
+ (hr == E_ACCESSDENIED) ||
+ (hr == E_FAIL);
+}
+
+//-----------------------------------------------------------------------------
+// Safe wait. Use this anytime we're waiting on:
+// - an event signaled by the helper thread.
+// - something signaled by a thread that holds the process lock.
+// Note that we must preserve GetLastError() semantics.
+//-----------------------------------------------------------------------------
+inline DWORD SafeWaitForSingleObject(CordbProcess * p, HANDLE h, DWORD dwTimeout)
+{
+ // Can't hold process lock while blocking
+ _ASSERTE(!p->ThreadHoldsProcessLock());
+
+ return ::WaitForSingleObject(h, dwTimeout);
+}
+
+#define CORDB_WAIT_TIMEOUT 360000 // milliseconds
+
+//---------------------------------------------------------------------------------------
+//
+// Get the timeout value used in waits.
+//
+// Return Value:
+// Number of milliseconds to waite or possible INFINITE (-1).
+//
+//
+// Notes:
+// Uses registry values for fine tuning.
+//
+
+// static
+static inline DWORD CordbGetWaitTimeout()
+{
+#ifdef _DEBUG
+ // 0 = Wait forever
+ // 1 = Wait for CORDB_WAIT_TIMEOUT
+ // n = Wait for n milliseconds
+ static ConfigDWORD cordbWaitTimeout;
+ DWORD dwTimeoutVal = cordbWaitTimeout.val(CLRConfig::INTERNAL_DbgWaitTimeout);
+ if (dwTimeoutVal == 0)
+ return DWORD(-1);
+ else if (dwTimeoutVal != 1)
+ return dwTimeoutVal;
+ else
+#endif
+ {
+ return CORDB_WAIT_TIMEOUT;
+ }
+}
+
+//----------------------------------------------------------------------------
+// Implementation of IDacDbiInterface::IMetaDataLookup.
+// lookup Internal Metadata Importer keyed by PEFile
+// isILMetaDataForNGENImage is true iff the IMDInternalImport returned represents a pointer to
+// metadata from an IL image when the module was an ngen'ed image.
+IMDInternalImport * CordbProcess::LookupMetaData(VMPTR_PEFile vmPEFile, bool &isILMetaDataForNGENImage)
+{
+ INTERNAL_DAC_CALLBACK(this);
+
+ HASHFIND hashFindAppDomain;
+ HASHFIND hashFindModule;
+ IMDInternalImport * pMDII = NULL;
+ isILMetaDataForNGENImage = false;
+
+ // Check to see if one of the cached modules has the metadata we need
+ // If not we will do a more exhaustive search below
+ for (CordbAppDomain * pAppDomain = m_appDomains.FindFirst(&hashFindAppDomain);
+ pAppDomain != NULL;
+ pAppDomain = m_appDomains.FindNext(&hashFindAppDomain))
+ {
+ for (CordbModule * pModule = pAppDomain->m_modules.FindFirst(&hashFindModule);
+ pModule != NULL;
+ pModule = pAppDomain->m_modules.FindNext(&hashFindModule))
+ {
+ if (pModule->GetPEFile() == vmPEFile)
+ {
+ pMDII = NULL;
+ ALLOW_DATATARGET_MISSING_MEMORY(
+ pMDII = pModule->GetInternalMD();
+ );
+ if(pMDII != NULL)
+ return pMDII;
+ }
+ }
+ }
+
+ // Cache didn't have it... time to search harder
+ PrepopulateAppDomainsOrThrow();
+
+ // There may be perf issues here. The DAC may make a lot of metadata requests, and so
+ // this may be an area for potential perf optimizations if we find things running slow.
+
+ // enumerate through all Modules
+ for (CordbAppDomain * pAppDomain = m_appDomains.FindFirst(&hashFindAppDomain);
+ pAppDomain != NULL;
+ pAppDomain = m_appDomains.FindNext(&hashFindAppDomain))
+ {
+ pAppDomain->PrepopulateModules();
+
+ for (CordbModule * pModule = pAppDomain->m_modules.FindFirst(&hashFindModule);
+ pModule != NULL;
+ pModule = pAppDomain->m_modules.FindNext(&hashFindModule))
+ {
+ if (pModule->GetPEFile() == vmPEFile)
+ {
+ pMDII = NULL;
+ ALLOW_DATATARGET_MISSING_MEMORY(
+ pMDII = pModule->GetInternalMD();
+ );
+
+ if ( pMDII == NULL)
+ {
+ // If we couldn't get metadata from the CordbModule, then we need to ask the
+ // debugger if it can find the metadata elsewhere.
+ // If this was live debugging, we should have just gotten the memory contents.
+ // Thus this code is for dump debugging, when you don't have the metadata in the dump.
+ pMDII = LookupMetaDataFromDebugger(vmPEFile, isILMetaDataForNGENImage, pModule);
+ }
+ return pMDII;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+
+IMDInternalImport * CordbProcess::LookupMetaDataFromDebugger(
+ VMPTR_PEFile vmPEFile,
+ bool &isILMetaDataForNGENImage,
+ CordbModule * pModule)
+{
+ DWORD dwImageTimeStamp = 0;
+ DWORD dwImageSize = 0;
+ bool isNGEN = false;
+ StringCopyHolder filePath;
+ IMDInternalImport * pMDII = NULL;
+
+ // First, see if the debugger can locate the exact metadata we want.
+ if (this->GetDAC()->GetMetaDataFileInfoFromPEFile(vmPEFile, dwImageTimeStamp, dwImageSize, isNGEN, &filePath))
+ {
+ _ASSERTE(filePath.IsSet());
+
+ // Since we track modules by their IL images, that presents a little bit of oddness here. The correct
+ // thing to do is preferentially load the NI content.
+ // We don't discriminate between timestamps & sizes becuase CLRv4 deterministic NGEN guarantees that the
+ // IL image and NGEN image have the same timestamp and size. Should that guarantee change, this code
+ // will be horribly broken.
+
+ // If we happen to have an NI file path, use it instead.
+ const WCHAR * pwszFilePath = pModule->GetNGenImagePath();
+ if (pwszFilePath)
+ {
+ // Force the issue, regardless of the older codepath's opinion.
+ isNGEN = true;
+ }
+ else
+ {
+ pwszFilePath = (WCHAR *)filePath;
+ }
+
+ ALLOW_DATATARGET_MISSING_MEMORY(
+ pMDII = LookupMetaDataFromDebuggerForSingleFile(pModule, pwszFilePath, dwImageTimeStamp, dwImageSize);
+ );
+
+ // If it's an ngen'ed image and the debugger couldn't find it, we can use the metadata from
+ // the corresponding IL image if the debugger can locate it.
+ filePath.Clear();
+ if ((pMDII == NULL) &&
+ (isNGEN) &&
+ (this->GetDAC()->GetILImageInfoFromNgenPEFile(vmPEFile, dwImageTimeStamp, dwImageSize, &filePath)))
+ {
+ _ASSERTE(filePath.IsSet());
+
+ WCHAR *mutableFilePath = (WCHAR *)filePath;
+
+#if defined(FEATURE_CORESYSTEM)
+ size_t pathLen = wcslen(mutableFilePath);
+
+ const wchar_t *nidll = W(".ni.dll");
+ const wchar_t *niexe = W(".ni.exe");
+ const size_t dllLen = wcslen(nidll); // used for ni.exe as well
+
+ const wchar_t *niwinmd = W(".ni.winmd");
+ const size_t winmdLen = wcslen(niwinmd);
+
+ if (pathLen > dllLen && _wcsicmp(mutableFilePath+pathLen-dllLen, nidll) == 0)
+ {
+ wcscpy_s(mutableFilePath+pathLen-dllLen, dllLen, W(".dll"));
+ }
+ else if (pathLen > dllLen && _wcsicmp(mutableFilePath+pathLen-dllLen, niexe) == 0)
+ {
+ wcscpy_s(mutableFilePath+pathLen-dllLen, dllLen, W(".exe"));
+ }
+ else if (pathLen > winmdLen && _wcsicmp(mutableFilePath+pathLen-winmdLen, niwinmd) == 0)
+ {
+ wcscpy_s(mutableFilePath+pathLen-winmdLen, winmdLen, W(".winmd"));
+ }
+#endif//FEATURE_CORESYSTEM
+
+ ALLOW_DATATARGET_MISSING_MEMORY(
+ pMDII = LookupMetaDataFromDebuggerForSingleFile(pModule, mutableFilePath, dwImageTimeStamp, dwImageSize);
+ );
+
+ if (pMDII != NULL)
+ {
+ isILMetaDataForNGENImage = true;
+ }
+ }
+ }
+ return pMDII;
+}
+
+// We do not know if the image being sent to us is an IL image or ngen image.
+// CordbProcess::LookupMetaDataFromDebugger() has this knowledge when it looks up the file to hand off
+// to this function.
+// DacDbiInterfaceImpl::GetMDImport() has this knowledge in the isNGEN flag.
+// The CLR v2 code that windbg used made a distinction whether the metadata came from
+// the exact binary or not (i.e. were we getting metadata from the IL image and using
+// it against the ngen image?) but that information was never used and so not brought forward.
+// It would probably be more interesting generally to track whether the debugger gives us back
+// a file that bears some relationship to the file we asked for, which would catch the NI/IL case
+// as well.
+IMDInternalImport * CordbProcess::LookupMetaDataFromDebuggerForSingleFile(
+ CordbModule * pModule,
+ LPCWSTR pwszFilePath,
+ DWORD dwTimeStamp,
+ DWORD dwSize)
+{
+ INTERNAL_DAC_CALLBACK(this);
+
+ ULONG32 cchLocalImagePath = MAX_LONGPATH;
+ ULONG32 cchLocalImagePathRequired;
+ NewArrayHolder<WCHAR> pwszLocalFilePath = NULL;
+ IMDInternalImport * pMDII = NULL;
+
+ const HRESULT E_NSF_BUFFER = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
+ HRESULT hr = E_NSF_BUFFER;
+ for(unsigned i=0; i<2 && hr == E_NSF_BUFFER; i++)
+ {
+ if (pwszLocalFilePath != NULL)
+ pwszLocalFilePath.Release();
+
+ if (NULL == (pwszLocalFilePath = new (nothrow) WCHAR[cchLocalImagePath+1]))
+ ThrowHR(E_OUTOFMEMORY);
+
+ cchLocalImagePathRequired = 0;
+
+ hr = m_pMetaDataLocator->GetMetaData(pwszFilePath,
+ dwTimeStamp,
+ dwSize,
+ cchLocalImagePath,
+ &cchLocalImagePathRequired,
+ pwszLocalFilePath);
+
+ pwszLocalFilePath[cchLocalImagePath] = W('\0');
+ cchLocalImagePath = cchLocalImagePathRequired;
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ hr = pModule->InitPublicMetaDataFromFile(pwszLocalFilePath, ofReadOnly, false);
+ if (SUCCEEDED(hr))
+ {
+ // While we're successfully returning a metadata reader, remember that there's
+ // absolutely no guarantee this metadata is an exact match for the vmPEFile.
+ // The debugger could literally send us back a path to any managed file with
+ // metadata content that is readable and we'll 'succeed'.
+ // For now, this is by-design. A debugger should be allowed to decide if it wants
+ // to take a risk by returning 'mostly matching' metadata to see if debugging is
+ // possible in the absense of a true match.
+ pMDII = pModule->GetInternalMD();
+ }
+ }
+
+ return pMDII;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Implement IDacDbiInterface::IAllocator::Alloc
+// Expected to throws on error.
+//
+// Arguments:
+// lenBytes - size of the byte array to allocate
+//
+// Return Value:
+// Return the newly allocated byte array, or throw on OOM
+//
+// Notes:
+// Since this function is a callback from DAC, it must not take the process lock.
+// If it does, we may deadlock between the DD lock and the process lock.
+// If we really need to take the process lock for whatever reason, we must take it in the DBI functions
+// which call the DAC API that ends up calling this function.
+// See code:InternalDacCallbackHolder for more information.
+//
+
+void * CordbProcess::Alloc(SIZE_T lenBytes)
+{
+ return new BYTE[lenBytes]; // throws
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Implements IDacDbiInterface::IAllocator::Free
+//
+// Arguments:
+// p - pointer to the memory to be released
+//
+// Notes:
+// Since this function is a callback from DAC, it must not take the process lock.
+// If it does, we may deadlock between the DD lock and the process lock.
+// If we really need to take the process lock for whatever reason, we must take it in the DBI functions
+// which call the DAC API that ends up calling this function.
+// See code:InternalDacCallbackHolder for more information.
+//
+
+void CordbProcess::Free(void * p)
+{
+ // This shouldn't throw.
+ delete [] ((BYTE *) p);
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// #DBIVersionChecking
+//
+// There are a few checks we need to do to make sure we are using the matching DBI and DAC for a particular
+// version of the runtime.
+//
+// 1. Runtime vs. DBI
+// - Desktop
+// This is done by making sure that the CorDebugInterfaceVersion passed to code:CreateCordbObject is
+// compatible with the version of the DBI.
+//
+// - Windows CoreCLR
+// This is done by dbgshim.dll. It checks whether the runtime DLL and the DBI DLL have the same
+// product version. See CreateDebuggingInterfaceForVersion() in dbgshim.cpp.
+//
+// - Remote transport (Mac CoreCLR + CoreSystem CoreCLR)
+// Since there is no dbgshim.dll for a remote CoreCLR, we have to do this check in some other place.
+// We do this in code:CordbProcess::CreateDacDbiInterface, by calling
+// code:DacDbiInterfaceImpl::CheckDbiVersion right after we have created the DDMarshal.
+// The IDacDbiInterface implementation on remote device checks the product version of the device
+// coreclr by:
+// mac - looking at the Info.plist file in the CoreCLR bundle.
+// CoreSystem - this check is skipped at the moment, but should be implemented if we release it
+//
+// The one twist here is that the DBI needs to communicate with the IDacDbiInterface
+// implementation on the device BEFORE it can verify the product versions. This means that we need to
+// have one IDacDbiInterface API which is consistent across all versions of the IDacDbiInterface.
+// This puts two constraints on CheckDbiVersion():
+//
+// 1. It has to be the first API on the IDacDbiInterface.
+// - Otherwise, a wrong version of the DBI may end up calling a different API on the
+// IDacDbiInterface and getting random results. (Really what matters is that it is
+// protocol message id 0, at present the source code position implies the message id)
+//
+// 2. Its parameters cannot change.
+// - Otherwise, we may run into random errors when we marshal/unmarshal the arguments for the
+// call to CheckDbiVersion(). Debugging will still fail, but we won't get the
+// version mismatch error. (Again, the protocol is what ultimately matters)
+// - To mitigate the impact of this constraint, we use the code:DbiVersion structure.
+// In addition to the DBI version, it also contains a format number (in case we decide to
+// check something else in the future), a breaking change number so that we can force
+// breaking changes between a DBI and a DAC, and space reserved for future use.
+//
+// 2. DBI vs. DAC
+// - Desktop and Windows CoreCLR (old architecture)
+// No verification is done. There is a transitive implication that if DBI matches runtime and DAC matches
+// runtime then DBI matches DAC. Technically because the DBI only matches runtime on major version number
+// runtime and DAC could be from different builds. However because we service all three binaries together
+// and DBI always loads the DAC that is sitting in the same directory DAC and DBI generally get tight
+// version coupling. A user with admin privleges could put different builds together and no version check
+// would ever fail though.
+//
+// - Desktop and Windows CoreCLR (new architecture)
+// No verification is done. Similar to above its implied that if DBI matches runtime and runtime matches
+// DAC then DBI matches DAC. The only difference is that here both the DBI and DAC are provided by the
+// debugger. We provide timestamp and filesize for both binaries which are relatively strongly bound hints,
+// but there is no enforcement on the returned binaries beyond the runtime compat checking.
+//
+// - Remote transport (Mac CoreCLR and CoreSystem CoreCLR)
+// Because the transport exists between DBI and DAC it becomes much more important to do a versioning check
+//
+// Mac - currently does a tightly bound version check between DBI and the runtime (CheckDbiVersion() above),
+// which transitively gives a tightly bound check to DAC. In same function there is also a check that is
+// logically a DAC DBI protocol check, verifying that the m_dwProtocolBreakingChangeCounter of DbiVersion
+// matches. However this check should be weaker than the build version check and doesn't add anything here.
+//
+// CoreSystem - currently skips the tightly bound version check to make internal deployment and usage easier.
+// We want to use old desktop side debugger components to target newer CoreCLR builds, only forcing a desktop
+// upgrade when the protocol actually does change. To do this we use two checks:
+// 1. The breaking change counter in CheckDbiVersion() whenever a dev knows they are breaking back
+// compat and wants to be explicit about it. This is the same as mac above.
+// 2. During the auto-generation of the DDMarshal classes we take an MD5 hash of IDacDbiInterface source
+// code and embed it in two DDMarshal functions, one which runs locally and one that runs remotely.
+// If both DBI and DAC were built from the same source then the local and remote hashes will match. If the
+// hashes don't match then we assume there has been a been a breaking change in the protocol. Note
+// this hash could have both false-positives and false-negatives. False positives could occur when
+// IDacDbiInterface is changed in a trivial way, such as changing a comment. False negatives could
+// occur when the semantics of the protocol are changed even though the interface is not. Another
+// case would be changing the DDMarshal proxy generation code. In addition to the hashes we also
+// embed timestamps when the auto-generated code was produced. However this isn't used for version
+// matching, only as a hint to indicate which of two mismatched versions is newer.
+//
+//
+// 3. Runtime vs. DAC
+// - Desktop, Windows CoreCLR, CoreSystem CoreCLR
+// In both cases we check this by matching the timestamp in the debug directory of the runtime image
+// and the timestamp we store in the DAC table when we generate the DAC dll. This is done in
+// code:ClrDataAccess::VerifyDlls.
+//
+// - Mac CoreCLR
+// On Mac, we don't have a timestamp in the runtime image. Instead, we rely on checking the 16-byte
+// UUID in the image. This UUID is used to check whether a symbol file matches the image, so
+// conceptually it's the same as the timestamp we use on Windows. This is also done in
+// code:ClrDataAccess::VerifyDlls.
+//
+//---------------------------------------------------------------------------------------
+//
+// Instantiates a DacDbi Interface object in a live-debugging scenario that matches
+// the current instance of mscorwks in this process.
+//
+// Return Value:
+// Returns on success. Else throws.
+//
+// Assumptions:
+// Client will code:CordbProcess::FreeDac when its done with the DacDbi interface.
+// Caller has initialized clrInstanceId.
+//
+// Notes:
+// This looks for the DAC next to this current DBI. This assumes that Dac and Dbi are both on
+// the local file system. That assumption will break in zero-copy deployment scenarios.
+//
+//---------------------------------------------------------------------------------------
+void
+CordbProcess::CreateDacDbiInterface()
+{
+ _ASSERTE(m_pDACDataTarget != NULL);
+ _ASSERTE(m_pDacPrimitives == NULL); // don't double-init
+
+ // Caller has already determined which CLR in the target is being debugged.
+ _ASSERTE(m_clrInstanceId != 0);
+
+ m_pDacPrimitives = NULL;
+
+ HRESULT hrStatus = S_OK;
+
+ // Non-marshalling path for live local dac.
+ // in the new arch we can get the module from OpenVirtualProcess2 but in the shim case
+ // and the deprecated OpenVirtualProcess case we must assume it comes from DAC in the
+ // same directory as DBI
+ if(m_hDacModule == NULL)
+ {
+ m_hDacModule.Assign(ShimProcess::GetDacModule());
+ }
+
+ //
+ // Get the access interface, passing our callback interfaces (data target, allocator and metadata lookup)
+ //
+
+ IDacDbiInterface::IAllocator * pAllocator = this;
+ IDacDbiInterface::IMetaDataLookup * pMetaDataLookup = this;
+
+
+ typedef HRESULT (STDAPICALLTYPE * PFN_DacDbiInterfaceInstance)(
+ ICorDebugDataTarget *,
+ CORDB_ADDRESS,
+ IDacDbiInterface::IAllocator *,
+ IDacDbiInterface::IMetaDataLookup *,
+ IDacDbiInterface **);
+
+ IDacDbiInterface* pInterfacePtr = NULL;
+ PFN_DacDbiInterfaceInstance pfnEntry = (PFN_DacDbiInterfaceInstance)GetProcAddress(m_hDacModule, "DacDbiInterfaceInstance");
+ if (!pfnEntry)
+ {
+ ThrowLastError();
+ }
+
+ hrStatus = pfnEntry(m_pDACDataTarget, m_clrInstanceId, pAllocator, pMetaDataLookup, &pInterfacePtr);
+ IfFailThrow(hrStatus);
+
+ // We now have a resource, pInterfacePtr, that needs to be freed.
+ m_pDacPrimitives = pInterfacePtr;
+
+ // Setup DAC target consistency checking based on what we're using for DBI
+ m_pDacPrimitives->DacSetTargetConsistencyChecks( m_fAssertOnTargetInconsistency );
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Is the DAC/DBI interface initialized?
+//
+// Return Value:
+// TRUE iff init.
+//
+// Notes:
+// The RS will try to initialize DD as soon as it detects the runtime as loaded.
+// If the DD interface has not initialized, then it very likely the runtime has not
+// been loaded into the target.
+//
+BOOL CordbProcess::IsDacInitialized()
+{
+ return m_pDacPrimitives != NULL;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Get the DAC interface.
+//
+// Return Value:
+// the Dac/Dbi interface pointer to the process.
+// Never returns NULL.
+//
+// Assumptions:
+// Caller is responsible for ensuring Data-Target is safe to access (eg, not
+// currently running).
+// Caller is responsible for ensuring DAC-cache is flushed. Call code:CordbProcess::ForceDacFlush
+// as needed.
+//
+//---------------------------------------------------------------------------------------
+IDacDbiInterface * CordbProcess::GetDAC()
+{
+ // Since the DD primitives may throw, easiest way to model that is to make this throw.
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ // We should always have the DAC/DBI interface.
+ _ASSERTE(m_pDacPrimitives != NULL);
+ return m_pDacPrimitives;
+}
+
+//---------------------------------------------------------------------------------------
+// Get the Data-Target
+//
+// Returns:
+// pointer to the data-target. Should be non-null.
+// Lifetime of the pointer is until this process object is neutered.
+//
+ICorDebugDataTarget * CordbProcess::GetDataTarget()
+{
+ return m_pDACDataTarget;
+}
+
+//---------------------------------------------------------------------------------------
+// Create a CordbProcess object around an existing OS process.
+//
+// Arguments:
+// pDataTarget - abstracts access to the debuggee.
+// clrInstanceId - identifies the CLR instance within the debuggee. (This is the
+// base address of mscorwks)
+// pCordb - Pointer to the implementation of the owning Cordb object implementing the
+// owning ICD interface.
+// This should go away - we can get the functionality from the pShim.
+// If this is null, then pShim must be null too.
+// processID - OS process ID of target process. 0 if pShim == NULL.
+// pShim - shim counter part object. This allows hooks back for v2 compat. This will
+// go away once we no longer support V2 backwards compat.
+// This must be non-null for any V2 paths (including non-DAC-ized code).
+// If this is null, then we're in a V3 path.
+// ppProcess - out parameter for new process object. This gets addreffed.
+//
+// Return Value:
+// S_OK on success, and *ppProcess set to newly created debuggee object. Else error.
+//
+// Notes:
+// @dbgtodo - , shim: Cordb, and pShim will all eventually go away.
+//
+//---------------------------------------------------------------------------------------
+
+// static
+HRESULT CordbProcess::OpenVirtualProcess(
+ ULONG64 clrInstanceId,
+ IUnknown * pDataTarget,
+ HMODULE hDacModule,
+ Cordb* pCordb,
+ DWORD dwProcessID,
+ ShimProcess * pShim,
+ CordbProcess ** ppProcess)
+{
+ _ASSERTE(pDataTarget != NULL);
+
+ // In DEBUG builds, verify that we do actually have an ICorDebugDataTarget (i.e. that
+ // someone hasn't messed up the COM interop marshalling, etc.).
+#ifdef _DEBUG
+ {
+ IUnknown * pTempDt;
+ HRESULT hrQi = pDataTarget->QueryInterface(IID_ICorDebugDataTarget, (void**)&pTempDt);
+ _ASSERTE_MSG(SUCCEEDED(hrQi), "OpenVirtualProcess was passed something that isn't actually an ICorDebugDataTarget");
+ pTempDt->Release();
+ }
+#endif
+
+ // If we're emulating V2, then both pCordb and pShim are non-NULL.
+ // If we're doing a real V3 path, then they're both NULL.
+ // Either way, they should have the same null-status.
+ _ASSERTE((pCordb == NULL) == (pShim == NULL));
+
+ // If we're doing real V3, then we must have a real instance ID
+ _ASSERTE(!((pShim == NULL) && (clrInstanceId == 0)));
+
+ *ppProcess = NULL;
+
+ HRESULT hr = S_OK;
+ RSUnsafeExternalSmartPtr<CordbProcess> pProcess;
+ pProcess.Assign(new (nothrow) CordbProcess(clrInstanceId, pDataTarget, hDacModule, pCordb, dwProcessID, pShim));
+
+ if (pProcess == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ ICorDebugProcess * pThis = pProcess;
+ (void)pThis; //prevent "unused variable" error from GCC
+
+ // CordbProcess::Init may need shim hooks, so connect Shim now.
+ // This will bump reference count.
+ if (pShim != NULL)
+ {
+ pShim->SetProcess(pProcess);
+
+ _ASSERTE(pShim->GetProcess() == pThis);
+ _ASSERTE(pShim->GetWin32EventThread() != NULL);
+ }
+
+ hr = pProcess->Init();
+
+ if (SUCCEEDED(hr))
+ {
+ *ppProcess = pProcess;
+ pProcess->ExternalAddRef();
+ }
+ else
+ {
+ // handle failure path
+ pProcess->CleanupHalfBakedLeftSide();
+
+ if (pShim != NULL)
+ {
+ // Shim still needs to be disposed to clean up other resources.
+ pShim->SetProcess(NULL);
+ }
+
+ // In failure case, pProcess's dtor will do the final release.
+ }
+
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+// CordbProcess constructor
+//
+// Arguments:
+// pDataTarget - Pointer to an implementation of ICorDebugDataTarget
+// (or ICorDebugMutableDataTarget), which virtualizes access to the process.
+// clrInstanceId - representation of the CLR to debug in the process. Must be specified
+// (non-zero) if pShim is NULL. If 0, use the first CLR that we see.
+// pCordb - Pointer to the implementation of the owning Cordb object implementing the
+// owning ICD interface.
+// pW32 - Pointer to the Win32 event thread to use when processing events for this
+// process.
+// dwProcessID - For V3, 0.
+// Else for shim codepaths, the processID of the process this object will represent.
+// pShim - Pointer to the shim for handling V2 debuggers on the V3 architecture.
+//
+//---------------------------------------------------------------------------------------
+
+CordbProcess::CordbProcess(ULONG64 clrInstanceId,
+ IUnknown * pDataTarget,
+ HMODULE hDacModule,
+ Cordb * pCordb,
+ DWORD dwProcessID,
+ ShimProcess * pShim)
+ : CordbBase(NULL, dwProcessID, enumCordbProcess),
+ m_fDoDelayedManagedAttached(false),
+ m_cordb(pCordb),
+ m_handle(NULL),
+ m_detached(false),
+ m_uninitializedStop(false),
+ m_exiting(false),
+ m_terminated(false),
+ m_unrecoverableError(false),
+ m_specialDeferment(false),
+ m_helperThreadDead(false),
+ m_loaderBPReceived(false),
+ m_cOutstandingEvals(0),
+ m_cOutstandingHandles(0),
+ m_clrInstanceId(clrInstanceId),
+ m_stopCount(0),
+ m_synchronized(false),
+ m_syncCompleteReceived(false),
+ m_pShim(pShim),
+ m_userThreads(11),
+ m_oddSync(false),
+#ifdef FEATURE_INTEROP_DEBUGGING
+ m_unmanagedThreads(11),
+#endif
+ m_appDomains(11),
+ m_sharedAppDomain(0),
+ m_steppers(11),
+ m_continueCounter(1),
+ m_flushCounter(0),
+ m_leftSideEventAvailable(NULL),
+ m_leftSideEventRead(NULL),
+#if defined(FEATURE_INTEROP_DEBUGGING)
+ m_leftSideUnmanagedWaitEvent(NULL),
+#endif // FEATURE_INTEROP_DEBUGGING
+ m_initialized(false),
+ m_stopRequested(false),
+ m_stopWaitEvent(NULL),
+#ifdef FEATURE_INTEROP_DEBUGGING
+ m_cFirstChanceHijackedThreads(0),
+ m_unmanagedEventQueue(NULL),
+ m_lastQueuedUnmanagedEvent(NULL),
+ m_lastQueuedOOBEvent(NULL),
+ m_outOfBandEventQueue(NULL),
+ m_lastDispatchedIBEvent(NULL),
+ m_dispatchingUnmanagedEvent(false),
+ m_dispatchingOOBEvent(false),
+ m_doRealContinueAfterOOBBlock(false),
+ m_state(0),
+#endif // FEATURE_INTEROP_DEBUGGING
+ m_helperThreadId(0),
+ m_pPatchTable(NULL),
+ m_cPatch(0),
+ m_rgData(NULL),
+ m_rgNextPatch(NULL),
+ m_rgUncommitedOpcode(NULL),
+ m_minPatchAddr(MAX_ADDRESS),
+ m_maxPatchAddr(MIN_ADDRESS),
+ m_iFirstPatch(0),
+ m_hHelperThread(NULL),
+ m_dispatchedEvent(DB_IPCE_DEBUGGER_INVALID),
+ m_pDefaultAppDomain(NULL),
+ m_hDacModule(hDacModule),
+ m_pDacPrimitives(NULL),
+ m_pEventChannel(NULL),
+ m_fAssertOnTargetInconsistency(false),
+ m_runtimeOffsetsInitialized(false),
+ m_writableMetadataUpdateMode(LegacyCompatPolicy)
+{
+ _ASSERTE((m_id == 0) == (pShim == NULL));
+
+ HRESULT hr = pDataTarget->QueryInterface(IID_ICorDebugDataTarget, reinterpret_cast<void **>(&m_pDACDataTarget));
+ IfFailThrow(hr);
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ m_DbgSupport.m_DebugEventQueueIdx = 0;
+ m_DbgSupport.m_TotalNativeEvents = 0;
+ m_DbgSupport.m_TotalIB = 0;
+ m_DbgSupport.m_TotalOOB = 0;
+ m_DbgSupport.m_TotalCLR = 0;
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ g_pRSDebuggingInfo->m_MRUprocess = this;
+
+ // This is a strong reference to ourselves.
+ // This is cleared in code:CordbProcess::Neuter
+ m_pProcess.Assign(this);
+
+#ifdef _DEBUG
+ // On Debug builds, we'll ASSERT by default whenever the target appears to be corrupt or
+ // otherwise inconsistent (both in DAC and DBI). But we also need the ability to
+ // explicitly test corrupt targets.
+ // Tests should set COMPlus_DbgIgnoreInconsistentTarget=1 to suppress these asserts
+ // Note that this controls two things:
+ // 1) DAC behavior - see code:IDacDbiInterface::DacSetTargetConsistencyChecks
+ // 2) RS-only consistency asserts - see code:CordbProcess::TargetConsistencyCheck
+ if( !CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgDisableTargetConsistencyAsserts) )
+ {
+ m_fAssertOnTargetInconsistency = true;
+ }
+#endif
+}
+
+/*
+ A list of which resources owned by this object are accounted for.
+
+ UNKNOWN
+ Cordb* m_cordb;
+ CordbHashTable m_unmanagedThreads; // Released in CordbProcess but not removed from hash
+ DebuggerIPCEvent* m_lastQueuedEvent;
+
+ // CordbUnmannagedEvent is a struct which is not derrived from CordbBase.
+ // It contains a CordbUnmannagedThread which may need to be released.
+ CordbUnmanagedEvent *m_unmanagedEventQueue;
+ CordbUnmanagedEvent *m_lastQueuedUnmanagedEvent;
+ CordbUnmanagedEvent *m_outOfBandEventQueue;
+ CordbUnmanagedEvent *m_lastQueuedOOBEvent;
+
+ BYTE* m_pPatchTable;
+ BYTE *m_rgData;
+ void *m_pbRemoteBuf;
+
+ RESOLVED
+ // Nutered
+ CordbHashTable m_userThreads;
+ CordbHashTable m_appDomains;
+
+ // Cleaned up in ExitProcess
+ DebuggerIPCEvent* m_queuedEventList;
+
+ CordbHashTable m_steppers; // Closed in ~CordbProcess
+
+ // Closed in CloseIPCEventHandles called from ~CordbProcess
+ HANDLE m_leftSideEventAvailable;
+ HANDLE m_leftSideEventRead;
+
+ // Closed in ~CordbProcess
+ HANDLE m_handle;
+ HANDLE m_leftSideUnmanagedWaitEvent;
+ HANDLE m_stopWaitEvent;
+
+ // Deleted in ~CordbProcess
+ CRITICAL_SECTION m_processMutex;
+
+*/
+
+
+CordbProcess::~CordbProcess()
+{
+ LOG((LF_CORDB, LL_INFO1000, "CP::~CP: deleting process 0x%08x\n", this));
+
+ DTOR_ENTRY(this);
+
+ _ASSERTE(IsNeutered());
+
+ _ASSERTE(m_cordb == NULL);
+
+ // We shouldn't still be in Cordb's list of processes. Unfortunately, our root Cordb object
+ // may have already been deleted b/c we're at the mercy of ref-counting, so we can't check.
+
+ _ASSERTE(m_sharedAppDomain == NULL);
+
+ m_processMutex.Destroy();
+ m_StopGoLock.Destroy();
+
+ // These handles were cleared in neuter
+ _ASSERTE(m_handle == NULL);
+#if defined(FEATURE_INTEROP_DEBUGGING)
+ _ASSERTE(m_leftSideUnmanagedWaitEvent == NULL);
+#endif // FEATURE_INTEROP_DEBUGGING
+ _ASSERTE(m_stopWaitEvent == NULL);
+
+ // Set this to mark that we really did cleanup.
+}
+
+//-----------------------------------------------------------------------------
+// Static build helper.
+// This will create a process under the pCordb root, and add it to the list.
+// We don't return the process - caller gets the pid and looks it up under
+// the Cordb object.
+//
+// Arguments:
+// pCordb - Pointer to the implementation of the owning Cordb object implementing the
+// owning ICD interface.
+// szProgramName - Name of the program to execute.
+// szProgramArgs - Command line arguments for the process.
+// lpProcessAttributes - OS-specific attributes for process creation.
+// lpThreadAttributes - OS-specific attributes for thread creation.
+// fInheritFlags - OS-specific flag for child process inheritance.
+// dwCreationFlags - OS-specific creation flags.
+// lpEnvironment - OS-specific environmental strings.
+// szCurrentDirectory - OS-specific string for directory to run in.
+// lpStartupInfo - OS-specific info on startup.
+// lpProcessInformation - OS-specific process information buffer.
+// corDebugFlags - What type of process to create, currently always managed.
+//-----------------------------------------------------------------------------
+HRESULT ShimProcess::CreateProcess(
+ Cordb * pCordb,
+ ICorDebugRemoteTarget * pRemoteTarget,
+ LPCWSTR szProgramName,
+ __in_z LPWSTR szProgramArgs,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL fInheritHandles,
+ DWORD dwCreationFlags,
+ PVOID lpEnvironment,
+ LPCWSTR szCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation,
+ CorDebugCreateProcessFlags corDebugFlags
+)
+{
+ _ASSERTE(pCordb != NULL);
+
+#if defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ // The transport cannot deal with creating a suspended process (it needs the debugger to start up and
+ // listen for connections).
+ _ASSERTE((dwCreationFlags & CREATE_SUSPENDED) == 0);
+#endif // FEATURE_DBGIPC_TRANSPORT_DI
+
+ HRESULT hr = S_OK;
+
+ RSExtSmartPtr<ShimProcess> pShim;
+ EX_TRY
+ {
+ pShim.Assign(new ShimProcess());
+
+ // Indicate that this process was started under the debugger as opposed to attaching later.
+ pShim->m_attached = false;
+
+ hr = pShim->CreateAndStartWin32ET(pCordb);
+ IfFailThrow(hr);
+
+ // Call out to newly created Win32-event Thread to create the process.
+ // If this succeeds, new CordbProcess will add a ref to the ShimProcess
+ hr = pShim->GetWin32EventThread()->SendCreateProcessEvent(pShim->GetMachineInfo(),
+ szProgramName,
+ szProgramArgs,
+ lpProcessAttributes,
+ lpThreadAttributes,
+ fInheritHandles,
+ dwCreationFlags,
+ lpEnvironment,
+ szCurrentDirectory,
+ lpStartupInfo,
+ lpProcessInformation,
+ corDebugFlags);
+ IfFailThrow(hr);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ // If this succeeds, then process takes ownership of thread. Else we need to kill it.
+ if (FAILED(hr))
+ {
+ if (pShim != NULL)
+ {
+ pShim->Dispose();
+ }
+ }
+ // Always release our ref to ShimProcess. If the Process was created, then it takes a reference.
+
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// Static build helper for the attach case.
+// On success, this will add the process to the pCordb list, and then
+// callers can look it up there by pid.
+//
+// Arguments:
+// pCordb - root under which this all lives
+// dwProcessID - OS process ID to attach to
+// fWin32Attach - are we interop debugging?
+//-----------------------------------------------------------------------------
+HRESULT ShimProcess::DebugActiveProcess(
+ Cordb * pCordb,
+ ICorDebugRemoteTarget * pRemoteTarget,
+ DWORD dwProcessID,
+ BOOL fWin32Attach
+)
+{
+ _ASSERTE(pCordb != NULL);
+
+ HRESULT hr = S_OK;
+
+ RSExtSmartPtr<ShimProcess> pShim;
+
+ EX_TRY
+ {
+ pShim.Assign(new ShimProcess());
+
+ // Indicate that this process was attached to, asopposed to being started under the debugger.
+ pShim->m_attached = true;
+
+ hr = pShim->CreateAndStartWin32ET(pCordb);
+ IfFailThrow(hr);
+
+ // If this succeeds, new CordbProcess will add a ref to the ShimProcess
+ hr = pShim->GetWin32EventThread()->SendDebugActiveProcessEvent(pShim->GetMachineInfo(),
+ dwProcessID,
+ fWin32Attach == TRUE,
+ NULL);
+ IfFailThrow(hr);
+
+ _ASSERTE(SUCCEEDED(hr));
+
+#if !defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ // Don't do this when we are remote debugging since we won't be getting the loader breakpoint.
+ // We don't support JIT attach in remote debugging scenarios anyway.
+ //
+ // When doing jit attach for pure managed debugging we allow the native attach event to be signaled
+ // after DebugActiveProcess completes which means we must wait here long enough to have set the debuggee
+ // bit indicating managed attach is coming.
+ // However in interop debugging we can't do that because there are debug events which come before the
+ // loader breakpoint (which is how far we need to get to set the debuggee bit). If we blocked
+ // DebugActiveProcess there then the debug events would be refering to an ICorDebugProcess that hasn't
+ // yet been returned to the caller of DebugActiveProcess. Instead, for interop debugging we force the
+ // native debugger to wait until it gets the loader breakpoint to set the event. Note we can't converge
+ // on that solution for the pure managed case because there is no loader breakpoint event. Hence pure
+ // managed and interop debugging each require their own solution
+ //
+ // See bugs Dev10 600873 and 595322 for examples of what happens if we wait in interop or don't wait
+ // in pure managed respectively
+ //
+ // Long term this should all go away because we won't need to set a managed attach pending bit because
+ // there shouldn't be any IPC events involved in managed attach. There might not even be a notion of
+ // being 'managed attached'
+ if(!pShim->m_fIsInteropDebugging)
+ {
+ DWORD dwHandles = 2;
+ HANDLE arrHandles[2];
+
+ arrHandles[0] = pShim->m_terminatingEvent;
+ arrHandles[1] = pShim->m_markAttachPendingEvent;
+
+ // Wait for the completion of marking pending attach bit or debugger detaching
+ WaitForMultipleObjectsEx(dwHandles, arrHandles, FALSE, INFINITE, FALSE);
+ }
+#endif //!FEATURE_DBGIPC_TRANSPORT_DI
+ }
+ EX_CATCH_HRESULT(hr);
+
+ // If this succeeds, then process takes ownership of thread. Else we need to kill it.
+ if (FAILED(hr))
+ {
+ if (pShim!= NULL)
+ {
+ pShim->Dispose();
+ }
+ }
+
+ // Always release our ref to ShimProcess. If the Process was created, then it takes a reference.
+
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// Neuter all of all children, but not the actual process object.
+//
+// Assumptions:
+// This clears Right-side state. Assumptions about left-side state are either:
+// 1. We're in a shutdown scenario, where all left-side state is already
+// freed.
+// 2. Caller already verified there are no left-side resources (eg, by calling
+// code:CordbProcess::IsReadyForDetach)
+// 3. Caller did code:CordbProcess::NeuterLeftSideResources first
+// to clean up left-side resources.
+//
+// Notes:
+// This could be called multiple times (code:CordbProcess::FlushAll), so
+// be sure to null out any potential dangling pointers. State may be rebuilt
+// up after each time.
+void CordbProcess::NeuterChildren()
+{
+ _ASSERTE(GetProcessLock()->HasLock());
+
+ // Frees left-side resources. See assumptions above.
+ m_LeftSideResourceCleanupList.NeuterAndClear(this);
+
+
+ m_EvalTable.Clear();
+
+
+ // Sweep neuter lists.
+ m_ExitNeuterList.NeuterAndClear(this);
+ m_ContinueNeuterList.NeuterAndClear(this);
+
+ m_userThreads.NeuterAndClear(GetProcessLock());
+
+ m_pDefaultAppDomain = NULL;
+
+ // Frees per-appdomain left-side resources. See assumptions above.
+ m_appDomains.NeuterAndClear(GetProcessLock());
+ if (m_sharedAppDomain != NULL)
+ {
+ m_sharedAppDomain->Neuter();
+ m_sharedAppDomain->InternalRelease();
+ m_sharedAppDomain = NULL;
+ }
+
+ m_steppers.NeuterAndClear(GetProcessLock());
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ m_unmanagedThreads.NeuterAndClear(GetProcessLock());
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ // Explicitly keep the Win32EventThread alive so that we can use it in the window
+ // between NeuterChildren + Neuter.
+}
+
+//-----------------------------------------------------------------------------
+// Neuter
+//
+// When the process dies, remove all the resources associated with this object.
+//
+// Notes:
+// Once we neuter ourself, we can no longer send IPC events. So this is useful
+// on detach. This will be called on FlushAll (which has Whidbey detach
+// semantics)
+//-----------------------------------------------------------------------------
+void CordbProcess::Neuter()
+{
+ // Process's Neuter is at the top of the neuter tree. So we take the process-lock
+ // here and then all child items (appdomains, modules, etc) will assert
+ // that they hold the lock.
+ _ASSERTE(!this->ThreadHoldsProcessLock());
+
+ // Take the process lock.
+ RSLockHolder lockHolder(GetProcessLock());
+
+
+ NeuterChildren();
+
+ // Release the metadata interfaces
+ m_pMetaDispenser.Clear();
+
+
+ if (m_hHelperThread != NULL)
+ {
+ CloseHandle(m_hHelperThread);
+ m_hHelperThread = NULL;
+ }
+
+ {
+ lockHolder.Release();
+ {
+ // We may still hold the Stop-Go lock.
+ // @dbgtodo - left-side resources / shutdown, shim: Currently
+ // the shim shutdown is too interwoven with CordbProcess to split
+ // it out from the locks. Must fully hoist the W32ET and make
+ // it safely outside the RS, and outside the protection of RS
+ // locks.
+ PUBLIC_API_UNSAFE_ENTRY_FOR_SHIM(this);
+
+ // Now that all of our children are neutered, it should be safe to kill the W32ET.
+ // Shutdown the shim, and this will also shutdown the W32ET.
+ // Do this outside of the process-lock so that we can shutdown the
+ // W23ET.
+ if (m_pShim != NULL)
+ {
+ m_pShim->Dispose();
+ m_pShim.Clear();
+ }
+ }
+
+ lockHolder.Acquire();
+ }
+
+ // Unload DAC, and then release our final data target references
+ FreeDac();
+ m_pDACDataTarget.Clear();
+ m_pMutableDataTarget.Clear();
+ m_pMetaDataLocator.Clear();
+
+ if (m_pEventChannel != NULL)
+ {
+ m_pEventChannel->Delete();
+ m_pEventChannel = NULL;
+ }
+
+ // Need process lock to clear the patch table
+ ClearPatchTable();
+
+ CordbProcess::CloseIPCHandles();
+
+ CordbBase::Neuter();
+
+ m_cordb.Clear();
+
+ // Need to release this reference to ourselves. Other leaf objects may still hold
+ // strong references back to this CordbProcess object.
+ _ASSERTE(m_pProcess == this);
+ m_pProcess.Clear();
+}
+
+// Wrapper to return metadata dispenser.
+//
+// Notes:
+// Does not adjust reference count of dispenser.
+// Dispenser is destroyed in code:CordbProcess::Neuter
+// Dispenser is non-null.
+IMetaDataDispenserEx * CordbProcess::GetDispenser()
+{
+ _ASSERTE(m_pMetaDispenser != NULL);
+ return m_pMetaDispenser;
+}
+
+
+void CordbProcess::CloseIPCHandles()
+{
+ INTERNAL_API_ENTRY(this);
+
+ // Close off Right Side's handles.
+ if (m_leftSideEventAvailable != NULL)
+ {
+ CloseHandle(m_leftSideEventAvailable);
+ m_leftSideEventAvailable = NULL;
+ }
+
+ if (m_leftSideEventRead != NULL)
+ {
+ CloseHandle(m_leftSideEventRead);
+ m_leftSideEventRead = NULL;
+ }
+
+ if (m_handle != NULL)
+ {
+ // @dbgtodo - We should probably add asserts to all calls to CloseHandles(), but this has been
+ // a particularly problematic spot in the past for Mac debugging.
+ BOOL fSuccess = CloseHandle(m_handle);
+ (void)fSuccess; //prevent "unused variable" error from GCC
+ _ASSERTE(fSuccess);
+
+ m_handle = NULL;
+ }
+
+#if defined(FEATURE_INTEROP_DEBUGGING)
+ if (m_leftSideUnmanagedWaitEvent != NULL)
+ {
+ CloseHandle(m_leftSideUnmanagedWaitEvent);
+ m_leftSideUnmanagedWaitEvent = NULL;
+ }
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ if (m_stopWaitEvent != NULL)
+ {
+ CloseHandle(m_stopWaitEvent);
+ m_stopWaitEvent = NULL;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Create new OS Thread for the Win32 Event Thread (the thread used in interop-debugging to sniff
+// native debug events). This is 1:1 w/ a CordbProcess object.
+// This will then be used to actuall create the CordbProcess object.
+// The process object will then take ownership of the thread.
+//
+// Arguments:
+// pCordb - the root object that the process lives under
+//
+// Return values:
+// S_OK on success.
+//-----------------------------------------------------------------------------
+HRESULT ShimProcess::CreateAndStartWin32ET(Cordb * pCordb)
+{
+
+ //
+ // Create the win32 event listening thread
+ //
+ CordbWin32EventThread * pWin32EventThread = new (nothrow) CordbWin32EventThread(pCordb, this);
+
+ HRESULT hr = S_OK;
+
+ if (pWin32EventThread != NULL)
+ {
+ hr = pWin32EventThread->Init();
+
+ if (SUCCEEDED(hr))
+ {
+ hr = pWin32EventThread->Start();
+ }
+
+ if (FAILED(hr))
+ {
+ delete pWin32EventThread;
+ pWin32EventThread = NULL;
+ }
+ }
+ else
+ {
+ hr = E_OUTOFMEMORY;
+ }
+
+ m_pWin32EventThread = pWin32EventThread;
+ return ErrWrapper(hr);
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Try to initialize the DAC. Called in scenarios where it may fail.
+//
+// Return Value:
+// TRUE - DAC is initialized.
+// FALSE - Not initialized, but can try again later. Common case if
+// target has not yet loaded the runtime.
+// Throws exception - fatal.
+//
+// Assumptions:
+// Target is stopped by OS, so we can safely inspect it without it moving on us.
+//
+// Notes:
+// This can be called eagerly to sniff if the LS is initialized.
+//
+//---------------------------------------------------------------------------------------
+BOOL CordbProcess::TryInitializeDac()
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ // Target is stopped by OS, so we can safely inspect it without it moving on us.
+
+ // We want to avoid exceptions in the normal case, so we do some pre-checks
+ // to detect failure without relying on exceptions.
+ // Can't initialize DAC until mscorwks is loaded. So that's a sanity test.
+ HRESULT hr = EnsureClrInstanceIdSet();
+ if (FAILED(hr))
+ {
+ return FALSE;
+ }
+
+ // By this point, we know which CLR in the target to debug. That means there is a CLR
+ // in the target, and it's safe to initialize DAC.
+ _ASSERTE(m_clrInstanceId != 0);
+
+ // Now expect it to succeed
+ InitializeDac();
+ return TRUE;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Load & Init DAC, expecting to succeed.
+//
+// Return Value:
+// Throws on failure.
+//
+// Assumptions:
+// Caller invokes this at a point where they can expect it to succeed.
+// This is called early in the startup path because DAC is needed for accessing
+// data in the target.
+//
+// Notes:
+// This needs to succeed, and should always succeed (baring a bad installation)
+// so we assert on failure paths.
+// This may be called mutliple times.
+//
+//---------------------------------------------------------------------------------------
+void CordbProcess::InitializeDac()
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+ INTERNAL_API_ENTRY(this);
+
+ // For Mac debugginger, m_hDacModule is not used, and it will always be NULL. To check whether DAC has
+ // been initialized, we need to check something else, namely m_pDacPrimitives.
+ if (m_pDacPrimitives == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "About to load DAC\n"));
+ CreateDacDbiInterface(); // throws
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO1000, "Dac already loaded, 0x%p\n", (HMODULE)m_hDacModule));
+ }
+
+ // Always flush dac.
+ ForceDacFlush();
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Free DAC resources
+//
+// Notes:
+// This should clean up state such that code:CordbProcess::InitializeDac could be called again.
+//
+//---------------------------------------------------------------------------------------
+void CordbProcess::FreeDac()
+{
+ CONTRACTL
+ {
+ NOTHROW; // backout code.
+ }
+ CONTRACTL_END;
+
+ if (m_pDacPrimitives != NULL)
+ {
+ m_pDacPrimitives->Destroy();
+ m_pDacPrimitives = NULL;
+ }
+
+ if (m_hDacModule != NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "Unloading DAC\n"));
+ m_hDacModule.Clear();
+ }
+}
+
+IEventChannel * CordbProcess::GetEventChannel()
+{
+ _ASSERTE(m_pEventChannel != NULL);
+ return m_pEventChannel;
+}
+
+//---------------------------------------------------------------------------------------
+// Mark that the process is being interop-debugged.
+//
+// Notes:
+// @dbgtodo shim: this should eventually move into the shim or go away.
+// It's only to support V2 legacy interop-debugging.
+// Called after code:CordbProcess::Init if we want to enable interop debugging.
+// This allows us to separate out Interop-debugging flags from the core initialization,
+// and paves the way for us to eventually remove it.
+//
+// Since we're always on the naitve-pipeline, the Enabling interop debugging just changes
+// how the native debug events are being handled. So this must be called after Init, but
+// before any events are actually handled.
+// This mus be calle on the win32 event thread to gaurantee that it's called before WFDE.
+void CordbProcess::EnableInteropDebugging()
+{
+ CONTRACTL
+ {
+ THROWS;
+ PRECONDITION(m_pShim != NULL);
+ }
+ CONTRACTL_END;
+
+ // Must be on W32ET to gaurantee that we're called after Init yet before WFDE (which
+ // are both called on the W32et).
+ _ASSERTE(IsWin32EventThread());
+#ifdef FEATURE_INTEROP_DEBUGGING
+
+ m_state |= PS_WIN32_ATTACHED;
+ if (GetDCB() != NULL)
+ {
+ GetDCB()->m_rightSideIsWin32Debugger = true;
+ UpdateLeftSideDCBField(&(GetDCB()->m_rightSideIsWin32Debugger), sizeof(GetDCB()->m_rightSideIsWin32Debugger));
+ }
+
+ // Tell the Shim we're interop-debugging.
+ m_pShim->SetIsInteropDebugging(true);
+#else
+ ThrowHR(CORDBG_E_INTEROP_NOT_SUPPORTED);
+#endif
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Init -- create any objects that the process object needs to operate.
+//
+// Arguments:
+//
+// Return Value:
+// S_OK on success
+//
+// Assumptions:
+// Called on Win32 Event Thread, after OS debugging pipeline is established but
+// before WaitForDebugEvent / ContinueDebugEvent. This means the target is stopped.
+//
+// Notes:
+// To enable interop-debugging, call code:CordbProcess::EnableInteropDebugging
+//---------------------------------------------------------------------------------------
+HRESULT CordbProcess::Init()
+{
+ INTERNAL_API_ENTRY(this);
+
+ HRESULT hr = S_OK;
+ BOOL fIsLSStarted = FALSE; // see meaning below.
+
+ FAIL_IF_NEUTERED(this);
+
+
+ EX_TRY
+ {
+ m_processMutex.Init("Process Lock", RSLock::cLockReentrant, RSLock::LL_PROCESS_LOCK);
+ m_StopGoLock.Init("Stop-Go Lock", RSLock::cLockReentrant, RSLock::LL_STOP_GO_LOCK);
+
+#ifdef _DEBUG
+ m_appDomains.DebugSetRSLock(GetProcessLock());
+ m_userThreads.DebugSetRSLock(GetProcessLock());
+#ifdef FEATURE_INTEROP_DEBUGGING
+ m_unmanagedThreads.DebugSetRSLock(GetProcessLock());
+#endif
+ m_steppers.DebugSetRSLock(GetProcessLock());
+#endif
+
+ // See if the data target is mutable, and cache the mutable interface if it is
+ // We must initialize this before we try to use the data target to access the memory in the target process.
+ m_pMutableDataTarget.Clear(); // if we were called already, release
+ hr = m_pDACDataTarget->QueryInterface(IID_ICorDebugMutableDataTarget, (void**)&m_pMutableDataTarget);
+ if (!SUCCEEDED(hr))
+ {
+ // The data target doesn't support mutation. We'll fail any requests that require mutation.
+ m_pMutableDataTarget.Assign(new ReadOnlyDataTargetFacade());
+ }
+
+ m_pMetaDataLocator.Clear();
+ hr = m_pDACDataTarget->QueryInterface(IID_ICorDebugMetaDataLocator, reinterpret_cast<void **>(&m_pMetaDataLocator));
+
+ // Get the metadata dispenser.
+ hr = InternalCreateMetaDataDispenser(IID_IMetaDataDispenserEx, (void **)&m_pMetaDispenser);
+
+ // We statically link in the dispenser. We expect it to succeed, except for OOM, which
+ // debugger doesn't yet handle.
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+ IfFailThrow(hr);
+
+ _ASSERTE(m_pMetaDispenser != NULL);
+
+ // In order to allow users to call the metadata reader from multiple threads we need to set
+ // a flag on the dispenser to create threadsafe readers. This is done best-effort but
+ // really shouldn't ever fail. See issue 696511.
+ VARIANT optionValue;
+ VariantInit(&optionValue);
+ V_VT(&optionValue) = VT_UI4;
+ V_UI4(&optionValue) = MDThreadSafetyOn;
+ m_pMetaDispenser->SetOption(MetaDataThreadSafetyOptions, &optionValue);
+
+ //
+ // Setup internal events.
+ // @dbgtodo shim: these events should eventually be in the shim.
+ //
+
+
+ // Managed debugging is built on the native-pipeline, and that will detect against double-attaches.
+
+ // @dbgtodo shim: In V2, LSEA + LSER were used by the LS's helper thread. Now with the V3 pipeline,
+ // that helper-thread uses native-debug events. The W32ET gets those events and then uses LSEA, LSER to
+ // signal existing RS infrastructure. Eventually get rid of LSEA, LSER completely.
+ //
+
+ m_leftSideEventAvailable = WszCreateEvent(NULL, FALSE, FALSE, NULL);
+ if (m_leftSideEventAvailable == NULL)
+ {
+ ThrowLastError();
+ }
+
+ m_leftSideEventRead = WszCreateEvent(NULL, FALSE, FALSE, NULL);
+ if (m_leftSideEventRead == NULL)
+ {
+ ThrowLastError();
+ }
+
+ m_stopWaitEvent = WszCreateEvent(NULL, TRUE, FALSE, NULL);
+ if (m_stopWaitEvent == NULL)
+ {
+ ThrowLastError();
+ }
+
+ if (m_pShim != NULL)
+ {
+ // Get a handle to the debuggee.
+ // This is not needed in the V3 pipeline because we don't assume we have a live, local, process.
+ m_handle = GetShim()->GetNativePipeline()->GetProcessHandle();
+
+ if (m_handle == NULL)
+ {
+ ThrowLastError();
+ }
+ }
+
+ // The LS startup goes through the following phases:
+ // 1) mscorwks not yet loaded (eg, any unmanaged app)
+ // 2) mscorwks loaded (DAC can now be used)
+ // 3) IPC Block created at OS level
+ // 4) IPC block data initialized (so we can read meainingful data from it)
+ // 5) LS marks that it's initialized (queryable by a DAC primitive) (may not be atomic)
+ // 6) LS fires a "Startup" exception (sniffed by WFDE).
+ //
+ // LS is currently stopped by OS debugging, so it's doesn't shift phases.
+ // From the RS's perspective:
+ // - after phase 5 is an attach
+ // - before phase 6 is a launch.
+ // This means there's an overlap: if we catch it at phase 5, we'll just get
+ // an extra Startup exception from phase 6, which is safe. This overlap is good
+ // because it means there's no bad window to do an attach in.
+
+ // fIsLSStarted means before phase 6 (eg, RS should expect a startup exception)
+
+ // Determines if the LS is started.
+
+ {
+ BOOL fReady = TryInitializeDac();
+
+ if (fReady)
+ {
+ // Invoke DAC primitive.
+ _ASSERTE(m_pDacPrimitives != NULL);
+ fIsLSStarted = m_pDacPrimitives->IsLeftSideInitialized();
+ }
+ else
+ {
+ _ASSERTE(m_pDacPrimitives == NULL);
+
+ // DAC is not yet loaded, so we're at least before phase 2, which is before phase 6.
+ // So leave fIsLSStarted = false. We'll get a startup exception later.
+ _ASSERTE(!fIsLSStarted);
+ }
+ }
+
+
+ if (fIsLSStarted)
+ {
+ // Left-side has started up. This is common for Attach cases when managed-code is already running.
+
+ if (m_pShim != NULL)
+ {
+ FinishInitializeIPCChannelWorker(); // throws
+
+ // At this point, the control block is complete and all four
+ // events are available and valid for the remote process.
+
+ // Request that the process object send an Attach IPC event.
+ // This is only used in an attach case.
+ // @dbgtodo sync: this flag can go away once the
+ // shim can use real sync APIs.
+ m_fDoDelayedManagedAttached = true;
+ }
+ else
+ {
+ // In the V3 pipeline case, if we have the DD-interface, then the runtime is loaded
+ // and we consider it initialized.
+ if (IsDacInitialized())
+ {
+ m_initialized = true;
+ }
+ }
+ }
+ else
+ {
+ // LS is not started yet. This would be common for "Launch" cases.
+ // We will get a Startup Exception notification when it does start.
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ if (FAILED(hr))
+ {
+ CleanupHalfBakedLeftSide();
+ }
+
+ return hr;
+}
+
+
+COM_METHOD CordbProcess::CanCommitChanges(ULONG cSnapshots,
+ ICorDebugEditAndContinueSnapshot *pSnapshots[],
+ ICorDebugErrorInfoEnum **pError)
+{
+ return E_NOTIMPL;
+}
+
+COM_METHOD CordbProcess::CommitChanges(ULONG cSnapshots,
+ ICorDebugEditAndContinueSnapshot *pSnapshots[],
+ ICorDebugErrorInfoEnum **pError)
+{
+ return E_NOTIMPL;
+}
+
+
+//
+// Terminating -- places the process into the terminated state. This should
+// also get any blocking process functions unblocked so they'll return
+// a failure code.
+//
+void CordbProcess::Terminating(BOOL fDetach)
+{
+ INTERNAL_API_ENTRY(this);
+
+ LOG((LF_CORDB, LL_INFO1000,"CP::T: Terminating process 0x%x detach=%d\n", m_id, fDetach));
+ m_terminated = true;
+
+ m_cordb->ProcessStateChanged();
+
+ // Set events that may be blocking stuff.
+ // But don't set RSER unless we actually read the event. We don't block on RSER
+ // since that wait also checks the leftside's process handle.
+ SetEvent(m_leftSideEventRead);
+ SetEvent(m_leftSideEventAvailable);
+ SetEvent(m_stopWaitEvent);
+
+ if (m_pShim != NULL)
+ m_pShim->SetTerminatingEvent();
+
+ if (fDetach && (m_pEventChannel != NULL))
+ {
+ m_pEventChannel->Detach();
+ }
+}
+
+
+// Wrapper to give shim access to code:CordbProcess::QueueManagedAttachIfNeededWorker
+void CordbProcess::QueueManagedAttachIfNeeded()
+{
+ PUBLIC_API_ENTRY_FOR_SHIM(this);
+ QueueManagedAttachIfNeededWorker();
+}
+
+//---------------------------------------------------------------------------------------
+// Hook from Shim to request a managed attach IPC event
+//
+// Notes:
+// Called by shim after the loader-breakpoint is handled.
+// @dbgtodo sync: ths should go away once the shim can initiate
+// a sync
+void CordbProcess::QueueManagedAttachIfNeededWorker()
+{
+ HRESULT hrQueue = S_OK;
+
+ // m_fDoDelayedManagedAttached ensures that we only send an Attach event if the LS is actually present.
+ if (m_fDoDelayedManagedAttached && GetShim()->GetAttached())
+ {
+ RSLockHolder lockHolder(&this->m_processMutex);
+ GetDAC()->MarkDebuggerAttachPending();
+
+ hrQueue = this->QueueManagedAttach();
+ }
+
+ if (m_pShim != NULL)
+ m_pShim->SetMarkAttachPendingEvent();
+
+ IfFailThrow(hrQueue);
+}
+
+//---------------------------------------------------------------------------------------
+//
+// QueueManagedAttach
+//
+// Send a managed attach. This is asynchronous and will return immediately.
+//
+// Return Value:
+// S_OK on success
+//
+//---------------------------------------------------------------------------------------
+HRESULT CordbProcess::QueueManagedAttach()
+{
+ INTERNAL_API_ENTRY(this);
+
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ _ASSERTE(m_fDoDelayedManagedAttached);
+ m_fDoDelayedManagedAttached = false;
+
+ _ASSERTE(IsDacInitialized());
+
+ // We don't know what Queue it.
+ SendAttachProcessWorkItem * pItem = new (nothrow) SendAttachProcessWorkItem(this);
+
+ if (pItem == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ this->m_cordb->m_rcEventThread->QueueAsyncWorkItem(pItem);
+
+ return S_OK;
+}
+
+// However, we still want to synchronize.
+// @dbgtodo sync: when we hoist attaching, we can send an DB_IPCE_ASYNC_BREAK event instead or Attach
+// (for V2 semantics, we still need to synchronize the process)?
+void SendAttachProcessWorkItem::Do()
+{
+ HRESULT hr;
+
+ // This is being processed on the RCET, where it's safe to take the Stop-Go lock.
+ RSLockHolder ch(this->GetProcess()->GetStopGoLock());
+
+ DebuggerIPCEvent *event = (DebuggerIPCEvent*) _alloca(CorDBIPC_BUFFER_SIZE);
+
+ // This just acts like an async-break, which will kick off things.
+ // This will not induce any faked attach events from the VM (like it did in V2).
+ // The Left-side will still slip foward allowing the async-break to happen, so
+ // we may get normal debug events in addition to the sync-complete.
+ //
+ // 1. In the common attach case, we should just get a sync-complete.
+ // 2. In Jit-attach cases, the LS is sending an event, and so we'll get that event and then the sync-complete.
+ GetProcess()->InitAsyncIPCEvent(event, DB_IPCE_ATTACHING, VMPTR_AppDomain::NullPtr());
+
+ // This should result in a sync-complete from the Left-side, which will be raised as an exception
+ // that the debugger passes into Filter and then internally goes through code:CordbProcess::TriageSyncComplete
+ // and that triggers code:CordbRCEventThread::FlushQueuedEvents to be called on the RCET.
+ // We already pre-queued a fake CreateProcess event.
+
+ // The left-side will also mark itself as attached in response to this event.
+ // We explicitly don't mark it as attached from the right-side because we want to let the left-side
+ // synchronize first (to stop all running threads) before marking the debugger as attached.
+ LOG((LF_CORDB, LL_INFO1000, "[%x] CP::S: sending attach.\n", GetCurrentThreadId()));
+
+ hr = GetProcess()->SendIPCEvent(event, CorDBIPC_BUFFER_SIZE);
+
+ LOG((LF_CORDB, LL_INFO1000, "[%x] CP::S: sent attach.\n", GetCurrentThreadId()));
+}
+
+//---------------------------------------------------------------------------------------
+// Try to lookup a cached thread object
+//
+// Arguments:
+// vmThread - vm identifier for thread.
+//
+// Returns:
+// Thread object if cached; null if not yet cached.
+//
+// Notes:
+// This does not create the thread object if it's not cached. Caching is unpredictable,
+// and so this may appear to randomly return NULL.
+// Callers should prefer code:CordbProcess::LookupOrCreateThread unless they expicitly
+// want to check RS state.
+CordbThread * CordbProcess::TryLookupThread(VMPTR_Thread vmThread)
+{
+ return m_userThreads.GetBase(VmPtrToCookie(vmThread));
+}
+
+//---------------------------------------------------------------------------------------
+// Lookup (or create) a CordbThread object by the given volatile OS id. Returns null if not a manged thread
+//
+// Arguments:
+// dwThreadId - os thread id that a managed thread may be using.
+//
+// Returns:
+// Thread instance if there is currently a managed thread scheduled to run on dwThreadId.
+// NULL if this tid is not a valid Managed thread. (This is considered a common case)
+// Throws on error.
+//
+// Notes:
+// OS Thread ID is not fiber-safe, so this is a dangerous function to call.
+// Avoid this as much as possible. Prefer using VMPTR_Thread and
+// code:CordbProcess::LookupOrCreateThread instead of OS thread IDs.
+// See code:CordbThread::GetID for details.
+CordbThread * CordbProcess::TryLookupOrCreateThreadByVolatileOSId(DWORD dwThreadId)
+{
+ PrepopulateThreadsOrThrow();
+ return TryLookupThreadByVolatileOSId(dwThreadId);
+}
+
+//---------------------------------------------------------------------------------------
+// Lookup a cached CordbThread object by the tid. Returns null if not in the cache (which
+// includes unmanged thread)
+//
+// Arguments:
+// dwThreadId - os thread id that a managed thread may be using.
+//
+// Returns:
+// Thread instance if there is currently a managed thread scheduled to run on dwThreadId.
+// NULL if this tid is not a valid Managed thread. (This is considered a common case)
+// Throws on error.
+//
+// Notes:
+// Avoids this method:
+// * OS Thread ID is not fiber-safe, so this is a dangerous function to call.
+// * This is juts a Lookup, not LookupOrCreate, so it should only be used by methods
+// that care about the RS state (instead of just LS state).
+// Prefer using VMPTR_Thread and code:CordbProcess::LookupOrCreateThread
+//
+CordbThread * CordbProcess::TryLookupThreadByVolatileOSId(DWORD dwThreadId)
+{
+ HASHFIND find;
+ for (CordbThread * pThread = m_userThreads.FindFirst(&find);
+ pThread != NULL;
+ pThread = m_userThreads.FindNext(&find))
+ {
+ _ASSERTE(pThread != NULL);
+
+ // Get the OS tid. This returns 0 if the thread is switched out.
+ DWORD dwThreadId2 = GetDAC()->TryGetVolatileOSThreadID(pThread->m_vmThreadToken);
+ if (dwThreadId2 == dwThreadId)
+ {
+ return pThread;
+ }
+ }
+
+ // This OS thread ID does not match any managed thread id.
+ return NULL;
+}
+
+//---------------------------------------------------------------------------------------
+// Preferred CordbThread lookup routine.
+//
+// Arguments:
+// vmThread - LS thread to lookup. Must be non-null.
+//
+// Returns:
+// CordbThread instance for given vmThread. May return a previously cached
+// instance or create a new instance. Never returns NULL.
+// Throw on error.
+CordbThread * CordbProcess::LookupOrCreateThread(VMPTR_Thread vmThread)
+{
+ _ASSERTE(!vmThread.IsNull());
+
+ // Return if we have an existing instance.
+ CordbThread * pReturn = TryLookupThread(vmThread);
+ if (pReturn != NULL)
+ {
+ return pReturn;
+ }
+
+ RSInitHolder<CordbThread> pThread(new CordbThread(this, vmThread)); // throws
+ pReturn = pThread.TransferOwnershipToHash(&m_userThreads);
+
+ return pReturn;
+}
+
+
+
+
+HRESULT CordbProcess::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugProcess)
+ {
+ *pInterface = static_cast<ICorDebugProcess*>(this);
+ }
+ else if (id == IID_ICorDebugController)
+ {
+ *pInterface = static_cast<ICorDebugController*>(static_cast<ICorDebugProcess*>(this));
+ }
+ else if (id == IID_ICorDebugProcess2)
+
+ {
+ *pInterface = static_cast<ICorDebugProcess2*>(this);
+ }
+ else if (id == IID_ICorDebugProcess3)
+ {
+ *pInterface = static_cast<ICorDebugProcess3*>(this);
+ }
+ else if (id == IID_ICorDebugProcess4)
+ {
+ *pInterface = static_cast<ICorDebugProcess4*>(this);
+ }
+ else if (id == IID_ICorDebugProcess5)
+ {
+ *pInterface = static_cast<ICorDebugProcess5*>(this);
+ }
+ else if (id == IID_ICorDebugProcess7)
+ {
+ *pInterface = static_cast<ICorDebugProcess7*>(this);
+ }
+ else if (id == IID_ICorDebugProcess8)
+ {
+ *pInterface = static_cast<ICorDebugProcess8*>(this);
+ }
+#ifdef FEATURE_LEGACYNETCF_DBG_HOST_CONTROL
+ else if (id == IID_ICorDebugLegacyNetCFHostCallbackInvoker_PrivateWindowsPhoneOnly)
+ {
+ *pInterface = static_cast<ICorDebugLegacyNetCFHostCallbackInvoker_PrivateWindowsPhoneOnly*>(this);
+ }
+#endif
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugProcess*>(this));
+ }
+
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+
+
+
+// Public implementation of ICorDebugProcess4::ProcessStateChanged
+HRESULT CordbProcess::ProcessStateChanged(CorDebugStateChange eChange)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this)
+ {
+ switch(eChange)
+ {
+ case PROCESS_RUNNING:
+ FlushProcessRunning();
+ break;
+
+ case FLUSH_ALL:
+ FlushAll();
+ break;
+
+ default:
+ ThrowHR(E_INVALIDARG);
+
+ }
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+
+HRESULT CordbProcess::EnumerateHeap(ICorDebugHeapEnum **ppObjects)
+{
+ if (!ppObjects)
+ return E_POINTER;
+
+ HRESULT hr = S_OK;
+ PUBLIC_API_ENTRY(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this);
+
+ EX_TRY
+ {
+ if (m_pDacPrimitives->AreGCStructuresValid())
+ {
+ CordbHeapEnum *pHeapEnum = new CordbHeapEnum(this);
+ GetContinueNeuterList()->Add(this, pHeapEnum);
+ hr = pHeapEnum->QueryInterface(__uuidof(ICorDebugHeapEnum), (void**)ppObjects);
+ }
+ else
+ {
+ hr = CORDBG_E_GC_STRUCTURES_INVALID;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+HRESULT CordbProcess::GetGCHeapInformation(COR_HEAPINFO *pHeapInfo)
+{
+ if (!pHeapInfo)
+ return E_INVALIDARG;
+
+ HRESULT hr = S_OK;
+ PUBLIC_API_ENTRY(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this);
+
+ EX_TRY
+ {
+ GetDAC()->GetGCHeapInformation(pHeapInfo);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+HRESULT CordbProcess::EnumerateHeapRegions(ICorDebugHeapSegmentEnum **ppRegions)
+{
+ if (!ppRegions)
+ return E_INVALIDARG;
+
+ HRESULT hr = S_OK;
+ PUBLIC_API_ENTRY(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this);
+
+ EX_TRY
+ {
+ DacDbiArrayList<COR_SEGMENT> segments;
+ hr = GetDAC()->GetHeapSegments(&segments);
+
+ if (SUCCEEDED(hr))
+ {
+ if (!segments.IsEmpty())
+ {
+ CordbHeapSegmentEnumerator *segEnum = new CordbHeapSegmentEnumerator(this, &segments[0], (DWORD)segments.Count());
+ GetContinueNeuterList()->Add(this, segEnum);
+ hr = segEnum->QueryInterface(__uuidof(ICorDebugHeapSegmentEnum), (void**)ppRegions);
+ }
+ else
+ {
+ hr = E_OUTOFMEMORY;
+ }
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+HRESULT CordbProcess::GetObject(CORDB_ADDRESS addr, ICorDebugObjectValue **pObject)
+{
+ HRESULT hr = S_OK;
+
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this);
+
+ EX_TRY
+ {
+ if (!m_pDacPrimitives->IsValidObject(addr))
+ {
+ hr = CORDBG_E_CORRUPT_OBJECT;
+ }
+ else if (pObject == NULL)
+ {
+ hr = E_INVALIDARG;
+ }
+ else
+ {
+ RSLockHolder ch(GetProcess()->GetStopGoLock());
+ RSLockHolder procLock(this->GetProcess()->GetProcessLock());
+
+ CordbAppDomain *cdbAppDomain = NULL;
+ CordbType *pType = NULL;
+ hr = GetTypeForObject(addr, &pType, &cdbAppDomain);
+
+ if (SUCCEEDED(hr))
+ {
+ _ASSERTE(pType != NULL);
+ _ASSERTE(cdbAppDomain != NULL);
+
+ DebuggerIPCE_ObjectData objData;
+ m_pDacPrimitives->GetBasicObjectInfo(addr, ELEMENT_TYPE_CLASS, cdbAppDomain->GetADToken(), &objData);
+
+ NewHolder<CordbObjectValue> pNewObjectValue(new CordbObjectValue(cdbAppDomain, pType, TargetBuffer(addr, (ULONG)objData.objSize), &objData));
+ hr = pNewObjectValue->Init();
+
+ if (SUCCEEDED(hr))
+ {
+ hr = pNewObjectValue->QueryInterface(__uuidof(ICorDebugObjectValue), (void**)pObject);
+ if (SUCCEEDED(hr))
+ pNewObjectValue.SuppressRelease();
+ }
+ }
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+
+HRESULT CordbProcess::EnumerateGCReferences(BOOL enumerateWeakReferences, ICorDebugGCReferenceEnum **ppEnum)
+{
+ if (!ppEnum)
+ return E_POINTER;
+
+ HRESULT hr = S_OK;
+ PUBLIC_API_ENTRY(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this);
+
+ EX_TRY
+ {
+ CordbRefEnum *pRefEnum = new CordbRefEnum(this, enumerateWeakReferences);
+ GetContinueNeuterList()->Add(this, pRefEnum);
+ hr = pRefEnum->QueryInterface(IID_ICorDebugGCReferenceEnum, (void**)ppEnum);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+HRESULT CordbProcess::EnumerateHandles(CorGCReferenceType types, ICorDebugGCReferenceEnum **ppEnum)
+{
+ if (!ppEnum)
+ return E_POINTER;
+
+ HRESULT hr = S_OK;
+ PUBLIC_API_ENTRY(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this);
+
+ EX_TRY
+ {
+ CordbRefEnum *pRefEnum = new CordbRefEnum(this, types);
+ GetContinueNeuterList()->Add(this, pRefEnum);
+ hr = pRefEnum->QueryInterface(IID_ICorDebugGCReferenceEnum, (void**)ppEnum);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+HRESULT CordbProcess::EnableNGENPolicy(CorDebugNGENPolicy ePolicy)
+{
+#ifdef FEATURE_CORECLR
+ return E_NOTIMPL;
+#else
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+
+ IDacDbiInterface* pDAC = GetProcess()->GetDAC();
+ hr = pDAC->EnableNGENPolicy(ePolicy);
+
+ PUBLIC_API_END(hr);
+ return hr;
+#endif
+}
+
+
+HRESULT CordbProcess::GetTypeID(CORDB_ADDRESS obj, COR_TYPEID *pId)
+{
+ if (pId == NULL)
+ return E_POINTER;
+
+ HRESULT hr = S_OK;
+ PUBLIC_API_ENTRY(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this);
+
+ EX_TRY
+ {
+ hr = GetProcess()->GetDAC()->GetTypeID(obj, pId);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+HRESULT CordbProcess::GetTypeForTypeID(COR_TYPEID id, ICorDebugType **ppType)
+{
+ if (ppType == NULL)
+ return E_POINTER;
+
+ HRESULT hr = S_OK;
+
+ PUBLIC_API_ENTRY(this);
+ RSLockHolder stopGoLock(this->GetProcess()->GetStopGoLock());
+ RSLockHolder procLock(this->GetProcess()->GetProcessLock());
+
+ EX_TRY
+ {
+ DebuggerIPCE_ExpandedTypeData data;
+ GetDAC()->GetObjectExpandedTypeInfoFromID(AllBoxed, VMPTR_AppDomain::NullPtr(), id, &data);
+
+ CordbType *type = 0;
+ hr = CordbType::TypeDataToType(GetSharedAppDomain(), &data, &type);
+
+ if (SUCCEEDED(hr))
+ hr = type->QueryInterface(IID_ICorDebugType, (void**)ppType);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+
+COM_METHOD CordbProcess::GetArrayLayout(COR_TYPEID id, COR_ARRAY_LAYOUT *pLayout)
+{
+ if (pLayout == NULL)
+ return E_POINTER;
+
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+
+ hr = GetProcess()->GetDAC()->GetArrayLayout(id, pLayout);
+
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+COM_METHOD CordbProcess::GetTypeLayout(COR_TYPEID id, COR_TYPE_LAYOUT *pLayout)
+{
+ if (pLayout == NULL)
+ return E_POINTER;
+
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+
+ hr = GetProcess()->GetDAC()->GetTypeLayout(id, pLayout);
+
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+COM_METHOD CordbProcess::GetTypeFields(COR_TYPEID id, ULONG32 celt, COR_FIELD fields[], ULONG32 *pceltNeeded)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+
+ hr = GetProcess()->GetDAC()->GetObjectFields(id, celt, fields, pceltNeeded);
+
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+COM_METHOD CordbProcess::SetWriteableMetadataUpdateMode(WriteableMetadataUpdateMode flags)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+
+ if(flags != LegacyCompatPolicy &&
+ flags != AlwaysShowUpdates)
+ {
+ hr = E_INVALIDARG;
+ }
+ else if(m_pShim != NULL)
+ {
+ if(flags != LegacyCompatPolicy)
+ {
+ hr = CORDBG_E_UNSUPPORTED;
+ }
+ }
+
+ if(SUCCEEDED(hr))
+ {
+ m_writableMetadataUpdateMode = flags;
+ }
+
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+COM_METHOD CordbProcess::EnableExceptionCallbacksOutsideOfMyCode(BOOL enableExceptionsOutsideOfJMC)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+
+ hr = GetProcess()->GetDAC()->SetSendExceptionsOutsideOfJMC(enableExceptionsOutsideOfJMC);
+
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+#ifdef FEATURE_LEGACYNETCF_DBG_HOST_CONTROL
+
+COM_METHOD CordbProcess::InvokePauseCallback()
+{
+ return S_OK;
+}
+
+COM_METHOD CordbProcess::InvokeResumeCallback()
+{
+ return S_OK;
+}
+
+#endif
+
+HRESULT CordbProcess::GetTypeForObject(CORDB_ADDRESS addr, CordbType **ppType, CordbAppDomain **pAppDomain)
+{
+ VMPTR_AppDomain appDomain;
+ VMPTR_Module mod;
+ VMPTR_DomainFile domainFile;
+
+ HRESULT hr = E_FAIL;
+ if (GetDAC()->GetAppDomainForObject(addr, &appDomain, &mod, &domainFile))
+ {
+ CordbAppDomain *cdbAppDomain = appDomain.IsNull() ? GetSharedAppDomain() : LookupOrCreateAppDomain(appDomain);
+
+ _ASSERTE(cdbAppDomain);
+
+ DebuggerIPCE_ExpandedTypeData data;
+ GetDAC()->GetObjectExpandedTypeInfo(AllBoxed, appDomain, addr, &data);
+
+ CordbType *type = 0;
+ hr = CordbType::TypeDataToType(cdbAppDomain, &data, &type);
+
+ if (SUCCEEDED(hr))
+ {
+ *ppType = type;
+ if (pAppDomain)
+ *pAppDomain = cdbAppDomain;
+ }
+ }
+
+ return hr;
+}
+
+
+// ******************************************
+// CordbRefEnum
+// ******************************************
+CordbRefEnum::CordbRefEnum(CordbProcess *proc, BOOL walkWeakRefs)
+ : CordbBase(proc, 0, enumCordbHeap), mRefHandle(0), mEnumStacksFQ(TRUE),
+ mHandleMask((UINT32)(walkWeakRefs ? CorHandleAll : CorHandleStrongOnly))
+{
+}
+
+CordbRefEnum::CordbRefEnum(CordbProcess *proc, CorGCReferenceType types)
+ : CordbBase(proc, 0, enumCordbHeap), mRefHandle(0), mEnumStacksFQ(FALSE),
+ mHandleMask((UINT32)types)
+{
+}
+
+void CordbRefEnum::Neuter()
+{
+ EX_TRY
+ {
+ if (mRefHandle)
+ {
+ GetProcess()->GetDAC()->DeleteRefWalk(mRefHandle);
+ mRefHandle = 0;
+ }
+ }
+ EX_CATCH
+ {
+ _ASSERTE(!"Hit an error freeing a ref walk.");
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ CordbBase::Neuter();
+}
+
+HRESULT CordbRefEnum::QueryInterface(REFIID riid, void **ppInterface)
+{
+ if (ppInterface == NULL)
+ return E_INVALIDARG;
+
+ if (riid == IID_ICorDebugGCReferenceEnum)
+ {
+ *ppInterface = static_cast<ICorDebugGCReferenceEnum*>(this);
+ }
+ else if (riid == IID_IUnknown)
+ {
+ *ppInterface = static_cast<IUnknown*>(static_cast<ICorDebugGCReferenceEnum*>(this));
+ }
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+HRESULT CordbRefEnum::Skip(ULONG celt)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT CordbRefEnum::Reset()
+{
+ PUBLIC_API_ENTRY(this);
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ if (mRefHandle)
+ {
+ GetProcess()->GetDAC()->DeleteRefWalk(mRefHandle);
+ mRefHandle = 0;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+HRESULT CordbRefEnum::Clone(ICorDebugEnum **ppEnum)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT CordbRefEnum::GetCount(ULONG *pcelt)
+{
+ return E_NOTIMPL;
+}
+
+
+//
+
+HRESULT CordbRefEnum::Next(ULONG celt, COR_GC_REFERENCE refs[], ULONG *pceltFetched)
+{
+ if (refs == NULL || pceltFetched == NULL)
+ return E_POINTER;
+
+ CordbProcess *process = GetProcess();
+ HRESULT hr = S_OK;
+
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(process);
+
+ RSLockHolder procLockHolder(process->GetProcessLock());
+
+ EX_TRY
+ {
+ if (!mRefHandle)
+ hr = process->GetDAC()->CreateRefWalk(&mRefHandle, mEnumStacksFQ, mEnumStacksFQ, mHandleMask);
+
+ if (SUCCEEDED(hr))
+ {
+ DacGcReference dacRefs[32];
+ ULONG toFetch = _countof(dacRefs);
+ ULONG total = 0;
+
+ for (ULONG c = 0; SUCCEEDED(hr) && c < (celt/_countof(dacRefs) + 1); ++c)
+ {
+ // Fetch 32 references at a time, the last time, only fetch the remainder (that is, if
+ // the user didn't fetch a multiple of 32).
+ if (c == celt/_countof(dacRefs))
+ toFetch = celt % _countof(dacRefs);
+
+ ULONG fetched = 0;
+ hr = process->GetDAC()->WalkRefs(mRefHandle, toFetch, dacRefs, &fetched);
+
+ if (SUCCEEDED(hr))
+ {
+ for (ULONG i = 0; i < fetched; ++i)
+ {
+ CordbAppDomain *pDomain = process->LookupOrCreateAppDomain(dacRefs[i].vmDomain);
+
+ ICorDebugAppDomain *pAppDomain;
+ ICorDebugValue *pOutObject = NULL;
+ if (dacRefs[i].pObject & 1)
+ {
+ dacRefs[i].pObject &= ~1;
+ ICorDebugObjectValue *pObjValue = NULL;
+
+ hr = process->GetObject(dacRefs[i].pObject, &pObjValue);
+
+ if (SUCCEEDED(hr))
+ {
+ hr = pObjValue->QueryInterface(IID_ICorDebugValue, (void**)&pOutObject);
+ pObjValue->Release();
+ }
+ }
+ else
+ {
+ ICorDebugReferenceValue *tmpValue = NULL;
+ IfFailThrow(CordbReferenceValue::BuildFromGCHandle(pDomain,
+ dacRefs[i].objHnd,
+ &tmpValue));
+
+ if (SUCCEEDED(hr))
+ {
+ hr = tmpValue->QueryInterface(IID_ICorDebugValue, (void**)&pOutObject);
+ tmpValue->Release();
+ }
+ }
+
+ if (SUCCEEDED(hr) && pDomain)
+ {
+ hr = pDomain->QueryInterface(IID_ICorDebugAppDomain, (void**)&pAppDomain);
+ }
+
+ if (FAILED(hr))
+ break;
+
+ refs[total].Domain = pAppDomain;
+ refs[total].Location = pOutObject;
+ refs[total].Type = (CorGCReferenceType)dacRefs[i].dwType;
+ refs[total].ExtraData = dacRefs[i].i64ExtraData;
+
+ total++;
+ }
+ }
+ }
+
+ *pceltFetched = total;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+
+// ******************************************
+// CordbHeapEnum
+// ******************************************
+CordbHeapEnum::CordbHeapEnum(CordbProcess *proc)
+ : CordbBase(proc, 0, enumCordbHeap), mHeapHandle(0)
+{
+}
+
+HRESULT CordbHeapEnum::QueryInterface(REFIID riid, void **ppInterface)
+{
+ if (ppInterface == NULL)
+ return E_INVALIDARG;
+
+ if (riid == IID_ICorDebugHeapEnum)
+ {
+ *ppInterface = static_cast<ICorDebugHeapEnum*>(this);
+ }
+ else if (riid == IID_IUnknown)
+ {
+ *ppInterface = static_cast<IUnknown*>(static_cast<ICorDebugHeapEnum*>(this));
+ }
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+HRESULT CordbHeapEnum::Skip(ULONG celt)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT CordbHeapEnum::Reset()
+{
+ Clear();
+ return S_OK;
+}
+
+void CordbHeapEnum::Clear()
+{
+ EX_TRY
+ {
+ if (mHeapHandle)
+ {
+ GetProcess()->GetDAC()->DeleteHeapWalk(mHeapHandle);
+ mHeapHandle = 0;
+ }
+ }
+ EX_CATCH
+ {
+ _ASSERTE(!"Hit an error freeing the heap walk.");
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+}
+
+HRESULT CordbHeapEnum::Clone(ICorDebugEnum **ppEnum)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT CordbHeapEnum::GetCount(ULONG *pcelt)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT CordbHeapEnum::Next(ULONG celt, COR_HEAPOBJECT objects[], ULONG *pceltFetched)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_ENTRY(this);
+ RSLockHolder stopGoLock(this->GetProcess()->GetStopGoLock());
+ RSLockHolder procLock(this->GetProcess()->GetProcessLock());
+ ULONG fetched = 0;
+
+ EX_TRY
+ {
+ if (mHeapHandle == 0)
+ {
+ hr = GetProcess()->GetDAC()->CreateHeapWalk(&mHeapHandle);
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ hr = GetProcess()->GetDAC()->WalkHeap(mHeapHandle, celt, objects, &fetched);
+ _ASSERTE(fetched <= celt);
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ // Return S_FALSE if we've reached the end of the enum.
+ if (fetched < celt)
+ hr = S_FALSE;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ // Set the fetched parameter to reflect the number of elements (if any)
+ // that were successfully saved to "objects"
+ if (pceltFetched)
+ *pceltFetched = fetched;
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+// Flush state for when the process starts running.
+//
+// Notes:
+// Helper for code:CordbProcess::ProcessStateChanged.
+// Since ICD Arrowhead does not own the eventing pipeline, it needs the debugger to
+// notifying it of when the process is running again. This is like the counterpart
+// to code:CordbProcess::Filter
+void CordbProcess::FlushProcessRunning()
+{
+ _ASSERTE(GetProcessLock()->HasLock());
+
+ // Update the continue counter.
+ m_continueCounter++;
+
+ // Safely dispose anything that should be neutered on continue.
+ MarkAllThreadsDirty();
+ ForceDacFlush();
+}
+
+//---------------------------------------------------------------------------------------
+// Flush all cached state and bring us back to "cold startup"
+//
+// Notes:
+// Helper for code:CordbProcess::ProcessStateChanged.
+// This is used if the data-target changes underneath us in a way that is
+// not consistent with the process running forward. For example, if for
+// a time-travel debugger, the data-target may flow "backwards" in time.
+//
+void CordbProcess::FlushAll()
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr;
+ _ASSERTE(GetProcessLock()->HasLock());
+
+ //
+ // First, determine if it's safe to Flush
+ //
+
+ hr = IsReadyForDetach();
+ IfFailThrow(hr);
+
+ // Check for outstanding CordbHandle values.
+ if (OutstandingHandles())
+ {
+ ThrowHR(CORDBG_E_DETACH_FAILED_OUTSTANDING_TARGET_RESOURCES);
+ }
+
+ // FlushAll is a superset of FlushProcessRunning.
+ // This will also ensure we clear the DAC cache.
+ FlushProcessRunning();
+
+ // If we detach before the CLR is loaded into the debuggee, then we can no-op a lot of work.
+ // We sure can't be sending IPC events to the LS before it exists.
+ NeuterChildren();
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Detach the Debugger from the LS process.
+//
+//
+// Return Value:
+// S_OK on successful detach. Else errror.
+//
+// Assumptions:
+// Target is stopped.
+//
+// Notes:
+// Once we're detached, the LS can resume running and exit.
+// So it's possible to get an ExitProcess callback in the middle of the Detach phase. If that happens,
+// we must return CORDBG_E_PROCESS_TERMINATED and pretend that the exit happened before we tried to detach.
+// Else if we detach successfully, return S_OK.
+//
+// @dbgtodo attach-bit: need to figure out semantics of Detach
+// in V3, especially w.r.t to an attach bit.
+//---------------------------------------------------------------------------------------
+HRESULT CordbProcess::Detach()
+{
+ PUBLIC_API_ENTRY(this);
+
+ FAIL_IF_NEUTERED(this);
+
+ if (IsInteropDebugging())
+ {
+ return CORDBG_E_INTEROP_NOT_SUPPORTED;
+ }
+
+
+ HRESULT hr = S_OK;
+ // A very important note: we require that the process is synchronized before doing a detach. This ensures
+ // that no events are on their way from the Left Side. We also require that the user has drained the
+ // managed event queue, but there is currently no way to really enforce that here.
+ // @todo- why can't we enforce that the managed event Q is drained?
+ ATT_REQUIRE_SYNCED_OR_NONINIT_MAY_FAIL(this);
+
+
+ hr = IsReadyForDetach();
+ if (FAILED(hr))
+ {
+ // Avoid neutering. Gives client a chance to fix detach issue and retry.
+ return hr;
+ }
+
+ // Since the detach may resume the LS and allow it to exit, which may invoke the EP callback
+ // which may destroy this process object, be sure to protect us w/ an extra AddRef/Release
+ RSSmartPtr<CordbProcess> pRef(this);
+
+
+
+ LOG((LF_CORDB, LL_INFO1000, "CP::Detach - beginning\n"));
+ if (m_pShim == NULL) // This API is moved off to the shim
+ {
+
+ // This is still invasive.
+ // Ignore failures. This will fail for a non-invasive target.
+ if (IsDacInitialized())
+ {
+ HRESULT hrIgnore = S_OK;
+ EX_TRY
+ {
+ GetDAC()->MarkDebuggerAttached(FALSE);
+ }
+ EX_CATCH_HRESULT(hrIgnore);
+ }
+ }
+ else
+ {
+ EX_TRY
+ {
+ DetachShim();
+ }
+ EX_CATCH_HRESULT(hr);
+ }
+
+ // Either way, neuter everything.
+ this->Neuter();
+
+ // Implicit release on pRef
+ LOG((LF_CORDB, LL_INFO1000, "CP::Detach - returning w/ hr=0x%x\n", hr));
+ return hr;
+}
+
+// Free up key left-side resources
+//
+// Called on detach
+// This does key neutering of objects that hold left-side resources and require
+// preemptively freeing the resources.
+// After this, code:CordbProcess::Neuter should only affect right-side state.
+void CordbProcess::NeuterChildrenLeftSideResources()
+{
+ _ASSERTE(GetStopGoLock()->HasLock());
+
+ _ASSERTE(!GetProcessLock()->HasLock());
+ RSLockHolder lockHolder(GetProcessLock());
+
+
+ // Need process-lock to operate on hashtable, but can't yet Neuter under process-lock,
+ // so we have to copy the contents to an auxilary list which we can then traverse outside the lock.
+ RSPtrArray<CordbAppDomain> listAppDomains;
+ m_appDomains.CopyToArray(&listAppDomains);
+
+
+
+ // Must not hold process lock so that we can be safe to send IPC events
+ // to cleanup left-side resources.
+ lockHolder.Release();
+ _ASSERTE(!GetProcessLock()->HasLock());
+
+ // Frees left-side resources. This may send IPC events.
+ // This will make normal neutering a nop.
+ m_LeftSideResourceCleanupList.NeuterLeftSideResourcesAndClear(this);
+
+ for(unsigned int idx = 0; idx < listAppDomains.Length(); idx++)
+ {
+ CordbAppDomain * pAppDomain = listAppDomains[idx];
+
+ // CordbHandleValue is in the appdomain exit list, and that needs
+ // to send an IPC event to cleanup and release the handle from
+ // the GCs handle table.
+ pAppDomain->GetSweepableExitNeuterList()->NeuterLeftSideResourcesAndClear(this);
+ }
+ listAppDomains.Clear();
+
+}
+
+//---------------------------------------------------------------------------------------
+// Detach the Debugger from the LS process for the V2 case
+//
+// Assumptions:
+// This will NeuterChildren(), caller will do the real Neuter()
+// Caller has already ensured that detach is safe.
+//
+// @dbgtodo attach-bit: this should be moved into the shim; need
+// to figure out semantics for freeing left-side resources (especially GC
+// handles) on detach.
+void CordbProcess::DetachShim()
+{
+
+ HASHFIND hashFind;
+ HRESULT hr = S_OK;
+
+ // If we detach before the CLR is loaded into the debuggee, then we can no-op a lot of work.
+ // We sure can't be sending IPC events to the LS before it exists.
+ if (m_initialized)
+ {
+ // The managed event queue is not necessarily drained. Cordbg could call detach between any callback.
+ // While the process is still stopped, neuter all of our children.
+ // This will make our Neuter() a nop and saves the W32ET from having to do dangerous work.
+ this->NeuterChildrenLeftSideResources();
+ {
+ RSLockHolder lockHolder(GetProcessLock());
+ this->NeuterChildren();
+ }
+
+ // Go ahead and detach from the entire process now. This is like sending a "Continue".
+ DebuggerIPCEvent * pIPCEvent = (DebuggerIPCEvent *) _alloca(CorDBIPC_BUFFER_SIZE);
+ InitIPCEvent(pIPCEvent, DB_IPCE_DETACH_FROM_PROCESS, true, VMPTR_AppDomain::NullPtr());
+
+ hr = m_cordb->SendIPCEvent(this, pIPCEvent, CorDBIPC_BUFFER_SIZE);
+ hr = WORST_HR(hr, pIPCEvent->hr);
+ IfFailThrow(hr);
+ }
+ else
+ {
+ // @dbgtodo attach-bit: push this up, once detach IPC event is hoisted.
+ RSLockHolder lockHolder(GetProcessLock());
+
+ // Shouldn't have any appdomains.
+ (void)hashFind; //prevent "unused variable" error from GCC
+ _ASSERTE(m_appDomains.FindFirst(&hashFind) == NULL);
+ }
+
+ LOG((LF_CORDB, LL_INFO10000, "CP::Detach - got reply from LS\n"));
+
+ // It's possible that the LS may exit after they reply to our detach_from_process, but
+ // before we update our internal state that they're detached. So still have to check
+ // failure codes here.
+ hr = this->m_pShim->GetWin32EventThread()->SendDetachProcessEvent(this);
+
+
+ // Since we're auto-continuing when we detach, we should set the stop count back to zero.
+ // This (along w/ m_detached) prevents anyone from calling Continue on this process
+ // after this call returns.
+ m_stopCount = 0;
+
+ if (hr != CORDBG_E_PROCESS_TERMINATED)
+ {
+ // Remember that we've detached from this process object. This will prevent any further operations on
+ // this process, just in case... :)
+ // If LS exited, then don't set this flag because it overrides m_terminated when reporting errors;
+ // and we want to provide a consistent story about whether we detached or whether the LS exited.
+ m_detached = true;
+ }
+ IfFailThrow(hr);
+
+
+ // Now that all complicated cleanup is done, caller can do a final neuter.
+ // This will implicitly stop our Win32 event thread as well.
+}
+
+// Delete all events from the queue without dispatching. This is useful in shutdown.
+// An event that is currently dispatching is not on the queue.
+void CordbProcess::DeleteQueuedEvents()
+{
+ INTERNAL_API_ENTRY(this);
+ // We must have the process lock to ensure that no one is trying to add an event
+ _ASSERTE(!ThreadHoldsProcessLock());
+
+ if (m_pShim != NULL)
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE0_NO_LOCK(this);
+
+ // DeleteAll() is part of the shim, and it will change external ref counts, so must really
+ // be marked as outside the RS.
+ m_pShim->GetManagedEventQueue()->DeleteAll();
+ }
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Track that we're about to dispatch a managed event.
+//
+// Arguments:
+// event - event being dispatched
+//
+// Assumptions:
+// This is used to support code:CordbProcess::AreDispatchingEvent
+// This is always called on the same thread as code:CordbProcess::FinishEventDispatch
+void CordbProcess::StartEventDispatch(DebuggerIPCEventType event)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ _ASSERTE(m_dispatchedEvent == DB_IPCE_DEBUGGER_INVALID);
+ _ASSERTE(event != DB_IPCE_DEBUGGER_INVALID);
+ m_dispatchedEvent = event;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Track that we're done dispatching a managed event.
+//
+//
+// Assumptions:
+// This is always called on the same thread as code:CordbProcess::StartEventDispatch
+//
+// Notes:
+// @dbgtodo shim: eventually this goes into the shim when we hoist Continue
+void CordbProcess::FinishEventDispatch()
+{
+ LIMITED_METHOD_CONTRACT;
+
+ _ASSERTE(m_dispatchedEvent != DB_IPCE_DEBUGGER_INVALID);
+ m_dispatchedEvent = DB_IPCE_DEBUGGER_INVALID;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Are we in the middle of dispatching an event?
+//
+// Notes:
+// This is used by code::CordbProcess::ContinueInternal. Continue logic takes
+// a shortcut if the continue is called on the dispatch thread.
+// It doesn't matter which event is being dispatch; only that we're on the dispatch thread.
+// @dbgtodo shim: eventually this goes into the shim when we hoist Continue
+bool CordbProcess::AreDispatchingEvent()
+{
+ LIMITED_METHOD_CONTRACT;
+
+ return m_dispatchedEvent != DB_IPCE_DEBUGGER_INVALID;
+}
+
+
+
+
+
+// Terminate the app. We'll still dispatch an ExitProcess callback, so the app
+// must wait for that before calling Cordb::Terminate.
+// If this fails, the client can always call the OS's TerminateProcess command
+// to rudely kill the debuggee.
+HRESULT CordbProcess::Terminate(unsigned int exitCode)
+{
+ PUBLIC_API_ENTRY(this);
+
+ LOG((LF_CORDB, LL_INFO1000, "CP::Terminate: with exitcode %u\n", exitCode));
+ FAIL_IF_NEUTERED(this);
+
+
+ // @dbgtodo shutdown: eventually, all of Terminate() will be in the Shim.
+ // Free all the remaining events. Since this will call into the shim, do this outside of any locks.
+ // (ATT_ takes locks).
+ DeleteQueuedEvents();
+
+ ATT_REQUIRE_SYNCED_OR_NONINIT_MAY_FAIL(this);
+
+ // When we terminate the process, it's handle will become signaled and
+ // Win32 Event Thread will leap into action and call CordbWin32EventThread::ExitProcess
+ // Unfortunately, that may destroy this object if the ExitProcess callback
+ // decides to call Release() on the process.
+
+
+ // Indicate that the process is exiting so that (among other things) we don't try and
+ // send messages to the left side while it's being deleted.
+ Lock();
+
+ // In case we're continuing from the loader bp, we don't want to try and kick off an attach. :)
+ m_fDoDelayedManagedAttached = false;
+ m_exiting = true;
+
+
+
+ // We'd like to just take a lock around everything here, but that may deadlock us
+ // since W32ET will wait on the lock, and Continue may wait on W32ET.
+ // So we just do an extra AddRef/Release to make sure we're still around.
+ // @todo - could we move this smartptr up so that it's well-nested w/ the lock?
+ RSSmartPtr<CordbProcess> pRef(this);
+
+ Unlock();
+
+
+ // At any point after this call, the w32 ET may run the ExitProcess code which will race w/ the continue call.
+ // This call only posts a request that the process terminate and does not guarantee the process actually
+ // terminates. In particular, the process can not exit until any outstanding IO requests are done (on cancelled).
+ // It also can not exit if we have an outstanding not-continued native-debug event.
+ // Fortunately, the interesting work in terminate is done in ExitProcessWorkItem::Do, which can take the Stop-Go lock.
+ // Since we're currently holding the stop-go lock, that means we at least get some serialization.
+ //
+ // Note that on Windows, the process isn't really terminated until we receive the EXIT_PROCESS_DEBUG_EVENT.
+ // Before then, we can still still access the debuggee's address space. On the other, for Mac debugging,
+ // the process can die any time after this call, and so we can no longer call into the DAC.
+ GetShim()->GetNativePipeline()->TerminateProcess(exitCode);
+
+ // We just call Continue() so that the debugger doesn't have to. (It's arguably odd
+ // to call Continue() after Terminate).
+ // We're stopped & Synced.
+ // For interop-debugging this is very important because the Terminate may not really kill the process
+ // until after we continue from the current native debug event.
+ ContinueInternal(FALSE);
+
+ // Implicit release on pRef here (since it's going out of scope)...
+ // After this release, this object may be destroyed. So don't use any member functions
+ // (including Locks) after here.
+
+
+ return S_OK;
+}
+
+// This can be called at any time, even if we're in an unrecoverable error state.
+HRESULT CordbProcess::GetID(DWORD *pdwProcessId)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ OK_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pdwProcessId, DWORD *);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // This shouldn't be used in V3 paths. Normally, we can enforce that by checking against
+ // m_pShim. However, this API can be called after being neutered, in which case m_pShim is cleared.
+ // So check against 0 instead.
+ if (m_id == 0)
+ {
+ *pdwProcessId = 0;
+ ThrowHR(E_NOTIMPL);
+ }
+ *pdwProcessId = GetPid();
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+// Helper to get PID internally. We know we'll always succeed.
+// This is more convient for internal callers since they can just use it as an expression
+// without having to check HRESULTS.
+DWORD CordbProcess::GetPid()
+{
+ // This shouldn't be used in V3 paths, in which case it's set to 0. Only the shim should be
+ // calling this. Assert to catch anybody else.
+ _ASSERTE(m_id != 0);
+
+ return (DWORD) m_id;
+}
+
+
+HRESULT CordbProcess::GetHandle(HANDLE *phProcessHandle)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this); // Once we neuter the process, we close our OS handle to it.
+ VALIDATE_POINTER_TO_OBJECT(phProcessHandle, HANDLE *);
+
+ if (m_pShim == NULL)
+ {
+ _ASSERTE(!"CordbProcess::GetHandle() should be not be called on the new architecture");
+ *phProcessHandle = NULL;
+ return E_NOTIMPL;
+ }
+ else
+ {
+ *phProcessHandle = m_handle;
+ return S_OK;
+ }
+}
+
+HRESULT CordbProcess::IsRunning(BOOL *pbRunning)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pbRunning, BOOL*);
+
+ *pbRunning = !GetSynchronized();
+
+ return S_OK;
+}
+
+HRESULT CordbProcess::EnableSynchronization(BOOL bEnableSynchronization)
+{
+ /* !!! */
+ PUBLIC_API_ENTRY(this);
+ return E_NOTIMPL;
+}
+
+HRESULT CordbProcess::Stop(DWORD dwTimeout)
+{
+ PUBLIC_API_ENTRY(this);
+ CORDBRequireProcessStateOK(this);
+
+ HRESULT hr = StopInternal(dwTimeout, VMPTR_AppDomain::NullPtr());
+
+ return ErrWrapper(hr);
+}
+
+HRESULT CordbProcess::StopInternal(DWORD dwTimeout, VMPTR_AppDomain pAppDomainToken)
+{
+ LOG((LF_CORDB, LL_INFO1000, "CP::S: stopping process 0x%x(%d) with timeout %d\n", m_id, m_id, dwTimeout));
+
+ INTERNAL_API_ENTRY(this);
+
+ // Stop + Continue are executed under the Stop-Go lock. This makes them atomic.
+ // We'll toggle the process-lock (b/c we communicate w/ the W32et, so just the process-lock is
+ // not sufficient to make this atomic).
+ // It's ok to take this lock before checking if the CordbProcess has been neutered because
+ // the lock is destroyed in the dtor after neutering.
+ RSLockHolder ch(&m_StopGoLock);
+
+ // Check if this CordbProcess has been neutered under the SG lock.
+ // Otherwise it's possible to race with Detach() and Terminate().
+ FAIL_IF_NEUTERED(this);
+ CORDBFailIfOnWin32EventThread(this);
+
+ if (m_pShim == NULL) // Stop/Go is moved off to the shim
+ {
+ return E_NOTIMPL;
+ }
+
+
+ DebuggerIPCEvent* event;
+ HRESULT hr = S_OK;
+
+ STRESS_LOG2(LF_CORDB, LL_INFO1000, "CP::SI, timeout=%d, this=%p\n", dwTimeout, this);
+
+ // Stop() is a syncronous (blocking) operation. Furthermore, we have no way to cancel the async-break request.
+ // Thus if we returned early on a timeout, then we'll be in a random state b/c the LS may get stopped at any
+ // later spot.
+ // One solution just require the param is INFINITE until we fix this and E_INVALIDARG if it's not.
+ // But that could be a breaking change (what if a debugger passes in a really large value that's effectively
+ // INFINITE).
+ // So we'll just ignore it and always treat it as infinite.
+ dwTimeout = INFINITE;
+
+ // Do the checks on the process state under the SG lock. This ensures that another thread cannot come in
+ // after we do the checks and take the lock before we do. For example, Detach() can race with Stop() such
+ // that:
+ // 1. Thread A calls CordbProcess::Detach() and takes the stop-go lock
+ // 2. Thread B calls CordbProcess::Stop(), passes all the checks, and then blocks on the stop-go lock
+ // 3. Thread A finishes the detach, invalides the process state, cleans all the resources, and then
+ // releases the stop-go lock
+ // 4. Thread B gets the lock, but everything has changed
+ CORDBRequireProcessStateOK(this);
+
+ Lock();
+
+ ASSERT_SINGLE_THREAD_ONLY(HoldsLock(&m_StopGoLock));
+
+ // Don't need to stop if the process hasn't even executed any managed code yet.
+ if (!m_initialized)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::S: process isn't initialized yet.\n"));
+
+ // Mark the process as synchronized so no events will be dispatched until the thing is continued.
+ SetSynchronized(true);
+
+ // Remember uninitialized stop...
+ m_uninitializedStop = true;
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ // If we're Win32 attached, then suspend all the unmanaged threads in the process.
+ // We may or may not be stopped at a native debug event.
+ if (IsInteropDebugging())
+ {
+ SuspendUnmanagedThreads();
+ }
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ // Get the RC Event Thread to stop listening to the process.
+ m_cordb->ProcessStateChanged();
+
+ hr = S_OK;
+ goto Exit;
+ }
+
+ // Don't need to stop if the process is already synchronized.
+ // @todo - Issue 129917. It's possible that we'll get a call to Stop when the LS is already stopped.
+ // Sending an AsyncBreak would deadlock here (b/c the LS will ignore the frivilous request,
+ // and thus never send a SyncComplete, and thus our Waiting on the SyncComplete will deadlock).
+ // We avoid this case by checking m_syncCompleteReceived (which should roughly correspond to
+ // the LS's m_stopped variable).
+ // One window this can happen is after a Continue() pings the RCET but before the RCET actually sweeps + flushes.
+
+ if (GetSynchronized() || GetSyncCompleteRecv())
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::S: process was already synchronized. m_syncCompleteReceived=%d\n", GetSyncCompleteRecv()));
+
+ if (GetSyncCompleteRecv())
+ {
+ // We must be in that window alluded to above (while the RCET is sweeping). Re-ping the RCET.
+ SetSynchronized(true);
+ m_cordb->ProcessStateChanged();
+ }
+ hr = S_OK;
+ goto Exit;
+ }
+
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::S: process not sync'd, requesting stop.\n");
+
+ m_stopRequested = true;
+
+ // We don't want to dispatch any Win32 debug events while we're trying to stop.
+ // Setting m_specialDeferment=true means that any debug event we get will be queued and not dispatched.
+ // We do this to avoid a nested call to Continue.
+ // These defered events will get dispatched when somebody calls continue (and since they're calling
+ // stop now, they must call continue eventually).
+ // Note that if we got a Win32 debug event between when we took the Stop-Go lock above and now,
+ // that even may have been dispatched. We're ok because SSFW32Stop will hijack that event and continue it,
+ // and then all future events will be queued.
+ m_specialDeferment = true;
+ Unlock();
+
+ BOOL asyncBreakSent;
+
+ // We need to ensure that the helper thread is alive.
+ hr = this->StartSyncFromWin32Stop(&asyncBreakSent);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+
+ if (asyncBreakSent)
+ {
+ hr = S_OK;
+ Lock();
+
+ m_stopRequested = false;
+
+ goto Exit;
+ }
+
+ // Send the async break event to the RC.
+ event = (DebuggerIPCEvent*) _alloca(CorDBIPC_BUFFER_SIZE);
+ InitIPCEvent(event, DB_IPCE_ASYNC_BREAK, false, pAppDomainToken);
+
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CP::S: sending async stop to appd 0x%x.\n", VmPtrToCookie(pAppDomainToken));
+
+ hr = m_cordb->SendIPCEvent(this, event, CorDBIPC_BUFFER_SIZE);
+ hr = WORST_HR(hr, event->hr);
+ if (FAILED(hr))
+ {
+ // We don't hold the lock so just return immediately. Don't adjust stop-count.
+ _ASSERTE(!ThreadHoldsProcessLock());
+ return hr;
+ }
+
+ LOG((LF_CORDB, LL_INFO1000, "CP::S: sent async stop to appd 0x%x.\n", VmPtrToCookie(pAppDomainToken)));
+
+ // Wait for the sync complete message to come in. Note: when the sync complete message arrives to the RCEventThread,
+ // it will mark the process as synchronized and _not_ dispatch any events. Instead, it will set m_stopWaitEvent
+ // which will let this function return. If the user wants to process any queued events, they will need to call
+ // Continue.
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::S: waiting for event.\n");
+
+ DWORD ret;
+ ret = SafeWaitForSingleObject(this, m_stopWaitEvent, dwTimeout);
+
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CP::S: got event, %d.\n", ret);
+
+ if (m_terminated)
+ {
+ return CORDBG_E_PROCESS_TERMINATED;
+ }
+
+ if (ret == WAIT_OBJECT_0)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::S: process stopped.\n"));
+
+ m_stopRequested = false;
+ m_cordb->ProcessStateChanged();
+
+ hr = S_OK;
+ Lock();
+ goto Exit;
+ }
+ else if (ret == WAIT_TIMEOUT)
+ {
+ hr = ErrWrapper(CORDBG_E_TIMEOUT);
+ }
+ else
+ hr = HRESULT_FROM_GetLastError();
+
+ // We came out of the wait, but we weren't signaled because a sync complete event came in. Re-check the process and
+ // remove the stop requested flag.
+ Lock();
+ m_stopRequested = false;
+
+ if (GetSynchronized())
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::S: process stopped.\n"));
+
+ m_cordb->ProcessStateChanged();
+
+ hr = S_OK;
+ }
+
+Exit:
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ // Stop queuing any Win32 Debug events. We should be synchronized now.
+ m_specialDeferment = false;
+
+ if (SUCCEEDED(hr))
+ {
+ IncStopCount();
+ }
+
+ STRESS_LOG2(LF_CORDB, LL_INFO1000, "CP::S: returning from Stop, hr=0x%08x, m_stopCount=%d.\n", hr, GetStopCount());
+
+ Unlock();
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+// Clear all RS state on all CordbThread objects.
+//
+// Notes:
+// This clears all the thread-related state that the RS may have cached,
+// such as locals, frames, etc.
+// This would be called if the debugger is resuming execution.
+void CordbProcess::MarkAllThreadsDirty()
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ CordbThread * pThread;
+ HASHFIND find;
+
+ // We don't need to prepopulate here (to collect LS state) because we're just updating RS state.
+ for (pThread = m_userThreads.FindFirst(&find);
+ pThread != NULL;
+ pThread = m_userThreads.FindNext(&find))
+ {
+ _ASSERTE(pThread != NULL);
+ pThread->MarkStackFramesDirty();
+ }
+
+ ClearPatchTable();
+}
+
+HRESULT CordbProcess::Continue(BOOL fIsOutOfBand)
+{
+ PUBLIC_API_ENTRY(this);
+
+ if (m_pShim == NULL) // This API is moved off to the shim
+ {
+ // bias towards failing with CORDBG_E_NUETERED.
+ FAIL_IF_NEUTERED(this);
+ return E_NOTIMPL;
+ }
+
+ HRESULT hr;
+
+ if (fIsOutOfBand)
+ {
+#ifdef FEATURE_INTEROP_DEBUGGING
+ hr = ContinueOOB();
+#else
+ hr = E_INVALIDARG;
+#endif // FEATURE_INTEROP_DEBUGGING
+ }
+ else
+ {
+ hr = ContinueInternal(fIsOutOfBand);
+ }
+
+ return hr;
+}
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+//---------------------------------------------------------------------------------------
+//
+// ContinueOOB
+//
+// Continue the Win32 event as an out-of-band event.
+//
+// Return Value:
+// S_OK on successful continue. Else error.
+//
+//---------------------------------------------------------------------------------------
+HRESULT CordbProcess::ContinueOOB()
+{
+ INTERNAL_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ HRESULT hr = S_OK;
+
+ // If we're continuing from an out-of-band unmanaged event, then just go
+ // ahead and get the Win32 event thread to continue the process. No other
+ // work needs to be done (i.e., don't need to send a managed continue message
+ // or dispatch any events) because any processing done due to the out-of-band
+ // message can't alter the synchronized state of the process.
+
+ Lock();
+ _ASSERTE(m_outOfBandEventQueue != NULL);
+
+ // Are we calling this from the unmanaged callback?
+ if (m_dispatchingOOBEvent)
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::CI: continue while dispatching unmanaged out-of-band event.\n");
+ // We don't know what thread we're on here.
+
+ // Tell the Win32 event thread to continue when it returns from handling its unmanaged callback.
+ m_dispatchingOOBEvent = false;
+
+ Unlock();
+ }
+ else
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::CI: continue outside of dispatching.\n");
+
+ // If we're not dispatching this, then they shouldn't be on the win32 event thread.
+ _ASSERTE(!this->IsWin32EventThread());
+
+ Unlock();
+
+ // Send an event to the Win32 event thread to do the continue. This is an out-of-band continue.
+ hr = this->m_pShim->GetWin32EventThread()->SendUnmanagedContinue(this, cOobUMContinue);
+ }
+
+ return hr;
+
+
+}
+#endif // FEATURE_INTEROP_DEBUGGING
+
+//---------------------------------------------------------------------------------------
+//
+// ContinueInternal
+//
+// Continue the Win32 event.
+//
+// Return Value:
+// S_OK on success. Else error.
+//
+//---------------------------------------------------------------------------------------
+HRESULT CordbProcess::ContinueInternal(BOOL fIsOutOfBand)
+{
+ INTERNAL_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ // Continue has an ATT similar to ATT_REQUIRE_STOPPED_MAY_FAIL, but w/ some subtle differences.
+ // - if we're stopped at a native DE, but not synchronized, we don't want to sync.
+ // - We may get Debug events (especially native ones) at weird times, and thus we have to continue
+ // at weird times.
+
+ // External APIs should not have the process lock.
+ _ASSERTE(!ThreadHoldsProcessLock());
+ _ASSERTE(m_pShim != NULL);
+
+ // OutOfBand should use ContinueOOB
+ _ASSERTE(!fIsOutOfBand);
+
+ // Since Continue is process-wide, just use a null appdomain pointer.
+ VMPTR_AppDomain pAppDomainToken = VMPTR_AppDomain::NullPtr();
+
+ HRESULT hr = S_OK;
+
+ if (m_unrecoverableError)
+ {
+ return CORDBHRFromProcessState(this, NULL);
+ }
+
+
+ // We can't call ContinueInternal for an inband event on the win32 event thread.
+ // This is an issue in the CLR (or an API design decision, depending on your perspective).
+ // Continue() may send an IPC event and we can't do that on the win32 event thread.
+
+ CORDBFailIfOnWin32EventThread(this);
+
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CP::CI: continuing IB, this=0x%X\n", this);
+
+ // Stop + Continue are executed under the Stop-Go lock. This makes them atomic.
+ // We'll toggle the process-lock (b/c we communicate w/ the W32et, so that's not sufficient).
+ RSLockHolder rsLockHolder(&m_StopGoLock);
+
+ // Check for other failures (do these after we have the SG lock).
+ if (m_terminated)
+ {
+ return CORDBG_E_PROCESS_TERMINATED;
+ }
+ if (m_detached)
+ {
+ return CORDBG_E_PROCESS_DETACHED;
+ }
+
+ Lock();
+
+ ASSERT_SINGLE_THREAD_ONLY(HoldsLock(&m_StopGoLock));
+ _ASSERTE(fIsOutOfBand == FALSE);
+
+ // If we've got multiple Stop calls, we need a Continue for each one. So, if the stop count > 1, just go ahead and
+ // return without doing anything. Note: this is only for in-band or managed events. OOB events are still handled as
+ // normal above.
+ _ASSERTE(GetStopCount() > 0);
+
+ if (GetStopCount() == 0)
+ {
+ Unlock();
+ _ASSERTE(!"Superflous Continue. ICorDebugProcess.Continue() called too many times");
+ return CORDBG_E_SUPERFLOUS_CONTINUE;
+ }
+
+ DecStopCount();
+
+ // We give managed events priority over unmanaged events. That way, the entire queued managed state can drain before
+ // we let any other unmanaged events through.
+
+ // Every stop or event must be matched by a corresponding Continue. m_stopCount counts outstanding stopping events
+ // along with calls to Stop. If the count is high at this point, we simply return. This ensures that even if someone
+ // calls Stop just as they're receiving an event that they can call Continue for that Stop and for that event
+ // without problems.
+ if (GetStopCount() > 0)
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CP::CI: m_stopCount=%d, Continue just returning S_OK...\n", GetStopCount());
+
+ Unlock();
+ return S_OK;
+ }
+
+ // We're no longer stopped, so reset the m_stopWaitEvent.
+ ResetEvent(m_stopWaitEvent);
+
+ // If we're continuing from an uninitialized stop, then we don't need to do much at all. No event need be sent to
+ // the Left Side (duh, it isn't even there yet.) We just need to get the RC Event Thread to start listening to the
+ // process again, and resume any unmanaged threads if necessary.
+ if (m_uninitializedStop)
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::CI: continuing from uninitialized stop.\n");
+
+ // No longer synchronized (it was a partial sync in the first place.)
+ SetSynchronized(false);
+ MarkAllThreadsDirty();
+
+ // No longer in an uninitialized stop.
+ m_uninitializedStop = false;
+
+ // Notify the RC Event Thread.
+ m_cordb->ProcessStateChanged();
+
+ Unlock();
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ // We may or may not have a native debug event queued here.
+ // If Cordbg called Stop() from a native debug event (to get the process Synchronized), then
+ // we'll have a native debug event, and we need to continue it.
+ // If Cordbg called Stop() to do an AsyncBreak, then there's no native-debug event.
+
+ // If we're Win32 attached, resume all the unmanaged threads.
+ if (IsInteropDebugging())
+ {
+ if(m_lastDispatchedIBEvent != NULL)
+ {
+ m_lastDispatchedIBEvent->SetState(CUES_UserContinued);
+ }
+
+ // Send to the Win32 event thread to do the unmanaged continue for us.
+ // If we're at a debug event, this will continue it.
+ // Else it will degenerate into ResumeUnmanagedThreads();
+ this->m_pShim->GetWin32EventThread()->SendUnmanagedContinue(this, cRealUMContinue);
+ }
+#endif // FEATURE_INTEROP_DEBUGGING
+
+
+ return S_OK;
+ }
+
+ // If there are more managed events, get them dispatched now.
+ if (!m_pShim->GetManagedEventQueue()->IsEmpty() && GetSynchronized())
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::CI: managed event queued.\n");
+
+ // Mark that we're not synchronized anymore.
+ SetSynchronized(false);
+
+ // If the callback queue is not empty, then the LS is not actually continuing, and so our cached
+ // state is still valid.
+
+ // If we're in the middle of dispatching a managed event, then simply return. This indicates to HandleRCEvent
+ // that the user called Continue and HandleRCEvent will dispatch the next queued event. But if Continue was
+ // called outside the managed callback, all we have to do is tell the RC event thread that something about the
+ // process has changed and it will dispatch the next managed event.
+ if (!AreDispatchingEvent())
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::CI: continuing while not dispatching managed event.\n");
+
+ m_cordb->ProcessStateChanged();
+ }
+
+ Unlock();
+ return S_OK;
+ }
+
+ // Neuter if we have an outstanding object.
+ // Only do this if we're really continuining the debuggee. So don't do this if our stop-count is high b/c we
+ // shouldn't neuter until we're done w/ the current event. And don't do this until we drain the current callback queue.
+ // Note that we can't hold the process lock while we do this b/c Neutering may send IPC events.
+ // However, we're still under the StopGo lock b/c that may help us serialize things.
+
+ // Sweep neuter list. This will catch anything that's marked as 'safe to neuter'. This includes
+ // all objects added to the 'neuter-on-Continue'.
+ // Only do this if we're synced- we don't want to do this if we're continuing from a Native Debug event.
+ if (GetSynchronized())
+ {
+ // Need process-lock to operate on hashtable, but can't yet Neuter under process-lock,
+ // so we have to copy the contents to an auxilary list which we can then traverse outside the lock.
+ RSPtrArray<CordbAppDomain> listAppDomains;
+ HRESULT hrCopy = S_OK;
+ EX_TRY // @dbgtodo cleanup: push this up
+ {
+ m_appDomains.CopyToArray(&listAppDomains);
+ }
+ EX_CATCH_HRESULT(hrCopy);
+ SetUnrecoverableIfFailed(GetProcess(), hrCopy);
+
+ m_ContinueNeuterList.NeuterAndClear(this);
+
+ // @dbgtodo left-side resources: eventually (once
+ // NeuterLeftSideResources is process-lock safe), do this all under the
+ // lock. Can't hold process lock b/c neutering left-side resources
+ // may send events.
+ Unlock();
+
+ // This may send IPC events.
+ // This will make normal neutering a nop.
+ // This will toggle the process lock.
+ m_LeftSideResourceCleanupList.SweepNeuterLeftSideResources(this);
+
+
+ // Many objects (especially CordbValue, FuncEval) don't have clear lifetime semantics and
+ // so they must be put into an exit-neuter list (Process/AppDomain) for worst-case scenarios.
+ // These objects are likely released early, and so we sweep them aggressively on each Continue (kind of like a mini-GC).
+ //
+ // One drawback is that there may be a lot of useless sweeping if the debugger creates a lot of
+ // objects that it holds onto. Consider instead of sweeping, have the object explicitly post itself
+ // to a list that's guaranteed to be cleared. This would let us avoid sweeping not-yet-ready objects.
+ // This will toggle the process lock
+ m_ExitNeuterList.SweepAllNeuterAtWillObjects(this);
+
+
+ for(unsigned int idx = 0; idx < listAppDomains.Length(); idx++)
+ {
+ CordbAppDomain * pAppDomain = listAppDomains[idx];
+
+ // CordbHandleValue is in the appdomain exit list, and that needs
+ // to send an IPC event to cleanup and release the handle from
+ // the GCs handle table.
+ // This will toggle the process lock.
+ pAppDomain->GetSweepableExitNeuterList()->SweepNeuterLeftSideResources(this);
+ }
+ listAppDomains.Clear();
+
+ Lock();
+ }
+
+
+ // At this point, if the managed event queue is empty, m_synchronized may still be true if we had previously
+ // synchronized.
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ // Next, check for unmanaged events that may be queued. If there are some queued, then we need to get the Win32
+ // event thread to go ahead and dispatch the next one. If there aren't any queued, then we can just fall through and
+ // send the continue message to the left side. This works even if we have an outstanding ownership request, because
+ // until that answer is received, its just like the event hasn't happened yet.
+ //
+ // If we're terminated, then we've already continued from the last win32 event and so don't continue.
+ // @todo - or we could ensure the PS_SOME_THREADS_SUSPENDED | PS_HIJACKS_IN_PLACE are removed.
+ // Either way, we're just protecting against exit-process at strange times.
+ bool fDoWin32Continue = !m_terminated && ((m_state & (PS_WIN32_STOPPED | PS_SOME_THREADS_SUSPENDED | PS_HIJACKS_IN_PLACE)) != 0);
+
+ // We need to store this before marking the event user continued below
+ BOOL fHasUserUncontinuedEvents = HasUserUncontinuedNativeEvents();
+
+ if(m_lastDispatchedIBEvent != NULL)
+ {
+ m_lastDispatchedIBEvent->SetState(CUES_UserContinued);
+ }
+
+ if (fHasUserUncontinuedEvents)
+ {
+ // ExitProcess is the last debug event we'll get. The Process Handle is not signaled until
+ // after we continue from ExitProcess. m_terminated is only set once we know the process is signaled.
+ // (This isn't 100% true for the detach case, but since you can't do interop detach, we don't care)
+ //_ASSERTE(!m_terminated);
+
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CP::CI: there are queued uncontinued events. m_dispatchingUnmanagedEvent = %d\n", m_dispatchingUnmanagedEvent);
+
+ // Are we being called while in the unmanaged event callback?
+ if (m_dispatchingUnmanagedEvent)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::CI: continue while dispatching.\n"));
+ // The Win32ET could have made a cross-thread call to Continue while dispatching,
+ // so we don't know if this is the win32 ET.
+
+ // Tell the Win32 thread to continue when it returns from handling its unmanaged callback.
+ m_dispatchingUnmanagedEvent = false;
+
+ // If there are no more unmanaged events, then we fall through and continue the process for real. Otherwise,
+ // we can simply return.
+ if (HasUndispatchedNativeEvents())
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::CI: more unmanaged events need dispatching.\n");
+
+ // Note: if we tried to access the Left Side while stopped but couldn't, then m_oddSync will be true. We
+ // need to reset it to false since we're continuing now.
+ m_oddSync = false;
+
+ Unlock();
+ return S_OK;
+ }
+ else
+ {
+ // Also, if there are no more unmanaged events, then when DispatchUnmanagedInBandEvent sees that
+ // m_dispatchingUnmanagedEvent is false, it will continue the process. So we set doWin32Continue to
+ // false here so that we don't try to double continue the process below.
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::CI: no more unmanaged events to dispatch.\n");
+
+ fDoWin32Continue = false;
+ }
+ }
+ else
+ {
+ // after the DebugEvent callback returned the continue still had no been issued. Then later
+ // on another thread the user called back to continue the event, which gets us to right here
+ LOG((LF_CORDB, LL_INFO1000, "CP::CI: continue outside of dispatching.\n"));
+
+ // This should be the common place to Dispatch an IB event that was hijacked for sync.
+
+ // If we're not dispatching, this better not be the win32 event thread.
+ _ASSERTE(!IsWin32EventThread());
+
+ // If the event at the head of the queue is really the last event, or if the event at the head of the queue
+ // hasn't been dispatched yet, then we simply fall through and continue the process for real. However, if
+ // its not the last event, we send to the Win32 event thread and get it to continue, then we return.
+ if (HasUndispatchedNativeEvents())
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::CI: more unmanaged events need dispatching.\n");
+
+ // Note: if we tried to access the Left Side while stopped but couldn't, then m_oddSync will be true. We
+ // need to reset it to false since we're continuing now.
+ m_oddSync = false;
+
+ Unlock();
+
+ hr = this->m_pShim->GetWin32EventThread()->SendUnmanagedContinue(this, cRealUMContinue);
+
+ return hr;
+ }
+ }
+ }
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ // Both the managed and unmanaged event queues are now empty. Go
+ // ahead and continue the process for real.
+ LOG((LF_CORDB, LL_INFO1000, "CP::CI: headed for true continue.\n"));
+
+ // We need to check these while under the lock, but action must be
+ // taked outside of the lock.
+ bool fIsExiting = m_exiting;
+ bool fWasSynchronized = GetSynchronized();
+
+ // Mark that we're no longer synchronized.
+ if (fWasSynchronized)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::CI: process was synchronized.\n"));
+
+ SetSynchronized(false);
+ SetSyncCompleteRecv(false);
+
+ // we're no longer in a callback, so set flags to indicate that we've finished.
+ GetShim()->NotifyOnContinue();
+
+ // Flush will update state, including continue counter and marking
+ // frames dirty.
+ this->FlushProcessRunning();
+
+
+ // Tell the RC event thread that something about this process has changed.
+ m_cordb->ProcessStateChanged();
+ }
+
+ m_continueCounter++;
+
+ // If m_oddSync is set, then out last synchronization was due to us syncing the process because we were Win32
+ // stopped. Therefore, while we do need to do most of the work to continue the process below, we don't actually have
+ // to send the managed continue event. Setting wasSynchronized to false here helps us do that.
+ if (m_oddSync)
+ {
+ fWasSynchronized = false;
+ m_oddSync = false;
+ }
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ // We must ensure that all managed threads are suspended here. We're about to let all managed threads run free via
+ // the managed continue message to the Left Side. If we don't suspend the managed threads, then they may start
+ // slipping forward even if we receive an in-band unmanaged event. We have to hijack in-band unmanaged events while
+ // getting the managed continue message over to the Left Side to keep the process running free. Otherwise, the
+ // SendIPCEvent will hang below. But in doing so, we could let managed threads slip to far. So we ensure they're all
+ // suspended here.
+ //
+ // Note: we only do this suspension if the helper thread hasn't died yet. If the helper thread has died, then we
+ // know that we're loosing the Runtime. No more managed code is going to run, so we don't bother trying to prevent
+ // managed threads from slipping via the call below.
+ //
+ // Note: we just remember here, under the lock, so we can unlock then wait for the syncing thread to free the
+ // debugger lock. Otherwise, we may block here and prevent someone from continuing from an OOB event, which also
+ // prevents the syncing thread from releasing the debugger lock like we want it to.
+ bool fNeedSuspend = fWasSynchronized && fDoWin32Continue && !m_helperThreadDead;
+
+ // If we receive a new in-band event once we unlock, we need to know to hijack it and keep going while we're still
+ // trying to send the managed continue event to the process.
+ if (fWasSynchronized && fDoWin32Continue && !fIsExiting)
+ {
+ m_specialDeferment = true;
+ }
+
+ if (fNeedSuspend)
+ {
+ // @todo - what does this actually accomplish? We already suspended everything when we first synced.
+
+ // Any thread that may hold a lock blocking the helper is
+ // inside of a can't stop region, and thus we won't suspend it.
+ SuspendUnmanagedThreads();
+ }
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ Unlock();
+
+ // Although we've released the Process-lock, we still have the Stop-Go lock.
+ _ASSERTE(m_StopGoLock.HasLock());
+
+ // If we're processing an ExitProcess managed event, then we don't want to really continue the process, so just fall
+ // thru. Note: we did let the unmanaged continue go through above for this case.
+ if (fIsExiting)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::CI: continuing from exit case.\n"));
+ }
+ else if (fWasSynchronized)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::CI: Sending continue to AppD:0x%x.\n", VmPtrToCookie(pAppDomainToken)));
+#ifdef FEATURE_INTEROP_DEBUGGING
+ STRESS_LOG2(LF_CORDB, LL_INFO1000, "Continue flags:special=%d, dowin32=%d\n", m_specialDeferment, fDoWin32Continue);
+#endif
+ // Send to the RC to continue the process.
+ DebuggerIPCEvent * pEvent = (DebuggerIPCEvent *) _alloca(CorDBIPC_BUFFER_SIZE);
+
+ InitIPCEvent(pEvent, DB_IPCE_CONTINUE, false, pAppDomainToken);
+
+ hr = m_cordb->SendIPCEvent(this, pEvent, CorDBIPC_BUFFER_SIZE);
+
+ // It is possible that we continue and then the process immediately exits before the helper
+ // thread is finished continuing and can report success back to us. That's arguably a success
+ // case sinceu the process did indeed continue, but since we didn't get the acknowledgement,
+ // we can't be sure it's success. So we call it S_FALSE instead of S_OK.
+ // @todo - how do we handle other failure here?
+ if (hr == CORDBG_E_PROCESS_TERMINATED)
+ {
+ hr = S_FALSE;
+ }
+ _ASSERTE(SUCCEEDED(pEvent->hr));
+
+ LOG((LF_CORDB, LL_INFO1000, "CP::CI: Continue sent to AppD:0x%x.\n", VmPtrToCookie(pAppDomainToken)));
+ }
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ // If we're win32 attached to the Left side, then we need to win32 continue the process too (unless, of course, it's
+ // already been done above.)
+ //
+ // Note: we do this here because we want to get the Left Side to receive and ack our continue message above if we
+ // were sync'd. If we were sync'd, then by definition the process (and the helper thread) is running anyway, so all
+ // this continue is going to do is to let the threads that have been suspended go.
+ if (fDoWin32Continue)
+ {
+#ifdef _DEBUG
+ {
+ // A little pause here extends the special deferment region and thus causes native-debug
+ // events to get hijacked. This test some wildly different corner case paths.
+ // See VSWhidbey bugs 131905, 168971
+ static DWORD dwRace = -1;
+ if (dwRace == -1)
+ dwRace = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgRace);
+
+ if ((dwRace & 1) == 1)
+ {
+ Sleep(30);
+ }
+ }
+#endif
+
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::CI: sending unmanaged continue.\n");
+
+ // Send to the Win32 event thread to do the unmanaged continue for us.
+ hr = this->m_pShim->GetWin32EventThread()->SendUnmanagedContinue(this, cRealUMContinue);
+ }
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::CI: continue done, returning.\n");
+
+ return hr;
+}
+
+HRESULT CordbProcess::HasQueuedCallbacks(ICorDebugThread *pThread,
+ BOOL *pbQueued)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pThread,ICorDebugThread *);
+ VALIDATE_POINTER_TO_OBJECT(pbQueued,BOOL *);
+
+ // Shim owns the event queue
+ if (m_pShim != NULL)
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE0_NO_LOCK(this); // Calling to shim, leaving RS.
+ *pbQueued = m_pShim->GetManagedEventQueue()->HasQueuedCallbacks(pThread);
+ return S_OK;
+ }
+ return E_NOTIMPL; // Not implemented in V3.
+}
+
+//
+// A small helper function to convert a CordbBreakpoint to an ICorDebugBreakpoint based on its type.
+//
+static ICorDebugBreakpoint *CordbBreakpointToInterface(CordbBreakpoint * pBreakpoint)
+{
+ _ASSERTE(pBreakpoint != NULL);
+
+ //
+ // I really dislike this. We've got three subclasses of CordbBreakpoint, but we store them all into the same hash
+ // (m_breakpoints), so when we get one out of the hash, we don't really know what type it is. But we need to know
+ // what type it is because we need to cast it to the proper interface before passing it out. I.e., when we create a
+ // function breakpoint, we return the breakpoint casted to an ICorDebugFunctionBreakpoint. But if we grab that same
+ // breakpoint out of the hash as a CordbBreakpoint and pass it out as an ICorDebugBreakpoint, then that's a
+ // different pointer, and its wrong. So I've added the type to the breakpoint so we can cast properly here. I'd love
+ // to do this a different way, though...
+ //
+ // -- Mon Dec 14 21:06:46 1998
+ //
+ switch(pBreakpoint->GetBPType())
+ {
+ case CBT_FUNCTION:
+ return static_cast<ICorDebugFunctionBreakpoint *>(static_cast<CordbFunctionBreakpoint *> (pBreakpoint));
+ break;
+
+ case CBT_MODULE:
+ return static_cast<ICorDebugModuleBreakpoint*>(static_cast<CordbModuleBreakpoint *> (pBreakpoint));
+ break;
+
+ case CBT_VALUE:
+ return static_cast<ICorDebugValueBreakpoint *>(static_cast<CordbValueBreakpoint *> (pBreakpoint));
+ break;
+
+ default:
+ _ASSERTE(!"Invalid breakpoint type!");
+ }
+
+ return NULL;
+}
+
+
+// Callback data for code:CordbProcess::GetAssembliesInLoadOrder
+class ShimAssemblyCallbackData
+{
+public:
+ // Ctor to intialize callback data
+ //
+ // Arguments:
+ // pAppDomain - appdomain that the assemblies are in.
+ // pAssemblies - preallocated array of smart pointers to hold assemblies
+ // countAssemblies - size of pAssemblies in elements.
+ ShimAssemblyCallbackData(
+ CordbAppDomain * pAppDomain,
+ RSExtSmartPtr<ICorDebugAssembly>* pAssemblies,
+ ULONG countAssemblies)
+ {
+ _ASSERTE(pAppDomain != NULL);
+ _ASSERTE(pAssemblies != NULL);
+
+ m_pProcess = pAppDomain->GetProcess();
+ m_pAppDomain = pAppDomain;
+ m_pAssemblies = pAssemblies;
+ m_countElements = countAssemblies;
+ m_index = 0;
+
+ // Just to be safe, clear them all out
+ for(ULONG i = 0; i < countAssemblies; i++)
+ {
+ pAssemblies[i].Clear();
+ }
+ }
+
+ // Dtor
+ //
+ // Notes:
+ // This can assert end-of-enumeration invariants.
+ ~ShimAssemblyCallbackData()
+ {
+ // Ensure that we went through all assemblies.
+ _ASSERTE(m_index == m_countElements);
+ }
+
+ // Callback invoked from DAC enumeration.
+ //
+ // arguments:
+ // vmDomainAssembly - VMPTR for assembly
+ // pData - a 'this' pointer
+ //
+ static void Callback(VMPTR_DomainAssembly vmDomainAssembly, void * pData)
+ {
+ ShimAssemblyCallbackData * pThis = static_cast<ShimAssemblyCallbackData *> (pData);
+ INTERNAL_DAC_CALLBACK(pThis->m_pProcess);
+
+ CordbAssembly * pAssembly = pThis->m_pAppDomain->LookupOrCreateAssembly(vmDomainAssembly);
+
+ pThis->SetAndMoveNext(pAssembly);
+ }
+
+ // Set the current index in the table and increment the cursor.
+ //
+ // Arguments:
+ // pAssembly - assembly from DAC enumerator
+ void SetAndMoveNext(CordbAssembly * pAssembly)
+ {
+ _ASSERTE(pAssembly != NULL);
+
+ if (m_index >= m_countElements)
+ {
+ // Enumerating the assemblies in the target should be fixed since
+ // the target is not running.
+ // We should never get here unless the target is unstable.
+ // The caller (the shim) pre-allocated the table of assemblies.
+ m_pProcess->TargetConsistencyCheck(!"Target changed assembly count");
+ return;
+ }
+
+ m_pAssemblies[m_index].Assign(pAssembly);
+ m_index++;
+ }
+
+protected:
+ CordbProcess * m_pProcess;
+ CordbAppDomain * m_pAppDomain;
+ RSExtSmartPtr<ICorDebugAssembly>* m_pAssemblies;
+ ULONG m_countElements;
+ ULONG m_index;
+};
+
+//---------------------------------------------------------------------------------------
+// Shim Helper to enumerate the assemblies in the load-order
+//
+// Arguments:
+// pAppdomain - non-null appdmomain to enumerate assemblies.
+// pAssemblies - caller pre-allocated array to hold assemblies
+// countAssemblies - size of the array.
+//
+// Notes:
+// Caller preallocated array (likely from ICorDebugAssemblyEnum::GetCount),
+// and now this function fills in the assemblies in the order they were
+// loaded.
+//
+// The target should be stable, such that the number of assemblies in the
+// target is stable, and therefore countAssemblies as determined by the
+// shim via ICorDebugAssemblyEnum::GetCount should match the number of
+// assemblies enumerated here.
+//
+// Called by code:ShimProcess::QueueFakeAttachEvents.
+// This provides the assemblies in load-order. In contrast,
+// ICorDebugAppDomain::EnumerateAssemblies is a random order. The shim needs
+// load-order to match Whidbey semantics for dispatching fake load-assembly
+// callbacks on attach. The debugger then uses the order
+// in its module display window.
+//
+void CordbProcess::GetAssembliesInLoadOrder(
+ ICorDebugAppDomain * pAppDomain,
+ RSExtSmartPtr<ICorDebugAssembly>* pAssemblies,
+ ULONG countAssemblies)
+{
+ PUBLIC_API_ENTRY_FOR_SHIM(this);
+ RSLockHolder lockHolder(GetProcessLock());
+
+ _ASSERTE(GetShim() != NULL);
+
+ CordbAppDomain * pAppDomainInternal = static_cast<CordbAppDomain *> (pAppDomain);
+
+ ShimAssemblyCallbackData data(pAppDomainInternal, pAssemblies, countAssemblies);
+
+ // Enumerate through and fill out pAssemblies table.
+ GetDAC()->EnumerateAssembliesInAppDomain(
+ pAppDomainInternal->GetADToken(),
+ ShimAssemblyCallbackData::Callback,
+ &data); // user data
+
+ // pAssemblies array has now been updated.
+}
+
+// Callback data for code:CordbProcess::GetModulesInLoadOrder
+class ShimModuleCallbackData
+{
+public:
+ // Ctor to intialize callback data
+ //
+ // Arguments:
+ // pAssembly - assembly that the Modules are in.
+ // pModules - preallocated array of smart pointers to hold Modules
+ // countModules - size of pModules in elements.
+ ShimModuleCallbackData(
+ CordbAssembly * pAssembly,
+ RSExtSmartPtr<ICorDebugModule>* pModules,
+ ULONG countModules)
+ {
+ _ASSERTE(pAssembly != NULL);
+ _ASSERTE(pModules != NULL);
+
+ m_pProcess = pAssembly->GetAppDomain()->GetProcess();
+ m_pAssembly = pAssembly;
+ m_pModules = pModules;
+ m_countElements = countModules;
+ m_index = 0;
+
+ // Just to be safe, clear them all out
+ for(ULONG i = 0; i < countModules; i++)
+ {
+ pModules[i].Clear();
+ }
+ }
+
+ // Dtor
+ //
+ // Notes:
+ // This can assert end-of-enumeration invariants.
+ ~ShimModuleCallbackData()
+ {
+ // Ensure that we went through all Modules.
+ _ASSERTE(m_index == m_countElements);
+ }
+
+ // Callback invoked from DAC enumeration.
+ //
+ // arguments:
+ // vmDomainFile - VMPTR for Module
+ // pData - a 'this' pointer
+ //
+ static void Callback(VMPTR_DomainFile vmDomainFile, void * pData)
+ {
+ ShimModuleCallbackData * pThis = static_cast<ShimModuleCallbackData *> (pData);
+ INTERNAL_DAC_CALLBACK(pThis->m_pProcess);
+
+ CordbModule * pModule = pThis->m_pAssembly->GetAppDomain()->LookupOrCreateModule(vmDomainFile);
+
+ pThis->SetAndMoveNext(pModule);
+ }
+
+ // Set the current index in the table and increment the cursor.
+ //
+ // Arguments:
+ // pModule - Module from DAC enumerator
+ void SetAndMoveNext(CordbModule * pModule)
+ {
+ _ASSERTE(pModule != NULL);
+
+ if (m_index >= m_countElements)
+ {
+ // Enumerating the Modules in the target should be fixed since
+ // the target is not running.
+ // We should never get here unless the target is unstable.
+ // The caller (the shim) pre-allocated the table of Modules.
+ m_pProcess->TargetConsistencyCheck(!"Target changed Module count");
+ return;
+ }
+
+ m_pModules[m_index].Assign(pModule);
+ m_index++;
+ }
+
+protected:
+ CordbProcess * m_pProcess;
+ CordbAssembly * m_pAssembly;
+ RSExtSmartPtr<ICorDebugModule>* m_pModules;
+ ULONG m_countElements;
+ ULONG m_index;
+};
+
+//---------------------------------------------------------------------------------------
+// Shim Helper to enumerate the Modules in the load-order
+//
+// Arguments:
+// pAppdomain - non-null appdmomain to enumerate Modules.
+// pModules - caller pre-allocated array to hold Modules
+// countModules - size of the array.
+//
+// Notes:
+// Caller preallocated array (likely from ICorDebugModuleEnum::GetCount),
+// and now this function fills in the Modules in the order they were
+// loaded.
+//
+// The target should be stable, such that the number of Modules in the
+// target is stable, and therefore countModules as determined by the
+// shim via ICorDebugModuleEnum::GetCount should match the number of
+// Modules enumerated here.
+//
+// Called by code:ShimProcess::QueueFakeAssemblyAndModuleEvent.
+// This provides the Modules in load-order. In contrast,
+// ICorDebugAssembly::EnumerateModules is a random order. The shim needs
+// load-order to match Whidbey semantics for dispatching fake load-Module
+// callbacks on attach. The most important thing is that the manifest module
+// gets a LodModule callback before any secondary modules. For dynamic
+// modules, this is necessary for operations on the secondary module
+// that rely on manifest metadata (eg. GetSimpleName).
+//
+// @dbgtodo : This is almost identical to GetAssembliesInLoadOrder, and
+// (together wih the CallbackData classes) seems a HUGE amount of code and
+// complexity for such a simple thing. We also have extra code to order
+// AppDomains and Threads. We should try and rip all of this extra complexity
+// out, and replace it with better data structures for storing these items.
+// Eg., if we used std::map, we could have efficient lookups and ordered
+// enumerations. However, we do need to be careful about exposing new invariants
+// through ICorDebug that customers may depend on, which could place a long-term
+// compatibility burden on us. We could have a simple generic data structure
+// (eg. built on std::hash_map and std::list) which provided efficient look-up
+// and both in-order and random enumeration.
+//
+void CordbProcess::GetModulesInLoadOrder(
+ ICorDebugAssembly * pAssembly,
+ RSExtSmartPtr<ICorDebugModule>* pModules,
+ ULONG countModules)
+{
+ PUBLIC_API_ENTRY_FOR_SHIM(this);
+ RSLockHolder lockHolder(GetProcessLock());
+
+ _ASSERTE(GetShim() != NULL);
+
+ CordbAssembly * pAssemblyInternal = static_cast<CordbAssembly *> (pAssembly);
+
+ ShimModuleCallbackData data(pAssemblyInternal, pModules, countModules);
+
+ // Enumerate through and fill out pModules table.
+ GetDAC()->EnumerateModulesInAssembly(
+ pAssemblyInternal->GetDomainAssemblyPtr(),
+ ShimModuleCallbackData::Callback,
+ &data); // user data
+
+ // pModules array has now been updated.
+}
+
+
+//---------------------------------------------------------------------------------------
+// Callback to count the number of enumerations in a process.
+//
+// Arguments:
+// id - the connection id.
+// pName - name of the connection
+// pUserData - an EnumerateConnectionsData
+//
+// Notes:
+// Helper function for code:CordbProcess::QueueFakeConnectionEvents
+//
+// static
+void CordbProcess::CountConnectionsCallback(DWORD id, LPCWSTR pName, void * pUserData)
+{
+#if defined(FEATURE_INCLUDE_ALL_INTERFACES)
+ EnumerateConnectionsData * pCallbackData = reinterpret_cast<EnumerateConnectionsData *>(pUserData);
+ INTERNAL_DAC_CALLBACK(pCallbackData->m_pThis);
+
+ pCallbackData->m_uIndex += 1;
+#endif // FEATURE_INCLUDE_ALL_INTERFACES
+}
+
+//---------------------------------------------------------------------------------------
+// Callback to enumerate all the connections in a process.
+//
+// Arguments:
+// id - the connection id.
+// pName - name of the connection
+// pUserData - an EnumerateConnectionsData
+//
+// Notes:
+// Helper function for code:CordbProcess::QueueFakeConnectionEvents
+//
+// static
+void CordbProcess::EnumerateConnectionsCallback(DWORD id, LPCWSTR pName, void * pUserData)
+{
+#if defined(FEATURE_INCLUDE_ALL_INTERFACES)
+ EnumerateConnectionsData * pCallbackData = reinterpret_cast<EnumerateConnectionsData *>(pUserData);
+ INTERNAL_DAC_CALLBACK(pCallbackData->m_pThis);
+
+ // get the next entry in the array to be filled in
+ EnumerateConnectionsEntry * pEntry = &(pCallbackData->m_pEntryArray[pCallbackData->m_uIndex]);
+
+ // initialize the StringCopyHolder in the entry and copy over the name of the connection
+ new (&(pEntry->m_pName)) StringCopyHolder;
+ pEntry->m_pName.AssignCopy(pName);
+ pEntry->m_dwID = id;
+
+ pCallbackData->m_uIndex += 1;
+#endif // FEATURE_INCLUDE_ALL_INTERFACES
+}
+
+//---------------------------------------------------------------------------------------
+// Callback from Shim to queue fake Connection events on attach.
+//
+// Notes:
+// See code:ShimProcess::QueueFakeAttachEvents
+void CordbProcess::QueueFakeConnectionEvents()
+{
+ PUBLIC_API_ENTRY_FOR_SHIM(this);
+
+#ifdef FEATURE_INCLUDE_ALL_INTERFACES
+ EnumerateConnectionsData callbackData;
+ callbackData.m_pThis = this;
+ callbackData.m_uIndex = 0;
+ callbackData.m_pEntryArray = NULL;
+
+ UINT32 uSize = 0;
+
+ // We must take the process lock before calling DAC primitives which will call back into DBI.
+ // On the other hand, we must NOT be holding the lock when we call out to the shim.
+ // So introduce a new scope here.
+ {
+ RSLockHolder lockHolder(GetProcessLock());
+ GetDAC()->EnumerateConnections(CountConnectionsCallback, &callbackData);
+
+ // save the size for later
+ uSize = callbackData.m_uIndex;
+
+ // Allocate the array to store the connections. This array will be released when the dtor runs.
+ callbackData.m_uIndex = 0;
+ callbackData.m_pEntryArray = new EnumerateConnectionsEntry[uSize];
+ GetDAC()->EnumerateConnections(EnumerateConnectionsCallback, &callbackData);
+ _ASSERTE(uSize == callbackData.m_uIndex);
+ }
+
+ {
+ // V2 would send CreateConnection for all connections, and then ChangeConnection
+ // for all connections.
+ PUBLIC_CALLBACK_IN_THIS_SCOPE0_NO_LOCK(this);
+ for (UINT32 i = 0; i < uSize; i++)
+ {
+ EnumerateConnectionsEntry * pEntry = &(callbackData.m_pEntryArray[i]);
+ GetShim()->GetShimCallback()->CreateConnection(
+ this,
+ (CONNID)pEntry->m_dwID,
+ const_cast<WCHAR *>((const WCHAR *)(pEntry->m_pName)));
+ }
+
+ for (UINT32 i = 0; i < uSize; i++)
+ {
+ EnumerateConnectionsEntry * pEntry = &(callbackData.m_pEntryArray[i]);
+ GetShim()->GetShimCallback()->ChangeConnection(this, (CONNID)pEntry->m_dwID);
+ }
+ }
+#endif
+}
+
+//
+// DispatchRCEvent -- dispatches a previously queued IPC event received
+// from the runtime controller. This represents the last amount of processing
+// the DI gets to do on an event before giving it to the user.
+//
+#ifdef _PREFAST_
+#pragma warning(push)
+#pragma warning(disable:21000) // Suppress PREFast warning about overly large function
+#endif
+void CordbProcess::DispatchRCEvent()
+{
+ INTERNAL_API_ENTRY(this);
+
+ CONTRACTL
+ {
+ // This is happening on the RCET thread, so there's no place to propogate an error back up.
+ NOTHROW;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(m_pShim != NULL); // V2 case
+
+ //
+ // Note: the current thread should have the process locked when it
+ // enters this method.
+ //
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ // Create/Launch paths already ensured that we had a callback.
+ _ASSERTE(m_cordb != NULL);
+ _ASSERTE(m_cordb->m_managedCallback != NULL);
+ _ASSERTE(m_cordb->m_managedCallback2 != NULL);
+ _ASSERTE(m_cordb->m_managedCallback3 != NULL);
+
+
+ // Bump up the stop count. Either we'll dispatch a managed event,
+ // or the logic below will decide not to dispatch one and call
+ // Continue itself. Either way, the stop count needs to go up by
+ // one...
+ _ASSERTE(this->GetSyncCompleteRecv());
+ SetSynchronized(true);
+ IncStopCount();
+
+ // As soon as we call Unlock(), we might get neutered and lose our reference to
+ // the shim. Grab it now for use later.
+ RSExtSmartPtr<ShimProcess> pShim(m_pShim);
+
+ Unlock();
+
+ _ASSERTE(!ThreadHoldsProcessLock());
+
+
+ // We want to stay synced until after the callbacks return. This is b/c we're on the RCET,
+ // and we may deadlock if we send IPC events on the RCET if we're not synced (see SendIPCEvent for details).
+ // So here, stopcount=1. The StopContinueHolder bumps it up to 2.
+ // - If Cordbg calls continue in the callback, that bumps it back down to 1, but doesn't actually continue.
+ // The holder dtor then bumps it down to 0, doing the real continue.
+ // - If Cordbg doesn't call continue in the callback, then stopcount stays at 2, holder dtor drops it down to 1,
+ // and then the holder was just a nop.
+ // This gives us delayed continues w/ no extra state flags.
+
+
+ // The debugger may call Detach() immediately after it returns from the callback, but before this thread returns
+ // from this function. Thus after we execute the callbacks, it's possible the CordbProcess object has been neutered.
+
+ // Since we're already sycned, the Stop from the holder here is practically a nop that just bumps up a count.
+ // Create an extra scope for the StopContinueHolder.
+ {
+ StopContinueHolder h;
+ HRESULT hr = h.Init(this);
+ if (FAILED(hr))
+ {
+ CORDBSetUnrecoverableError(this, hr, 0);
+ }
+
+ HRESULT hrCallback = S_OK;
+ // It's possible a ICorDebugProcess::Detach() may have occurred by now.
+ {
+ // @dbgtodo shim: eventually the entire RCET should be considered outside the RS.
+ PUBLIC_CALLBACK_IN_THIS_SCOPE0_NO_LOCK(this);
+
+
+ // Snag the first event off the queue.
+ // Holder will call Delete, which will invoke virtual Dtor that will release ICD objects.
+ // Since these are external refs, we want to do it while "outside" the RS.
+ NewHolder<ManagedEvent> pEvent(pShim->DequeueManagedEvent());
+
+ // Normally pEvent shouldn't be NULL, since this method is called when the queue is not empty.
+ // But due to a race between CordbProcess::Terminate(), CordbWin32EventThread::ExitProcess() and this method
+ // it is totally possible that the queue has already been cleaned up and we can't expect that event is always available.
+ if (pEvent != NULL)
+ {
+ // Since we need to access a member (m_cordb), protect this block with a
+ // lock and a check for Neutering (in case process detach has just
+ // occurred). We'll release the lock around the dispatch later on.
+ RSLockHolder lockHolder(GetProcessLock());
+ if (!IsNeutered())
+ {
+#ifdef _DEBUG
+ // On a debug build, keep track of the last IPC event we dispatched.
+ m_pDBGLastIPCEventType = pEvent->GetDebugCookie();
+#endif
+
+ ManagedEvent::DispatchArgs args(m_cordb->m_managedCallback, m_cordb->m_managedCallback2, m_cordb->m_managedCallback3);
+
+ {
+ // Release lock around the dispatch of the event
+ RSInverseLockHolder inverseLockHolder(GetProcessLock());
+
+ EX_TRY
+ {
+ // This dispatches almost directly into the user's callbacks.
+ // It does not update any RS state.
+ hrCallback = pEvent->Dispatch(args);
+ }
+ EX_CATCH_HRESULT(hrCallback);
+ }
+ }
+ }
+
+ } // we're now back inside the RS
+
+ if (hrCallback == E_NOTIMPL)
+ {
+ ContinueInternal(FALSE);
+ }
+
+
+ } // forces Continue to be called
+
+ Lock();
+
+};
+
+#ifdef _DEBUG
+//---------------------------------------------------------------------------------------
+// Debug-only callback to ensure that an appdomain is not available after the ExitAppDomain event.
+//
+// Arguments:
+// vmAppDomain - appdomain from enumeration
+// pUserData - pointer to a DbgAssertAppDomainDeletedData which contains the VMAppDomain that was just deleted.
+// notes:
+// see code:CordbProcess::DbgAssertAppDomainDeleted for details.
+void CordbProcess::DbgAssertAppDomainDeletedCallback(VMPTR_AppDomain vmAppDomain, void * pUserData)
+{
+ DbgAssertAppDomainDeletedData * pCallbackData = reinterpret_cast<DbgAssertAppDomainDeletedData *>(pUserData);
+ INTERNAL_DAC_CALLBACK(pCallbackData->m_pThis);
+
+ VMPTR_AppDomain vmAppDomainDeleted = pCallbackData->m_vmAppDomainDeleted;
+ CONSISTENCY_CHECK_MSGF((vmAppDomain != vmAppDomainDeleted),
+ ("An ExitAppDomain event was sent for appdomain, but it still shows up in the enumeration.\n vmAppDomain=%p\n",
+ VmPtrToCookie(vmAppDomainDeleted)));
+}
+
+//---------------------------------------------------------------------------------------
+// Debug-only helper to Assert that VMPTR is actually removed.
+//
+// Arguments:
+// vmAppDomainDeleted - vmptr of appdomain that we just got exit event for.
+// This should not be discoverable from the RS.
+//
+// Notes:
+// See code:IDacDbiInterface#Enumeration for rules that we're asserting.
+// Once the exit appdomain event is dispatched, the appdomain should not be discoverable by the RS.
+// Else the RS may use the AppDomain* after it's deleted.
+// This asserts that the AppDomain* is not discoverable.
+//
+// Since this is a debug-only function, it should have no side-effects.
+void CordbProcess::DbgAssertAppDomainDeleted(VMPTR_AppDomain vmAppDomainDeleted)
+{
+ DbgAssertAppDomainDeletedData callbackData;
+ callbackData.m_pThis = this;
+ callbackData.m_vmAppDomainDeleted = vmAppDomainDeleted;
+
+ GetDAC()->EnumerateAppDomains(
+ CordbProcess::DbgAssertAppDomainDeletedCallback,
+ &callbackData);
+}
+
+#endif // _DEBUG
+
+//---------------------------------------------------------------------------------------
+// Update state and potentially Dispatch a single event.
+//
+// Arguments:
+// pEvent - non-null pointer to debug event.
+// pCallback1 - callback object to dispatch on (for V1 callbacks)
+// pCallback2 - 2nd callback object to dispatch on (for new V2 callbacks)
+// pCallback3 - 3rd callback object to dispatch on (for new V4 callbacks)
+//
+//
+// Returns:
+// Nothing. Throws on error.
+//
+// Notes:
+// Generally, this will dispatch exactly 1 callback. It may dispatch 0 callbacks if there is an error
+// or in other corner cases (documented within the dispatch code below).
+// Errors could occur because:
+// - the event is corrupted (exceptional case)
+// - the RS is corrupted / OOM (exceptional case)
+// Exception errors here will propogate back to the Filter() call, and there's not really anything
+// a debugger can do about an error here (perhaps report it to the user).
+// Errors must leave IcorDebug in a consistent state.
+//
+// This is dispatched directly on the Win32Event Thread in response to calling Filter.
+// Therefore, this can't send any IPC events (Not an issue once everything is DAC-ized).
+// A V2 shim can provide a proxy calllack that takes these events and queues them and
+// does the real dispatch to the user to emulate V2 semantics.
+//
+void CordbProcess::RawDispatchEvent(
+ DebuggerIPCEvent * pEvent,
+ RSLockHolder * pLockHolder,
+ ICorDebugManagedCallback * pCallback1,
+ ICorDebugManagedCallback2 * pCallback2,
+ ICorDebugManagedCallback3 * pCallback3)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr = S_OK;
+ // We start off with the lock, and we'll toggle it.
+ _ASSERTE(ThreadHoldsProcessLock());
+
+
+ //
+ // Call StartEventDispatch to true to guard against calls to Continue()
+ // from within the user's callback. We need Continue() to behave a little
+ // bit differently in such a case.
+ //
+ // Also note that Win32EventThread::ExitProcess will take the lock and free all
+ // events in the queue. (the current event is already off the queue, so
+ // it will be ok). But we can't do the EP callback in the middle of this dispatch
+ // so if this flag is set, EP will wait on the miscWaitEvent (which will
+ // get set in FlushQueuedEvents when we return from here) and let us finish here.
+ //
+ StartEventDispatch(pEvent->type);
+
+ // Keep strong references to these objects in case a callback deletes them from underneath us.
+ RSSmartPtr<CordbAppDomain> pAppDomain;
+ CordbThread * pThread = NULL;
+
+
+ // Get thread that this event is on. In attach scenarios, this may be the first time ICorDebug has seen this thread.
+ if (!pEvent->vmThread.IsNull())
+ {
+ pThread = LookupOrCreateThread(pEvent->vmThread);
+ }
+
+ if (!pEvent->vmAppDomain.IsNull())
+ {
+ pAppDomain.Assign(LookupOrCreateAppDomain(pEvent->vmAppDomain));
+ }
+
+ DWORD dwVolatileThreadId = 0;
+ if (pThread != NULL)
+ {
+ dwVolatileThreadId = pThread->GetUniqueId();
+ }
+
+
+ //
+ // Update the app domain that this thread lives in.
+ //
+ if ((pThread != NULL) && (pAppDomain != NULL))
+ {
+ // It shouldn't be possible for us to see an exited AppDomain here
+ _ASSERTE( !pAppDomain->IsNeutered() );
+
+ pThread->m_pAppDomain = pAppDomain;
+ }
+
+ _ASSERTE(pEvent != NULL);
+ _ASSERTE(pCallback1 != NULL);
+ _ASSERTE(pCallback2 != NULL);
+ _ASSERTE(pCallback3 != NULL);
+
+
+ STRESS_LOG1(LF_CORDB, LL_EVERYTHING, "Pre-Dispatch IPC event: %s\n", IPCENames::GetName(pEvent->type));
+
+ switch (pEvent->type & DB_IPCE_TYPE_MASK)
+ {
+ case DB_IPCE_CREATE_PROCESS:
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback1->CreateProcess(static_cast<ICorDebugProcess*> (this));
+ }
+ break;
+
+ case DB_IPCE_BREAKPOINT:
+ {
+ _ASSERTE(pThread != NULL);
+ _ASSERTE(pAppDomain != NULL);
+
+ // Find the breakpoint object on this side.
+ CordbBreakpoint *pBreakpoint = NULL;
+
+ // We've found cases out in the wild where we get this event on a thread we don't recognize.
+ // We're not sure how this happens. Add a runtime check to protect ourselves to avoid the
+ // an AV. We still assert because this should not be happening.
+ // It likely means theres some issue where we failed to send a CreateThread notification.
+ TargetConsistencyCheck(pThread != NULL);
+ pBreakpoint = pAppDomain->m_breakpoints.GetBase(LsPtrToCookie(pEvent->BreakpointData.breakpointToken));
+
+ if (pBreakpoint != NULL)
+ {
+ ICorDebugBreakpoint * pIBreakpoint = CordbBreakpointToInterface(pBreakpoint);
+ _ASSERTE(pIBreakpoint != NULL);
+
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE2(this, pLockHolder, pEvent, "thread=0x%p, bp=0x%p", pThread, pBreakpoint);
+ pCallback1->Breakpoint(pAppDomain, pThread, pIBreakpoint);
+ }
+ }
+ }
+ break;
+
+ case DB_IPCE_USER_BREAKPOINT:
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "[%x] RCET::DRCE: user breakpoint.\n",
+ GetCurrentThreadId());
+
+ _ASSERTE(pThread != NULL);
+ _ASSERTE(pAppDomain != NULL);
+ _ASSERTE(pThread->m_pAppDomain != NULL);
+
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback1->Break(pThread->m_pAppDomain, pThread);
+ }
+
+ }
+ break;
+
+ case DB_IPCE_STEP_COMPLETE:
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "[%x] RCET::DRCE: step complete.\n",
+ GetCurrentThreadId());
+
+ PREFIX_ASSUME(pThread != NULL);
+
+ CordbStepper * pStepper = m_steppers.GetBase(LsPtrToCookie(pEvent->StepData.stepperToken));
+
+ // It's possible the stepper is NULL if:
+ // - event X & step-complete are both in the queue
+ // - during dispatch for event X, Cordbg cancels the stepper (thus removing it from m_steppers)
+ // - the Step-Complete still stays in the queue, and so we're here, but out stepper's been removed.
+ // (This could happen for breakpoints too)
+ // Don't dispatch a callback if the stepper is NULL.
+ if (pStepper != NULL)
+ {
+ RSSmartPtr<CordbStepper> pRef(pStepper);
+ pStepper->m_active = false;
+ m_steppers.RemoveBase((ULONG_PTR)pStepper->m_id);
+
+ {
+ _ASSERTE(pThread->m_pAppDomain != NULL);
+ PUBLIC_CALLBACK_IN_THIS_SCOPE2(this, pLockHolder, pEvent, "thrad=0x%p, stepper=0x%p", pThread, pStepper);
+ pCallback1->StepComplete(pThread->m_pAppDomain, pThread, pStepper, pEvent->StepData.reason);
+ }
+
+ // implicit Release on pRef
+ }
+ }
+ break;
+
+ case DB_IPCE_EXCEPTION:
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "[%x] RCET::DRCE: exception.\n",
+ GetCurrentThreadId());
+
+ _ASSERTE(pAppDomain != NULL);
+
+ // For some exceptions very early in startup (eg, TypeLoad), this may have occurred before we
+ // even executed jitted code on the thread. We may have not received a CreateThread yet.
+ // In V2, we detected this and sent a LogMessage on a random thread.
+ // In V3, we lazily create the CordbThread objects (possibly before the CreateThread event),
+ // and so we know we should have one.
+ _ASSERTE(pThread != NULL);
+
+ pThread->SetExInfo(pEvent->Exception.vmExceptionHandle);
+
+ _ASSERTE(pThread->m_pAppDomain != NULL);
+
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback1->Exception(pThread->m_pAppDomain, pThread, !pEvent->Exception.firstChance);
+ }
+
+ }
+ break;
+
+ case DB_IPCE_SYNC_COMPLETE:
+ _ASSERTE(!"Should have never queued a sync complete pEvent.");
+ break;
+
+ case DB_IPCE_THREAD_ATTACH:
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO100, "RCET::DRCE: thread attach : ID=%x.\n", dwVolatileThreadId);
+
+ TargetConsistencyCheck(pThread != NULL);
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE1(this, pLockHolder, pEvent, "thread=0x%p", pThread);
+ pCallback1->CreateThread(pAppDomain, pThread);
+ }
+ }
+ break;
+
+ case DB_IPCE_THREAD_DETACH:
+ {
+ STRESS_LOG2(LF_CORDB, LL_INFO100, "[%x] RCET::HRCE: thread detach : ID=%x \n",
+ GetCurrentThreadId(), dwVolatileThreadId);
+
+ // If the runtime thread never entered managed code, there
+ // won't be a CordbThread, and CreateThread was never
+ // called, so don't bother calling ExitThread.
+ if (pThread != NULL)
+ {
+ AddToNeuterOnContinueList(pThread);
+
+ RSSmartPtr<CordbThread> pRefThread(pThread);
+
+ _ASSERTE(pAppDomain != NULL);
+
+ // A thread is reported as dead before we get the exit event.
+ // See code:IDacDbiInterface#IsThreadMarkedDead for the invariant being asserted here.
+ TargetConsistencyCheck(pThread->IsThreadDead());
+
+ // Enforce the enumeration invariants (see code:IDacDbiInterface#Enumeration)that the thread is not discoverable.
+ INDEBUG(pThread->DbgAssertThreadDeleted());
+
+ // Remove the thread from the hash. If we've removed it from the hash, we really should
+ // neuter it ... but that causes test failures.
+ // We'll neuter it in continue.
+ m_userThreads.RemoveBase(VmPtrToCookie(pThread->m_vmThreadToken));
+
+
+ LOG((LF_CORDB, LL_INFO1000, "[%x] RCET::HRCE: sending thread detach.\n", GetCurrentThreadId()));
+
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback1->ExitThread(pAppDomain, pThread);
+ }
+
+ // Implicit release on thread & pAppDomain
+ }
+ }
+ break;
+
+ case DB_IPCE_METADATA_UPDATE:
+ {
+ CordbModule * pModule = pAppDomain->LookupOrCreateModule(pEvent->MetadataUpdateData.vmDomainFile);
+ pModule->RefreshMetaData();
+ }
+ break;
+
+ case DB_IPCE_LOAD_MODULE:
+ {
+ _ASSERTE (pAppDomain != NULL);
+ CordbModule * pModule = pAppDomain->LookupOrCreateModule(pEvent->LoadModuleData.vmDomainFile);
+
+ {
+ pModule->SetLoadEventContinueMarker();
+
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback1->LoadModule(pAppDomain, pModule);
+ }
+
+ }
+ break;
+
+ case DB_IPCE_CREATE_CONNECTION:
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO100,
+ "RCET::HRCE: Connection change %d \n",
+ pEvent->CreateConnection.connectionId);
+
+ // pass back the connection id and the connection name.
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback2->CreateConnection(
+ this,
+ pEvent->CreateConnection.connectionId,
+ const_cast<WCHAR*> (pEvent->CreateConnection.wzConnectionName.GetString()));
+ }
+ break;
+
+ case DB_IPCE_DESTROY_CONNECTION:
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO100,
+ "RCET::HRCE: Connection destroyed %d \n",
+ pEvent->ConnectionChange.connectionId);
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback2->DestroyConnection(this, pEvent->ConnectionChange.connectionId);
+ }
+ break;
+
+ case DB_IPCE_CHANGE_CONNECTION:
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO100,
+ "RCET::HRCE: Connection changed %d \n",
+ pEvent->ConnectionChange.connectionId);
+
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback2->ChangeConnection(this, pEvent->ConnectionChange.connectionId);
+ }
+ break;
+
+ case DB_IPCE_UNLOAD_MODULE:
+ {
+ STRESS_LOG3(LF_CORDB, LL_INFO100, "RCET::HRCE: unload module on thread %#x Mod:0x%x AD:0x%08x\n",
+ dwVolatileThreadId,
+ VmPtrToCookie(pEvent->UnloadModuleData.vmDomainFile),
+ VmPtrToCookie(pEvent->vmAppDomain));
+
+ PREFIX_ASSUME (pAppDomain != NULL);
+
+ CordbModule *module = pAppDomain->LookupOrCreateModule(pEvent->UnloadModuleData.vmDomainFile);
+
+ if (module == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO100, "Already unloaded Module - continue()ing!" ));
+ break;
+ }
+ _ASSERTE(module != NULL);
+ INDEBUG(module->DbgAssertModuleDeleted());
+
+ // The appdomain we're unloading in must be the appdomain we were loaded in. Otherwise, we've got mismatched
+ // module and appdomain pointers. Bugs 65943 & 81728.
+ _ASSERTE(pAppDomain == module->GetAppDomain());
+
+ // Ensure the module gets neutered once we call continue.
+ AddToNeuterOnContinueList(module); // throws
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback1->UnloadModule(pAppDomain, module);
+ }
+
+ pAppDomain->m_modules.RemoveBase(VmPtrToCookie(pEvent->UnloadModuleData.vmDomainFile));
+ }
+ break;
+
+ case DB_IPCE_LOAD_CLASS:
+ {
+ CordbClass *pClass = NULL;
+
+ LOG((LF_CORDB, LL_INFO10000,
+ "RCET::HRCE: load class on thread %#x Tok:0x%08x Mod:0x%08x Asm:0x%08x AD:0x%08x\n",
+ dwVolatileThreadId,
+ pEvent->LoadClass.classMetadataToken,
+ VmPtrToCookie(pEvent->LoadClass.vmDomainFile),
+ LsPtrToCookie(pEvent->LoadClass.classDebuggerAssemblyToken),
+ VmPtrToCookie(pEvent->vmAppDomain)));
+
+ _ASSERTE (pAppDomain != NULL);
+
+ CordbModule* pModule = pAppDomain->LookupOrCreateModule(pEvent->LoadClass.vmDomainFile);
+ if (pModule == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO100, "Load Class on not-loaded Module - continue()ing!" ));
+ break;
+ }
+ _ASSERTE(pModule != NULL);
+
+ BOOL fDynamic = pModule->IsDynamic();
+
+ // If this is a class load in a dynamic module, the metadata has become invalid.
+ if (fDynamic)
+ {
+ pModule->RefreshMetaData();
+ }
+
+ hr = pModule->LookupOrCreateClass(pEvent->LoadClass.classMetadataToken, &pClass);
+ _ASSERTE(SUCCEEDED(hr) == (pClass != NULL));
+ IfFailThrow(hr);
+
+ // Prevent class load from being sent twice.
+ // @dbgtodo - Microsoft, cordbclass: this is legacy. Can this really happen? Investigate as we dac-ize CordbClass.
+ if (pClass->LoadEventSent())
+ {
+ // Dynamic modules are dynamic at the module level -
+ // you can't add a new version of a class once the module
+ // is baked.
+ // EnC adds completely new classes.
+ // There shouldn't be any other way to send multiple
+ // ClassLoad events.
+ // Except that there are race conditions between loading
+ // an appdomain, and loading a class, so if we get the extra
+ // class load, we should ignore it.
+ break; //out of the switch statement
+ }
+ pClass->SetLoadEventSent(TRUE);
+
+
+ if (pClass != NULL)
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback1->LoadClass(pAppDomain, pClass);
+ }
+ }
+ break;
+
+ case DB_IPCE_UNLOAD_CLASS:
+ {
+ LOG((LF_CORDB, LL_INFO10000,
+ "RCET::HRCE: unload class on thread %#x Tok:0x%08x Mod:0x%08x AD:0x%08x\n",
+ dwVolatileThreadId,
+ pEvent->UnloadClass.classMetadataToken,
+ VmPtrToCookie(pEvent->UnloadClass.vmDomainFile),
+ VmPtrToCookie(pEvent->vmAppDomain)));
+
+ // get the appdomain object
+ _ASSERTE (pAppDomain != NULL);
+
+ CordbModule *pModule = pAppDomain->LookupOrCreateModule(pEvent->UnloadClass.vmDomainFile);
+ if (pModule == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO100, "Unload Class on not-loaded Module - continue()ing!" ));
+ break;
+ }
+ _ASSERTE(pModule != NULL);
+
+ CordbClass *pClass = pModule->LookupClass(pEvent->UnloadClass.classMetadataToken);
+
+ if (pClass != NULL && !pClass->HasBeenUnloaded())
+ {
+ pClass->SetHasBeenUnloaded(true);
+
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback1->UnloadClass(pAppDomain, pClass);
+ }
+ }
+ break;
+
+ case DB_IPCE_FIRST_LOG_MESSAGE:
+ {
+ _ASSERTE(pThread != NULL);
+ _ASSERTE(pAppDomain != NULL);
+
+ const WCHAR * pszContent = pEvent->FirstLogMessage.szContent.GetString();
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback1->LogMessage(
+ pAppDomain,
+ pThread,
+ pEvent->FirstLogMessage.iLevel,
+ const_cast<WCHAR*> (pEvent->FirstLogMessage.szCategory.GetString()),
+ const_cast<WCHAR*> (pszContent));
+ }
+ }
+ break;
+
+ case DB_IPCE_LOGSWITCH_SET_MESSAGE:
+ {
+
+ LOG((LF_CORDB, LL_INFO10000,
+ "[%x] RCET::DRCE: Log Switch Setting Message.\n",
+ GetCurrentThreadId()));
+
+ _ASSERTE(pThread != NULL);
+
+ const WCHAR *pstrLogSwitchName = pEvent->LogSwitchSettingMessage.szSwitchName.GetString();
+ const WCHAR *pstrParentName = pEvent->LogSwitchSettingMessage.szParentSwitchName.GetString();
+
+ // from the thread object get the appdomain object
+ _ASSERTE(pAppDomain == pThread->m_pAppDomain);
+ _ASSERTE (pAppDomain != NULL);
+
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback1->LogSwitch(
+ pAppDomain,
+ pThread,
+ pEvent->LogSwitchSettingMessage.iLevel,
+ pEvent->LogSwitchSettingMessage.iReason,
+ const_cast<WCHAR*> (pstrLogSwitchName),
+ const_cast<WCHAR*> (pstrParentName));
+
+ }
+ }
+
+ break;
+ case DB_IPCE_CUSTOM_NOTIFICATION:
+ {
+ _ASSERTE(pThread != NULL);
+ _ASSERTE(pAppDomain != NULL);
+
+
+ // determine first whether custom notifications for this type are enabled -- if not
+ // we just return without doing anything.
+ CordbClass * pNotificationClass = LookupClass(pAppDomain,
+ pEvent->CustomNotification.vmDomainFile,
+ pEvent->CustomNotification.classToken);
+
+ // if the class is NULL, that means the debugger never enabled notifications for it. Otherwise,
+ // the CordbClass instance would already have been created when the notifications were
+ // enabled.
+ if ((pNotificationClass != NULL) && pNotificationClass->CustomNotificationsEnabled())
+
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback3->CustomNotification(pThread, pAppDomain);
+ }
+ }
+
+ break;
+
+ case DB_IPCE_CREATE_APP_DOMAIN:
+ {
+ STRESS_LOG2(LF_CORDB, LL_INFO100,
+ "RCET::HRCE: create appdomain on thread %#x AD:0x%08x \n",
+ dwVolatileThreadId,
+ VmPtrToCookie(pEvent->vmAppDomain));
+
+
+ // Enumerate may have prepopulated the appdomain, so check if it already exists.
+ // Either way, still send the CreateEvent. (We don't want to skip the Create event
+ // just because the debugger did an enumerate)
+ // We remove AppDomains from the hash as soon as they are exited.
+ pAppDomain.Assign(LookupOrCreateAppDomain(pEvent->AppDomainData.vmAppDomain));
+ _ASSERTE(pAppDomain != NULL); // throws on failure
+
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ hr = pCallback1->CreateAppDomain(this, pAppDomain);
+ }
+ }
+
+
+ break;
+
+ case DB_IPCE_EXIT_APP_DOMAIN:
+ {
+ STRESS_LOG2(LF_CORDB, LL_INFO100, "RCET::HRCE: exit appdomain on thread %#x AD:0x%08x \n",
+ dwVolatileThreadId,
+ VmPtrToCookie(pEvent->vmAppDomain));
+
+ // In debug-only builds, assert that the appdomain is indeed deleted and not discoverable.
+ INDEBUG(DbgAssertAppDomainDeleted(pEvent->vmAppDomain));
+
+ // If we get an ExitAD message for which we have no AppDomain, then ignore it.
+ // This can happen if an AD gets torn down very early (before the LS AD is to the
+ // point that it can be published).
+ // This could also happen if we attach a debugger right before the Exit event is sent.
+ // In this case, the debuggee is no longer publishing the appdomain.
+ if (pAppDomain == NULL)
+ {
+ break;
+ }
+ _ASSERTE (pAppDomain != NULL);
+
+ // See if this is the default AppDomain exiting. This should only happen very late in
+ // the shutdown cycle, and so we shouldn't do anything significant with m_pDefaultDomain==NULL.
+ // We should try and remove m_pDefaultDomain entirely since we can't count on it always existing.
+ if (pAppDomain == m_pDefaultAppDomain)
+ {
+ m_pDefaultAppDomain = NULL;
+ }
+
+ // Update any threads which were last seen in this AppDomain. We don't
+ // get any notification when a thread leaves an AppDomain, so our idea
+ // of what AppDomain the thread is in may be out of date.
+ UpdateThreadsForAdUnload( pAppDomain );
+
+ // This will still maintain weak references so we could call Continue.
+ AddToNeuterOnContinueList(pAppDomain);
+
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ hr = pCallback1->ExitAppDomain(this, pAppDomain);
+ }
+
+ // @dbgtodo appdomain: This should occur before the callback.
+ // Even after ExitAppDomain, the outside world will want to continue calling
+ // Continue (and thus they may need to call CordbAppDomain::GetProcess(), which Neutering
+ // would clear). Thus we can't neuter yet.
+
+ // Remove this app domain. This means any attempt to lookup the AppDomain
+ // will fail (which we do at the top of this method). Since any threads (incorrectly) referring
+ // to this AppDomain have been moved to the default AppDomain, no one should be
+ // interested in looking this AppDomain up anymore.
+ m_appDomains.RemoveBase(VmPtrToCookie(pEvent->vmAppDomain));
+ }
+
+ break;
+
+ case DB_IPCE_LOAD_ASSEMBLY:
+ {
+ LOG((LF_CORDB, LL_INFO100,
+ "RCET::HRCE: load assembly on thread %#x Asm:0x%08x AD:0x%08x \n",
+ dwVolatileThreadId,
+ VmPtrToCookie(pEvent->AssemblyData.vmDomainAssembly),
+ VmPtrToCookie(pEvent->vmAppDomain)));
+
+ _ASSERTE (pAppDomain != NULL);
+
+ // Determine if this Assembly is cached.
+ CordbAssembly * pAssembly = pAppDomain->LookupOrCreateAssembly(pEvent->AssemblyData.vmDomainAssembly);
+ _ASSERTE(pAssembly != NULL); // throws on error
+
+ // If created, or have, an Assembly, notify callback.
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ hr = pCallback1->LoadAssembly(pAppDomain, pAssembly);
+ }
+ }
+
+ break;
+
+ case DB_IPCE_UNLOAD_ASSEMBLY:
+ {
+ LOG((LF_CORDB, LL_INFO100, "RCET::DRCE: unload assembly on thread %#x Asm:0x%x AD:0x%x\n",
+ dwVolatileThreadId,
+ VmPtrToCookie(pEvent->AssemblyData.vmDomainAssembly),
+ VmPtrToCookie(pEvent->vmAppDomain)));
+
+ _ASSERTE (pAppDomain != NULL);
+
+ CordbAssembly * pAssembly = pAppDomain->LookupOrCreateAssembly(pEvent->AssemblyData.vmDomainAssembly);
+
+ if (pAssembly == NULL)
+ {
+ // No assembly. This could happen if we attach right before an unload event is sent.
+ return;
+ }
+ _ASSERTE(pAssembly != NULL);
+ INDEBUG(pAssembly->DbgAssertAssemblyDeleted());
+
+ // Ensure the assembly gets neutered when we call continue.
+ AddToNeuterOnContinueList(pAssembly); // throws
+
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ hr = pCallback1->UnloadAssembly(pAppDomain, pAssembly);
+ }
+
+ pAppDomain->RemoveAssemblyFromCache(pEvent->AssemblyData.vmDomainAssembly);
+ }
+
+ break;
+
+ case DB_IPCE_FUNC_EVAL_COMPLETE:
+ {
+ LOG((LF_CORDB, LL_INFO1000, "RCET::DRCE: func eval complete.\n"));
+
+ CordbEval *pEval = NULL;
+ {
+ pEval = pEvent->FuncEvalComplete.funcEvalKey.UnWrapAndRemove(this);
+ if (pEval == NULL)
+ {
+ _ASSERTE(!"Bogus FuncEval handle in IPC block.");
+ // Bogus handle in IPC block.
+ break;
+ }
+ }
+ _ASSERTE(pEval != NULL);
+
+ _ASSERTE(pThread != NULL);
+ _ASSERTE(pAppDomain != NULL);
+
+ CONSISTENCY_CHECK_MSGF(pEval->m_DbgAppDomainStarted == pAppDomain,
+ ("AppDomain changed from Func-Eval. Eval=%p, Started=%p, Now=%p\n",
+ pEval, pEval->m_DbgAppDomainStarted, (void*) pAppDomain));
+
+ // Hold the data about the result in the CordbEval for later.
+ pEval->m_complete = true;
+ pEval->m_successful = !!pEvent->FuncEvalComplete.successful;
+ pEval->m_aborted = !!pEvent->FuncEvalComplete.aborted;
+ pEval->m_resultAddr = pEvent->FuncEvalComplete.resultAddr;
+ pEval->m_vmObjectHandle = pEvent->FuncEvalComplete.vmObjectHandle;
+ pEval->m_resultType = pEvent->FuncEvalComplete.resultType;
+ pEval->m_resultAppDomainToken = pEvent->FuncEvalComplete.vmAppDomain;
+
+ CordbAppDomain *pResultAppDomain = LookupOrCreateAppDomain(pEvent->FuncEvalComplete.vmAppDomain);
+
+ _ASSERTE(OutstandingEvalCount() > 0);
+ DecrementOutstandingEvalCount();
+
+ CONSISTENCY_CHECK_MSGF(pEval->m_DbgAppDomainStarted == pAppDomain,
+ ("AppDomain changed from Func-Eval. Eval=%p, Started=%p, Now=%p\n",
+ pEval, pEval->m_DbgAppDomainStarted, (void*) pAppDomain));
+
+ // If we did this func eval with this thread stopped at an excpetion, then we need to pretend as if we
+ // really didn't continue from the exception, since, of course, we really didn't on the Left Side.
+ if (pEval->IsEvalDuringException())
+ {
+ pThread->SetExInfo(pEval->m_vmThreadOldExceptionHandle);
+ }
+
+ bool fEvalCompleted = pEval->m_successful || pEval->m_aborted;
+
+ // If a CallFunction() is aborted, the LHS may not complete the abort
+ // immediately and hence we cant do a SendCleanup() at that point. Also,
+ // the debugger may (incorrectly) release the CordbEval before this
+ // DB_IPCE_FUNC_EVAL_COMPLETE event is received. Hence, we maintain an
+ // extra ref-count to determine when this can be done.
+ // Note that this can cause a two-way DB_IPCE_FUNC_EVAL_CLEANUP event
+ // to be sent. Hence, it has to be done before the Continue (see issue 102745).
+
+
+ // Note that if the debugger has already (incorrectly) released the CordbEval,
+ // pEval will be pointing to garbage and should not be used by the debugger.
+ if (fEvalCompleted)
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE2(this, pLockHolder, pEvent, "thread=0x%p, eval=0x%p. (Complete)", pThread, pEval);
+ pCallback1->EvalComplete(pResultAppDomain, pThread, pEval);
+ }
+ else
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE2(this, pLockHolder, pEvent, "pThread=0x%p, eval=0x%p. (Exception)", pThread, pEval);
+ pCallback1->EvalException(pResultAppDomain, pThread, pEval);
+ }
+
+ // This release may send an DB_IPCE_FUNC_EVAL_CLEANUP IPC event. That's ok b/c
+ // we're still synced even if if Continue was called inside the callback.
+ // That's because the StopContinueHolder bumped up the stopcount.
+ // Corresponding AddRef() in CallFunction().
+ // @todo - this is leaked if we don't get an EvalComplete event (eg, process exits with
+ // in middle of func-eval).
+ pEval->Release();
+ }
+ break;
+
+
+ case DB_IPCE_NAME_CHANGE:
+ {
+ LOG((LF_CORDB, LL_INFO1000, "RCET::HRCE: Name Change %d 0x%p\n",
+ dwVolatileThreadId,
+ VmPtrToCookie(pEvent->NameChange.vmAppDomain)));
+
+ pThread = NULL;
+ pAppDomain.Clear();
+ if (pEvent->NameChange.eventType == THREAD_NAME_CHANGE)
+ {
+ // Lookup the CordbThread that matches this runtime thread.
+ if (!pEvent->NameChange.vmThread.IsNull())
+ {
+ pThread = LookupOrCreateThread(pEvent->NameChange.vmThread);
+ }
+ }
+ else
+ {
+ _ASSERTE (pEvent->NameChange.eventType == APP_DOMAIN_NAME_CHANGE);
+ pAppDomain.Assign(LookupOrCreateAppDomain(pEvent->NameChange.vmAppDomain));
+ if (pAppDomain)
+ {
+ pAppDomain->InvalidateName();
+ }
+ }
+
+ if (pThread || pAppDomain)
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback1->NameChange(pAppDomain, pThread);
+ }
+ }
+
+ break;
+
+ case DB_IPCE_UPDATE_MODULE_SYMS:
+ {
+ RSExtSmartPtr<IStream> pStream;
+
+ // Find the app domain the module lives in.
+ _ASSERTE (pAppDomain != NULL);
+
+ // Find the Right Side module for this module.
+ CordbModule * pModule = pAppDomain->LookupOrCreateModule(pEvent->UpdateModuleSymsData.vmDomainFile);
+ _ASSERTE(pModule != NULL);
+
+ // This is a legacy event notification for updated PDBs.
+ // Creates a new IStream object. Ownership is handed off via callback.
+ IDacDbiInterface::SymbolFormat symFormat = pModule->GetInMemorySymbolStream(&pStream);
+
+ // We shouldn't get this event if there aren't PDB symbols waiting. Specifically we don't want
+ // to incur the cost of copying over ILDB symbols here without the debugger asking for them.
+ // Eventually we may remove this callback as well and always rely on explicit requests.
+ _ASSERTE(symFormat == IDacDbiInterface::kSymbolFormatPDB);
+
+ if (symFormat == IDacDbiInterface::kSymbolFormatPDB)
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+
+ _ASSERTE(pStream != NULL); // Shouldn't send the event if we don't have a stream.
+
+ pCallback1->UpdateModuleSymbols(pAppDomain, pModule, pStream);
+ }
+
+ }
+ break;
+
+ case DB_IPCE_MDA_NOTIFICATION:
+ {
+ RSInitHolder<CordbMDA> pMDA(new CordbMDA(this, &pEvent->MDANotification)); // throws
+
+ // Ctor leaves both internal + ext Ref at 0, adding to neuter list bumps int-ref up to 1.
+ // Neutering will dump it back down to zero.
+ this->AddToNeuterOnExitList(pMDA);
+
+ // We bump up and down the external ref so that even if the callback doensn't touch the refs,
+ // our Ext-Release here will still cause a 1->0 ext-ref transition, which will get it
+ // swept on the neuter list.
+ RSExtSmartPtr<ICorDebugMDA> pExternalMDARef;
+ pMDA.TransferOwnershipExternal(&pExternalMDARef);
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+
+ pCallback2->MDANotification(
+ this,
+ pThread, // may be null
+ pExternalMDARef);
+
+ // pExternalMDARef's dtor will do an external release,
+ // which is very significant because it may be the one that does the 1->0 ext ref transition,
+ // which may mean cause the "NeuterAtWill" bit to get flipped on this CordbMDA object.
+ // Since this is an external release, do it in the PUBLIC_CALLBACK scope.
+ pExternalMDARef.Clear();
+ }
+
+ break;
+ }
+
+ case DB_IPCE_CONTROL_C_EVENT:
+ {
+ hr = S_FALSE;
+
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ hr = pCallback1->ControlCTrap((ICorDebugProcess*) this);
+ }
+
+ {
+ RSLockHolder ch(this->GetStopGoLock());
+
+ DebuggerIPCEvent eventControlCResult;
+
+ InitIPCEvent(&eventControlCResult,
+ DB_IPCE_CONTROL_C_EVENT_RESULT,
+ false,
+ VMPTR_AppDomain::NullPtr());
+
+ // Indicate whether the debugger has handled the event.
+ eventControlCResult.hr = hr;
+
+ // Send the reply to the LS.
+ SendIPCEvent(&eventControlCResult, sizeof(eventControlCResult));
+ } // release SG lock
+
+ }
+ break;
+
+ // EnC Remap opportunity
+ case DB_IPCE_ENC_REMAP:
+ {
+ LOG((LF_CORDB, LL_INFO1000, "[%x] RCET::DRCE: EnC Remap!.\n",
+ GetCurrentThreadId()));
+
+ _ASSERTE(NULL != pAppDomain);
+
+ CordbModule * pModule = pAppDomain->LookupOrCreateModule(pEvent->EnCRemap.vmDomainFile);
+ PREFIX_ASSUME(pModule != NULL);
+
+ CordbFunction * pCurFunction = NULL;
+ CordbFunction * pResumeFunction = NULL;
+
+ // lookup the version of the function that we are mapping from
+ // this is the one that is currently running
+ pCurFunction = pModule->LookupOrCreateFunction(
+ pEvent->EnCRemap.funcMetadataToken, pEvent->EnCRemap.currentVersionNumber);
+
+ // lookup the version of the function that we are mapping to
+ // it will always be the most recent
+ pResumeFunction = pModule->LookupOrCreateFunction(
+ pEvent->EnCRemap.funcMetadataToken, pEvent->EnCRemap.resumeVersionNumber);
+
+ _ASSERTE(pCurFunction->GetEnCVersionNumber() < pResumeFunction->GetEnCVersionNumber());
+
+ RSSmartPtr<CordbFunction> pRefCurFunction(pCurFunction);
+ RSSmartPtr<CordbFunction> pRefResumeFunction(pResumeFunction);
+
+ // Verify we're not about to overwrite an outstanding remap IP
+ // This should only be set while a remap opportunity is being handled,
+ // and cleared (by CordbThread::MarkStackFramesDirty) on Continue.
+ // We want to be absolutely sure we don't accidentally keep a stale pointer
+ // around because it would point to arbitrary stack space in the CLR potentially
+ // leading to stack corruption.
+ _ASSERTE( pThread->m_EnCRemapFunctionIP == NULL );
+
+ // Stash the address of the remap IP buffer. This indicates that calling
+ // RemapFunction is valid and provides a communications channel between the RS
+ // and LS for the remap IL offset.
+ pThread->m_EnCRemapFunctionIP = pEvent->EnCRemap.resumeILOffset;
+
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback2->FunctionRemapOpportunity(
+ pAppDomain,
+ pThread,
+ pCurFunction,
+ pResumeFunction,
+ (ULONG32)pEvent->EnCRemap.currentILOffset);
+ }
+
+ // Implicit release on pCurFunction and pResumeFunction.
+ }
+ break;
+
+ // EnC Remap complete
+ case DB_IPCE_ENC_REMAP_COMPLETE:
+ {
+ LOG((LF_CORDB, LL_INFO1000, "[%x] RCET::DRCE: EnC Remap Complete!.\n",
+ GetCurrentThreadId()));
+
+ _ASSERTE(NULL != pAppDomain);
+
+ CordbModule* pModule = pAppDomain->LookupOrCreateModule(pEvent->EnCRemap.vmDomainFile);
+ PREFIX_ASSUME(pModule != NULL);
+
+ // Find the function we're remapping to, which must be the latest version
+ CordbFunction *pRemapFunction=
+ pModule->LookupFunctionLatestVersion(pEvent->EnCRemapComplete.funcMetadataToken);
+ PREFIX_ASSUME(pRemapFunction != NULL);
+
+ // Dispatch the FunctionRemapComplete callback
+ RSSmartPtr<CordbFunction> pRef(pRemapFunction);
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback2->FunctionRemapComplete(pAppDomain, pThread, pRemapFunction);
+ }
+ // Implicit release on pRemapFunction via holder
+ }
+ break;
+
+ case DB_IPCE_BREAKPOINT_SET_ERROR:
+ {
+ LOG((LF_CORDB, LL_INFO1000, "RCET::DRCE: breakpoint set error.\n"));
+
+ RSSmartPtr<CordbBreakpoint> pRef;
+
+ _ASSERTE(pThread != NULL);
+ _ASSERTE(pAppDomain != NULL);
+
+ // Find the breakpoint object on this side.
+ CordbBreakpoint * pBreakpoint = NULL;
+
+
+ if (pThread == NULL)
+ {
+ // We've found cases out in the wild where we get this event on a thread we don't recognize.
+ // We're not sure how this happens. Add a runtime check to protect ourselves to avoid the
+ // an AV. We still assert because this should not be happening.
+ // It likely means theres some issue where we failed to send a CreateThread notification.
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "BreakpointSetError on unrecognized thread. %p\n", pBreakpoint);
+
+ _ASSERTE(!"Missing thread on bp set error");
+ break;
+ }
+
+ pBreakpoint = pAppDomain->m_breakpoints.GetBase(LsPtrToCookie(pEvent->BreakpointSetErrorData.breakpointToken));
+
+ if (pBreakpoint != NULL)
+ {
+ ICorDebugBreakpoint * pIBreakpoint = CordbBreakpointToInterface(pBreakpoint);
+ _ASSERTE(pIBreakpoint != NULL);
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE2(this, pLockHolder, pEvent, "thread=0x%p, bp=0x%p", pThread, pBreakpoint);
+ pCallback1->BreakpointSetError(pAppDomain, pThread, pIBreakpoint, 0);
+ }
+ }
+ // Implicit release on pRef.
+ }
+ break;
+
+
+ case DB_IPCE_EXCEPTION_CALLBACK2:
+ {
+ STRESS_LOG4(LF_CORDB, LL_INFO100,
+ "RCET::DRCE: Exception2 0x%p 0x%X 0x%X 0x%X\n",
+ pEvent->ExceptionCallback2.framePointer.GetSPValue(),
+ pEvent->ExceptionCallback2.nOffset,
+ pEvent->ExceptionCallback2.eventType,
+ pEvent->ExceptionCallback2.dwFlags
+ );
+
+ if (pThread == NULL)
+ {
+ // We've got an exception on a thread we don't know about. This could be a thread that
+ // has never run any managed code, so let's just ignore the exception. We should have
+ // already sent a log message about this situation for the EXCEPTION callback above.
+ _ASSERTE( pEvent->ExceptionCallback2.eventType == DEBUG_EXCEPTION_UNHANDLED );
+ break;
+ }
+
+ pThread->SetExInfo(pEvent->ExceptionCallback2.vmExceptionHandle);
+
+ //
+ // Send all the information back to the debugger.
+ //
+ RSSmartPtr<CordbFrame> pFrame;
+
+ FramePointer fp = pEvent->ExceptionCallback2.framePointer;
+ if (fp != LEAF_MOST_FRAME)
+ {
+ // The interface forces us to to pass a FramePointer via an ICorDebugFrame.
+ // However, we can't get a real ICDFrame without a stackwalk, and we don't
+ // want to do a stackwalk now. so pass a netuered proxy frame. The shim
+ // can map this to a real frame.
+ // See comments at CordbPlaceHolderFrame class for details.
+ pFrame.Assign(new CordbPlaceholderFrame(this, fp));
+ }
+
+ CorDebugExceptionCallbackType type = pEvent->ExceptionCallback2.eventType;
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE3(this, pLockHolder, pEvent, "pThread=0x%p, frame=%p, type=%d", pThread, (ICorDebugFrame*) pFrame, type);
+ hr = pCallback2->Exception(
+ pThread->m_pAppDomain,
+ pThread,
+ pFrame,
+ (ULONG32)(pEvent->ExceptionCallback2.nOffset),
+ type,
+ pEvent->ExceptionCallback2.dwFlags);
+ }
+ }
+ break;
+
+ case DB_IPCE_EXCEPTION_UNWIND:
+ {
+ STRESS_LOG2(LF_CORDB, LL_INFO100,
+ "RCET::DRCE: Exception Unwind 0x%X 0x%X\n",
+ pEvent->ExceptionCallback2.eventType,
+ pEvent->ExceptionCallback2.dwFlags
+ );
+
+ if (pThread == NULL)
+ {
+ // We've got an exception on a thread we don't know about. This probably should never
+ // happen (if it's unwinding, then we expect a managed frame on the stack, and so we should
+ // know about the thread), but if it does fall back to ignoring the exception.
+ _ASSERTE( !"Got unwind event for unknown exception" );
+ break;
+ }
+
+ //
+ // Send all the information back to the debugger.
+ //
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE1(this, pLockHolder, pEvent, "pThread=0x%p", pThread);
+ hr = pCallback2->ExceptionUnwind(
+ pThread->m_pAppDomain,
+ pThread,
+ pEvent->ExceptionUnwind.eventType,
+ pEvent->ExceptionUnwind.dwFlags);
+ }
+ }
+ break;
+
+
+ case DB_IPCE_INTERCEPT_EXCEPTION_COMPLETE:
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO100, "RCET::DRCE: Exception Interception Complete.\n");
+
+ if (pThread == NULL)
+ {
+ // We've got an exception on a thread we don't know about. This probably should never
+ // happen (if it's unwinding, then we expect a managed frame on the stack, and so we should
+ // know about the thread), but if it does fall back to ignoring the exception.
+ _ASSERTE( !"Got complete event for unknown exception" );
+ break;
+ }
+
+ //
+ // Tell the debugger that the exception has been intercepted. This is similar to the
+ // notification we give when we start unwinding for a non-intercepted exception, except that the
+ // interception has been completed at this point, which means that we are conceptually at the end
+ // of the second pass.
+ //
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE1(this, pLockHolder, pEvent, "pThread=0x%p", pThread);
+ hr = pCallback2->ExceptionUnwind(
+ pThread->m_pAppDomain,
+ pThread,
+ DEBUG_EXCEPTION_INTERCEPTED,
+ 0);
+ }
+ }
+ break;
+#ifdef TEST_DATA_CONSISTENCY
+ case DB_IPCE_TEST_CRST:
+ {
+ EX_TRY
+ {
+ // the left side has signaled that we should test whether pEvent->TestCrstData.vmCrst is held
+ GetDAC()->TestCrst(pEvent->TestCrstData.vmCrst);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ if (pEvent->TestCrstData.fOkToTake)
+ {
+ _ASSERTE(hr == S_OK);
+ if (hr != S_OK)
+ {
+ // we want to catch this in retail builds too
+ ThrowHR(E_FAIL);
+ }
+ }
+ else // the lock was already held
+ {
+ // see if we threw because the lock was held
+ _ASSERTE(hr == CORDBG_E_PROCESS_NOT_SYNCHRONIZED);
+ if (hr != CORDBG_E_PROCESS_NOT_SYNCHRONIZED)
+ {
+ // we want to catch this in retail builds too
+ ThrowHR(E_FAIL);
+ }
+ }
+
+ }
+ break;
+
+ case DB_IPCE_TEST_RWLOCK:
+ {
+ EX_TRY
+ {
+ // the left side has signaled that we should test whether pEvent->TestRWLockData.vmRWLock is held
+ GetDAC()->TestRWLock(pEvent->TestRWLockData.vmRWLock);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ if (pEvent->TestRWLockData.fOkToTake)
+ {
+ _ASSERTE(hr == S_OK);
+ if (hr != S_OK)
+ {
+ // we want to catch this in retail builds too
+ ThrowHR(E_FAIL);
+ }
+ }
+ else // the lock was already held
+ {
+ // see if we threw because the lock was held
+ _ASSERTE(hr == CORDBG_E_PROCESS_NOT_SYNCHRONIZED);
+ if (hr != CORDBG_E_PROCESS_NOT_SYNCHRONIZED)
+ {
+ // we want to catch this in retail builds too
+ ThrowHR(E_FAIL);
+ }
+ }
+ }
+ break;
+#endif
+
+ default:
+ _ASSERTE(!"Unknown event");
+ LOG((LF_CORDB, LL_INFO1000,
+ "[%x] RCET::HRCE: Unknown event: 0x%08x\n",
+ GetCurrentThreadId(), pEvent->type));
+ }
+
+
+ FinishEventDispatch();
+}
+#ifdef _PREFAST_
+#pragma warning(pop)
+#endif
+
+//---------------------------------------------------------------------------------------
+// Callback for prepopulating threads.
+//
+// Arugments:
+// vmThread - thread as part of the eunmeration.
+// pUserData - data supplied with callback. It's a CordbProcess* object.
+//
+
+// static
+void CordbProcess::ThreadEnumerationCallback(VMPTR_Thread vmThread, void * pUserData)
+{
+ CordbProcess * pThis = reinterpret_cast<CordbProcess *> (pUserData);
+ INTERNAL_DAC_CALLBACK(pThis);
+
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "ThreadEnumerationCallback()\n");
+
+ // Do lookup / lazy-create.
+ pThis->LookupOrCreateThread(vmThread);
+}
+
+//---------------------------------------------------------------------------------------
+// Fully build up the CordbThread cache to match VM state.
+void CordbProcess::PrepopulateThreadsOrThrow()
+{
+ RSLockHolder lockHolder(GetProcessLock());
+ if (IsDacInitialized())
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "PrepopulateThreadsOrThrow()\n");
+ GetDAC()->EnumerateThreads(ThreadEnumerationCallback, this);
+ }
+}
+
+//---------------------------------------------------------------------------------------
+// Create a Thread enumerator
+//
+// Arguments:
+// pOwnerObj - object (a CordbProcess or CordbThread) that will own the enumerator.
+// pOwnerList - the neuter list that the enumerator will live on
+// pHolder - an outparameter for the enumerator to be initialized.
+//
+void CordbProcess::BuildThreadEnum(CordbBase * pOwnerObj, NeuterList * pOwnerList, RSInitHolder<CordbHashTableEnum> * pHolder)
+{
+ CordbHashTableEnum::BuildOrThrow(
+ pOwnerObj,
+ pOwnerList,
+ &m_userThreads,
+ IID_ICorDebugThreadEnum,
+ pHolder);
+}
+
+// Public implementation of ICorDebugProcess::EnumerateThreads
+HRESULT CordbProcess::EnumerateThreads(ICorDebugThreadEnum **ppThreads)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+ {
+ if (m_detached)
+ {
+ // #Detach_Check:
+ //
+ // FUTURE: Consider adding this IF block to the PUBLIC_API macros so that
+ // typical public APIs fail quickly if we're trying to do a detach. For
+ // now, I'm hand-adding this check only to the few problematic APIs that get
+ // called while queuing the fake attach events. In these cases, it is not
+ // enough to check if CordbProcess::IsNeutered(), as the detaching thread
+ // may have begun the detaching and neutering process, but not be
+ // finished--in which case m_detached is true, but
+ // CordbProcess::IsNeutered() is still false.
+ ThrowHR(CORDBG_E_PROCESS_DETACHED);
+ }
+
+ ValidateOrThrow(ppThreads);
+
+ RSInitHolder<CordbHashTableEnum> pEnum;
+ InternalEnumerateThreads(pEnum.GetAddr());
+
+ pEnum.TransferOwnershipExternal(ppThreads);
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+// Internal implementation of EnumerateThreads
+VOID CordbProcess::InternalEnumerateThreads(RSInitHolder<CordbHashTableEnum> *ppThreads)
+{
+ INTERNAL_API_ENTRY(this);
+ // Needs to prepopulate
+ PrepopulateThreadsOrThrow();
+ BuildThreadEnum(this, this->GetContinueNeuterList(), ppThreads);
+}
+
+// Implementation of ICorDebugProcess::GetThread
+HRESULT CordbProcess::GetThread(DWORD dwThreadId, ICorDebugThread **ppThread)
+{
+ PUBLIC_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(ppThread, ICorDebugThread **);
+
+ // No good pre-existing ATT_* contract for this.
+ // Because for legacy, we have to allow this on the win32 event thread.
+ *ppThread = NULL;
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ RSLockHolder lockHolder(GetProcessLock());
+ if (m_detached)
+ {
+ // See code:CordbProcess::EnumerateThreads#Detach_Check
+ ThrowHR(CORDBG_E_PROCESS_DETACHED);
+ }
+ CordbThread * pThread = TryLookupOrCreateThreadByVolatileOSId(dwThreadId);
+ if (pThread == NULL)
+ {
+ // This is a common case because we may be looking up an unmanaged thread.
+ hr = E_INVALIDARG;
+ }
+ else
+ {
+ *ppThread = static_cast<ICorDebugThread*> (pThread);
+ pThread->ExternalAddRef();
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ LOG((LF_CORDB, LL_INFO10000, "CP::GT returns id=0x%x hr=0x%x ppThread=0x%p",
+ dwThreadId, hr, *ppThread));
+ return hr;
+}
+
+HRESULT CordbProcess::ThreadForFiberCookie(DWORD fiberCookie,
+ ICorDebugThread **ppThread)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT CordbProcess::GetHelperThreadID(DWORD *pThreadID)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ _ASSERTE(m_pShim != NULL);
+ if (pThreadID == NULL)
+ {
+ return (E_INVALIDARG);
+ }
+
+ HRESULT hr = S_OK;
+ // Return the ID of the current helper thread. There may be no thread in the process, or there may be a true helper
+ // thread.
+ if ((m_helperThreadId != 0) && !m_helperThreadDead)
+ {
+ *pThreadID = m_helperThreadId;
+ }
+ else if ((GetDCB() != NULL) && (GetDCB()->m_helperThreadId != 0))
+ {
+ EX_TRY
+ {
+ // be sure we have the latest information
+ UpdateRightSideDCB();
+ *pThreadID = GetDCB()->m_helperThreadId;
+ }
+ EX_CATCH_HRESULT(hr);
+
+ }
+ else
+ {
+ *pThreadID = 0;
+ }
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Sends IPC event to set all the managed threads, except for the one given, to the given state
+//
+// Arguments:
+// state - The state to set the threads to.
+// pExceptThread - The thread to not set. This is usually the thread that is currently
+// sending an IPC event to the RS, and should be excluded.
+//
+// Return Value:
+// Typical HRESULT symantics, nothing abnormal.
+//
+HRESULT CordbProcess::SetAllThreadsDebugState(CorDebugThreadState state,
+ ICorDebugThread * pExceptThread)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pExceptThread, ICorDebugThread *);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this);
+
+ if (GetShim() == NULL)
+ {
+ return E_NOTIMPL;
+ }
+ CordbThread * pCordbExceptThread = static_cast<CordbThread *> (pExceptThread);
+
+ LOG((LF_CORDB, LL_INFO1000, "CP::SATDS: except thread=0x%08x 0x%x\n",
+ pExceptThread,
+ (pCordbExceptThread != NULL) ? pCordbExceptThread->m_id : 0));
+
+ // Send one event to the Left Side to twiddle each thread's state.
+ DebuggerIPCEvent event;
+
+ InitIPCEvent(&event, DB_IPCE_SET_ALL_DEBUG_STATE, true, VMPTR_AppDomain::NullPtr());
+
+ event.SetAllDebugState.vmThreadToken = ((pCordbExceptThread != NULL) ?
+ pCordbExceptThread->m_vmThreadToken : VMPTR_Thread::NullPtr());
+
+ event.SetAllDebugState.debugState = state;
+
+ HRESULT hr = SendIPCEvent(&event, sizeof(DebuggerIPCEvent));
+
+ hr = WORST_HR(hr, event.hr);
+
+ // If that worked, then loop over all the threads on this side and set their states.
+ if (SUCCEEDED(hr))
+ {
+ RSLockHolder lockHolder(GetProcessLock());
+ HASHFIND hashFind;
+ CordbThread * pThread;
+
+ // We don't need to prepopulate here (to collect LS state) because we're just updating RS state.
+ for (pThread = m_userThreads.FindFirst(&hashFind);
+ pThread != NULL;
+ pThread = m_userThreads.FindNext(&hashFind))
+ {
+ if (pThread != pCordbExceptThread)
+ {
+ pThread->m_debugState = state;
+ }
+ }
+ }
+
+ return hr;
+}
+
+
+HRESULT CordbProcess::EnumerateObjects(ICorDebugObjectEnum **ppObjects)
+{
+ /* !!! */
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppObjects, ICorDebugObjectEnum **);
+
+ return E_NOTIMPL;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Determines if the target address is a "CLR transition stub".
+//
+// Arguments:
+// address - The address of an instruction to check in the target address space.
+// pfTransitionStub - Space to store the result, TRUE if the address belongs to a
+// transition stub, FALSE if not. Only valid if this method returns a success code.
+//
+// Return Value:
+// Typical HRESULT symantics, nothing abnormal.
+//
+//---------------------------------------------------------------------------------------
+HRESULT CordbProcess::IsTransitionStub(CORDB_ADDRESS address, BOOL *pfTransitionStub)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pfTransitionStub, BOOL *);
+
+ // Default to FALSE
+ *pfTransitionStub = FALSE;
+
+ if (this->m_helperThreadDead)
+ {
+ return S_OK;
+ }
+
+ // If we're not initialized, then it can't be a stub...
+ if (!m_initialized)
+ {
+ return S_OK;
+ }
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ DebuggerIPCEvent eventData;
+
+ InitIPCEvent(&eventData, DB_IPCE_IS_TRANSITION_STUB, true, VMPTR_AppDomain::NullPtr());
+
+ eventData.IsTransitionStub.address = CORDB_ADDRESS_TO_PTR(address);
+
+ hr = SendIPCEvent(&eventData, sizeof(eventData));
+ hr = WORST_HR(hr, eventData.hr);
+ IfFailThrow(hr);
+
+ _ASSERTE(eventData.type == DB_IPCE_IS_TRANSITION_STUB_RESULT);
+
+ *pfTransitionStub = eventData.IsTransitionStubResult.isStub;
+ LOG((LF_CORDB, LL_INFO1000, "CP::ITS: addr=0x%p result=%d\n", address, *pfTransitionStub));
+ // @todo - beware that IsTransitionStub has a very important sideeffect - it synchronizes the runtime!
+ // This for example covers an OS bug where SetThreadContext may silently fail if we're not synchronized.
+ // (See IMDArocess::SetThreadContext for details on that bug).
+ // If we ever stop using IPC events here and only use DAC; we need to be aware of that.
+
+ // Check against DAC primitives
+ {
+ BOOL fIsStub2 = GetDAC()->IsTransitionStub(address);
+ (void)fIsStub2; //prevent "unused variable" error from GCC
+ CONSISTENCY_CHECK_MSGF(*pfTransitionStub == fIsStub2, ("IsStub2 failed, DAC2:%d, IPC:%d, addr:0x%p", (int) fIsStub2, (int) *pfTransitionStub, CORDB_ADDRESS_TO_PTR(address)));
+
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ if(FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::ITS: FAILED hr=0x%x\n", hr));
+ }
+ return hr;
+}
+
+
+HRESULT CordbProcess::SetStopState(DWORD threadID, CorDebugThreadState state)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ return E_NOTIMPL;
+}
+
+HRESULT CordbProcess::IsOSSuspended(DWORD threadID, BOOL *pbSuspended)
+{
+ PUBLIC_API_ENTRY(this);
+ // Gotta have a place for the result!
+ if (!pbSuspended)
+ return E_INVALIDARG;
+
+ FAIL_IF_NEUTERED(this);
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ RSLockHolder lockHolder(GetProcessLock());
+
+ // Have we seen this thread?
+ CordbUnmanagedThread *ut = GetUnmanagedThread(threadID);
+
+ // If we have, and if we've suspended it, then say so.
+ if (ut && ut->IsSuspended())
+ {
+ *pbSuspended = TRUE;
+ }
+ else
+ {
+ *pbSuspended = FALSE;
+ }
+#else
+ // Not interop-debugging, we never OS suspend.
+ *pbSuspended = FALSE;
+#endif
+ return S_OK;
+}
+
+//
+// This routine reads a thread context from the process being debugged, taking into account the fact that the context
+// record may be a different size than the one we compiled with. On systems < NT5, then OS doesn't usually allocate
+// space for the extended registers. However, the CONTEXT struct that we compile with does have this space.
+//
+HRESULT CordbProcess::SafeReadThreadContext(LSPTR_CONTEXT pContext, DT_CONTEXT * pCtx)
+{
+ HRESULT hr = S_OK;
+
+ INTERNAL_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ EX_TRY
+ {
+
+ void *pRemoteContext = pContext.UnsafeGet();
+ TargetBuffer tbFull(pRemoteContext, sizeof(DT_CONTEXT));
+
+ // The context may have 2 parts:
+ // 1. Base register, which are always present.
+ // 2. Optional extended registers, which are only present if CONTEXT_EXTENDED_REGISTERS is set
+ // in the flags.
+
+ // At a minimum we have room for a whole context up to the extended registers.
+ #if defined(DT_CONTEXT_EXTENDED_REGISTERS)
+ ULONG32 minContextSize = offsetof(DT_CONTEXT, ExtendedRegisters);
+ #else
+ ULONG32 minContextSize = sizeof(DT_CONTEXT);
+ #endif
+
+ // Read the minimum part.
+ TargetBuffer tbMin = tbFull.SubBuffer(0, minContextSize);
+ SafeReadBuffer(tbMin, (BYTE*) pCtx);
+
+ #if defined(DT_CONTEXT_EXTENDED_REGISTERS)
+ void *pCurExtReg = (void*)((UINT_PTR)pCtx + minContextSize);
+ TargetBuffer tbExtended = tbFull.SubBuffer(minContextSize);
+
+ // Now, read the extended registers if the context contains them. If the context does not have extended registers,
+ // just set them to zero.
+ if (SUCCEEDED(hr) && (pCtx->ContextFlags & CONTEXT_EXTENDED_REGISTERS) == CONTEXT_EXTENDED_REGISTERS)
+ {
+ SafeReadBuffer(tbExtended, (BYTE*) pCurExtReg);
+ }
+ else
+ {
+ memset(pCurExtReg, 0, tbExtended.cbSize);
+ }
+ #endif
+
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+//
+// This routine writes a thread context to the process being debugged, taking into account the fact that the context
+// record may be a different size than the one we compiled with. On systems < NT5, then OS doesn't usually allocate
+// space for the extended registers. However, the CONTEXT struct that we compile with does have this space.
+//
+HRESULT CordbProcess::SafeWriteThreadContext(LSPTR_CONTEXT pContext, const DT_CONTEXT * pCtx)
+{
+ INTERNAL_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ HRESULT hr = S_OK;
+ DWORD sizeToWrite = sizeof(DT_CONTEXT);
+
+ BYTE * pRemoteContext = (BYTE*) pContext.UnsafeGet();
+ BYTE * pCtxSource = (BYTE*) pCtx;
+
+
+#if defined(DT_CONTEXT_EXTENDED_REGISTERS)
+ // If our context has extended registers, then write the whole thing. Otherwise, just write the minimum part.
+ if ((pCtx->ContextFlags & DT_CONTEXT_EXTENDED_REGISTERS) != DT_CONTEXT_EXTENDED_REGISTERS)
+ {
+ sizeToWrite = offsetof(DT_CONTEXT, ExtendedRegisters);
+ }
+#endif
+
+// 64 bit windows puts space for the first 6 stack parameters in the CONTEXT structure so that
+// kernel to usermode transitions don't have to allocate a CONTEXT and do a seperate sub rsp
+// to allocate stack spill space for the arguments. This means that writing to P1Home - P6Home
+// will overwrite the arguments of some function higher on the stack, very bad. Conceptually you
+// can think of these members as not being part of the context, ie they don't represent something
+// which gets saved or restored on context switches. They are just space we shouldn't overwrite.
+// See issue 630276 for more details.
+#if defined DBG_TARGET_AMD64
+ pRemoteContext += offsetof(CONTEXT, ContextFlags); // immediately follows the 6 parameters P1-P6
+ pCtxSource += offsetof(CONTEXT, ContextFlags);
+ sizeToWrite -= offsetof(CONTEXT, ContextFlags);
+#endif
+
+ EX_TRY
+ {
+ // Write the context.
+ TargetBuffer tb(pRemoteContext, sizeToWrite);
+ SafeWriteBuffer(tb, (const BYTE*) pCtxSource);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+
+HRESULT CordbProcess::GetThreadContext(DWORD threadID, ULONG32 contextSize, BYTE context[])
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ FAIL_IF_MANAGED_ONLY(this);
+
+ DT_CONTEXT * pContext;
+ LOG((LF_CORDB, LL_INFO10000, "CP::GTC: thread=0x%x\n", threadID));
+
+ RSLockHolder lockHolder(GetProcessLock());
+
+ if (contextSize != sizeof(DT_CONTEXT))
+ {
+ LOG((LF_CORDB, LL_INFO10000, "CP::GTC: thread=0x%x, context size is invalid.\n", threadID));
+ return E_INVALIDARG;
+ }
+
+ pContext = reinterpret_cast<DT_CONTEXT *>(context);
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(context, BYTE, contextSize, true, true);
+
+#if !defined(FEATURE_INTEROP_DEBUGGING)
+ return E_NOTIMPL;
+#else
+ // Find the unmanaged thread
+ CordbUnmanagedThread *ut = GetUnmanagedThread(threadID);
+
+ if (ut == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "CP::GTC: thread=0x%x, thread id is invalid.\n", threadID));
+
+ return E_INVALIDARG;
+ }
+
+ return ut->GetThreadContext((DT_CONTEXT*)context);
+#endif // FEATURE_INTEROP_DEBUGGING
+}
+
+// Public implementation of ICorDebugProcess::SetThreadContext.
+// @dbgtodo interop-debugging: this should go away in V3. Use the data-target instead. This is
+// interop-debugging aware (and cooperates with hijacks)
+HRESULT CordbProcess::SetThreadContext(DWORD threadID, ULONG32 contextSize, BYTE context[])
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_MANAGED_ONLY(this);
+
+#if !defined(FEATURE_INTEROP_DEBUGGING)
+ return E_NOTIMPL;
+#else
+ HRESULT hr = S_OK;
+
+ RSLockHolder lockHolder(GetProcessLock());
+
+ CordbUnmanagedThread *ut = NULL;
+
+ if (contextSize != sizeof(DT_CONTEXT))
+ {
+ LOG((LF_CORDB, LL_INFO10000, "CP::STC: thread=0x%x, context size is invalid.\n", threadID));
+ hr = E_INVALIDARG;
+ goto Label_Done;
+ }
+
+ // @todo - could we look at the context flags and return E_INVALIDARG if they're bad?
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(context, BYTE, contextSize, true, true);
+
+ // Find the unmanaged thread
+ ut = GetUnmanagedThread(threadID);
+
+ if (ut == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "CP::STC: thread=0x%x, thread is invalid.\n", threadID));
+ hr = E_INVALIDARG;
+ goto Label_Done;
+ }
+
+ hr = ut->SetThreadContext((DT_CONTEXT*)context);
+
+
+ // Update the register set for the leaf-unmanaged chain so that it's consistent w/ the context.
+ // We may not necessarily be synchronized, and so these frames may be stale. Even so, no harm done.
+ if (SUCCEEDED(hr))
+ {
+ // @dbgtodo stackwalk: this should all disappear with V3 stackwalker and getting rid of SetThreadContext.
+ EX_TRY
+ {
+ // Find the managed thread. Returns NULL if thread is not managed.
+ // If we don't have a thread prveiously cached, then there's no state to update.
+ CordbThread * pThread = TryLookupThreadByVolatileOSId(threadID);
+
+ if (pThread != NULL)
+ {
+ // In V2, we used to update the CONTEXT of the leaf chain if the chain is an unmanaged chain.
+ // In Arrowhead, we just force a cleanup of the stackwalk cache. This is a more correct
+ // thing to do anyway, since the CONTEXT being set could be anything.
+ pThread->CleanupStack();
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ }
+
+Label_Done:
+ return ErrWrapper(hr);
+
+#endif // FEATURE_INTEROP_DEBUGGING
+}
+
+
+// @dbgtodo ICDProcess - When we DACize this function, we should use code:DacReplacePatches
+HRESULT CordbProcess::ReadMemory(CORDB_ADDRESS address,
+ DWORD size,
+ BYTE buffer[],
+ SIZE_T *read)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ // A read of 0 bytes is okay.
+ if (size == 0)
+ return S_OK;
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(buffer, BYTE, size, true, true);
+ VALIDATE_POINTER_TO_OBJECT(buffer, SIZE_T *);
+
+ if (address == NULL)
+ return E_INVALIDARG;
+
+ // If no read parameter is supplied, we ignore it. This matches the semantics of kernel32!ReadProcessMemory.
+ SIZE_T dummyRead;
+ if (read == NULL)
+ {
+ read = &dummyRead;
+ }
+ *read = 0;
+
+ HRESULT hr = S_OK;
+
+ CORDBRequireProcessStateOK(this);
+
+ // Grab the memory we want to read
+ // Note that this will return success on a partial read
+ ULONG32 cbRead;
+ hr = GetDataTarget()->ReadVirtual(address, buffer, size, &cbRead);
+ if (FAILED(hr))
+ {
+ hr = CORDBG_E_READVIRTUAL_FAILURE;
+ goto LExit;
+ }
+
+ // Read at least one byte
+ *read = (SIZE_T) cbRead;
+
+ // There seem to be strange cases where ReadProcessMemory will return a seemingly negative number into *read, which
+ // is an unsigned value. So we check the sanity of *read by ensuring that its no bigger than the size we tried to
+ // read.
+ if ((*read > 0) && (*read <= size))
+ {
+ LOG((LF_CORDB, LL_INFO100000, "CP::RM: read %d bytes from 0x%08x, first byte is 0x%x\n",
+ *read, (DWORD)address, buffer[0]));
+
+ if (m_initialized)
+ {
+ RSLockHolder ch(&this->m_processMutex);
+
+ // If m_pPatchTable is NULL, then it's been cleaned out b/c of a Continue for the left side. Get the table
+ // again. Only do this, of course, if the managed state of the process is initialized.
+ if (m_pPatchTable == NULL)
+ {
+ hr = RefreshPatchTable(address, *read, buffer);
+ }
+ else
+ {
+ // The previously fetched table is still good, so run through it & see if any patches are applicable
+ hr = AdjustBuffer(address, *read, buffer, NULL, AB_READ);
+ }
+ }
+ }
+
+LExit:
+ if (FAILED(hr))
+ {
+ RSLockHolder ch(&this->m_processMutex);
+ ClearPatchTable();
+ }
+ else if (*read < size)
+ {
+ // Unlike the DT api, our API is supposed to return an error on partial read
+ hr = HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY);
+ }
+ return hr;
+}
+
+// Update patches & buffer to make the left-side's usage of patches transparent
+// to our client. Behavior depends on AB_MODE:
+// AB_READ:
+// - use the RS patch table structure to replace patch opcodes in buffer.
+// AB_WRITE:
+// - update the RS patch table structure w/ new replace-opcode values
+// if we've written over them. And put the int3 back in for write-memory.
+//
+// Note: If we're writing memory over top of a patch, then it must be JITted or stub code.
+// Writing over JITed or Stub code can be dangerous since the CLR may not expect it
+// (eg. JIT data structures about the code layout may be incorrect), but in certain
+// narrow cases it may be safe (eg. replacing a constant). VS says they wouldn't expect
+// this to work, but we'll keep the support in for legacy reasons.
+//
+// address, size - describe buffer in LS memory
+// buffer - local copy of buffer that will be read/written from/to LS.
+// bufferCopy - for writeprocessmemory, copy of original buffer (w/o injected patches)
+// pbUpdatePatchTable - flag if patchtable got dirty and needs to be updated.
+HRESULT CordbProcess::AdjustBuffer( CORDB_ADDRESS address,
+ SIZE_T size,
+ BYTE buffer[],
+ BYTE **bufferCopy,
+ AB_MODE mode,
+ BOOL *pbUpdatePatchTable)
+{
+ INTERNAL_API_ENTRY(this);
+
+ _ASSERTE(m_initialized);
+ _ASSERTE(this->ThreadHoldsProcessLock());
+
+ if ( address == NULL
+ || size == NULL
+ || buffer == NULL
+ || (mode != AB_READ && mode != AB_WRITE) )
+ return E_INVALIDARG;
+
+ if (pbUpdatePatchTable != NULL )
+ *pbUpdatePatchTable = FALSE;
+
+ // If we don't have a patch table loaded, then return S_OK since there are no patches to adjust
+ if (m_pPatchTable == NULL)
+ return S_OK;
+
+ //is the requested memory completely out-of-range?
+ if ((m_minPatchAddr > (address + (size - 1))) ||
+ (m_maxPatchAddr < address))
+ {
+ return S_OK;
+ }
+
+ // Without runtime offsets, we can't adjust - this should only ever happen on dumps, where there's
+ // no W32ET to get the offsets, and so they stay zeroed
+ if (!m_runtimeOffsetsInitialized)
+ return S_OK;
+
+ LOG((LF_CORDB,LL_INFO10000, "CordbProcess::AdjustBuffer at addr 0x%p\n", address));
+
+ if (mode == AB_WRITE)
+ {
+ // We don't want to mess up the original copy of the buffer, so
+ // for right now, just copy it wholesale.
+ (*bufferCopy) = new (nothrow) BYTE[size];
+ if (NULL == (*bufferCopy))
+ return E_OUTOFMEMORY;
+
+ memmove((*bufferCopy), buffer, size);
+ }
+
+ ULONG iNextFree = m_iFirstPatch;
+ while( iNextFree != DPT_TERMINATING_INDEX )
+ {
+ BYTE *DebuggerControllerPatch = m_pPatchTable + m_runtimeOffsets.m_cbPatch*iNextFree;
+ PRD_TYPE opcode = *(PRD_TYPE *)(DebuggerControllerPatch + m_runtimeOffsets.m_offOpcode);
+ CORDB_ADDRESS patchAddress = PTR_TO_CORDB_ADDRESS(*(BYTE**)(DebuggerControllerPatch + m_runtimeOffsets.m_offAddr));
+
+ if (IsPatchInRequestedRange(address, size, patchAddress))
+ {
+ if (mode == AB_READ)
+ {
+ CORDbgSetInstructionEx(buffer, address, patchAddress, opcode, size);
+ }
+ else if (mode == AB_WRITE)
+ {
+ _ASSERTE( pbUpdatePatchTable != NULL );
+ _ASSERTE( bufferCopy != NULL );
+
+ //There can be multiple patches at the same address: we don't want 2nd+ patches to get the
+ // break opcode, so we read from the unmodified copy.
+ m_rgUncommitedOpcode[iNextFree] =
+ CORDbgGetInstructionEx(*bufferCopy, address, patchAddress, opcode, size);
+
+ //put the breakpoint into the memory itself
+ CORDbgInsertBreakpointEx(buffer, address, patchAddress, opcode, size);
+
+ *pbUpdatePatchTable = TRUE;
+ }
+ else
+ _ASSERTE( !"CordbProcess::AdjustBuffergiven non(Read|Write) mode!" );
+ }
+
+ iNextFree = m_rgNextPatch[iNextFree];
+ }
+
+ // If we created a copy of the buffer but didn't modify it, then free it now.
+ if( ( mode == AB_WRITE ) && ( !*pbUpdatePatchTable ) )
+ {
+ delete [] *bufferCopy;
+ *bufferCopy = NULL;
+ }
+
+ return S_OK;
+}
+
+
+void CordbProcess::CommitBufferAdjustments( CORDB_ADDRESS start,
+ CORDB_ADDRESS end )
+{
+ INTERNAL_API_ENTRY(this);
+
+ _ASSERTE(m_initialized);
+ _ASSERTE(this->ThreadHoldsProcessLock());
+ _ASSERTE(m_runtimeOffsetsInitialized);
+
+ ULONG iPatch = m_iFirstPatch;
+ while( iPatch != DPT_TERMINATING_INDEX )
+ {
+ BYTE *DebuggerControllerPatch = m_pPatchTable +
+ m_runtimeOffsets.m_cbPatch*iPatch;
+
+ BYTE *patchAddress = *(BYTE**)(DebuggerControllerPatch + m_runtimeOffsets.m_offAddr);
+
+ if (IsPatchInRequestedRange(start, (SIZE_T)(end - start), PTR_TO_CORDB_ADDRESS(patchAddress)) &&
+ !PRDIsBreakInst(&(m_rgUncommitedOpcode[iPatch])))
+ {
+ //copy this back to the copy of the patch table
+ *(PRD_TYPE *)(DebuggerControllerPatch + m_runtimeOffsets.m_offOpcode) =
+ m_rgUncommitedOpcode[iPatch];
+ }
+
+ iPatch = m_rgNextPatch[iPatch];
+ }
+}
+
+void CordbProcess::ClearBufferAdjustments( )
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(this->ThreadHoldsProcessLock());
+
+ ULONG iPatch = m_iFirstPatch;
+ while( iPatch != DPT_TERMINATING_INDEX )
+ {
+ InitializePRDToBreakInst(&(m_rgUncommitedOpcode[iPatch]));
+ iPatch = m_rgNextPatch[iPatch];
+ }
+}
+
+void CordbProcess::ClearPatchTable(void )
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(this->ThreadHoldsProcessLock());
+
+ if (m_pPatchTable != NULL )
+ {
+ delete [] m_pPatchTable;
+ m_pPatchTable = NULL;
+
+ delete [] m_rgNextPatch;
+ m_rgNextPatch = NULL;
+
+ delete [] m_rgUncommitedOpcode;
+ m_rgUncommitedOpcode = NULL;
+
+ m_iFirstPatch = DPT_TERMINATING_INDEX;
+ m_minPatchAddr = MAX_ADDRESS;
+ m_maxPatchAddr = MIN_ADDRESS;
+ m_rgData = NULL;
+ m_cPatch = 0;
+ }
+}
+
+HRESULT CordbProcess::RefreshPatchTable(CORDB_ADDRESS address, SIZE_T size, BYTE buffer[])
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ }
+ CONTRACTL_END;
+
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(m_initialized);
+ _ASSERTE(this->ThreadHoldsProcessLock());
+
+ HRESULT hr = S_OK;
+ BYTE *rgb = NULL;
+
+ // All of m_runtimeOffsets will be zeroed out if there's been no call to code:CordbProcess::GetRuntimeOffsets.
+ // Thus for things to work, we'd have to have a live target that went and got the real values.
+ // For dumps, things are still all zeroed out because we don't have any events sent to the W32ET, don't
+ // have a live process to investigate, etc.
+ if (!m_runtimeOffsetsInitialized)
+ return S_OK;
+
+ _ASSERTE( m_runtimeOffsets.m_cbOpcode == sizeof(PRD_TYPE) );
+
+ CORDBRequireProcessStateOK(this);
+
+ if (m_pPatchTable == NULL )
+ {
+ // First, check to be sure the patch table is valid on the Left Side. If its not, then we won't read it.
+ BOOL fPatchTableValid = FALSE;
+
+ hr = SafeReadStruct(PTR_TO_CORDB_ADDRESS(m_runtimeOffsets.m_pPatchTableValid), &fPatchTableValid);
+ if (FAILED(hr) || !fPatchTableValid)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "Wont refresh patch table because its not valid now.\n"));
+ return S_OK;
+ }
+
+ SIZE_T offStart = 0;
+ SIZE_T offEnd = 0;
+ UINT cbTableSlice = 0;
+
+ // Grab the patch table info
+ offStart = min(m_runtimeOffsets.m_offRgData, m_runtimeOffsets.m_offCData);
+ offEnd = max(m_runtimeOffsets.m_offRgData, m_runtimeOffsets.m_offCData) + sizeof(SIZE_T);
+ cbTableSlice = (UINT)(offEnd - offStart);
+
+ if (cbTableSlice == 0)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "Wont refresh patch table because its not valid now.\n"));
+ return S_OK;
+ }
+
+ EX_TRY
+ {
+ rgb = new BYTE[cbTableSlice]; // throws
+
+ TargetBuffer tbSlice((BYTE*)m_runtimeOffsets.m_pPatches + offStart, cbTableSlice);
+ this->SafeReadBuffer(tbSlice, rgb); // Throws;
+
+ // Note that rgData is a pointer in the left side address space
+ m_rgData = *(BYTE**)(rgb + m_runtimeOffsets.m_offRgData - offStart);
+ m_cPatch = *(ULONG*)(rgb + m_runtimeOffsets.m_offCData - offStart);
+
+ // Grab the patch table
+ UINT cbPatchTable = (UINT)(m_cPatch * m_runtimeOffsets.m_cbPatch);
+
+ if (cbPatchTable == 0)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "Wont refresh patch table because its not valid now.\n"));
+ _ASSERTE(hr == S_OK);
+ goto LExit; // can't return since we're in a Try/Catch
+ }
+
+ // Throwing news
+ m_pPatchTable = new BYTE[ cbPatchTable ];
+ m_rgNextPatch = new ULONG[m_cPatch];
+ m_rgUncommitedOpcode = new PRD_TYPE[m_cPatch];
+
+ TargetBuffer tb(m_rgData, cbPatchTable);
+ this->SafeReadBuffer(tb, m_pPatchTable); // Throws
+
+ //As we go through the patch table we do a number of things:
+ //
+ // 1. collect min,max address seen for quick fail check
+ //
+ // 2. Link all valid entries into a linked list, the first entry of which is m_iFirstPatch
+ //
+ // 3. Initialize m_rgUncommitedOpcode, so that we can undo local patch table changes if WriteMemory can't write
+ // atomically.
+ //
+ // 4. If the patch is in the memory we grabbed, unapply it.
+
+ ULONG iDebuggerControllerPatchPrev = DPT_TERMINATING_INDEX;
+
+ m_minPatchAddr = MAX_ADDRESS;
+ m_maxPatchAddr = MIN_ADDRESS;
+ m_iFirstPatch = DPT_TERMINATING_INDEX;
+
+ for (ULONG iPatch = 0; iPatch < m_cPatch;iPatch++)
+ {
+ // <REVISIT_TODO>@todo port: we're making assumptions about the size of opcodes,address pointers, etc</REVISIT_TODO>
+ BYTE *DebuggerControllerPatch = m_pPatchTable + m_runtimeOffsets.m_cbPatch * iPatch;
+ PRD_TYPE opcode = *(PRD_TYPE*)(DebuggerControllerPatch + m_runtimeOffsets.m_offOpcode);
+ CORDB_ADDRESS patchAddress = PTR_TO_CORDB_ADDRESS(*(BYTE**)(DebuggerControllerPatch + m_runtimeOffsets.m_offAddr));
+
+ // A non-zero opcode indicates to us that this patch is valid.
+ if (!PRDIsEmpty(opcode))
+ {
+ _ASSERTE( patchAddress != 0 );
+
+ // (1), above
+ // Note that GetPatchEndAddr() returns the address immediately AFTER the patch,
+ // so we have to subtract 1 from it below.
+ if (m_minPatchAddr > patchAddress )
+ m_minPatchAddr = patchAddress;
+ if (m_maxPatchAddr < patchAddress )
+ m_maxPatchAddr = GetPatchEndAddr(patchAddress) - 1;
+
+ // (2), above
+ if ( m_iFirstPatch == DPT_TERMINATING_INDEX)
+ {
+ m_iFirstPatch = iPatch;
+ _ASSERTE( iPatch != DPT_TERMINATING_INDEX);
+ }
+
+ if (iDebuggerControllerPatchPrev != DPT_TERMINATING_INDEX)
+ {
+ m_rgNextPatch[iDebuggerControllerPatchPrev] = iPatch;
+ }
+
+ iDebuggerControllerPatchPrev = iPatch;
+
+ // (3), above
+ InitializePRDToBreakInst(&(m_rgUncommitedOpcode[iPatch]));
+
+ // (4), above
+ if (IsPatchInRequestedRange(address, size, patchAddress))
+ {
+ _ASSERTE( buffer != NULL );
+ _ASSERTE( size != NULL );
+
+
+ //unapply the patch here.
+ CORDbgSetInstructionEx(buffer, address, patchAddress, opcode, size);
+ }
+
+ }
+ }
+
+ if (iDebuggerControllerPatchPrev != DPT_TERMINATING_INDEX)
+ {
+ m_rgNextPatch[iDebuggerControllerPatchPrev] = DPT_TERMINATING_INDEX;
+ }
+ }
+LExit:
+ ;
+ EX_CATCH_HRESULT(hr);
+ }
+
+
+ if (rgb != NULL )
+ {
+ delete [] rgb;
+ }
+
+ if (FAILED( hr ) )
+ {
+ ClearPatchTable();
+ }
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Given an address, see if there is a patch in the patch table that matches it and return
+// if its an unmanaged patch or not.
+//
+// Arguments:
+// address - The address of an instruction to check in the target address space.
+// pfPatchFound - Space to store the result, TRUE if the address belongs to a
+// patch, FALSE if not. Only valid if this method returns a success code.
+// pfPatchIsUnmanaged - Space to store the result, TRUE if the address is a patch
+// and the patch is unmanaged, FALSE if not. Only valid if this method returns a
+// success code.
+//
+// Return Value:
+// Typical HRESULT symantics, nothing abnormal.
+//
+// Note: this method is pretty in-efficient. It refreshes the patch table, then scans it.
+// Refreshing the patch table involves a scan, too, so this method could be folded
+// with that.
+//
+//---------------------------------------------------------------------------------------
+HRESULT CordbProcess::FindPatchByAddress(CORDB_ADDRESS address, bool *pfPatchFound, bool *pfPatchIsUnmanaged)
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(ThreadHoldsProcessLock());
+ _ASSERTE((pfPatchFound != NULL) && (pfPatchIsUnmanaged != NULL));
+ _ASSERTE(m_runtimeOffsetsInitialized);
+ FAIL_IF_NEUTERED(this);
+
+ *pfPatchFound = false;
+ *pfPatchIsUnmanaged = false;
+
+ // First things first. If the process isn't initialized, then there can be no patch table, so we know the breakpoint
+ // doesn't belong to the Runtime.
+ if (!m_initialized)
+ {
+ return S_OK;
+ }
+
+ // This method is called from the main loop of the win32 event thread in response to a first chance breakpoint event
+ // that we know is not a flare. The process has been runnning, and it may have invalidated the patch table, so we'll
+ // flush it here before refreshing it to make sure we've got the right thing.
+ //
+ // Note: we really should have the Left Side mark the patch table dirty to help optimize this.
+ ClearPatchTable();
+
+ // Refresh the patch table.
+ HRESULT hr = RefreshPatchTable();
+
+ if (FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::FPBA: failed to refresh the patch table\n"));
+ return hr;
+ }
+
+ // If there is no patch table yet, then we know there is no patch at the given address, so return S_OK with
+ // *patchFound = false.
+ if (m_pPatchTable == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::FPBA: no patch table\n"));
+ return S_OK;
+ }
+
+ // Scan the patch table for a matching patch.
+ for (ULONG iNextPatch = m_iFirstPatch; iNextPatch != DPT_TERMINATING_INDEX; iNextPatch = m_rgNextPatch[iNextPatch])
+ {
+ BYTE *patch = m_pPatchTable + (m_runtimeOffsets.m_cbPatch * iNextPatch);
+ BYTE *patchAddress = *(BYTE**)(patch + m_runtimeOffsets.m_offAddr);
+ DWORD traceType = *(DWORD*)(patch + m_runtimeOffsets.m_offTraceType);
+
+ if (address == PTR_TO_CORDB_ADDRESS(patchAddress))
+ {
+ *pfPatchFound = true;
+
+ if (traceType == m_runtimeOffsets.m_traceTypeUnmanaged)
+ {
+ *pfPatchIsUnmanaged = true;
+
+#if defined(_DEBUG)
+ HRESULT hrDac = S_OK;
+ EX_TRY
+ {
+ // We should be able to double check w/ DAC that this really is outside of the runtime.
+ IDacDbiInterface::AddressType addrType = GetDAC()->GetAddressType(address);
+ CONSISTENCY_CHECK_MSGF(addrType == IDacDbiInterface::kAddressUnrecognized, ("Bad address type = %d", addrType));
+ }
+ EX_CATCH_HRESULT(hrDac);
+ CONSISTENCY_CHECK_MSGF(SUCCEEDED(hrDac), ("DAC::GetAddressType failed, hr=0x%08x", hrDac));
+#endif
+ }
+
+ break;
+ }
+ }
+
+ // If we didn't find a patch, its actually still possible that this breakpoint exception belongs to us. There are
+ // races with very large numbers of threads entering the Runtime through the same managed function. We will have
+ // multiple threads adding and removing ref counts to an int 3 in the code stream. Sometimes, this count will go to
+ // zero and the int 3 will be removed, then it will come back up and the int 3 will be replaced. The in-process
+ // logic takes pains to ensure that such cases are handled properly, therefore we need to perform the same check
+ // here to make the correct decision. Basically, the check is to see if there is indeed an int 3 at the exception
+ // address. If there is _not_ an int 3 there, then we've hit this race. We will lie and say a managed patch was
+ // found to cover this case. This is tracking the logic in DebuggerController::ScanForTriggers, where we call
+ // IsPatched.
+ if (*pfPatchFound == false)
+ {
+ // Read one instruction from the faulting address...
+#if defined(DBG_TARGET_ARM) || defined(DBG_TARGET_ARM64)
+ PRD_TYPE TrapCheck = 0;
+#else
+ BYTE TrapCheck = 0;
+#endif
+
+ HRESULT hr2 = SafeReadStruct(address, &TrapCheck);
+
+ if (SUCCEEDED(hr2) && (TrapCheck != CORDbg_BREAK_INSTRUCTION))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::FPBA: patchFound=true based on odd missing int 3 case.\n"));
+
+ *pfPatchFound = true;
+ }
+ }
+
+ LOG((LF_CORDB, LL_INFO1000, "CP::FPBA: patchFound=%d, patchIsUnmanaged=%d\n", *pfPatchFound, *pfPatchIsUnmanaged));
+
+ return S_OK;
+}
+
+HRESULT CordbProcess::WriteMemory(CORDB_ADDRESS address, DWORD size,
+ BYTE buffer[], SIZE_T *written)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ CORDBRequireProcessStateOK(this);
+ _ASSERTE(m_runtimeOffsetsInitialized);
+
+
+ if (size == 0 || address == NULL)
+ return E_INVALIDARG;
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(buffer, BYTE, size, true, true);
+ VALIDATE_POINTER_TO_OBJECT(written, SIZE_T *);
+
+
+#if defined(_DEBUG) && defined(FEATURE_INTEROP_DEBUGGING)
+ // Shouldn't be using this to write int3. Use UM BP API instead.
+ // This is technically legal (what if the '0xcc' is data or something), so we can't fail in retail.
+ // But we can add this debug-only check to help VS migrate to the new API.
+ static ConfigDWORD configCheckInt3;
+ DWORD fCheckInt3 = configCheckInt3.val(CLRConfig::INTERNAL_DbgCheckInt3);
+ if (fCheckInt3)
+ {
+#if defined(DBG_TARGET_X86) || defined(DBG_TARGET_AMD64)
+ if (size == 1 && buffer[0] == 0xCC)
+ {
+ CONSISTENCY_CHECK_MSGF(false,
+ ("You're using ICorDebugProcess::WriteMemory() to write an 'int3' (1 byte 0xCC) at address 0x%p.\n"
+ "If you're trying to set a breakpoint, you should be using ICorDebugProcess::SetUnmanagedBreakpoint() instead.\n"
+ "(This assert is only enabled under the COM+ knob DbgCheckInt3.)\n",
+ CORDB_ADDRESS_TO_PTR(address)));
+ }
+#endif // DBG_TARGET_X86 || DBG_TARGET_AMD64
+
+ // check if we're replaced an opcode.
+ if (size == 1)
+ {
+ RSLockHolder ch(&this->m_processMutex);
+
+ NativePatch * p = GetNativePatch(CORDB_ADDRESS_TO_PTR(address));
+ if (p != NULL)
+ {
+ CONSISTENCY_CHECK_MSGF(false,
+ ("You're using ICorDebugProcess::WriteMemory() to write an 'opcode (0x%x)' at address 0x%p.\n"
+ "There's already a native patch at that address from ICorDebugProcess::SetUnmanagedBreakpoint().\n"
+ "If you're trying to remove the breakpoint, use ICDProcess::ClearUnmanagedBreakpoint() instead.\n"
+ "(This assert is only enabled under the COM+ knob DbgCheckInt3.)\n",
+ (DWORD) (buffer[0]), CORDB_ADDRESS_TO_PTR(address)));
+ }
+ }
+ }
+#endif // _DEBUG && FEATURE_INTEROP_DEBUGGING
+
+
+ *written = 0;
+
+ HRESULT hr = S_OK;
+ HRESULT hrSaved = hr; // this will hold the 'real' hresult in case of a
+ // partially completed operation
+ HRESULT hrPartialCopy = HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY);
+
+ BOOL bUpdateOriginalPatchTable = FALSE;
+ BYTE *bufferCopy = NULL;
+
+ // Only update the patch table if the managed state of the process
+ // is initialized.
+ if (m_initialized)
+ {
+ RSLockHolder ch(&this->m_processMutex);
+
+ if (m_pPatchTable == NULL )
+ {
+ if (!SUCCEEDED( hr = RefreshPatchTable() ) )
+ {
+ goto LExit;
+ }
+ }
+
+ if ( !SUCCEEDED( hr = AdjustBuffer( address,
+ size,
+ buffer,
+ &bufferCopy,
+ AB_WRITE,
+ &bUpdateOriginalPatchTable)))
+ {
+ goto LExit;
+ }
+ }
+
+ //conveniently enough, SafeWriteBuffer will throw if it can't complete the entire operation
+ EX_TRY
+ {
+ TargetBuffer tb(address, size);
+ SafeWriteBuffer(tb, buffer); // throws
+ *written = tb.cbSize; // DT's Write does everything or fails.
+ }
+ EX_CATCH_HRESULT(hr);
+
+ if (FAILED(hr))
+ {
+ if(hr != hrPartialCopy)
+ goto LExit;
+ else
+ hrSaved = hr;
+ }
+
+
+ LOG((LF_CORDB, LL_INFO100000, "CP::WM: wrote %d bytes at 0x%08x, first byte is 0x%x\n",
+ *written, (DWORD)address, buffer[0]));
+
+ if (bUpdateOriginalPatchTable == TRUE )
+ {
+ {
+ RSLockHolder ch(&this->m_processMutex);
+
+ //don't tweak patch table for stuff that isn't written to LeftSide
+ CommitBufferAdjustments(address, address + *written);
+ }
+
+ // The only way this should be able to fail is if
+ //someone else fiddles with the memory protections on the
+ //left side while it's frozen
+ EX_TRY
+ {
+ TargetBuffer tb(m_rgData, (ULONG) (m_cPatch*m_runtimeOffsets.m_cbPatch));
+ SafeWriteBuffer(tb, m_pPatchTable);
+ }
+ EX_CATCH_HRESULT(hr);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+ }
+
+ // Since we may have
+ // overwritten anything (objects, code, etc), we should mark
+ // everything as needing to be re-cached.
+ m_continueCounter++;
+
+ LExit:
+ if (m_initialized)
+ {
+ RSLockHolder ch(&this->m_processMutex);
+ ClearBufferAdjustments( );
+ }
+
+ //we messed up our local copy, so get a clean copy the next time
+ //we need it
+ if (bUpdateOriginalPatchTable==TRUE)
+ {
+ if (bufferCopy != NULL)
+ {
+ memmove(buffer, bufferCopy, size);
+ delete bufferCopy;
+ }
+ }
+
+ if (FAILED( hr ))
+ {
+ //we messed up our local copy, so get a clean copy the next time
+ //we need it
+ if (bUpdateOriginalPatchTable==TRUE)
+ {
+ RSLockHolder ch(&this->m_processMutex);
+ ClearPatchTable();
+ }
+ }
+ else if( FAILED(hrSaved) )
+ {
+ hr = hrSaved;
+ }
+
+ return hr;
+}
+
+HRESULT CordbProcess::ClearCurrentException(DWORD threadID)
+{
+#ifndef FEATURE_INTEROP_DEBUGGING
+ return E_INVALIDARG;
+#else
+ PUBLIC_API_ENTRY(this);
+
+ RSLockHolder lockHolder(GetProcessLock());
+
+ // There's something wrong if you're calling this an there are no queued unmanaged events.
+ if ((m_unmanagedEventQueue == NULL) && (m_outOfBandEventQueue == NULL))
+ return E_INVALIDARG;
+
+ // Grab the unmanaged thread object.
+ CordbUnmanagedThread *pUThread = GetUnmanagedThread(threadID);
+
+ if (pUThread == NULL)
+ return E_INVALIDARG;
+
+ LOG((LF_CORDB, LL_INFO1000, "CP::CCE: tid=0x%x\n", threadID));
+
+ // We clear both the IB and OOB event.
+ if (pUThread->HasIBEvent() && !pUThread->IBEvent()->IsEventUserContinued())
+ {
+ pUThread->IBEvent()->SetState(CUES_ExceptionCleared);
+ }
+
+ if (pUThread->HasOOBEvent())
+ {
+ // must decide exception status _before_ we continue the event.
+ _ASSERTE(!pUThread->OOBEvent()->IsEventContinuedUnhijacked());
+ pUThread->OOBEvent()->SetState(CUES_ExceptionCleared);
+ }
+
+ // If the thread is hijacked, then set the thread's debugger word to 0 to indicate to it that the
+ // exception has been cleared.
+ if (pUThread->IsGenericHijacked())
+ {
+ HRESULT hr = pUThread->SetEEDebuggerWord(0);
+ _ASSERTE(SUCCEEDED(hr));
+ }
+
+ return S_OK;
+#endif // FEATURE_INTEROP_DEBUGGING
+}
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+CordbUnmanagedThread *CordbProcess::HandleUnmanagedCreateThread(DWORD dwThreadId, HANDLE hThread, void *lpThreadLocalBase)
+{
+ INTERNAL_API_ENTRY(this);
+ CordbUnmanagedThread *ut = new (nothrow) CordbUnmanagedThread(this, dwThreadId, hThread, lpThreadLocalBase);
+
+ if (ut != NULL)
+ {
+ HRESULT hr = m_unmanagedThreads.AddBase(ut); // InternalAddRef, release on EXIT_THREAD events.
+
+ if (!SUCCEEDED(hr))
+ {
+ delete ut;
+
+ LOG((LF_CORDB, LL_INFO10000, "Failed adding unmanaged thread to process!\n"));
+ CORDBSetUnrecoverableError(this, hr, 0);
+ }
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO10000, "New CordbThread failed!\n"));
+ CORDBSetUnrecoverableError(this, E_OUTOFMEMORY, 0);
+ }
+
+ return ut;
+}
+#endif // FEATURE_INTEROP_DEBUGGING
+
+
+//-----------------------------------------------------------------------------
+// Initializes the DAC
+// Arguments: none--initializes the DAC for this CordbProcess instance
+// Note: Throws on error
+//-----------------------------------------------------------------------------
+void CordbProcess::InitDac()
+{
+ // Go-Go DAC power!!
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ InitializeDac();
+ }
+ EX_CATCH_HRESULT(hr);
+
+ // We Need DAC to debug for both Managed & Interop.
+ if (FAILED(hr))
+ {
+ // We assert here b/c we're trying to be friendly. Most likely, the cause is either:
+ // - a bad installation
+ // - a CLR dev built mscorwks but didn't build DAC.
+ SIMPLIFYING_ASSUMPTION_MSGF(false, ("Failed to load DAC while for debugging. hr=0x%08x", hr));
+ ThrowHR(hr);
+ }
+} //CordbProcess::InitDac
+
+// Update the entire RS copy of the debugger control block by reading the LS copy. The RS copy is treated as
+// a throw-away temporary buffer, rather than a true cache. That is, we make no assumptions about the
+// validity of the information over time. Thus, before using any of the values, we need to update it. We
+// update everything for simplicity; any perf hit we take by doing this instead of updating the individual
+// fields we want at any given point isn't significant, particularly if we are updating multiple fields.
+
+// Arguments:
+// none, but reads process memory from the LS debugger control block
+// Return Value: none (copies from LS DCB to RS buffer GetDCB())
+// Note: throws if SafeReadBuffer fails
+void CordbProcess::UpdateRightSideDCB()
+{
+ IfFailThrow(m_pEventChannel->UpdateRightSideDCB());
+} // CordbProcess::UpdateRightSideDCB
+
+// Update a single field with a value stored in the RS copy of the DCB. We can't update the entire LS DCB
+// because in some cases, the LS and RS are simultaneously initializing the DCB. If we initialize a field on
+// the RS and write back the whole thing, we may overwrite something the LS has initialized in the interim.
+
+// Arguments:
+// input: rsFieldAddr - the address of the field in the RS copy of the DCB that we want to write back to
+// the LS DCB. We use this to compute the offset of the field from the beginning of the
+// DCB and then add this offset to the starting address of the LS DCB to get the LS
+// address of the field we are updating
+// size - the size of the field we're updating.
+// Return value: none
+// Note: throws if SafeWriteBuffer fails
+void CordbProcess::UpdateLeftSideDCBField(void * rsFieldAddr, SIZE_T size)
+{
+ IfFailThrow(m_pEventChannel->UpdateLeftSideDCBField(rsFieldAddr, size));
+} // CordbProcess::UpdateRightSideDCB
+
+
+//-----------------------------------------------------------------------------
+// Gets the remote address of the event block for the Target and verifies that it's valid.
+// We use this address when we need to read from or write to the debugger control block.
+// Also allocates the RS buffer used for temporary storage for information from the DCB and
+// copies the LS DCB into the RS buffer.
+// Arguments:
+// output: pfBlockExists - true iff the LS DCB has been successfully allocated. Note that
+// we need this information even if the function throws, so we can't simply send it back
+// as a return value.
+// Return value:
+// None, but allocates GetDCB() on success. If the LS DCB has not
+// been successfully initialized or if this throws, GetDCB() will be NULL.
+//
+// Notes:
+// Throws on error
+//
+//-----------------------------------------------------------------------------
+void CordbProcess::GetEventBlock(BOOL * pfBlockExists)
+{
+ if (GetDCB() == NULL) // we only need to do this once
+ {
+ _ASSERTE(m_pShim != NULL);
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ // This will Initialize the DAC/DBI interface.
+ BOOL fDacReady = TryInitializeDac();
+
+ if (fDacReady)
+ {
+ // Ensure that we have a DAC interface.
+ _ASSERTE(m_pDacPrimitives != NULL);
+
+ // This is not technically necessary for Mac debugging. The event channel doesn't rely on
+ // knowing the target address of the DCB on the LS.
+ CORDB_ADDRESS pLeftSideDCB = NULL;
+ pLeftSideDCB = (GetDAC()->GetDebuggerControlBlockAddress());
+ if (pLeftSideDCB == NULL)
+ {
+ *pfBlockExists = false;
+ ThrowHR(CORDBG_E_DEBUGGING_NOT_POSSIBLE);
+ }
+
+ IfFailThrow(NewEventChannelForThisPlatform(pLeftSideDCB,
+ m_pMutableDataTarget,
+ GetPid(),
+ m_pShim->GetMachineInfo(),
+ &m_pEventChannel));
+ _ASSERTE(m_pEventChannel != NULL);
+
+ // copy information from left side DCB
+ UpdateRightSideDCB();
+
+ // Verify that the control block is valid.
+ // This will throw on error.
+ VerifyControlBlock();
+
+ *pfBlockExists = true;
+ }
+ else
+ {
+ // we can't initialize the DAC, so we can't get the block
+ *pfBlockExists = false;
+ }
+ }
+ else // we got the block before
+ {
+ *pfBlockExists = true;
+ }
+
+} // CordbProcess::GetEventBlock()
+
+
+//
+// Verify that the version info in the control block matches what we expect. The minimum supported protocol from the
+// Left Side must be greater or equal to the minimum required protocol of the Right Side. Note: its the Left Side's job
+// to conform to whatever protocol the Right Side requires, so long as minimum is supported.
+//
+void CordbProcess::VerifyControlBlock()
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(m_pShim != NULL);
+
+ if (GetDCB()->m_DCBSize == 0)
+ {
+ // the LS is still initializing the DCB
+ ThrowHR(CORDBG_E_DEBUGGING_NOT_POSSIBLE);
+ }
+
+ // Fill in the protocol numbers for the Right Side and update the LS DCB.
+ GetDCB()->m_rightSideProtocolCurrent = CorDB_RightSideProtocolCurrent;
+ UpdateLeftSideDCBField(&(GetDCB()->m_rightSideProtocolCurrent), sizeof(GetDCB()->m_rightSideProtocolCurrent));
+
+ GetDCB()->m_rightSideProtocolMinSupported = CorDB_RightSideProtocolMinSupported;
+ UpdateLeftSideDCBField(&(GetDCB()->m_rightSideProtocolMinSupported),
+ sizeof(GetDCB()->m_rightSideProtocolMinSupported));
+
+ // For Telesto, Dbi and Wks have a more flexible versioning allowed, as described by the Debugger
+ // Version Protocol String in DEBUGGER_PROTOCOL_STRING in DbgIpcEvents.h. This allows different build
+ // numbers, but the other protocol numbers should still match.
+#if !defined(FEATURE_CORECLR)
+ bool fSkipVerCheck = false;
+#if _DEBUG
+ // In debug builds, allow us to disable the version check to help with applying hotfixes.
+ // The hotfix may be built against a compatible IPC protocol, but have a slightly different build number.
+ fSkipVerCheck = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgSkipVerCheck) != 0;
+#endif
+
+ if (!fSkipVerCheck)
+ {
+ //
+ // These asserts double check that the version of the Right Side matches the version of the left side.
+ //
+ // If you hit these asserts, it is probably because you rebuilt mscordbi without rebuilding mscorwks, or rebuilt
+ // mscorwks without rebuilding mscordbi. You might be able to ignore these asserts, but proceed at your own risk.
+ //
+ CONSISTENCY_CHECK_MSGF(VER_PRODUCTBUILD == GetDCB()->m_verMajor,
+ ("version of %s (%d) in the debuggee does not match version of mscordbi.dll (%d) in the debugger.\n"
+ "This means your setup is wrong. You can ignore this but proceed at your own risk.\n",
+ MAIN_CLR_DLL_NAME_A, GetDCB()->m_verMajor, VER_PRODUCTBUILD));
+ CONSISTENCY_CHECK_MSGF(VER_PRODUCTBUILD_QFE == GetDCB()->m_verMinor,
+ ("QFE version of %s (%d) in the debuggee does not match QFE version of mscordbi.dll (%d) in the debugger.\n"
+ "Both dlls have build # (%d).\n"
+ "This means your setup is wrong. You can ignore this but proceed at your own risk.\n",
+ MAIN_CLR_DLL_NAME_A, GetDCB()->m_verMinor, VER_PRODUCTBUILD_QFE, VER_PRODUCTBUILD));
+ }
+#endif // !FEATURE_CORECLR
+
+ // These assertions verify that the debug manager is behaving correctly.
+ // An assertion failure here means that the runtime version of the debuggee is different from the runtime version of
+ // the debugger is capable of debugging.
+
+ // The Debug Manager should properly match LS & RS, and thus guarantee that this assert should never fire.
+ // But just in case the installation is corrupted, we'll check it.
+ if (GetDCB()->m_DCBSize != sizeof(DebuggerIPCControlBlock))
+ {
+ CONSISTENCY_CHECK_MSGF(false, ("DCB in LS is %d bytes, in RS is %d bytes. Version mismatch!!\n",
+ GetDCB()->m_DCBSize, sizeof(DebuggerIPCControlBlock)));
+ ThrowHR(CORDBG_E_INCOMPATIBLE_PROTOCOL);
+ }
+
+ // The Left Side has to support at least our minimum required protocol.
+ if (GetDCB()->m_leftSideProtocolCurrent < GetDCB()->m_rightSideProtocolMinSupported)
+ {
+ _ASSERTE(GetDCB()->m_leftSideProtocolCurrent >= GetDCB()->m_rightSideProtocolMinSupported);
+ ThrowHR(CORDBG_E_INCOMPATIBLE_PROTOCOL);
+ }
+
+ // The Left Side has to be able to emulate at least our minimum required protocol.
+ if (GetDCB()->m_leftSideProtocolMinSupported > GetDCB()->m_rightSideProtocolCurrent)
+ {
+ _ASSERTE(GetDCB()->m_leftSideProtocolMinSupported <= GetDCB()->m_rightSideProtocolCurrent);
+ ThrowHR(CORDBG_E_INCOMPATIBLE_PROTOCOL);
+ }
+
+#ifdef _DEBUG
+ char buf[MAX_LONGPATH];
+ DWORD len = GetEnvironmentVariableA("CORDBG_NotCompatibleTest", buf, sizeof(buf));
+ _ASSERTE(len < sizeof(buf));
+
+ if (len > 0)
+ ThrowHR(CORDBG_E_INCOMPATIBLE_PROTOCOL);
+#endif
+
+ if (GetDCB()->m_bHostingInFiber)
+ {
+ ThrowHR(CORDBG_E_CANNOT_DEBUG_FIBER_PROCESS);
+ }
+
+ _ASSERTE(!GetDCB()->m_rightSideShouldCreateHelperThread);
+} // CordbProcess::VerifyControlBlock
+
+//-----------------------------------------------------------------------------
+// This is the CordbProcess objects chance to inspect the DCB and intialize stuff
+//
+// Return Value:
+// Typical HRESULT return values, nothing abnormal.
+// If succeeded, then the block exists and is valid.
+//
+//-----------------------------------------------------------------------------
+HRESULT CordbProcess::GetRuntimeOffsets()
+{
+ INTERNAL_API_ENTRY(this);
+
+ _ASSERTE(m_pShim != NULL);
+ UpdateRightSideDCB();
+
+ // Can't get a handle to the helper thread if the target is remote.
+
+ // If we got this far w/o failing, then we should be able to get the helper thread handle.
+ // RS will handle not having the helper-thread handle, so we just make a best effort here.
+ DWORD dwHelperTid = GetDCB()->m_realHelperThreadId;
+ _ASSERTE(dwHelperTid != 0);
+
+
+ {
+#if !defined FEATURE_CORESYSTEM
+ // kernel32!OpenThread does not exist on all platforms (missing on Win98).
+ // So we need to delay load it.
+ typedef HANDLE (WINAPI *FPOPENTHREAD)(DWORD dwDesiredAccess,
+ BOOL bInheritHandle,
+ DWORD dwThreadId);
+
+
+
+ HMODULE mod = WszGetModuleHandle(W("kernel32.dll"));
+
+ _ASSERTE(mod != NULL); // can't fail since Kernel32.dll is already loaded.
+
+ const FPOPENTHREAD pfnOpenThread = (FPOPENTHREAD)GetProcAddress(mod, "OpenThread");
+
+ if (pfnOpenThread != NULL)
+ {
+ m_hHelperThread = pfnOpenThread(SYNCHRONIZE, FALSE, dwHelperTid);
+ CONSISTENCY_CHECK_MSGF(m_hHelperThread != NULL, ("Failed to get helper-thread handle. tid=0x%x\n", dwHelperTid));
+ }
+#elif FEATURE_PAL
+ m_hHelperThread = NULL; //RS is supposed to be able to live without a helper thread handle.
+#else
+ m_hHelperThread = OpenThread(SYNCHRONIZE, FALSE, dwHelperTid);
+ CONSISTENCY_CHECK_MSGF(m_hHelperThread != NULL, ("Failed to get helper-thread handle. tid=0x%x\n", dwHelperTid));
+#endif
+ }
+
+ // get the remote address of the runtime offsets structure and read the structure itself
+ HRESULT hrRead = SafeReadStruct(PTR_TO_CORDB_ADDRESS(GetDCB()->m_pRuntimeOffsets), &m_runtimeOffsets);
+
+ if (FAILED(hrRead))
+ {
+ return hrRead;
+ }
+
+ LOG((LF_CORDB, LL_INFO10000, "CP::GRO: got runtime offsets: \n"));
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ LOG((LF_CORDB, LL_INFO10000, " m_genericHijackFuncAddr= 0x%p\n",
+ m_runtimeOffsets.m_genericHijackFuncAddr));
+ LOG((LF_CORDB, LL_INFO10000, " m_signalHijackStartedBPAddr= 0x%p\n",
+ m_runtimeOffsets.m_signalHijackStartedBPAddr));
+ LOG((LF_CORDB, LL_INFO10000, " m_excepNotForRuntimeBPAddr= 0x%p\n",
+ m_runtimeOffsets.m_excepNotForRuntimeBPAddr));
+ LOG((LF_CORDB, LL_INFO10000, " m_notifyRSOfSyncCompleteBPAddr= 0x%p\n",
+ m_runtimeOffsets.m_notifyRSOfSyncCompleteBPAddr));
+ LOG((LF_CORDB, LL_INFO10000, " m_raiseException= 0x%p\n",
+ m_runtimeOffsets.m_raiseExceptionAddr));
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ LOG((LF_CORDB, LL_INFO10000, " m_TLSIndex= 0x%08x\n",
+ m_runtimeOffsets.m_TLSIndex));
+ LOG((LF_CORDB, LL_INFO10000, " m_EEThreadStateOffset= 0x%08x\n",
+ m_runtimeOffsets.m_EEThreadStateOffset));
+ LOG((LF_CORDB, LL_INFO10000, " m_EEThreadStateNCOffset= 0x%08x\n",
+ m_runtimeOffsets.m_EEThreadStateNCOffset));
+ LOG((LF_CORDB, LL_INFO10000, " m_EEThreadPGCDisabledOffset= 0x%08x\n",
+ m_runtimeOffsets.m_EEThreadPGCDisabledOffset));
+ LOG((LF_CORDB, LL_INFO10000, " m_EEThreadPGCDisabledValue= 0x%08x\n",
+ m_runtimeOffsets.m_EEThreadPGCDisabledValue));
+ LOG((LF_CORDB, LL_INFO10000, " m_EEThreadDebuggerWordOffset= 0x%08x\n",
+ m_runtimeOffsets.m_EEThreadDebuggerWordOffset));
+ LOG((LF_CORDB, LL_INFO10000, " m_EEThreadFrameOffset= 0x%08x\n",
+ m_runtimeOffsets.m_EEThreadFrameOffset));
+ LOG((LF_CORDB, LL_INFO10000, " m_EEThreadMaxNeededSize= 0x%08x\n",
+ m_runtimeOffsets.m_EEThreadMaxNeededSize));
+ LOG((LF_CORDB, LL_INFO10000, " m_EEThreadSteppingStateMask= 0x%08x\n",
+ m_runtimeOffsets.m_EEThreadSteppingStateMask));
+ LOG((LF_CORDB, LL_INFO10000, " m_EEMaxFrameValue= 0x%08x\n",
+ m_runtimeOffsets.m_EEMaxFrameValue));
+ LOG((LF_CORDB, LL_INFO10000, " m_EEThreadDebuggerFilterContextOffset= 0x%08x\n",
+ m_runtimeOffsets.m_EEThreadDebuggerFilterContextOffset));
+ LOG((LF_CORDB, LL_INFO10000, " m_EEThreadCantStopOffset= 0x%08x\n",
+ m_runtimeOffsets.m_EEThreadCantStopOffset));
+ LOG((LF_CORDB, LL_INFO10000, " m_EEFrameNextOffset= 0x%08x\n",
+ m_runtimeOffsets.m_EEFrameNextOffset));
+ LOG((LF_CORDB, LL_INFO10000, " m_EEIsManagedExceptionStateMask= 0x%08x\n",
+ m_runtimeOffsets.m_EEIsManagedExceptionStateMask));
+ LOG((LF_CORDB, LL_INFO10000, " m_pPatches= 0x%08x\n",
+ m_runtimeOffsets.m_pPatches));
+ LOG((LF_CORDB, LL_INFO10000, " m_offRgData= 0x%08x\n",
+ m_runtimeOffsets.m_offRgData));
+ LOG((LF_CORDB, LL_INFO10000, " m_offCData= 0x%08x\n",
+ m_runtimeOffsets.m_offCData));
+ LOG((LF_CORDB, LL_INFO10000, " m_cbPatch= 0x%08x\n",
+ m_runtimeOffsets.m_cbPatch));
+ LOG((LF_CORDB, LL_INFO10000, " m_offAddr= 0x%08x\n",
+ m_runtimeOffsets.m_offAddr));
+ LOG((LF_CORDB, LL_INFO10000, " m_offOpcode= 0x%08x\n",
+ m_runtimeOffsets.m_offOpcode));
+ LOG((LF_CORDB, LL_INFO10000, " m_cbOpcode= 0x%08x\n",
+ m_runtimeOffsets.m_cbOpcode));
+ LOG((LF_CORDB, LL_INFO10000, " m_offTraceType= 0x%08x\n",
+ m_runtimeOffsets.m_offTraceType));
+ LOG((LF_CORDB, LL_INFO10000, " m_traceTypeUnmanaged= 0x%08x\n",
+ m_runtimeOffsets.m_traceTypeUnmanaged));
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ // Flares are only used for interop debugging.
+
+ // Do check that the flares are all at unique offsets.
+ // Since this is determined at link-time, we need a run-time check (an
+ // assert isn't good enough, since this would only happen in a super
+ // optimized / bbt run).
+ {
+ const void * flares[] = {
+ m_runtimeOffsets.m_signalHijackStartedBPAddr,
+ m_runtimeOffsets.m_excepForRuntimeHandoffStartBPAddr,
+ m_runtimeOffsets.m_excepForRuntimeHandoffCompleteBPAddr,
+ m_runtimeOffsets.m_signalHijackCompleteBPAddr,
+ m_runtimeOffsets.m_excepNotForRuntimeBPAddr,
+ m_runtimeOffsets.m_notifyRSOfSyncCompleteBPAddr,
+ };
+
+ const int NumFlares = NumItems(flares);
+
+ // Ensure that all of the flares are unique.
+ for(int i = 0; i < NumFlares; i++)
+ {
+ for(int j = i+1; j < NumFlares; j++)
+ {
+ if (flares[i] == flares[j])
+ {
+ // If we ever fail here, that means the LS build is busted.
+
+ // This assert is useful if we drop a checked RS onto a retail
+ // LS (that's legal).
+ _ASSERTE(!"LS has matching Flares.");
+ LOG((LF_CORDB, LL_ALWAYS, "Failing because of matching flares.\n"));
+ return CORDBG_E_INCOMPATIBLE_PROTOCOL;
+ }
+ }
+ }
+ }
+
+#endif // FEATURE_INTEROP_DEBUGGING
+ m_runtimeOffsetsInitialized = true;
+ return S_OK;
+}
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+
+//-----------------------------------------------------------------------------
+// Resume hijacked threads.
+//-----------------------------------------------------------------------------
+void CordbProcess::ResumeHijackedThreads()
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(m_pShim != NULL);
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ LOG((LF_CORDB, LL_INFO10000, "CP::RHT: entered\n"));
+ if (this->m_state & (CordbProcess::PS_SOME_THREADS_SUSPENDED | CordbProcess::PS_HIJACKS_IN_PLACE))
+ {
+ // On XP, This will also resume the threads suspended for Sync.
+ this->ResumeUnmanagedThreads();
+ }
+
+ // Hijacks send their ownership flares and then wait on this event. By setting this
+ // we let the hijacks run free.
+ if (this->m_leftSideUnmanagedWaitEvent != NULL)
+ {
+ SetEvent(this->m_leftSideUnmanagedWaitEvent);
+ }
+ else
+ {
+ // Only reason we expect to not have this event is if the CLR hasn't been loaded yet.
+ // In that case, we won't hijack, so nobody's listening for this event either.
+ _ASSERTE(!m_initialized);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// For debugging support, record the win32 events.
+// Note that although this is for debugging, we want it in retail because we'll
+// be debugging retail most of the time :(
+// pEvent - the win32 debug event we just received
+// pUThread - our unmanaged thread object for the event. We could look it up
+// from pEvent->dwThreadId, but passed in for perf reasons.
+//-----------------------------------------------------------------------------
+void CordbProcess::DebugRecordWin32Event(const DEBUG_EVENT * pEvent, CordbUnmanagedThread * pUThread)
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ // Although we could look up the Unmanaged thread, it's faster to have it just passed in.
+ // So here we do a consistency check.
+ _ASSERTE(pUThread != NULL);
+ _ASSERTE(pUThread->m_id == pEvent->dwThreadId);
+
+ m_DbgSupport.m_TotalNativeEvents++; // bump up the counter.
+
+ MiniDebugEvent * pMiniEvent = &m_DbgSupport.m_DebugEventQueue[m_DbgSupport.m_DebugEventQueueIdx];
+ pMiniEvent->code = (BYTE) pEvent->dwDebugEventCode;
+ pMiniEvent->pUThread = pUThread;
+
+ DWORD tid = pEvent->dwThreadId;
+
+ // Record debug-event specific data.
+ switch(pEvent->dwDebugEventCode)
+ {
+ case LOAD_DLL_DEBUG_EVENT:
+ pMiniEvent->u.ModuleData.pBaseAddress = pEvent->u.LoadDll.lpBaseOfDll;
+ STRESS_LOG2(LF_CORDB, LL_INFO1000, "Win32 Debug Event received: tid=0x%8x, Load Dll. Addr=%p\n",
+ tid,
+ pEvent->u.LoadDll.lpBaseOfDll);
+ break;
+ case UNLOAD_DLL_DEBUG_EVENT:
+ pMiniEvent->u.ModuleData.pBaseAddress = pEvent->u.UnloadDll.lpBaseOfDll;
+ STRESS_LOG2(LF_CORDB, LL_INFO1000, "Win32 Debug Event received: tid=0x%8x, Unload Dll. Addr=%p\n",
+ tid,
+ pEvent->u.UnloadDll.lpBaseOfDll);
+ break;
+ case EXCEPTION_DEBUG_EVENT:
+ pMiniEvent->u.ExceptionData.pAddress = pEvent->u.Exception.ExceptionRecord.ExceptionAddress;
+ pMiniEvent->u.ExceptionData.dwCode = pEvent->u.Exception.ExceptionRecord.ExceptionCode;
+
+ STRESS_LOG3(LF_CORDB, LL_INFO1000, "Win32 Debug Event received: tid=%8x, (1) Exception. Code=0x%08x, Addr=%p\n",
+ tid,
+ pMiniEvent->u.ExceptionData.dwCode,
+ pMiniEvent->u.ExceptionData.pAddress
+ );
+ break;
+ default:
+ STRESS_LOG2(LF_CORDB, LL_INFO1000, "Win32 Debug Event received tid=%8x, %d\n", tid, pEvent->dwDebugEventCode);
+ break;
+ }
+
+
+ // Go to the next entry in the queue.
+ m_DbgSupport.m_DebugEventQueueIdx = (m_DbgSupport.m_DebugEventQueueIdx + 1) % DEBUG_EVENTQUEUE_SIZE;
+}
+
+void CordbProcess::QueueUnmanagedEvent(CordbUnmanagedThread *pUThread, const DEBUG_EVENT *pEvent)
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(ThreadHoldsProcessLock());
+ _ASSERTE(m_pShim != NULL);
+
+ LOG((LF_CORDB, LL_INFO10000, "CP::QUE: queued unmanaged event %d for thread 0x%x\n",
+ pEvent->dwDebugEventCode, pUThread->m_id));
+
+
+ _ASSERTE(pEvent->dwDebugEventCode == EXCEPTION_DEBUG_EVENT);
+
+ // Copy the event into the given thread
+ CordbUnmanagedEvent *ue;
+
+ // Use the primary IB event slot unless this is the special stack overflow event case.
+ if (!pUThread->HasSpecialStackOverflowCase())
+ ue = pUThread->IBEvent();
+ else
+ ue = pUThread->IBEvent2();
+
+ if(pUThread->HasIBEvent() && !pUThread->HasSpecialStackOverflowCase())
+ {
+ // Any event being replaced should at least have been continued outside of the hijack
+ // We don't track whether or not we expect the exception to retrigger but if we are replacing
+ // the event then it did not.
+ _ASSERTE(ue->IsEventContinuedUnhijacked());
+ LOG((LF_CORDB, LL_INFO10000, "CP::QUE: A previously seen event is being discarded 0x%x 0x%p\n",
+ ue->m_currentDebugEvent.u.Exception.ExceptionRecord.ExceptionCode,
+ ue->m_currentDebugEvent.u.Exception.ExceptionRecord.ExceptionAddress));
+ DequeueUnmanagedEvent(ue->m_owner);
+ }
+
+ memcpy(&(ue->m_currentDebugEvent), pEvent, sizeof(DEBUG_EVENT));
+ ue->m_state = CUES_IsIBEvent;
+ ue->m_next = NULL;
+
+ // Enqueue the event.
+ pUThread->SetState(CUTS_HasIBEvent);
+
+ if (m_unmanagedEventQueue == NULL)
+ m_unmanagedEventQueue = ue;
+ else
+ m_lastQueuedUnmanagedEvent->m_next = ue;
+
+ m_lastQueuedUnmanagedEvent = ue;
+}
+
+void CordbProcess::DequeueUnmanagedEvent(CordbUnmanagedThread *ut)
+{
+ INTERNAL_API_ENTRY(this);
+
+ _ASSERTE(m_unmanagedEventQueue != NULL);
+ _ASSERTE(ut->HasIBEvent() || ut->HasSpecialStackOverflowCase());
+ _ASSERTE(ThreadHoldsProcessLock());
+
+
+ CordbUnmanagedEvent *ue;
+
+ if (ut->HasIBEvent())
+ ue = ut->IBEvent();
+ else
+ {
+ ue = ut->IBEvent2();
+
+ // Since we're dequeuing the special stack overflow event, we're no longer in the special stack overflow case.
+ ut->ClearState(CUTS_HasSpecialStackOverflowCase);
+ }
+
+ DWORD ec = ue->m_currentDebugEvent.dwDebugEventCode;
+ LOG((LF_CORDB, LL_INFO10000, "CP::DUE: dequeue unmanaged event %d for thread 0x%x\n", ec, ut->m_id));
+
+ _ASSERTE(ec == EXCEPTION_DEBUG_EVENT);
+
+ CordbUnmanagedEvent **tmp = &m_unmanagedEventQueue;
+ CordbUnmanagedEvent **prev = NULL;
+
+ // Note: this supports out-of-order dequeing of unmanaged events. This is necessary because we queue events even if
+ // we're not clear on the ownership question. When we get the answer, and if the event belongs to the Runtime, we go
+ // ahead and yank the event out of the queue, wherever it may be.
+ while (*tmp && *tmp != ue)
+ {
+ prev = tmp;
+ tmp = &((*tmp)->m_next);
+ }
+
+ _ASSERTE(*tmp == ue);
+
+ *tmp = (*tmp)->m_next;
+
+ if (m_unmanagedEventQueue == NULL)
+ m_lastQueuedUnmanagedEvent = NULL;
+ else if (m_lastQueuedUnmanagedEvent == ue)
+ {
+ _ASSERTE(prev != NULL);
+ m_lastQueuedUnmanagedEvent = *prev;
+ }
+
+ ut->ClearState(CUTS_HasIBEvent);
+
+}
+
+void CordbProcess::QueueOOBUnmanagedEvent(CordbUnmanagedThread *pUThread, const DEBUG_EVENT * pEvent)
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(ThreadHoldsProcessLock());
+ _ASSERTE(!pUThread->HasOOBEvent());
+ _ASSERTE(IsWin32EventThread());
+ _ASSERTE(m_pShim != NULL);
+
+ LOG((LF_CORDB, LL_INFO10000, "CP::QUE: queued OOB unmanaged event %d for thread 0x%x\n",
+ pEvent->dwDebugEventCode, pUThread->m_id));
+
+ // Copy the event into the given thread
+ CordbUnmanagedEvent *ue = pUThread->OOBEvent();
+ memcpy(&(ue->m_currentDebugEvent), pEvent, sizeof(DEBUG_EVENT));
+ ue->m_state = CUES_None;
+ ue->m_next = NULL;
+
+ // Enqueue the event.
+ pUThread->SetState(CUTS_HasOOBEvent);
+
+ if (m_outOfBandEventQueue == NULL)
+ m_outOfBandEventQueue = ue;
+ else
+ m_lastQueuedOOBEvent->m_next = ue;
+
+ m_lastQueuedOOBEvent = ue;
+}
+
+void CordbProcess::DequeueOOBUnmanagedEvent(CordbUnmanagedThread *ut)
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(m_outOfBandEventQueue != NULL);
+ _ASSERTE(ut->HasOOBEvent());
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ CordbUnmanagedEvent *ue = ut->OOBEvent();
+ DWORD ec = ue->m_currentDebugEvent.dwDebugEventCode;
+
+ LOG((LF_CORDB, LL_INFO10000, "CP::DUE: dequeue OOB unmanaged event %d for thread 0x%x\n", ec, ut->m_id));
+
+ CordbUnmanagedEvent **tmp = &m_outOfBandEventQueue;
+ CordbUnmanagedEvent **prev = NULL;
+
+ // Note: this supports out-of-order dequeing of unmanaged events. This is necessary because we queue events even if
+ // we're not clear on the ownership question. When we get the answer, and if the event belongs to the Runtime, we go
+ // ahead and yank the event out of the queue, wherever it may be.
+ while (*tmp && *tmp != ue)
+ {
+ prev = tmp;
+ tmp = &((*tmp)->m_next);
+ }
+
+ _ASSERTE(*tmp == ue);
+
+ *tmp = (*tmp)->m_next;
+
+ if (m_outOfBandEventQueue == NULL)
+ m_lastQueuedOOBEvent = NULL;
+ else if (m_lastQueuedOOBEvent == ue)
+ {
+ _ASSERTE(prev != NULL);
+ m_lastQueuedOOBEvent = *prev;
+ }
+
+ ut->ClearState(CUTS_HasOOBEvent);
+}
+
+HRESULT CordbProcess::SuspendUnmanagedThreads()
+{
+ INTERNAL_API_ENTRY(this);
+
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ // Iterate over all unmanaged threads...
+ CordbUnmanagedThread* ut;
+ HASHFIND find;
+
+ for (ut = m_unmanagedThreads.FindFirst(&find); ut != NULL; ut = m_unmanagedThreads.FindNext(&find))
+ {
+
+ // Don't suspend any thread in a can't stop region. This includes cooperative mode threads & preemptive
+ // threads that haven't pushed a NativeTransitionFrame. The ultimate problem here is that a thread
+ // in this state is effectively inside the runtime, and thus may take a lock that blocks the helper thread.
+ // IsCan'tStop also includes the helper thread & hijacked threads - which we shouldn't suspend anyways.
+
+ // Only suspend those unmanaged threads that aren't already suspended by us and that aren't already hijacked by
+ // us.
+
+ if (!ut->IsSuspended() &&
+ !ut->IsDeleted() &&
+ !ut->IsCantStop() &&
+ !ut->IsBlockingForSync()
+ )
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::SUT: suspending unmanaged thread 0x%x, handle 0x%x\n", ut->m_id, ut->m_handle));
+
+ DWORD succ = SuspendThread(ut->m_handle);
+
+ if (succ == 0xFFFFFFFF)
+ {
+ // This is okay... the thread may be dying after an ExitThread event.
+ LOG((LF_CORDB, LL_INFO1000, "CP::SUT: failed to suspend thread 0x%x\n", ut->m_id));
+ }
+ else
+ {
+ m_state |= PS_SOME_THREADS_SUSPENDED;
+
+ ut->SetState(CUTS_Suspended);
+ }
+ }
+ }
+
+ return S_OK;
+}
+
+HRESULT CordbProcess::ResumeUnmanagedThreads()
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(ThreadHoldsProcessLock());
+ FAIL_IF_NEUTERED(this);
+
+ // Iterate over all unmanaged threads...
+ CordbUnmanagedThread* ut;
+ HASHFIND find;
+
+ for (ut = m_unmanagedThreads.FindFirst(&find); ut != NULL; ut = m_unmanagedThreads.FindNext(&find))
+ {
+ // Only resume those unmanaged threads that were suspended by us.
+ if (ut->IsSuspended())
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::RUT: resuming unmanaged thread 0x%x\n", ut->m_id));
+
+ DWORD succ = ResumeThread(ut->m_handle);
+
+ if (succ == 0xFFFFFFFF)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::RUT: failed to resume thread 0x%x\n", ut->m_id));
+ }
+ else
+ ut->ClearState(CUTS_Suspended);
+ }
+ }
+
+ m_state &= ~PS_SOME_THREADS_SUSPENDED;
+
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// DispatchUnmanagedInBandEvent
+//
+// Handler for Win32 events already known to be Unmanaged and in-band.
+//-----------------------------------------------------------------------------
+void CordbProcess::DispatchUnmanagedInBandEvent()
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ // There should be no queued OOB events!!! If there are, then we have a breakdown in our protocol, since all OOB
+ // events should be dispatched before attempting to really continue from any in-band event.
+ _ASSERTE(m_outOfBandEventQueue == NULL);
+ _ASSERTE(m_cordb != NULL);
+ _ASSERTE(m_cordb->m_unmanagedCallback != NULL);
+ _ASSERTE(!m_dispatchingUnmanagedEvent);
+
+ CordbUnmanagedThread * pUnmanagedThread = NULL;
+ CordbUnmanagedEvent * pUnmanagedEvent = m_unmanagedEventQueue;
+
+ while (true)
+ {
+ // get the next queued event that isn't dispatched yet
+ while(pUnmanagedEvent != NULL && pUnmanagedEvent->IsDispatched())
+ {
+ pUnmanagedEvent = pUnmanagedEvent->m_next;
+ }
+
+ if(pUnmanagedEvent == NULL)
+ break;
+
+ // Get the thread for this event
+ pUnmanagedThread = pUnmanagedEvent->m_owner;
+
+ // We better not have dispatched it yet!
+ _ASSERTE(!pUnmanagedEvent->IsDispatched());
+
+ // We shouldn't be dispatching IB events on a thread that has exited.
+ // Though it's possible that the thread may exit *after* the IB event has been dispatched
+ // if it gets hijacked.
+ _ASSERTE(!pUnmanagedThread->IsDeleted());
+
+ // Make sure we keep the thread alive while we're playing with it.
+ pUnmanagedThread->InternalAddRef();
+
+ LOG((LF_CORDB, LL_INFO10000, "CP::DUE: dispatching unmanaged event %d for thread 0x%x\n",
+ pUnmanagedEvent->m_currentDebugEvent.dwDebugEventCode, pUnmanagedThread->m_id));
+
+ m_dispatchingUnmanagedEvent = true;
+
+ // Add/Remove a reference which is scoped to the time that m_lastDispatchedIBEvent
+ // is set to pUnmanagedEvent (it is an interior pointer)
+ // see DevDiv issue 818301 for more details
+ if(m_lastDispatchedIBEvent != NULL)
+ {
+ m_lastDispatchedIBEvent->m_owner->InternalRelease();
+ m_lastDispatchedIBEvent = NULL;
+ }
+ pUnmanagedThread->InternalAddRef();
+ m_lastDispatchedIBEvent = pUnmanagedEvent;
+ pUnmanagedEvent->SetState(CUES_Dispatched);
+
+ IncStopCount();
+
+ Unlock();
+
+ {
+ // Interface is semantically const, but does not include const in signature.
+ DEBUG_EVENT * pEvent = const_cast<DEBUG_EVENT *> (&(pUnmanagedEvent->m_currentDebugEvent));
+ PUBLIC_WIN32_CALLBACK_IN_THIS_SCOPE(this,pEvent, FALSE);
+ m_cordb->m_unmanagedCallback->DebugEvent(pEvent, FALSE);
+ }
+
+ Lock();
+
+ // Calling IMDA::Continue() will set m_dispatchingUnmanagedEvent = false.
+ // So if Continue() was called && we have more events, we'll loop and dispatch more events.
+ // Else we'll break out of the while loop.
+ if(m_dispatchingUnmanagedEvent)
+ break;
+
+ // Continue was called in the dispatch callback, but that continue path just
+ // clears the dispatch flag and returns. The continue right here is the logical
+ // completion of the user's continue request
+ // Note it is sometimes the case that these events have already been continued because
+ // they had defered dispatching. At the time of deferal they were immediately continued.
+ // If the event is already continued then this continue becomes a no-op.
+ m_pShim->GetWin32EventThread()->DoDbgContinue(this, pUnmanagedEvent);
+
+ // Release our reference to the unmanaged thread that we dispatched
+ if (pUnmanagedThread)
+ {
+ // This event should have been continued long ago...
+ _ASSERTE(!pUnmanagedThread->IBEvent()->IsEventWaitingForContinue());
+ pUnmanagedThread->InternalRelease();
+ pUnmanagedThread = NULL;
+ }
+ }
+
+ m_dispatchingUnmanagedEvent = false;
+
+ // Release our reference to the last thread that we dispatched now...
+ if(pUnmanagedThread)
+ {
+ pUnmanagedThread->InternalRelease();
+ pUnmanagedThread = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// DispatchUnmanagedOOBEvent
+//
+// Handler for Win32 events already known to be Unmanaged and out-of-band.
+//-----------------------------------------------------------------------------
+void CordbProcess::DispatchUnmanagedOOBEvent()
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(ThreadHoldsProcessLock());
+ _ASSERTE(IsWin32EventThread());
+
+ // There should be OOB events queued...
+ _ASSERTE(m_outOfBandEventQueue != NULL);
+ _ASSERTE(m_cordb->m_unmanagedCallback != NULL);
+
+ do
+ {
+ // Get the first event in the OOB Queue...
+ CordbUnmanagedEvent * pUnmanagedEvent = m_outOfBandEventQueue;
+ CordbUnmanagedThread * pUnmanagedThread = pUnmanagedEvent->m_owner;
+
+ // Make sure we keep the thread alive while we're playing with it.
+ RSSmartPtr<CordbUnmanagedThread> pRef(pUnmanagedThread);
+
+ LOG((LF_CORDB, LL_INFO10000, "[%x] CP::DUE: dispatching OOB unmanaged event %d for thread 0x%x\n",
+ GetCurrentThreadId(), pUnmanagedEvent->m_currentDebugEvent.dwDebugEventCode, pUnmanagedThread->m_id));
+
+ m_dispatchingOOBEvent = true;
+ pUnmanagedEvent->SetState(CUES_Dispatched);
+ Unlock();
+
+ {
+ // Interface is semantically const, but does not include const in signature.
+ DEBUG_EVENT * pEvent = const_cast<DEBUG_EVENT *> (&(pUnmanagedEvent->m_currentDebugEvent));
+ PUBLIC_WIN32_CALLBACK_IN_THIS_SCOPE(this, pEvent, TRUE);
+ m_cordb->m_unmanagedCallback->DebugEvent(pEvent, TRUE);
+ }
+
+ Lock();
+
+ // If they called Continue from the callback, then continue the OOB event right now before dispatching the next
+ // one.
+ if (!m_dispatchingOOBEvent)
+ {
+ DequeueOOBUnmanagedEvent(pUnmanagedThread);
+
+ // Should not have continued from this debug event yet.
+ _ASSERTE(pUnmanagedEvent->IsEventWaitingForContinue());
+
+ // Do a little extra work if that was an OOB exception event...
+ HRESULT hr = pUnmanagedEvent->m_owner->FixupAfterOOBException(pUnmanagedEvent);
+ _ASSERTE(SUCCEEDED(hr));
+
+ // Go ahead and continue now...
+ this->m_pShim->GetWin32EventThread()->DoDbgContinue(this, pUnmanagedEvent);
+ }
+
+ // Implicit release of pUnmanagedThread via pRef
+ }
+ while (!m_dispatchingOOBEvent && (m_outOfBandEventQueue != NULL));
+
+ m_dispatchingOOBEvent = false;
+
+ LOG((LF_CORDB, LL_INFO10000, "CP::DUE: done dispatching OOB events. Queue=0x%08x\n", m_outOfBandEventQueue));
+}
+#endif // FEATURE_INTEROP_DEBUGGING
+
+//-----------------------------------------------------------------------------
+// StartSyncFromWin32Stop
+//
+// Get the process from a Fozen state or a Live state to a Synchronized State.
+// Note that Process Exit is considered to be synchronized.
+// This is a nop if we're not Interop Debugging.
+// If this function succeeds, we're in a synchronized state.
+//
+// Arguments:
+// pfAsyncBreakSent - returns if this method sent an async-break or not.
+//
+// Return value:
+// typical HRESULT return values, nothing sinister here.
+//-----------------------------------------------------------------------------
+HRESULT CordbProcess::StartSyncFromWin32Stop(BOOL * pfAsyncBreakSent)
+{
+ INTERNAL_API_ENTRY(this);
+ if (m_pShim == NULL) // This API is moved off to the shim
+ {
+ return E_NOTIMPL;
+ }
+
+ HRESULT hr = S_OK;
+
+ // Caller should have taken the stop-go lock. This prevents us from racing w/ a continue.
+ _ASSERTE(m_StopGoLock.HasLock());
+
+ // Process should be init before we try to sync it.
+ _ASSERTE(this->m_initialized);
+
+ // If nobody's listening for an AsyncBreak, and we're not stopped, then our caller
+ // doesn't know if we're sending an AsyncBreak or not; and thus we may not continue.
+ // Failing this assert means that we're stopping but we don't think we're going to get a continue
+ // down the road, and thus we're headed for a deadlock.
+ _ASSERTE((pfAsyncBreakSent != NULL) || (m_stopCount > 0));
+
+ if (pfAsyncBreakSent)
+ {
+ *pfAsyncBreakSent = FALSE;
+ }
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+
+ // If we're win32 stopped (but not out-of-band win32 stopped), or if we're running free on the Left Side but we're
+ // just not synchronized (and we're win32 attached), then go ahead and do an internal continue and send an async
+ // break event to get the Left Side sync'd up.
+ //
+ // The process can be running free as far as Win32 events are concerned, but still not synchronized as far as the
+ // Runtime is concerned. This can happen in a lot of cases where we end up with the Runtime not sync'd but with the
+ // process running free due to hijacking, etc...
+ if (((m_state & CordbProcess::PS_WIN32_STOPPED) && (m_outOfBandEventQueue == NULL)) ||
+ (!GetSynchronized() && IsInteropDebugging()))
+ {
+ Lock();
+
+ if (((m_state & CordbProcess::PS_WIN32_STOPPED) && (m_outOfBandEventQueue == NULL)) ||
+ (!GetSynchronized() && IsInteropDebugging()))
+ {
+ // This can't be the win32 ET b/c we need that thread to be alive and pumping win32 DE so that
+ // our Async Break can get across.
+ // So nobody should ever be calling this on the w32 ET. But they could, since we do trickle in from
+ // outside APIs. So we need a retail check.
+ if (IsWin32EventThread())
+ {
+ _ASSERTE(!"Don't call this API on the W32 Event Thread");
+
+ Unlock();
+ return ErrWrapper(CORDBG_E_CANT_CALL_ON_THIS_THREAD);
+ }
+
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "[%x] CP::SSFW32S: sending internal continue\n", GetCurrentThreadId());
+
+ // Can't do this on the win32 event thread.
+ _ASSERTE(!this->IsWin32EventThread());
+
+ // If the helper thread is already dead, then we just return as if we sync'd the process.
+ if (m_helperThreadDead)
+ {
+ if (pfAsyncBreakSent)
+ {
+ *pfAsyncBreakSent = TRUE;
+ }
+
+ // Mark the process as synchronized so no events will be dispatched until the thing is
+ // continued. However, the marking here is not a usual marking for synchronized. It has special
+ // semantics when we're interop debugging. We use m_oddSync to remember this so that we can take special
+ // action in Continue().
+ SetSynchronized(true);
+ m_oddSync = true;
+
+ // Get the RC Event Thread to stop listening to the process.
+ m_cordb->ProcessStateChanged();
+
+ Unlock();
+
+ return S_OK;
+ }
+
+ m_stopRequested = true;
+
+ // See ::Stop for why we defer this. The delayed events will be dispatched when some one calls continue.
+ // And we know they'll call continue b/c (stopCount > 0) || (our caller knows we're sending an AsyncBreak).
+ m_specialDeferment = true;
+
+ Unlock();
+
+ // If the process gets synchronized between the Unlock() and here, then SendUnmanagedContinue() will end up
+ // not doing anything at all since a) it holds the process lock when working and b) it gates everything on
+ // if the process is sync'd or not. This is exactly what we want.
+ hr = this->m_pShim->GetWin32EventThread()->SendUnmanagedContinue(this, cInternalUMContinue);
+
+ LOG((LF_CORDB, LL_INFO1000, "[%x] CP::SSFW32S: internal continue returned\n", GetCurrentThreadId()));
+
+ // Send an async break to the left side now that its running.
+ DebuggerIPCEvent * pEvent = (DebuggerIPCEvent *) _alloca(CorDBIPC_BUFFER_SIZE);
+ InitIPCEvent(pEvent, DB_IPCE_ASYNC_BREAK, false, VMPTR_AppDomain::NullPtr());
+
+ LOG((LF_CORDB, LL_INFO1000, "[%x] CP::SSFW32S: sending async stop\n", GetCurrentThreadId()));
+
+ // If the process gets synchronized between the Unlock() and here, then this message will do nothing (Left
+ // Side catches it) and we'll never get a response, and it won't hurt anything.
+ hr = m_cordb->SendIPCEvent(this, pEvent, CorDBIPC_BUFFER_SIZE);
+ // @Todo- how do we handle a failure here?
+
+ // If the send returns with the helper thread being dead, then we know we don't need to wait for the process
+ // to sync.
+ if (!m_helperThreadDead)
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "[%x] CP::SSFW32S: sent async stop, waiting for event\n", GetCurrentThreadId());
+
+ // If we got synchronized between the Unlock() and here its okay since m_stopWaitEvent is still high
+ // from the last sync.
+ DWORD dwWaitResult = SafeWaitForSingleObject(this, m_stopWaitEvent, INFINITE);
+
+ STRESS_LOG2(LF_CORDB, LL_INFO1000, "[%x] CP::SSFW32S: got event, %d\n", GetCurrentThreadId(), dwWaitResult);
+
+ _ASSERTE(dwWaitResult == WAIT_OBJECT_0);
+ }
+
+ Lock();
+
+ m_specialDeferment = false;
+
+ if (pfAsyncBreakSent)
+ {
+ *pfAsyncBreakSent = TRUE;
+ }
+
+ // If the helper thread died while we were trying to send an event to it, then we just do the same odd sync
+ // logic we do above.
+ if (m_helperThreadDead)
+ {
+ SetSynchronized(true);
+ m_oddSync = true;
+ hr = S_OK;
+ }
+
+ m_stopRequested = false;
+ m_cordb->ProcessStateChanged();
+ }
+
+ Unlock();
+ }
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ return hr;
+}
+
+// Check if the left side has exited. If so, get the right-side
+// into shutdown mode. Only use this to avert us from going into
+// an unrecoverable error.
+bool CordbProcess::CheckIfLSExited()
+{
+// Check by waiting on the handle with no timeout.
+ if (WaitForSingleObject(m_handle, 0) == WAIT_OBJECT_0)
+ {
+ Lock();
+ m_terminated = true;
+ m_exiting = true;
+ Unlock();
+ }
+
+ LOG((LF_CORDB, LL_INFO10, "CP::IsLSExited() returning '%s'\n",
+ m_exiting ? "true" : "false"));
+
+ return m_exiting;
+}
+
+// Call this if something really bad happened and we can't do
+// anything meaningful with the CordbProcess.
+void CordbProcess::UnrecoverableError(HRESULT errorHR,
+ unsigned int errorCode,
+ const char *errorFile,
+ unsigned int errorLine)
+{
+ LOG((LF_CORDB, LL_INFO10, "[%x] CP::UE: unrecoverable error 0x%08x "
+ "(%d) %s:%d\n",
+ GetCurrentThreadId(),
+ errorHR, errorCode, errorFile, errorLine));
+
+ // We definitely want to know about any of these.
+ STRESS_LOG3(LF_CORDB, LL_EVERYTHING, "Unrecoverable Error:0x%08x, File=%s, line=%d\n", errorHR, errorFile, errorLine);
+
+ // It's possible for an unrecoverable error to occur if the user detaches the
+ // debugger while inside CordbProcess::DispatchRCEvent() (as that function deliberately
+ // calls Unlock() while calling into the Shim). Detect such cases here & bail before we
+ // try to access invalid fields on this CordbProcess.
+ //
+ // Normally, we'd need to take the cordb process lock around the IsNeutered check
+ // (and the code that follows). And perhaps this is a good thing to do in the
+ // future. But for now we're not for two reasons:
+ //
+ // 1) It's scary. We're in UnrecoverableError() for gosh sake. I don't know all
+ // the possible bad states we can be in to get here. Will taking the process lock
+ // have ordering issues? Will the process lock even be valid to take here (or might
+ // we AV)? Since this is error handling, we should probably be as light as we can
+ // not to cause more errors.
+ //
+ // 2) It's unnecessary. For the Watson dump I investigated that caused this fix in
+ // the first place, we already detached before entering UnrecoverableError()
+ // (indeed, the only reason we're in UnrecoverableError is that we already detached
+ // and that caused a prior API to fail). Thus, there's no timing issue (in that
+ // case, anyway), wrt to entering UnrecoverableError() and detaching / neutering.
+ if (IsNeutered())
+ return;
+
+#ifdef _DEBUG
+ // Ping our error trapping logic
+ HRESULT hrDummy;
+ hrDummy = ErrWrapper(errorHR);
+#endif
+
+ if (m_pShim == NULL)
+ {
+ // @dbgtodo - , shim: Once everything is hoisted, we can remove
+ // this code.
+ // In the v3 case, we should never get an unrecoverable error. Instead, the HR should be propogated
+ // and returned at the top-level public API.
+ _ASSERTE(!"Unrecoverable error dispatched in V3 case.");
+ }
+
+ CONSISTENCY_CHECK_MSGF(IsLegalFatalError(errorHR), ("Unrecoverable internal error: hr=0x%08x!", errorHR));
+
+ if (!IsLegalFatalError(errorHR) || (errorHR != CORDBG_E_CANNOT_DEBUG_FIBER_PROCESS))
+ {
+ // This will throw everything into a Zombie state. The ATT_ macros will check this and fail immediately.
+ m_unrecoverableError = true;
+
+ //
+ // Mark the process as no longer synchronized.
+ //
+ Lock();
+ SetSynchronized(false);
+ IncStopCount();
+ Unlock();
+ }
+
+ // Set the error flags in the process so that if parts of it are
+ // still alive, it will realize that its in this mode and do the
+ // right thing.
+ if (GetDCB() != NULL)
+ {
+ GetDCB()->m_errorHR = errorHR;
+ GetDCB()->m_errorCode = errorCode;
+ EX_TRY
+ {
+ UpdateLeftSideDCBField(&(GetDCB()->m_errorHR), sizeof(GetDCB()->m_errorHR));
+ UpdateLeftSideDCBField(&(GetDCB()->m_errorCode), sizeof(GetDCB()->m_errorCode));
+ }
+ EX_CATCH
+ {
+ _ASSERTE(!"Writing process memory failed, perhaps due to an unexpected disconnection from the target.");
+ }
+ EX_END_CATCH(SwallowAllExceptions);
+ }
+
+ //
+ // Let the user know that we've hit an unrecoverable error.
+ //
+ if (m_cordb->m_managedCallback)
+ {
+ // We are about to send DebuggerError call back. The state of RS is undefined.
+ // So we use the special Public Callback. We may be holding locks and stuff.
+ // We may also be deeply nested within the RS.
+ PUBLIC_CALLBACK_IN_THIS_SCOPE_DEBUGGERERROR(this);
+ m_cordb->m_managedCallback->DebuggerError((ICorDebugProcess*) this,
+ errorHR,
+ errorCode);
+ }
+}
+
+
+HRESULT CordbProcess::CheckForUnrecoverableError()
+{
+ HRESULT hr = S_OK;
+
+ if (GetDCB() != NULL)
+ {
+ // be sure we have the latest information
+ UpdateRightSideDCB();
+
+ if (GetDCB()->m_errorHR != S_OK)
+ {
+ UnrecoverableError(GetDCB()->m_errorHR,
+ GetDCB()->m_errorCode,
+ __FILE__, __LINE__);
+
+ hr = GetDCB()->m_errorHR;
+ }
+ }
+
+ return hr;
+}
+
+
+/*
+ * EnableLogMessages enables/disables sending of log messages to the
+ * debugger for logging.
+ */
+HRESULT CordbProcess::EnableLogMessages(BOOL fOnOff)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this);
+ HRESULT hr = S_OK;
+
+ DebuggerIPCEvent *event = (DebuggerIPCEvent*) _alloca(CorDBIPC_BUFFER_SIZE);
+ InitIPCEvent(event, DB_IPCE_ENABLE_LOG_MESSAGES, false, VMPTR_AppDomain::NullPtr());
+ event->LogSwitchSettingMessage.iLevel = (int)fOnOff;
+
+ hr = m_cordb->SendIPCEvent(this, event, CorDBIPC_BUFFER_SIZE);
+ hr = WORST_HR(hr, event->hr);
+
+ LOG((LF_CORDB, LL_INFO10000, "[%x] CP::EnableLogMessages: EnableLogMessages=%d sent.\n",
+ GetCurrentThreadId(), fOnOff));
+
+ return hr;
+}
+
+/*
+ * ModifyLogSwitch modifies the specified switch's severity level.
+ */
+COM_METHOD CordbProcess::ModifyLogSwitch(__in_z WCHAR *pLogSwitchName, LONG lLevel)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this);
+
+ HRESULT hr = S_OK;
+
+ _ASSERTE (pLogSwitchName != NULL);
+
+ DebuggerIPCEvent *event = (DebuggerIPCEvent*) _alloca(CorDBIPC_BUFFER_SIZE);
+ InitIPCEvent(event, DB_IPCE_MODIFY_LOGSWITCH, false, VMPTR_AppDomain::NullPtr());
+ event->LogSwitchSettingMessage.iLevel = lLevel;
+ event->LogSwitchSettingMessage.szSwitchName.SetStringTruncate(pLogSwitchName);
+
+ hr = m_cordb->SendIPCEvent(this, event, CorDBIPC_BUFFER_SIZE);
+ hr = WORST_HR(hr, event->hr);
+
+ LOG((LF_CORDB, LL_INFO10000, "[%x] CP::ModifyLogSwitch: ModifyLogSwitch sent.\n",
+ GetCurrentThreadId()));
+
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// Writes a buffer from the target and performs checks similar to SafeWriteStruct
+//
+// Arguments:
+// tb - TargetBuffer which represents the target memory we want to write to
+// pLocalBuffer - local pointer into source buffer
+// cbSize - the size of local buffer
+//
+// Exceptions
+// On error throws the result of WriteVirtual unless a short write is performed,
+// in which case throws ERROR_PARTIAL_COPY
+//
+void CordbProcess::SafeWriteBuffer(TargetBuffer tb,
+ const BYTE * pLocalBuffer)
+{
+ _ASSERTE(m_pMutableDataTarget != NULL);
+ HRESULT hr = m_pMutableDataTarget->WriteVirtual(tb.pAddress,
+ pLocalBuffer,
+ tb.cbSize);
+ IfFailThrow(hr);
+}
+
+//-----------------------------------------------------------------------------
+// Reads a buffer from the target and performs checks similar to SafeWriteStruct
+//
+// Arguments:
+// tb - TargetBuffer which represents the target memory to read from
+// pLocalBuffer - local pointer into source buffer
+// cbSize - the size of the remote buffer
+// throwOnError - determines whether the function throws exceptions or returns HRESULTs
+// in failure cases
+//
+// Exceptions:
+// If throwOnError is TRUE
+// On error always throws the special CORDBG_E_READVIRTUAL_FAILURE, unless a short write is performed
+// in which case throws ERROR_PARTIAL_COPY
+// If throwOnError is FALSE
+// No exceptions are thrown, and instead the same error codes are returned as HRESULTs
+//
+HRESULT CordbProcess::SafeReadBuffer(TargetBuffer tb, BYTE * pLocalBuffer, BOOL throwOnError)
+{
+ ULONG32 cbRead;
+ HRESULT hr = m_pDACDataTarget->ReadVirtual(tb.pAddress,
+ pLocalBuffer,
+ tb.cbSize,
+ &cbRead);
+
+ if (FAILED(hr))
+ {
+ if (throwOnError)
+ ThrowHR(CORDBG_E_READVIRTUAL_FAILURE);
+ else
+ return CORDBG_E_READVIRTUAL_FAILURE;
+ }
+
+ if (cbRead != tb.cbSize)
+ {
+ if (throwOnError)
+ ThrowWin32(ERROR_PARTIAL_COPY);
+ else
+ return HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY);
+ }
+ return S_OK;
+}
+
+
+//---------------------------------------------------------------------------------------
+// Lookup or create an appdomain.
+//
+// Arguments:
+// vmAppDomain - CLR appdomain to lookup
+//
+// Returns:
+// Instance of CordbAppDomain for the given appdomain. This is a cached instance.
+// If the CordbAppDomain does not yet exist, it will be created and added to the cache.
+// Never returns NULL. Throw on error.
+CordbAppDomain * CordbProcess::LookupOrCreateAppDomain(VMPTR_AppDomain vmAppDomain)
+{
+ CordbAppDomain * pAppDomain = m_appDomains.GetBase(VmPtrToCookie(vmAppDomain));
+ if (pAppDomain != NULL)
+ {
+ return pAppDomain;
+ }
+ return CacheAppDomain(vmAppDomain);
+}
+
+CordbAppDomain * CordbProcess::GetSharedAppDomain()
+{
+ if (m_sharedAppDomain == NULL)
+ {
+ CordbAppDomain *pAD = new CordbAppDomain(this, VMPTR_AppDomain::NullPtr());
+ if (InterlockedCompareExchangeT<CordbAppDomain*>(&m_sharedAppDomain, pAD, NULL) != NULL)
+ {
+ delete pAD;
+ }
+ m_sharedAppDomain->InternalAddRef();
+ }
+
+ return m_sharedAppDomain;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Add a new appdomain to the cache.
+//
+// Arguments:
+// vmAppDomain - appdomain to add.
+//
+// Return Value:
+// Pointer to newly created appdomain, which should be the normal case.
+// Throws on failure. Never returns null.
+//
+// Assumptions:
+// Caller ensure the appdomain is not already cached.
+// Caller should have stop-go lock, which provides thread-safety.
+//
+// Notes:
+// This sets unrecoverable error on failure.
+//
+//---------------------------------------------------------------------------------------
+CordbAppDomain * CordbProcess::CacheAppDomain(VMPTR_AppDomain vmAppDomain)
+{
+ INTERNAL_API_ENTRY(GetProcess());
+
+ _ASSERTE(GetProcessLock()->HasLock());
+
+ RSInitHolder<CordbAppDomain> pAppDomain;
+ pAppDomain.Assign(new CordbAppDomain(this, vmAppDomain)); // throws
+
+ // Add to the hash. This will addref the pAppDomain.
+ // Caller ensures we're not already cached.
+ // The cache will take ownership.
+ m_appDomains.AddBaseOrThrow(pAppDomain);
+
+ // see if this is the default AppDomain
+ IDacDbiInterface * pDac = m_pProcess->GetDAC();
+ BOOL fIsDefaultDomain = FALSE;
+
+ fIsDefaultDomain = pDac->IsDefaultDomain(vmAppDomain); // throws
+
+ if (fIsDefaultDomain)
+ {
+ // If this assert fires, then it likely means the target is corrupted.
+ TargetConsistencyCheck(m_pDefaultAppDomain == NULL);
+ m_pDefaultAppDomain = pAppDomain;
+ }
+
+ CordbAppDomain * pReturn = pAppDomain;
+ pAppDomain.ClearAndMarkDontNeuter();
+
+ _ASSERTE(pReturn != NULL);
+ return pReturn;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Callback for Appdomain enumeration.
+//
+// Arguments:
+// vmAppDomain - new appdomain to add to enumeration
+// pUserData - data passed with callback (a 'this' ptr for CordbProcess)
+//
+//
+// Assumptions:
+// Invoked as callback from code:CordbProcess::PrepopulateAppDomains
+//
+//
+//---------------------------------------------------------------------------------------
+
+// static
+void CordbProcess::AppDomainEnumerationCallback(VMPTR_AppDomain vmAppDomain, void * pUserData)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ CordbProcess * pProcess = static_cast<CordbProcess *> (pUserData);
+ INTERNAL_DAC_CALLBACK(pProcess);
+
+ pProcess->LookupOrCreateAppDomain(vmAppDomain);
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Traverse appdomains in the target and build up our list.
+//
+// Arguments:
+//
+// Return Value:
+// returns on success.
+// Throws on error. AppDomain cache may be partially populated.
+//
+// Assumptions:
+// This is an non-invasive inspection operation called when the debuggee is stopped.
+//
+// Notes:
+// This can be called multiple times. If the list is non-empty, it will nop.
+//---------------------------------------------------------------------------------------
+void CordbProcess::PrepopulateAppDomainsOrThrow()
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ INTERNAL_API_ENTRY(this);
+
+ if (!IsDacInitialized())
+ {
+ return;
+ }
+
+ // DD-primitive that invokes a callback. This may throw.
+ GetDAC()->EnumerateAppDomains(
+ CordbProcess::AppDomainEnumerationCallback,
+ this);
+}
+
+//---------------------------------------------------------------------------------------
+//
+// EnumerateAppDomains enumerates all app domains in the process.
+//
+// Arguments:
+// ppAppDomains - get appdomain enumerator
+//
+// Return Value:
+// S_OK on success.
+//
+// Assumptions:
+//
+//
+// Notes:
+// This operation is non-invasive target.
+//
+//---------------------------------------------------------------------------------------
+HRESULT CordbProcess::EnumerateAppDomains(ICorDebugAppDomainEnum **ppAppDomains)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+ {
+ ValidateOrThrow(ppAppDomains);
+
+ // Ensure list is populated.
+ PrepopulateAppDomainsOrThrow();
+
+ RSInitHolder<CordbHashTableEnum> pEnum;
+ CordbHashTableEnum::BuildOrThrow(
+ this,
+ GetContinueNeuterList(),
+ &m_appDomains,
+ IID_ICorDebugAppDomainEnum,
+ pEnum.GetAddr());
+
+ *ppAppDomains = static_cast<ICorDebugAppDomainEnum*> (pEnum);
+ pEnum->ExternalAddRef();
+
+ pEnum.ClearAndMarkDontNeuter();
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+/*
+ * GetObject returns the runtime process object.
+ * Note: This method is not yet implemented.
+ */
+HRESULT CordbProcess::GetObject(ICorDebugValue **ppObject)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppObject, ICorDebugObjectValue **);
+
+ return E_NOTIMPL;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Given a taskid, finding the corresponding thread. The function can fail if we do not
+// find any thread with the given taskid
+//
+// Arguments:
+// taskId - The task ID to look for.
+// ppThread - OUT: Space for storing the thread corresponding to the taskId given.
+//
+// Return Value:
+// Typical HRESULT symantics, nothing abnormal.
+//
+HRESULT CordbProcess::GetThreadForTaskID(TASKID taskId, ICorDebugThread2 ** ppThread)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+ RSLockHolder lockHolder(GetProcessLock());
+
+ if (ppThread == NULL)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ // On initialization, the task ID of every thread is INVALID_TASK_ID, unless a host is present and
+ // the host calls IClrTask::SetTaskIdentifier(). So we need to explicitly check for INVALID_TASK_ID
+ // here and return NULL if necessary. We return S_FALSE because that's the return value for the case
+ // where we can't find a thread for the specified task ID.
+ if (taskId == INVALID_TASK_ID)
+ {
+ *ppThread = NULL;
+ hr = S_FALSE;
+ }
+ else
+ {
+ PrepopulateThreadsOrThrow();
+
+ // now find the ICorDebugThread corresponding to it
+ CordbThread * pThread;
+ HASHFIND hashFind;
+
+
+ for (pThread = m_userThreads.FindFirst(&hashFind);
+ pThread != NULL;
+ pThread = m_userThreads.FindNext(&hashFind))
+ {
+ if (pThread->GetTaskID() == taskId)
+ {
+ break;
+ }
+ }
+
+ if (pThread == NULL)
+ {
+ *ppThread = NULL;
+ hr = S_FALSE;
+ }
+ else
+ {
+ *ppThread = pThread;
+ pThread->ExternalAddRef();
+ }
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+} // CordbProcess::GetThreadForTaskid
+
+HRESULT
+CordbProcess::GetVersion(COR_VERSION* pVersion)
+{
+ if (NULL == pVersion)
+ {
+ return E_INVALIDARG;
+ }
+
+ //
+ // Because we require a matching version of mscordbi.dll to debug a certain version of the runtime,
+ // we can just use constants found in this particular mscordbi.dll to determine the version of the left side.
+ pVersion->dwMajor = VER_MAJORVERSION;
+ pVersion->dwMinor = VER_MINORVERSION;
+ pVersion->dwBuild = VER_PRODUCTBUILD;
+ pVersion->dwSubBuild = VER_PRODUCTBUILD_QFE;
+
+ return S_OK;
+}
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+//-----------------------------------------------------------------------------
+// Search for a native patch given the address. Return null if not found.
+// Since we return an address, this is only valid until the table is disturbed.
+//-----------------------------------------------------------------------------
+NativePatch * CordbProcess::GetNativePatch(const void * pAddress)
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ int cTotal = m_NativePatchList.Count();
+ NativePatch * pTable = m_NativePatchList.Table();
+ if (pTable == NULL)
+ {
+ return NULL;
+ }
+
+ for(int i = 0; i < cTotal; i++)
+ {
+ if (pTable[i].pAddress == pAddress)
+ {
+ return &pTable[i];
+ }
+ }
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Is there an break-opcode (int3 on x86) at the address in the debuggee?
+//-----------------------------------------------------------------------------
+bool CordbProcess::IsBreakOpcodeAtAddress(const void * address)
+{
+ // There should have been an int3 there already. Since we already put it in there,
+ // we should be able to safely read it out.
+ BYTE opcodeTest = 0;
+
+ HRESULT hr = SafeReadStruct(PTR_TO_CORDB_ADDRESS(address), &opcodeTest);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+
+ return (opcodeTest == CORDbg_BREAK_INSTRUCTION);
+}
+#endif // FEATURE_INTEROP_DEBUGGING
+
+//-----------------------------------------------------------------------------
+// CordbProcess::SetUnmanagedBreakpoint
+// Called by a native debugger to add breakpoints during Interop.
+// address - remote address into the debuggee
+// bufsize, buffer[] - initial size & buffer for the opcode that we're replacing.
+// buflen - size of the buffer that we write to.
+//-----------------------------------------------------------------------------
+HRESULT
+CordbProcess::SetUnmanagedBreakpoint(CORDB_ADDRESS address, ULONG32 bufsize, BYTE buffer[], ULONG32 * bufLen)
+{
+ LOG((LF_CORDB, LL_INFO100, "CP::SetUnBP: pProcess=%x, address=%p.\n", this, CORDB_ADDRESS_TO_PTR(address)));
+#ifndef FEATURE_INTEROP_DEBUGGING
+ return E_NOTIMPL;
+#else
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ FAIL_IF_MANAGED_ONLY(this);
+ _ASSERTE(!ThreadHoldsProcessLock());
+ Lock();
+ HRESULT hr = SetUnmanagedBreakpointInternal(address, bufsize, buffer, bufLen);
+ Unlock();
+ return hr;
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// CordbProcess::SetUnmanagedBreakpointInternal
+// The worker behind SetUnmanagedBreakpoint, this function can set both public
+// breakpoints used by the debugger and internal breakpoints used for utility
+// purposes in interop debugging.
+// address - remote address into the debuggee
+// bufsize, buffer[] - initial size & buffer for the opcode that we're replacing.
+// buflen - size of the buffer that we write to.
+//-----------------------------------------------------------------------------
+HRESULT
+CordbProcess::SetUnmanagedBreakpointInternal(CORDB_ADDRESS address, ULONG32 bufsize, BYTE buffer[], ULONG32 * bufLen)
+{
+ LOG((LF_CORDB, LL_INFO100, "CP::SetUnBPI: pProcess=%x, address=%p.\n", this, CORDB_ADDRESS_TO_PTR(address)));
+#ifndef FEATURE_INTEROP_DEBUGGING
+ return E_NOTIMPL;
+#else
+
+ INTERNAL_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ FAIL_IF_MANAGED_ONLY(this);
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ HRESULT hr = S_OK;
+
+ NativePatch * p = NULL;
+#if defined(DBG_TARGET_X86) || defined(DBG_TARGET_AMD64)
+ const BYTE patch = CORDbg_BREAK_INSTRUCTION;
+ BYTE opcode;
+
+ // Make sure args are good
+ if ((buffer == NULL) || (bufsize < sizeof(patch)) || (bufLen == NULL))
+ {
+ hr = E_INVALIDARG;
+ goto ErrExit;
+ }
+
+ // Fail if there's already a patch at this address.
+ if (GetNativePatch(CORDB_ADDRESS_TO_PTR(address)) != NULL)
+ {
+ hr = CORDBG_E_NATIVE_PATCH_ALREADY_AT_ADDR;
+ goto ErrExit;
+ }
+
+ // Preallocate this now so that if are oom, we can fail before we get half-way through.
+ p = m_NativePatchList.Append();
+ if (p == NULL)
+ {
+ hr = E_OUTOFMEMORY;
+ goto ErrExit;
+ }
+
+
+ // Read out opcode. 1 byte on x86
+
+ hr = ApplyRemotePatch(this, CORDB_ADDRESS_TO_PTR(address), &p->opcode);
+ if (FAILED(hr))
+ goto ErrExit;
+
+ // It's all successful, so now update our out-params & internal bookkeaping.
+ opcode = (BYTE) p->opcode;
+ buffer[0] = opcode;
+ *bufLen = sizeof(opcode);
+
+ p->pAddress = CORDB_ADDRESS_TO_PTR(address);
+ p->opcode = opcode;
+
+ _ASSERTE(SUCCEEDED(hr));
+#elif defined(DBG_TARGET_WIN64)
+ PORTABILITY_ASSERT("NYI: CordbProcess::SetUnmanagedBreakpoint, interop debugging NYI on this platform");
+ hr = E_NOTIMPL;
+ goto ErrExit;
+#else
+ hr = E_NOTIMPL;
+ goto ErrExit;
+#endif // DBG_TARGET_X8_
+
+
+ErrExit:
+ // If we failed, then free the patch
+ if (FAILED(hr) && (p != NULL))
+ {
+ m_NativePatchList.Delete(*p);
+ }
+
+ return hr;
+
+#endif // FEATURE_INTEROP_DEBUGGING
+}
+
+
+//-----------------------------------------------------------------------------
+// CordbProcess::ClearUnmanagedBreakpoint
+// Called by a native debugger to remove breakpoints during Interop.
+// The patch is deleted even if the function fails.
+//-----------------------------------------------------------------------------
+HRESULT
+CordbProcess::ClearUnmanagedBreakpoint(CORDB_ADDRESS address)
+{
+ LOG((LF_CORDB, LL_INFO100, "CP::ClearUnBP: pProcess=%x, address=%p.\n", this, CORDB_ADDRESS_TO_PTR(address)));
+#ifndef FEATURE_INTEROP_DEBUGGING
+ return E_NOTIMPL;
+#else
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ FAIL_IF_MANAGED_ONLY(this);
+
+ _ASSERTE(!ThreadHoldsProcessLock());
+
+ HRESULT hr = S_OK;
+ PRD_TYPE opcode;
+
+ Lock();
+
+ // Make sure this is a valid patch.
+ int cTotal = m_NativePatchList.Count();
+ NativePatch * pTable = m_NativePatchList.Table();
+ if (pTable == NULL)
+ {
+ hr = CORDBG_E_NO_NATIVE_PATCH_AT_ADDR;
+ goto ErrExit;
+ }
+
+ int i;
+ for(i = 0; i < cTotal; i++)
+ {
+ if (pTable[i].pAddress == CORDB_ADDRESS_TO_PTR(address))
+ break;
+ }
+
+ if (i >= cTotal)
+ {
+ hr = CORDBG_E_NO_NATIVE_PATCH_AT_ADDR;
+ goto ErrExit;
+ }
+
+ // Found it! Remove it from our table. Note that this may shuffle table contents
+ // around, so don't keep pointers into the table.
+ opcode = pTable[i].opcode;
+
+ m_NativePatchList.Delete(pTable[i]);
+ _ASSERTE(m_NativePatchList.Count() == cTotal - 1);
+
+ // Now remove the patch.
+
+
+
+ // Just call through to Write ProcessMemory
+ hr = RemoveRemotePatch(this, CORDB_ADDRESS_TO_PTR(address), opcode);
+ if (FAILED(hr))
+ goto ErrExit;
+
+
+ // Our internal bookeaping was already updated to remove the patch, so now we're done.
+ // If we had a failure, we should have already bailed.
+ _ASSERTE(SUCCEEDED(hr));
+
+ErrExit:
+ Unlock();
+ return hr;
+#endif // FEATURE_INTEROP_DEBUGGING
+}
+
+
+//------------------------------------------------------------------------------------
+// StopCount, Sync, SyncReceived form our stop-status. This status is super-critical
+// to most hangs, so we stress log it.
+//------------------------------------------------------------------------------------
+void CordbProcess::SetSynchronized(bool fSynch)
+{
+ _ASSERTE(ThreadHoldsProcessLock() || !"Must have process lock to toggle SyncStatus");
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CP:: set sync=%d\n", fSynch);
+ m_synchronized = fSynch;
+}
+
+bool CordbProcess::GetSynchronized()
+{
+ // This can be accessed whether we're Locked or not. This means that the result
+ // may change underneath us.
+ return m_synchronized;
+}
+
+void CordbProcess::IncStopCount()
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+ m_stopCount++;
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CP:: Inc StopCount=%d\n", m_stopCount);
+}
+void CordbProcess::DecStopCount()
+{
+ // We can inc w/ just the process lock (b/c we can dispatch events from the W32ET)
+ // But decrementing (eg, Continue), requires the stop-go lock.
+ // This if an operation takes the SG lock, it ensures we don't continue from underneath it.
+ ASSERT_SINGLE_THREAD_ONLY(HoldsLock(&m_StopGoLock));
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ m_stopCount--;
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CP:: Dec StopCount=%d\n", m_stopCount);
+}
+
+// Just gets whether we're stopped or not (m_stopped > 0).
+// You only need the StopGo lock for this.
+bool CordbProcess::IsStopped()
+{
+ // We don't require the process-lock, just the SG-lock.
+ // Holding the SG lock prevents another thread from continuing underneath you.
+ // (see DecStopCount()).
+ // But you could still be running free, and have another thread stop-underneath you.
+ // Thus IsStopped() leans towards returning false.
+ ASSERT_SINGLE_THREAD_ONLY(HoldsLock(&m_StopGoLock));
+
+ return (m_stopCount > 0);
+}
+
+int CordbProcess::GetStopCount()
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+ return m_stopCount;
+}
+
+bool CordbProcess::GetSyncCompleteRecv()
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+ return m_syncCompleteReceived;
+}
+
+void CordbProcess::SetSyncCompleteRecv(bool fSyncRecv)
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CP:: set syncRecv=%d\n", fSyncRecv);
+ m_syncCompleteReceived = fSyncRecv;
+}
+
+// This can be used if we ever need the RS to emulate old behavior of previous versions.
+// This can not be used in QIs to deny queries for new interfaces.
+// QIs must be consistent across the lifetime of an object. Say CordbThread used this in a QI
+// do deny returning a ICorDebugThread2 interface when emulating v1.1. Once that Thread is neutered,
+// it no longer has a pointer to the process, and it no longer knows if it should be denying
+// the v2.0 query. An object's QI can't start returning new interfaces onces its neutered.
+bool CordbProcess::SupportsVersion(CorDebugInterfaceVersion featureVersion)
+{
+ _ASSERTE(featureVersion == CorDebugVersion_2_0);
+ return true;
+}
+
+
+//---------------------------------------------------------------------------------------
+// Add an object to the process's Left-Side resource cleanup list
+//
+// Arguments:
+// pObject - non-null object to be added
+//
+// Notes:
+// This list tracks objects with process-scope that hold left-side
+// resources (like func-eval).
+// See code:CordbAppDomain::GetSweepableExitNeuterList for per-appdomain
+// objects with left-side resources.
+void CordbProcess::AddToLeftSideResourceCleanupList(CordbBase * pObject)
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(pObject != NULL);
+
+ m_LeftSideResourceCleanupList.Add(this, pObject);
+}
+
+// This list will get actively swept (looking for objects w/ external ref = 0) between continues.
+void CordbProcess::AddToNeuterOnExitList(CordbBase *pObject)
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(pObject != NULL);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ this->m_ExitNeuterList.Add(this, pObject);
+ }
+ EX_CATCH_HRESULT(hr);
+ SetUnrecoverableIfFailed(GetProcess(), hr);
+}
+
+// Mark that this object should be neutered the next time we Continue the process.
+void CordbProcess::AddToNeuterOnContinueList(CordbBase *pObject)
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(pObject != NULL);
+
+ m_ContinueNeuterList.Add(this, pObject); // throws
+}
+
+
+/* ------------------------------------------------------------------------- *
+ * Runtime Controller Event Thread class
+ * ------------------------------------------------------------------------- */
+
+//
+// Constructor
+//
+CordbRCEventThread::CordbRCEventThread(Cordb* cordb)
+{
+ _ASSERTE(cordb != NULL);
+
+ m_cordb.Assign(cordb);
+ m_thread = NULL;
+ m_threadId = 0;
+ m_run = TRUE;
+ m_threadControlEvent = NULL;
+ m_processStateChanged = FALSE;
+
+ g_pRSDebuggingInfo->m_RCET = this;
+}
+
+
+//
+// Destructor. Cleans up all of the open handles and such.
+// This expects that the thread has been stopped and has terminated
+// before being called.
+//
+CordbRCEventThread::~CordbRCEventThread()
+{
+ if (m_threadControlEvent != NULL)
+ CloseHandle(m_threadControlEvent);
+
+ if (m_thread != NULL)
+ CloseHandle(m_thread);
+
+ g_pRSDebuggingInfo->m_RCET = NULL;
+}
+
+//
+// Init sets up all the objects that the thread will need to run.
+//
+HRESULT CordbRCEventThread::Init()
+{
+ if (m_cordb == NULL)
+ return E_INVALIDARG;
+
+ m_threadControlEvent = WszCreateEvent(NULL, FALSE, FALSE, NULL);
+
+ if (m_threadControlEvent == NULL)
+ return HRESULT_FROM_GetLastError();
+
+ return S_OK;
+}
+
+
+#if defined(FEATURE_INTEROP_DEBUGGING)
+//
+// Helper to duplicate a handle or thorw
+//
+// Arguments:
+// pLocalHandle - handle to duplicate into the remote process
+// pRemoteHandle - RemoteHandle structure in IPC block to hold the remote handle.
+// Return value:
+// None. Throws on error.
+//
+void CordbProcess::DuplicateHandleToLocalProcess(HANDLE * pLocalHandle, RemoteHANDLE * pRemoteHandle)
+{
+ _ASSERTE(m_pShim != NULL);
+
+ // Dup RSEA and RSER into this process if we don't already have them.
+ // On Launch, we don't have them yet, but on attach we do.
+ if (*pLocalHandle == NULL)
+ {
+ BOOL fSuccess = pRemoteHandle->DuplicateToLocalProcess(m_handle, pLocalHandle);
+ if (!fSuccess)
+ {
+ ThrowLastError();
+ }
+ }
+
+}
+#endif // FEATURE_INTEROP_DEBUGGING
+
+// Public entry wrapper for code:CordbProcess::FinishInitializeIPCChannelWorker
+void CordbProcess::FinishInitializeIPCChannel()
+{
+ // This is called directly from a shim callback.
+ PUBLIC_API_ENTRY_FOR_SHIM(this);
+ FinishInitializeIPCChannelWorker();
+}
+
+//
+// Initialize the IPC channel. After this, IPC events can flow in both ways.
+//
+// Return value:
+// Returns S_OK on success.
+//
+// Notes:
+// This will dispatch an UnrecoverableError callback if it fails.
+// This will also initialize key state in the CordbProcess object.
+//
+// @dbgtodo remove helper-thread: this should eventually go away once we get rid of IPC events.
+//
+void CordbProcess::FinishInitializeIPCChannelWorker()
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr = S_OK;
+ _ASSERTE(m_pShim != NULL);
+
+ RSLockHolder lockHolder(&this->m_processMutex);
+
+ // If it's already initialized, then nothing left to do.
+ // this protects us if this function is called multiple times.
+ if (m_initialized)
+ {
+ _ASSERTE(GetDCB() != NULL);
+ return;
+ }
+
+ EX_TRY
+ {
+ LOG((LF_CORDB, LL_INFO1000, "[%x] RCET::HFRCE: first event..., process %p\n", GetCurrentThreadId(), this));
+
+ BOOL fBlockExists;
+ GetEventBlock(&fBlockExists); // throws on error
+
+ LOG((LF_CORDB, LL_EVERYTHING, "Size of CdbP is %d\n", sizeof(CordbProcess)));
+
+ m_pEventChannel->Init(m_handle);
+
+#if defined(FEATURE_INTEROP_DEBUGGING)
+ DuplicateHandleToLocalProcess(&m_leftSideUnmanagedWaitEvent, &GetDCB()->m_leftSideUnmanagedWaitEvent);
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ // Read the Runtime Offsets struct out of the debuggee.
+ hr = GetRuntimeOffsets();
+ IfFailThrow(hr);
+
+ // we need to be careful here. The LS will have a thread running free that may be initializing
+ // fields of the DCB (specifically it may be setting up the helper thread), so we need to make sure
+ // we don't overwrite any fields that the LS is writing. We need to be sure we only write to RS
+ // status fields.
+ m_initialized = true;
+ GetDCB()->m_rightSideIsWin32Debugger = IsInteropDebugging();
+ UpdateLeftSideDCBField(&(GetDCB()->m_rightSideIsWin32Debugger), sizeof(GetDCB()->m_rightSideIsWin32Debugger));
+
+ LOG((LF_CORDB, LL_INFO1000, "[%x] RCET::HFRCE: ...went fine\n", GetCurrentThreadId()));
+ _ASSERTE(SUCCEEDED(hr));
+
+ } EX_CATCH_HRESULT(hr);
+ if (SUCCEEDED(hr))
+ {
+ return;
+ }
+
+ // We only land here on failure cases.
+ // We must have jumped to this label. Maybe we didn't set HR, so check now.
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "HFCR: FAILED hr=0x%08x\n", hr);
+
+ CloseIPCHandles();
+
+ // Rethrow
+ ThrowHR(hr);
+}
+
+
+//---------------------------------------------------------------------------------------
+// Marshals over a string buffer in a managed event
+//
+// Arguments:
+// pTarget - data-target for read the buffer from the LeftSide.
+//
+// Throws on error
+void Ls_Rs_BaseBuffer::CopyLSDataToRSWorker(ICorDebugDataTarget * pTarget)
+{
+ //
+ const DWORD cbCacheSize = m_cbSize;
+
+ // SHOULD not happen for more than once in well-behaved case.
+ if (m_pbRS != NULL)
+ {
+ SIMPLIFYING_ASSUMPTION(!"m_pbRS is non-null; is this a corrupted event?");
+ ThrowHR(E_INVALIDARG);
+ }
+
+ NewHolder<BYTE> pData(new BYTE[cbCacheSize]);
+
+ ULONG32 cbRead;
+ HRESULT hrRead = pTarget->ReadVirtual(PTR_TO_CORDB_ADDRESS(m_pbLS), pData, cbCacheSize , &cbRead);
+
+ if(FAILED(hrRead))
+ {
+ hrRead = CORDBG_E_READVIRTUAL_FAILURE;
+ }
+
+ if (SUCCEEDED(hrRead) && (cbCacheSize != cbRead))
+ {
+ hrRead = HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY);
+ }
+ IfFailThrow(hrRead);
+
+ // Now do Transfer
+ m_pbRS = pData;
+ pData.SuppressRelease();
+}
+
+//---------------------------------------------------------------------------------------
+// Marshals over a Byte buffer in a managed event
+//
+// Arguments:
+// pTarget - data-target for read the buffer from the LeftSide.
+//
+// Throws on error
+void Ls_Rs_ByteBuffer::CopyLSDataToRS(ICorDebugDataTarget * pTarget)
+{
+ CopyLSDataToRSWorker(pTarget);
+}
+
+//---------------------------------------------------------------------------------------
+// Marshals over a string buffer in a managed event
+//
+// Arguments:
+// pTarget - data-target for read the buffer from the LeftSide.
+//
+// Throws on error
+void Ls_Rs_StringBuffer::CopyLSDataToRS(ICorDebugDataTarget * pTarget)
+{
+ CopyLSDataToRSWorker(pTarget);
+
+ // Ensure we're a valid, well-formed string.
+ // @dbgtodo - this should only happen in corrupted scenarios. Perhaps a better HR here?
+ // - null terminated.
+ // - no embedded nulls.
+
+ const WCHAR * pString = GetString();
+ SIZE_T dwExpectedLenWithNull = m_cbSize / sizeof(WCHAR);
+
+ // Should at least have 1 character for the null-terminator.
+ if (dwExpectedLenWithNull == 0)
+ {
+ ThrowHR(CORDBG_E_TARGET_INCONSISTENT);
+ }
+
+ // Ensure that there's a null where we expect it to be.
+ if (pString[dwExpectedLenWithNull-1] != 0)
+ {
+ ThrowHR(CORDBG_E_TARGET_INCONSISTENT);
+ }
+
+ // Now we know it's safe to call wcslen. The buffer is local, so we know the pages are there.
+ // And we know there's a null capping the max length of the string.
+ SIZE_T dwActualLenWithNull = wcslen(pString) + 1;
+ if (dwActualLenWithNull != dwExpectedLenWithNull)
+ {
+ ThrowHR(CORDBG_E_TARGET_INCONSISTENT);
+ }
+}
+
+//---------------------------------------------------------------------------------------
+// Marshals the arguments in a managed-debug event.
+//
+// Arguments:
+// pManagedEvent - (IN/OUT) debug event to marshal. Events are not usable in the host process
+// until they are marshalled. This will marshal the event in-place, and may convert
+// some target addresses to host addresses.
+//
+// Return Value:
+// S_OK on success. Else Error.
+//
+// Assumptions:
+// Target is currently stopped and inspectable.
+// After the event is marshalled, it has resources that must be cleaned up
+// by calling code:DeleteIPCEventHelper.
+//
+// Notes:
+// Call a Copy function (CopyManagedEventFromTarget, CopyRCEventFromIPCBlock)to
+// get the event to marshal.
+// This will marshal args from the target into the host.
+// The debug event is fixed size. But since the debuggee is stopped, this can copy
+// arbitrary-length buffers out of of the debuggee.
+//
+// This could be rolled into code:CordbProcess::RawDispatchEvent
+//---------------------------------------------------------------------------------------
+void CordbProcess::MarshalManagedEvent(DebuggerIPCEvent * pManagedEvent)
+{
+ CONTRACTL
+ {
+ THROWS;
+
+ // Event has already been copied, now we do some quick Marshalling.
+ // Thsi should be a private local copy, and not the one in the IPC block or Target.
+ PRECONDITION(CheckPointer(pManagedEvent));
+ }
+ CONTRACTL_END;
+
+ IfFailThrow(pManagedEvent->hr);
+
+ // This may throw part way through marshalling. But that's ok because
+ // code:DeleteIPCEventHelper can cleanup a partially-marshalled event.
+
+ // Do a pre-processing on the event
+ switch (pManagedEvent->type & DB_IPCE_TYPE_MASK)
+ {
+ case DB_IPCE_MDA_NOTIFICATION:
+ {
+ pManagedEvent->MDANotification.szName.CopyLSDataToRS(this->m_pDACDataTarget);
+ pManagedEvent->MDANotification.szDescription.CopyLSDataToRS(this->m_pDACDataTarget);
+ pManagedEvent->MDANotification.szXml.CopyLSDataToRS(this->m_pDACDataTarget);
+ break;
+ }
+
+ case DB_IPCE_FIRST_LOG_MESSAGE:
+ {
+ pManagedEvent->FirstLogMessage.szContent.CopyLSDataToRS(this->m_pDACDataTarget);
+ break;
+ }
+
+ default:
+ break;
+ }
+
+
+}
+
+
+//---------------------------------------------------------------------------------------
+// Copy a managed debug event from the target process into this local process
+//
+// Arguments:
+// pRecord - native-debug event serving as the envelope for the managed event.
+// pLocalManagedEvent - (dst) required local buffer to hold managed event.
+//
+// Return Value:
+// * True if the event belongs to this runtime. This is very useful when multiple CLRs are
+// loaded into the target and all sending events wit the same exception code.
+// * False if this does not belong to this instance of ICorDebug. (perhaps it's an event
+// intended for another instance of the CLR in the target, or some rogue user code happening
+// to use our exception code).
+// In either case, the event can still be cleaned up via code:DeleteIPCEventHelper.
+//
+// Throws on error. In the error case, the contents of pLocalManagedEvent are undefined.
+// They may have been partially copied from the target. The local managed event does not own
+// any resources until it's marshalled, so the buffer can be ignored if this function fails.
+//
+// Assumptions:
+//
+// Notes:
+// The events are sent form the target via code:Debugger::SendRawEvent
+// This just does a raw Byte copy, but does not do any Marshalling.
+// This should always succeed in the well-behaved case. However, A bad debuggee can
+// always send a poor-formed debug event.
+// We don't distinguish between a badly formed event and an event that's not ours.
+// The event still needs to be Marshaled before being used. (see code:CordbProcess::MarshalManagedEvent)
+//
+//---------------------------------------------------------------------------------------
+#if defined(_MSC_VER) && defined(_TARGET_ARM_)
+// This is a temporary workaround for an ARM specific MS C++ compiler bug (internal LKG build 18.1).
+// Branch < if (ptrRemoteManagedEvent == NULL) > was always taken and the function always returned false.
+// TODO: It should be removed once the bug is fixed.
+#pragma optimize("", off)
+#endif
+bool CordbProcess::CopyManagedEventFromTarget(
+ const EXCEPTION_RECORD * pRecord,
+ DebuggerIPCEvent * pLocalManagedEvent)
+{
+ _ASSERTE(pRecord != NULL);
+ _ASSERTE(pLocalManagedEvent != NULL);
+
+ // Initialize the event enough such backout code can call code:DeleteIPCEventHelper.
+ pLocalManagedEvent->type = DB_IPCE_DEBUGGER_INVALID;
+
+ // Ensure we have a CLR instance ID by now. Either we had one already, or we're in
+ // V2 mode and this is the startup event, and so we'll set it now.
+ HRESULT hr = EnsureClrInstanceIdSet();
+ IfFailThrow(hr);
+ _ASSERTE(m_clrInstanceId != 0);
+
+ // Determine if the event is really a debug event, and for our instance.
+ CORDB_ADDRESS ptrRemoteManagedEvent = IsEventDebuggerNotification(pRecord, m_clrInstanceId);
+
+ if (ptrRemoteManagedEvent == NULL)
+ {
+ return false;
+ }
+
+ // What we are doing on Windows here is dangerous. Any buffer for IPC events must be at least
+ // CorDBIPC_BUFFER_SIZE big, but here we are only copying sizeof(DebuggerIPCEvent). Fortunately, the
+ // only case where an IPC event is bigger than sizeof(DebuggerIPCEvent) is for the second category
+ // described in the comment for code:IEventChannel. In this case, we are just transferring the IPC
+ // event from the native pipeline to the event channel, and the event channel will read it directly from
+ // the send buffer on the LS. See code:CordbRCEventThread::WaitForIPCEventFromProcess.
+#if !defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ hr = SafeReadStruct(ptrRemoteManagedEvent, pLocalManagedEvent);
+#else
+ // For Mac remote debugging the address returned above is actually a local address.
+ // Also, we need to copy the entire buffer because once a debug event is read from the debugger
+ // transport, it won't be available afterwards.
+ memcpy(reinterpret_cast<BYTE *>(pLocalManagedEvent),
+ CORDB_ADDRESS_TO_PTR(ptrRemoteManagedEvent),
+ CorDBIPC_BUFFER_SIZE);
+ hr = S_OK;
+#endif
+ SIMPLIFYING_ASSUMPTION(SUCCEEDED(hr));
+ IfFailThrow(hr);
+
+ return true;
+}
+#if defined(_MSC_VER) && defined(_TARGET_ARM_)
+#pragma optimize("", on)
+#endif
+
+//---------------------------------------------------------------------------------------
+// EnsureClrInstanceIdSet - Ensure we have a CLR Instance ID to debug
+//
+// In Arrowhead scenarios, the debugger is required to pass a valid CLR instance ID
+// to us in OpenVirtualProcess. In V2 scenarios, for compatibility, we'll allow a
+// CordbProcess object to exist for a process that doesn't yet have the CLR loaded.
+// In this case the CLR instance ID will start off as 0, but be filled in when we see the
+// startup exception indicating the CLR has been loaded.
+//
+// If we don't already have an instance ID, this function sets it to the only CLR in the
+// target process. This requires that a CLR be loaded in the target process.
+//
+// Return Value:
+// S_OK - if m_clrInstanceId was already set, or is now set to a valid CLR instance ID
+// an error HRESULT - if m_clrInstanceId was 0, and cannot be set to a valid value
+// (i.e. because we cannot find a CLR in the target process).
+//
+// Note that we need to probe for this on attach, and it's common to attach before the
+// CLR has been loaded, so we avoid using exceptions for this common case.
+//
+HRESULT CordbProcess::EnsureClrInstanceIdSet()
+{
+ // If we didn't expect a specific CLR, then attempt to attach to any.
+ if (m_clrInstanceId == 0)
+ {
+
+#ifdef FEATURE_CORESYSTEM
+ if(m_cordb->GetTargetCLR() != 0)
+ {
+ m_clrInstanceId = PTR_TO_CORDB_ADDRESS(m_cordb->GetTargetCLR());
+ return S_OK;
+ }
+#endif
+
+ // The only case in which we're allowed to request the "default" CLR instance
+ // ID is when we're running in V2 mode. In V3, the client is required to pass
+ // a non-zero value to OpenVirtualProcess.
+ _ASSERTE(m_pShim != NULL);
+
+ HRESULT hr = m_pShim->FindLoadedCLR(&m_clrInstanceId);
+ if (FAILED(hr))
+ {
+ // Couldn't find a loaded clr - no CLR instance ID yet
+ _ASSERTE(m_clrInstanceId == 0);
+ return hr;
+ }
+ }
+
+ // We've (now) got a valid CLR instance id
+ return S_OK;
+}
+
+//---------------------------------------------------------------------------------------
+// // Copy event from IPC block into local.
+//
+// Arguments:
+// pLocalManagedEvent - required local buffer to hold managed event.
+//
+// Return Value:
+// None. Always succeeds.
+//
+// Assumptions:
+// The IPC block has already been opened and filled in with an event.
+//
+// Notes:
+// This is copying from a shared-memory block, which is treated as local memory.
+// This just does a raw Byte copy, but does not do any Marshalling.
+// This does no validation on the event.
+// The event still needs to be Marshaled before being used. (see code:CordbProcess::MarshalManagedEvent)
+//
+//---------------------------------------------------------------------------------------
+void inline CordbProcess::CopyRCEventFromIPCBlock(DebuggerIPCEvent * pLocalManagedEvent)
+{
+ _ASSERTE(pLocalManagedEvent != NULL);
+
+ IfFailThrow(m_pEventChannel->GetEventFromLeftSide(pLocalManagedEvent));
+}
+
+// Return true if this is the RCEvent thread, else false.
+bool CordbRCEventThread::IsRCEventThread()
+{
+ return (m_threadId == GetCurrentThreadId());
+}
+
+//---------------------------------------------------------------------------------------
+// Runtime assert, throws CORDBG_E_TARGET_INCONSISTENT if the expression is not true.
+//
+// Arguments:
+// fExpression - assert parameter. If true, this function is a nop. If false,
+// this will throw a CORDBG_E_TARGET_INCONSISTENT error.
+//
+// Notes:
+// Use this for runtime checks to validate assumptions about the data-target.
+// IcorDebug can't trust that data from the debugee is consistent (perhaps it's
+// corrupted).
+void CordbProcess::TargetConsistencyCheck(bool fExpression)
+{
+ if (!fExpression)
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO10000, "Target consistency check failed");
+
+ // When debugging possibly corrupt targets, this failure may be expected. For debugging purposes,
+ // assert if we're not expecting any target inconsistencies.
+ CONSISTENCY_CHECK_MSG( !m_fAssertOnTargetInconsistency, "Target consistency check failed unexpectedly");
+
+ ThrowHR(CORDBG_E_TARGET_INCONSISTENT);
+ }
+}
+
+//
+// SendIPCEvent -- send an IPC event to the runtime controller. All this
+// really does is copy the event into the process's send buffer and sets
+// the RSEA then waits on RSER.
+//
+// Note: when sending a two-way event (replyRequired = true), the
+// eventSize must be large enough for both the event sent and the
+// result event.
+//
+// Returns whether the event was sent successfully. This is different than event->eventHr.
+//
+HRESULT CordbRCEventThread::SendIPCEvent(CordbProcess* process,
+ DebuggerIPCEvent* event,
+ SIZE_T eventSize)
+{
+
+ _ASSERTE(process != NULL);
+ _ASSERTE(event != NULL);
+ _ASSERTE(process->GetShim() != NULL);
+
+#ifdef _DEBUG
+ // We need to be synchronized whenever we're sending an IPC Event.
+ // This may require our callers' using a Stop-Continue holder.
+ // Attach + AsyncBreak are the only (obvious) exceptions.
+ // For continue, we set Sync-Status to false before sending, so we exclude that too.
+ // Everybody else should only be sending events when synced. We should never ever ever
+ // send an event from a CorbXYZ dtor (b/c that would be called at any random time). Instead,
+ // use a NeuterList.
+ switch (event->type)
+ {
+ case DB_IPCE_ATTACHING:
+ case DB_IPCE_ASYNC_BREAK:
+ case DB_IPCE_CONTINUE:
+ break;
+
+ default:
+ CONSISTENCY_CHECK_MSGF(process->GetSynchronized(), ("Must by synced while sending IPC event: %s (0x%x)",
+ IPCENames::GetName(event->type), event->type));
+ }
+#endif
+
+
+ LOG((LF_CORDB, LL_EVERYTHING, "SendIPCEvent in CordbRCEventThread called\n"));
+
+ // For simplicity sake, we have the following conservative invariants when sending IPC events:
+ // - Always hold the Stop-Go lock.
+ // - never on the W32ET.
+ // - Never hold the Process-lock (this allows the w32et to take that lock to pump)
+
+ // Must have the stop-go lock to send an IPC event.
+ CONSISTENCY_CHECK_MSGF(process->GetStopGoLock()->HasLock(), ("Must have stop-go lock to send event. proc=%p, event=%s",
+ process, IPCENames::GetName(event->type)));
+
+ // The w32 ET will need to take the process lock. So if we're holding it here, then we'll
+ // deadlock (since W32 ET is blocked on lock, which we would hold; and we're blocked on W32 ET
+ // to keep pumping.
+ _ASSERTE(!process->ThreadHoldsProcessLock() || !"Can't hold P-lock while sending blocking IPC event");
+
+
+ // Can't be on the w32 ET, or we can't be pumping.
+ // Although we can trickle in here from public APIs, our caller should have validated
+ // that we weren't on the w32et, so the assert here is justified. But just in case there's something we missed,
+ // we have a runtime check (as a final backstop against a deadlock).
+ _ASSERTE(!process->IsWin32EventThread());
+ CORDBFailIfOnWin32EventThread(process);
+
+
+ // If this is an async event, then we expect it to be sent while the process is locked.
+ if (event->asyncSend)
+ {
+ // This may be on the w32et, so we can't hold the stop-go lock.
+ _ASSERTE(event->type == DB_IPCE_ATTACHING); // only async event should be attaching.
+ }
+
+
+ // This will catch us if we've detached or exited.
+ // Note if we exited, then we should have been neutered and so shouldn't even be sending an IPC event,
+ // but just in case, we'll check.
+ CORDBRequireProcessStateOK(process);
+
+
+#ifdef _DEBUG
+ // We should never send an Async Break on the RCET. This will deadlock.
+ // - if we're on the RCET, we should be stopped, and thus Stop() should just bump up a stop count,
+ // and not actually send an AsyncBreak.
+ // - Delayed-Continues help enforce this.
+ // This is a special case of the deadlock check below.
+ if (IsRCEventThread())
+ {
+ _ASSERTE(event->type != DB_IPCE_ASYNC_BREAK);
+ }
+#endif
+
+#ifdef _DEBUG
+ // This assert protects us against a deadlock.
+ // 1) (RCET) blocked on (This function): If we're on the RCET, then the RCET is blocked until we return (duh).
+ // 2) (LS) blocked on (RCET): If the LS is not synchronized, then it may be sending an event to the RCET, and thus blocked on the RCET.
+ // 3) (Helper thread) blocked on (LS): That LS thread may be holding a lock that the helper thread needs, thus blocking the helper thread.
+ // 4) (This function) blocked on (Helper Thread): We block until the helper thread can process our IPC event.
+ // #4 is not true for async events.
+ //
+ // If we hit this assert, it means we may get the deadlock above and we're calling SendIPCEvent at a time we shouldn't.
+ // Note this race is as old as dirt.
+ if (IsRCEventThread() && !event->asyncSend)
+ {
+ // Note that w/ Continue & Attach, GetSynchronized() has a different meaning and the race above won't happen.
+ BOOL fPossibleDeadlock = process->GetSynchronized() || (event->type == DB_IPCE_CONTINUE) || (event->type == DB_IPCE_ATTACHING);
+ CONSISTENCY_CHECK_MSGF(fPossibleDeadlock, ("Possible deadlock while sending: '%s'\n", IPCENames::GetName(event->type)));
+ }
+#endif
+
+
+
+ // Cache this process into the MRU so that we can find it if we're debugging in retail.
+ g_pRSDebuggingInfo->m_MRUprocess = process;
+
+ HRESULT hr = S_OK;
+ HRESULT hrEvent = S_OK;
+ _ASSERTE(event != NULL);
+
+ // NOTE: the eventSize parameter is only so you can specify an event size that is SMALLER than the process send
+ // buffer size!!
+ if (eventSize > CorDBIPC_BUFFER_SIZE)
+ return E_INVALIDARG;
+
+ STRESS_LOG4(LF_CORDB, LL_INFO1000, "CRCET::SIPCE: sending %s to AD 0x%x, proc 0x%x(%d)\n",
+ IPCENames::GetName(event->type), VmPtrToCookie(event->vmAppDomain), process->m_id, process->m_id);
+
+ // For 2-way events, this check is unnecessary (since we already check for LS exit)
+ // But for async events, we need this.
+ // So just check it up here and make everyone's life easier.
+ if (process->m_terminated)
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO10000, "CRCET::SIPCE: LS already terminated, shortcut exiting\n");
+ return CORDBG_E_PROCESS_TERMINATED;
+ }
+
+ // If the helper thread has died, we can't send an IPC event (and it's never coming back either).
+ // Although we do wait on the thread's handle, there are strange windows where the thread's handle
+ // is not yet signaled even though we've continued from the exit-thread event for the helper.
+ if (process->m_helperThreadDead)
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO10000, "CRCET::SIPCE: Helper-thread dead, shortcut exiting\n");
+ return CORDBG_E_PROCESS_TERMINATED;
+ }
+
+ BOOL fUnrecoverableError = TRUE;
+ EX_TRY
+ {
+ hr = process->GetEventChannel()->SendEventToLeftSide(event, eventSize);
+ fUnrecoverableError = FALSE;
+ }
+ EX_CATCH_HRESULT(hr);
+
+
+ // If we're sending a Continue() event, then after this, the LS may run free.
+ // If this is the last managed event before the LS exits, (which is the case
+ // if we're responding to either an Exit-Thread or if we respond to a Detach)
+ // the LS may exit at anytime from here on, so we need to be careful.
+
+
+ if (fUnrecoverableError)
+ {
+ _ASSERTE(FAILED(hr));
+ CORDBSetUnrecoverableError(process, hr, 0);
+ }
+ else
+ {
+ // Get a handle to the target process - this call always succeeds
+ HANDLE hLSProcess = NULL;
+ process->GetHandle(&hLSProcess);
+
+ // We take locks to ensure that the CordbProcess object is still alive,
+ // even if the OS process exited.
+ _ASSERTE(hLSProcess != NULL);
+
+ // Check if Sending the IPC event failed
+ if (FAILED(hr))
+ {
+ // The failure to send an event may be due to the target process terminating
+ // (especially, but not exclusively, in the case of async events).
+ // There is a race here - we can't rely on any check above SendEventToLeftSide
+ // to tell us whether the process has exited yet.
+ // Check for that case and return an accurate hresult.
+ DWORD ret = WaitForSingleObject(hLSProcess, 0);
+ if (ret == WAIT_OBJECT_0)
+ {
+ return CORDBG_E_PROCESS_TERMINATED;
+ }
+
+ // Some other failure sending the IPC event - just return it.
+ return hr;
+ }
+
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CRCET::SIPCE: sent...\n");
+
+ // If this is an async send, then don't wait for the left side to acknowledge that its read the event.
+ _ASSERTE(!event->asyncSend || !event->replyRequired);
+
+ if (process->GetEventChannel()->NeedToWaitForAck(event))
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000,"CRCET::SIPCE: waiting for left side to read event. (on RSER)\n");
+
+ DWORD ret;
+
+ // Wait for either a reply (common case) or the left side to go away.
+ // We can't detach while waiting for a reply (because detach needs to send events).
+ // All of the outcomes from this wait are completely disjoint.
+ // It's possible for the LS to reply and then exit normally (Thread_Detach, Process_Detach)
+ // and so ExitProcess may have been called, but it doesn't matter.
+
+ enum {
+ ID_RSER = WAIT_OBJECT_0,
+ ID_LSPROCESS,
+ ID_HELPERTHREAD,
+ };
+
+ // Only wait on the helper thread for cases where the process is stopped (and thus we don't expect it do exit on us).
+ // If the process is running and we lose our helper thread, it ought to be during shutdown and we ough to
+ // follow up with an exit.
+ // This includes when we've dispatch Native events, and it includes the AsyncBreak sent to get us from a
+ // win32 frozen state to a synchronized state).
+ HANDLE hHelperThread = NULL;
+ if (process->IsStopped())
+ {
+ hHelperThread = process->GetHelperThreadHandle();
+ }
+
+
+ // Note that in case of a tie (multiple handles signaled), WaitForMultipleObjects gives
+ // priority to the handle earlier in the array.
+ HANDLE waitSet[] = { process->GetEventChannel()->GetRightSideEventAckHandle(), hLSProcess, hHelperThread};
+ DWORD cWaitSet = NumItems(waitSet);
+ if (hHelperThread == NULL)
+ {
+ cWaitSet--;
+ }
+
+ do
+ {
+ ret = WaitForMultipleObjectsEx(cWaitSet, waitSet, FALSE, CordbGetWaitTimeout(), FALSE);
+ // If we timeout because we're waiting for an uncontinued OOB event, we need to just keep waiting.
+ } while ((ret == WAIT_TIMEOUT) && process->IsWaitingForOOBEvent());
+
+ switch(ret)
+ {
+ case ID_RSER:
+ // Normal reply from LS.
+ // This is set iff the LS replied to our event. The LS may have exited since it replied
+ // but we don't care. We still have the reply and we'll pass it on.
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CRCET::SIPCE: left side read the event.\n");
+
+ // If this was a two-way event, then the result is already ready for us. Simply copy the result back
+ // over the original event that was sent. Otherwise, the left side has simply read the event and is
+ // processing it...
+ if (event->replyRequired)
+ {
+ process->GetEventChannel()->GetReplyFromLeftSide(event, eventSize);
+ hrEvent = event->hr;
+ }
+ break;
+
+ case ID_LSPROCESS:
+ // Left side exited on us.
+ // ExitProcess may or may not have been called here (since it's on a different thread).
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CRCET::SIPCE: left side exiting while RS was waiting for reply.\n");
+ hr = CORDBG_E_PROCESS_TERMINATED;
+ break;
+
+ case ID_HELPERTHREAD:
+ // We can only send most IPC events while the LS is synchronized. We shouldn't lose our helper thread
+ // when synced under any sort of normal conditions.
+ // This won't fire if the process already exited, because LSPROCESS gets higher priority in the wait
+ // (since it was placed earlier).
+ // Thus the only "legitimate" window where this could happen would be in a shutdown scenario after
+ // the helper is dead but before the process has died. We shouldn't be synced in that scenario,
+ // so we shouldn't be sending IPC events during it.
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CRCET::SIPCE: lost helper thread.\n");
+
+
+ // Assert because we want to know if we ever actually hit this in any detectable scenario.
+ // However, shutdown can occur in preemptive mode. Thus if the RS does an AsyncBreak late
+ // enough, then the LS will appear to be stopped but may still shutdown.
+ // Since the debuggee can exit asynchronously at any time (eg, suppose somebody forcefully
+ // kills it with taskman), this doesn't introduce a new case.
+ // That aside, it would be great to be able to assert this:
+ //_ASSERTE(!"Potential deadlock - Randomly Lost helper thread");
+
+ // We'll piggy back this on the terminated case.
+ hr = CORDBG_E_PROCESS_TERMINATED;
+ break;
+
+ default:
+ {
+ // If we timed out/failed, check the left side to see if it is in the unrecoverable error mode. If it is,
+ // return the HR from the left side that caused the error. Otherwise, return that we timed out and that
+ // we don't really know why.
+ HRESULT realHR = (ret == WAIT_FAILED) ? HRESULT_FROM_GetLastError() : ErrWrapper(CORDBG_E_TIMEOUT);
+
+ hr = process->CheckForUnrecoverableError();
+
+ if (hr == S_OK)
+ {
+ CORDBSetUnrecoverableError(process, realHR, 0);
+ hr = realHR;
+ }
+
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CRCET::SIPCE: left side timeout/fail while RS waiting for reply. hr = 0x%08x\n", hr);
+ }
+ break;
+ }
+
+ // If the LS picked up RSEA, it will be reset (since it's an auto event).
+ // But in the case that the wait failed or that the LS exited, we need to explicitly reset RSEA
+ if (hr != S_OK)
+ {
+ process->GetEventChannel()->ClearEventForLeftSide();
+ }
+
+ // Done waiting for reply.
+
+ }
+ }
+
+ process->ForceDacFlush();
+
+ // The hr and hrEvent are 2 very different things.
+ // hr tells us whether the event was sent successfully.
+ // hrEvent tells us how the LS responded to it.
+ // if FAILED(hr), then hrEvent is useless b/c the LS never got it.
+ // But if SUCCEEDED(hr), then hrEvent may still have failed and that could be
+ // valuable information.
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+// FlushQueuedEvents flushes a process's event queue.
+//
+// Arguments:
+// pProcess - non-null process object whose queue will be drained
+//
+// Notes:
+// @dbgtodo shim: this should be part of the shim.
+// This dispatches events that are queued up. The queue is populated by
+// the shim's proxy callback (see code:ShimProxyCallback). This will dispatch events
+// to the 'real' callback supplied by the debugger. This will dispatch events
+// as long as the debugger keeps calling continue.
+//
+// This requires that the process lock be held, although it will toggle the lock.
+void CordbRCEventThread::FlushQueuedEvents(CordbProcess* process)
+{
+ CONTRACTL
+ {
+ NOTHROW; // This is happening on the RCET thread, so there's no place to propogate an error back up.
+ }
+ CONTRACTL_END;
+
+ STRESS_LOG0(LF_CORDB,LL_INFO10000, "CRCET::FQE: Beginning to flush queue\n");
+
+ _ASSERTE(process->GetShim() != NULL);
+
+ // We should only call this is we already have queued events
+ _ASSERTE(!process->GetShim()->GetManagedEventQueue()->IsEmpty());
+
+ //
+ // Dispatch queued events so long as they keep calling Continue()
+ // before returning from their callback. If they call Continue(),
+ // process->m_synchronized will be false again and we know to
+ // loop around and dispatch the next event.
+ //
+ _ASSERTE(process->ThreadHoldsProcessLock());
+
+
+ // Give shim a chance to queue any faked attach events. Grab a pointer to the
+ // ShimProcess now, while we still hold the process lock. Once we release the lock,
+ // GetShim() may not work.
+ RSExtSmartPtr<ShimProcess> pShim(process->GetShim());
+
+ // Release lock before we call out to shim to Queue fake events.
+ {
+ RSInverseLockHolder inverseLockHolder(process->GetProcessLock());
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE0_NO_LOCK(pProcess);
+
+ // Because we've released the lock, at any point from here forward the
+ // CorDbProcess may suddenly get neutered if the user detaches the debugger.
+
+ pShim->QueueFakeAttachEventsIfNeeded(false);
+ }
+ }
+
+ // Now that we're holding the process lock again, we can safely check whether
+ // process has become neutered
+ if (process->IsNeutered())
+ {
+ return;
+ }
+
+ {
+
+ // Main dispatch loop here. DispatchRCEvent will take events out of the
+ // queue and invoke callbacks
+ do
+ {
+ // DispatchRCEvent will mark the process as stopped before dispatching.
+ process->DispatchRCEvent();
+
+ LOG((LF_CORDB,LL_INFO10000, "CRCET::FQE: Finished w/ "
+ "DispatchRCEvent\n"));
+ }
+ while (process->GetSyncCompleteRecv() &&
+ (process->GetSynchronized() == false) &&
+ (process->GetShim() != NULL) && // may have lost Shim if we detached while dispatch
+ (!process->GetShim()->GetManagedEventQueue()->IsEmpty()) &&
+ (process->m_unrecoverableError == false));
+ }
+
+ //
+ // If they returned from a callback without calling Continue() then
+ // the process is still synchronized, so let the rc event thread
+ // know that it need to update its process list and remove the
+ // process's event.
+ //
+ if (process->GetSynchronized())
+ {
+ ProcessStateChanged();
+ }
+
+ LOG((LF_CORDB,LL_INFO10000, "CRCET::FQE: finished\n"));
+}
+
+//---------------------------------------------------------------------------------------
+// Preliminary Handle an Notification event from the target. This may queue the event,
+// but does not actually dispatch the event.
+//
+// Arguments:
+// pManagedEvent - local managed-event. On success, this function assumes ownership of the
+// event and will delete its memory. Assumed that caller allocated via 'new'.
+// pCallback - callback obecjt to dispatch events on.
+//
+// Return Value:
+// None. Throws on error. On error, caller still owns the pManagedEvent and must free it.
+//
+// Assumptions:
+// This should be called once a notification event is received from the target.
+//
+// Notes:
+// HandleRCEvent -- handle an IPC event received from the runtime controller.
+// This will update ICorDebug state and immediately dispatch the event.
+//
+//---------------------------------------------------------------------------------------
+void CordbProcess::HandleRCEvent(
+ DebuggerIPCEvent * pManagedEvent,
+ RSLockHolder * pLockHolder,
+ ICorDebugManagedCallback * pCallback)
+{
+ CONTRACTL
+ {
+ THROWS;
+ PRECONDITION(CheckPointer(pManagedEvent));
+ PRECONDITION(CheckPointer(pCallback));
+ PRECONDITION(ThreadHoldsProcessLock());
+ }
+ CONTRACTL_END;
+
+ if (!this->IsSafeToSendEvents() || this->m_exiting)
+ {
+ return;
+ }
+
+ // Marshals over some standard data from event.
+ MarshalManagedEvent(pManagedEvent);
+
+ STRESS_LOG4(LF_CORDB, LL_INFO1000, "RCET::TP: Got %s for AD 0x%x, proc 0x%x(%d)\n",
+ IPCENames::GetName(pManagedEvent->type), VmPtrToCookie(pManagedEvent->vmAppDomain), this->m_id, this->m_id);
+
+ RSExtSmartPtr<ICorDebugManagedCallback2> pCallback2;
+ pCallback->QueryInterface(IID_ICorDebugManagedCallback2, reinterpret_cast<void **> (&pCallback2));
+
+ RSExtSmartPtr<ICorDebugManagedCallback3> pCallback3;
+ pCallback->QueryInterface(IID_ICorDebugManagedCallback3, reinterpret_cast<void **> (&pCallback3));
+
+ // Dispatch directly. May not necessarily dispatch an event.
+ // Toggles the lock to dispatch callbacks.
+ RawDispatchEvent(pManagedEvent, pLockHolder, pCallback, pCallback2, pCallback3);
+}
+
+//
+// ProcessStateChanged -- tell the rc event thread that the ICorDebug's
+// process list has changed by setting its flag and thread control event.
+// This will cause the rc event thread to update its set of handles to wait
+// on.
+//
+void CordbRCEventThread::ProcessStateChanged()
+{
+ m_cordb->LockProcessList();
+ STRESS_LOG0(LF_CORDB, LL_INFO100000, "CRCET::ProcessStateChanged\n");
+ m_processStateChanged = TRUE;
+ SetEvent(m_threadControlEvent);
+ m_cordb->UnlockProcessList();
+}
+
+
+//---------------------------------------------------------------------------------------
+// Primary loop of the Runtime Controller event thread. This routine loops during the
+// debug session taking IPC events from the IPC block and calling out to process them.
+//
+// Arguments:
+// None.
+//
+// Return Value:
+// None.
+//
+// Notes:
+// @dbgtodo shim: eventually hoist the entire RCET into the shim.
+//---------------------------------------------------------------------------------------
+void CordbRCEventThread::ThreadProc()
+{
+ HANDLE waitSet[MAXIMUM_WAIT_OBJECTS];
+ CordbProcess * rgProcessSet[MAXIMUM_WAIT_OBJECTS];
+ unsigned int waitCount;
+
+#ifdef _DEBUG
+ memset(&rgProcessSet, NULL, MAXIMUM_WAIT_OBJECTS * sizeof(CordbProcess *));
+ memset(&waitSet, NULL, MAXIMUM_WAIT_OBJECTS * sizeof(HANDLE));
+#endif
+
+
+ // First event to wait on is always the thread control event.
+ waitSet[0] = m_threadControlEvent;
+ rgProcessSet[0] = NULL;
+ waitCount = 1;
+
+ while (m_run)
+ {
+ DWORD dwStatus = WaitForMultipleObjectsEx(waitCount, waitSet, FALSE, 2000, FALSE);
+
+ if (dwStatus == WAIT_FAILED)
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO10000, "CordbRCEventThread::ThreadProc WaitFor"
+ "MultipleObjects failed: 0x%x\n", GetLastError());
+ }
+#ifdef _DEBUG
+ else if ((dwStatus >= WAIT_OBJECT_0) && (dwStatus < WAIT_OBJECT_0 + waitCount) && m_run)
+ {
+ // Got an event. Figure out which process it came from.
+ unsigned int procNumber = dwStatus - WAIT_OBJECT_0;
+
+ if (procNumber != 0)
+ {
+ // @dbgtodo shim: rip all of this out. Leave the assert in for now to verify that we're not accidentally
+ // going down this codepath. Once we rip this out, we can also simplify some of the code below.
+ // Notification events (including Sync-complete) should be coming from Win32 event thread via
+ // V3 pipeline.
+ _ASSERTE(!"Shouldn't be here");
+
+ }
+ }
+#endif
+
+ // Empty any queued work items.
+ DrainWorkerQueue();
+
+ // Check a flag to see if we need to update our list of processes to wait on.
+ if (m_processStateChanged)
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "RCET::TP: refreshing process list.\n");
+
+ unsigned int i;
+
+ //
+ // free the old wait list
+ //
+ for (i = 1; i < waitCount; i++)
+ {
+ rgProcessSet[i]->InternalRelease();
+ }
+
+ // Pass 1: iterate the hash of all processes and collect the unsynchronized ones into the wait list.
+ // Note that Stop / Continue can still be called on a different thread while we're doing this.
+ m_cordb->LockProcessList();
+ m_processStateChanged = FALSE;
+
+ waitCount = 1;
+
+ CordbSafeHashTable<CordbProcess> * pHashTable = m_cordb->GetProcessList();
+ HASHFIND hashFind;
+ CordbProcess * pProcess;
+
+ for (pProcess = pHashTable->FindFirst(&hashFind); pProcess != NULL; pProcess = pHashTable->FindNext(&hashFind))
+ {
+ _ASSERTE(waitCount < MAXIMUM_WAIT_OBJECTS);
+
+ if( waitCount >= MAXIMUM_WAIT_OBJECTS )
+ {
+ break;
+ }
+
+ // Only listen to unsynchronized processes. Processes that are synchronized will not send events without
+ // being asked by us first, so there is no need to async listen to them.
+ //
+ // Note: if a process is not synchronized then there is no way for it to transition to the syncrhonized
+ // state without this thread receiving an event and taking action. So there is no need to lock the
+ // per-process mutex when checking the process's synchronized flag here.
+ if (!pProcess->GetSynchronized() && pProcess->IsSafeToSendEvents())
+ {
+ STRESS_LOG2(LF_CORDB, LL_INFO1000, "RCET::TP: listening to process 0x%x(%d)\n",
+ pProcess->m_id, pProcess->m_id);
+
+ waitSet[waitCount] = pProcess->m_leftSideEventAvailable;
+ rgProcessSet[waitCount] = pProcess;
+ rgProcessSet[waitCount]->InternalAddRef();
+ waitCount++;
+ }
+ }
+
+ m_cordb->UnlockProcessList();
+
+ // Pass 2: for each process that we placed in the wait list, determine if there are any existing queued
+ // events that need to be flushed.
+
+ // Start i at 1 to skip the control event...
+ i = 1;
+
+ while(i < waitCount)
+ {
+ pProcess = rgProcessSet[i];
+
+ // Take the process lock so we can check the queue safely
+ pProcess->Lock();
+
+ // Now that we've just locked the processes, we can safely inspect it and dispatch events.
+ // The process may have changed since when we first added it to the process list in Pass 1,
+ // so we can't make any assumptions about whether it's sync, live, or exiting.
+
+ // Flush the queue if necessary. Note, we only do this if we've actually received a SyncComplete message
+ // from this process. If we haven't received a SyncComplete yet, then we don't attempt to drain any
+ // queued events yet. They'll be drained when the SyncComplete event is actually received.
+ if (pProcess->GetSyncCompleteRecv() &&
+ (pProcess->GetShim() != NULL) &&
+ !pProcess->GetSynchronized())
+ {
+ if (pProcess->GetShim()->GetManagedEventQueue()->IsEmpty())
+ {
+ // Effectively what we are doing here is to continue everything without actually
+ // handling an event. We can get here if the event raised by the LS is a duplicate
+ // creation event, which the shim discards without adding it to the event queue.
+ // See code:ShimProcess::IsDuplicateCreationEvent.
+ //
+ // To continue, we need to increment the stop count first. Also, we can't call
+ // Continue() while holding the process lock.
+ pProcess->SetSynchronized(true);
+ pProcess->IncStopCount();
+ pProcess->Unlock();
+ pProcess->ContinueInternal(FALSE);
+ pProcess->Lock();
+ }
+ else
+ {
+ // This may toggle the process-lock
+ FlushQueuedEvents(pProcess);
+ }
+ }
+
+ // Flushing could have left the process synchronized...
+ // Common case is if the callback didn't call Continue().
+ if (pProcess->GetSynchronized())
+ {
+ // remove the process from the wait list by moving all the other processes down one.
+ if ((i + 1) < waitCount)
+ {
+ memcpy(&rgProcessSet[i], &(rgProcessSet[i+1]), sizeof(rgProcessSet[0]) * (waitCount - i - 1));
+ memcpy(&waitSet[i], &waitSet[i+1], sizeof(waitSet[0]) * (waitCount - i - 1));
+ }
+
+ // drop the count of processes to wait on
+ waitCount--;
+
+ pProcess->Unlock();
+
+ // make sure to release the reference we added when the process was added to the wait list.
+ pProcess->InternalRelease();
+
+ // We don't have to increment i because we've copied the next element into
+ // the current value at i.
+ }
+ else
+ {
+ // Even after flushing, its still not syncd, so leave it in the wait list.
+ pProcess->Unlock();
+
+ // Increment i normally.
+ i++;
+ }
+ }
+ } // end ProcessStateChanged
+ } // while (m_run)
+
+#ifdef _DEBUG_IMPL
+ // We intentionally return while leaking some CordbProcess objects inside
+ // rgProcessSet, in some cases (e.g., I've seen this happen when detaching from a
+ // debuggee almost immediately after attaching to it). In the future, we should
+ // really consider not leaking these anymore. However, I'm unsure how safe it is to just
+ // go and InternalRelease() those guys, as above we intentionally DON'T release them when
+ // they're not synchronized. So for now, to make debug builds happy, exclude those
+ // references when we run CheckMemLeaks() later on. In our next side-by-side release,
+ // consider actually doing InternalRelease() on the remaining CordbProcesses on
+ // retail, and then we can remove the following loop.
+ for (UINT i=1; i < waitCount; i++)
+ {
+ InterlockedDecrement(&Cordb::s_DbgMemTotalOutstandingInternalRefs);
+ }
+#endif //_DEBUG_IMPL
+}
+
+
+//
+// This is the thread's real thread proc. It simply calls to the
+// thread proc on the given object.
+//
+/*static*/
+DWORD WINAPI CordbRCEventThread::ThreadProc(LPVOID parameter)
+{
+ CordbRCEventThread * pThread = (CordbRCEventThread *) parameter;
+
+ INTERNAL_THREAD_ENTRY(pThread);
+ pThread->ThreadProc();
+ return 0;
+}
+
+template<typename T>
+InterlockedStack<T>::InterlockedStack()
+{
+ m_pHead = NULL;
+}
+
+template<typename T>
+InterlockedStack<T>::~InterlockedStack()
+{
+ // This is an arbitrary choice. We expect the stacks be drained.
+ _ASSERTE(m_pHead == NULL);
+}
+
+// Thread safe pushes + pops.
+// Many threads can push simultaneously.
+// Only 1 thread can pop.
+template<typename T>
+void InterlockedStack<T>::Push(T * pItem)
+{
+ // InterlockedCompareExchangePointer(&dest, ex, comp).
+ // Really behaves like:
+ // val = *dest;
+ // if (*dest == comp) { *dest = ex; }
+ // return val;
+ //
+ // We can do a thread-safe assign { comp = dest; dest = ex } via:
+ // do { comp = dest } while (ICExPtr(&dest, ex, comp) != comp));
+
+
+ do
+ {
+ pItem->m_next = m_pHead;
+ }
+ while(InterlockedCompareExchangeT(&m_pHead, pItem, pItem->m_next) != pItem->m_next);
+}
+
+// Returns NULL on empty,
+// else returns the head of the list.
+template<typename T>
+T * InterlockedStack<T>::Pop()
+{
+ if (m_pHead == NULL)
+ {
+ return NULL;
+ }
+
+ // This allows 1 thread to Pop() and race against N threads doing a Push().
+ T * pItem = NULL;
+ do
+ {
+ pItem = m_pHead;
+ } while(InterlockedCompareExchangeT(&m_pHead, pItem->m_next, pItem) != pItem);
+
+ return pItem;
+}
+
+
+// RCET will take ownership of this item and delete it.
+// This can be done w/o taking any locks (thus it can be called from any lock context)
+// This may race w/ the RCET draining the queue.
+void CordbRCEventThread::QueueAsyncWorkItem(RCETWorkItem * pItem)
+{
+ // @todo -
+ // Non-blocking insert into queue.
+
+ _ASSERTE(pItem != NULL);
+
+ m_WorkerStack.Push(pItem);
+
+ // Ping the RCET so that it drains the queue.
+ SetEvent(m_threadControlEvent);
+}
+
+// Execute & delete all workitems in the queue.
+// This can be done w/o taking any locks. (though individual items may take locks).
+void CordbRCEventThread::DrainWorkerQueue()
+{
+ _ASSERTE(IsRCEventThread());
+
+ while(true)
+ {
+ RCETWorkItem* pCur = m_WorkerStack.Pop();
+ if (pCur == NULL)
+ {
+ break;
+ }
+
+ pCur->Do();
+ delete pCur;
+ }
+}
+
+
+//---------------------------------------------------------------------------------------
+// Wait for an reply from the debuggee.
+//
+// Arguments:
+// pProcess - process for debuggee.
+// pAppDomain - not used.
+// pEvent - caller-allocated event to be filled out.
+// This is expected to be at least as big as CorDBIPC_BUFFER_SIZE.
+//
+// Return Value:
+// S_OK on success. else failure.
+//
+// Assumptions:
+// Caller allocates
+//
+// Notes:
+// WaitForIPCEventFromProcess waits for an event from just the specified
+// process. This should only be called when the process is in a synchronized
+// state, which ensures that the RCEventThread isn't listening to the
+// process's event, too, which would get confusing.
+//
+// @dbgtodo - this function should eventually be obsolete once everything
+// is using DAC calls instead of helper-thread.
+//
+//---------------------------------------------------------------------------------------
+HRESULT CordbRCEventThread::WaitForIPCEventFromProcess(CordbProcess * pProcess,
+ CordbAppDomain * pAppDomain,
+ DebuggerIPCEvent * pEvent)
+{
+ CORDBRequireProcessStateOKAndSync(pProcess, pAppDomain);
+
+ DWORD dwStatus;
+ HRESULT hr = S_OK;
+
+ do
+ {
+ dwStatus = SafeWaitForSingleObject(pProcess,
+ pProcess->m_leftSideEventAvailable,
+ CordbGetWaitTimeout());
+
+ if (pProcess->m_terminated)
+ {
+ return CORDBG_E_PROCESS_TERMINATED;
+ }
+ // If we timeout because we're waiting for an uncontinued OOB event, we need to just keep waiting.
+ } while ((dwStatus == WAIT_TIMEOUT) && pProcess->IsWaitingForOOBEvent());
+
+
+
+
+ if (dwStatus == WAIT_OBJECT_0)
+ {
+ pProcess->CopyRCEventFromIPCBlock(pEvent);
+
+ EX_TRY
+ {
+ pProcess->MarshalManagedEvent(pEvent);
+
+ STRESS_LOG4(LF_CORDB, LL_INFO1000, "CRCET::SIPCE: Got %s for AD 0x%x, proc 0x%x(%d)\n",
+ IPCENames::GetName(pEvent->type),
+ VmPtrToCookie(pEvent->vmAppDomain),
+ pProcess->m_id,
+ pProcess->m_id);
+
+ }
+ EX_CATCH_HRESULT(hr)
+
+ SetEvent(pProcess->m_leftSideEventRead);
+
+ return hr;
+ }
+ else if (dwStatus == WAIT_TIMEOUT)
+ {
+ //
+ // If we timed out, check the left side to see if it is in the
+ // unrecoverable error mode. If it is, return the HR from the
+ // left side that caused the error. Otherwise, return that we timed
+ // out and that we don't really know why.
+ //
+ HRESULT realHR = ErrWrapper(CORDBG_E_TIMEOUT);
+
+ hr = pProcess->CheckForUnrecoverableError();
+
+ if (hr == S_OK)
+ {
+ CORDBSetUnrecoverableError(pProcess, realHR, 0);
+ return realHR;
+ }
+ else
+ return hr;
+ }
+ else
+ {
+ _ASSERTE(dwStatus == WAIT_FAILED);
+
+ hr = HRESULT_FROM_GetLastError();
+
+ CORDBSetUnrecoverableError(pProcess, hr, 0);
+
+ return hr;
+ }
+}
+
+
+//
+// Start actually creates and starts the thread.
+//
+HRESULT CordbRCEventThread::Start()
+{
+ if (m_threadControlEvent == NULL)
+ {
+ return E_INVALIDARG;
+ }
+
+ m_thread = CreateThread(NULL,
+ 0,
+ &CordbRCEventThread::ThreadProc,
+ (LPVOID) this,
+ 0,
+ &m_threadId);
+
+ if (m_thread == NULL)
+ {
+ return HRESULT_FROM_GetLastError();
+ }
+
+ return S_OK;
+}
+
+
+//
+// Stop causes the thread to stop receiving events and exit. It
+// waits for it to exit before returning.
+//
+HRESULT CordbRCEventThread::Stop()
+{
+ if (m_thread != NULL)
+ {
+ LOG((LF_CORDB, LL_INFO100000, "CRCET::Stop\n"));
+
+ m_run = FALSE;
+
+ SetEvent(m_threadControlEvent);
+
+ DWORD ret = WaitForSingleObject(m_thread, INFINITE);
+
+ if (ret != WAIT_OBJECT_0)
+ {
+ return HRESULT_FROM_GetLastError();
+ }
+ }
+
+ m_cordb.Clear();
+
+ return S_OK;
+}
+
+
+/* ------------------------------------------------------------------------- *
+ * Win32 Event Thread class
+ * ------------------------------------------------------------------------- */
+
+enum
+{
+ W32ETA_NONE = 0,
+ W32ETA_CREATE_PROCESS = 1,
+ W32ETA_ATTACH_PROCESS = 2,
+ W32ETA_CONTINUE = 3,
+ W32ETA_DETACH = 4
+};
+
+
+
+//---------------------------------------------------------------------------------------
+// Constructor
+//
+// Arguments:
+// pCordb - Pointer to the owning cordb object for this event thread.
+// pShim - Pointer to the shim for supporting V2 debuggers on V3 architecture.
+//
+//---------------------------------------------------------------------------------------
+CordbWin32EventThread::CordbWin32EventThread(
+ Cordb * pCordb,
+ ShimProcess * pShim
+ ) :
+ m_thread(NULL), m_threadControlEvent(NULL),
+ m_actionTakenEvent(NULL), m_run(TRUE),
+ m_action(W32ETA_NONE)
+{
+ m_cordb.Assign(pCordb);
+ _ASSERTE(pCordb != NULL);
+
+ m_pShim = pShim;
+
+ m_pNativePipeline = NULL;
+}
+
+
+//
+// Destructor. Cleans up all of the open handles and such.
+// This expects that the thread has been stopped and has terminated
+// before being called.
+//
+CordbWin32EventThread::~CordbWin32EventThread()
+{
+ if (m_thread != NULL)
+ CloseHandle(m_thread);
+
+ if (m_threadControlEvent != NULL)
+ CloseHandle(m_threadControlEvent);
+
+ if (m_actionTakenEvent != NULL)
+ CloseHandle(m_actionTakenEvent);
+
+ if (m_pNativePipeline != NULL)
+ {
+ m_pNativePipeline->Delete();
+ m_pNativePipeline = NULL;
+ }
+
+ m_sendToWin32EventThreadMutex.Destroy();
+}
+
+
+//
+// Init sets up all the objects that the thread will need to run.
+//
+HRESULT CordbWin32EventThread::Init()
+{
+ if (m_cordb == NULL)
+ return E_INVALIDARG;
+
+ m_sendToWin32EventThreadMutex.Init("Win32-Send lock", RSLock::cLockFlat, RSLock::LL_WIN32_SEND_LOCK);
+
+ m_threadControlEvent = WszCreateEvent(NULL, FALSE, FALSE, NULL);
+ if (m_threadControlEvent == NULL)
+ return HRESULT_FROM_GetLastError();
+
+ m_actionTakenEvent = WszCreateEvent(NULL, FALSE, FALSE, NULL);
+ if (m_actionTakenEvent == NULL)
+ return HRESULT_FROM_GetLastError();
+
+ m_pNativePipeline = NewPipelineWithDebugChecks();
+ if (m_pNativePipeline == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ return S_OK;
+}
+
+//
+// Main function of the Win32 Event Thread
+//
+void CordbWin32EventThread::ThreadProc()
+{
+#if defined(RSCONTRACTS)
+ DbgRSThread::GetThread()->SetThreadType(DbgRSThread::cW32ET);
+
+ // The win32 ET conceptually holds a lock (all threads do).
+ DbgRSThread::GetThread()->TakeVirtualLock(RSLock::LL_WIN32_EVENT_THREAD);
+#endif
+
+ // In V2, the debuggee decides what to do if the debugger rudely exits / detaches. (This is
+ // handled by host policy). With the OS native-debuggging pipeline, the debugger by default
+ // kills the debuggee if it exits. To emulate V2 behavior, we need to override that default.
+ BOOL fOk = m_pNativePipeline->DebugSetProcessKillOnExit(FALSE);
+ (void)fOk; //prevent "unused variable" error from GCC
+ _ASSERTE(fOk);
+
+
+ // Run the top-level event loop.
+ Win32EventLoop();
+
+#if defined(RSCONTRACTS)
+ // The win32 ET conceptually holds a lock (all threads do).
+ DbgRSThread::GetThread()->ReleaseVirtualLock(RSLock::LL_WIN32_EVENT_THREAD);
+#endif
+}
+
+// Define a holder that calls code:DeleteIPCEventHelper
+NEW_WRAPPER_TEMPLATE1(DeleteIPCEventHolderHelper, DeleteIPCEventHelper);
+typedef DeleteIPCEventHolderHelper<DebuggerIPCEvent> DeleteIPCEventHolder;
+
+//---------------------------------------------------------------------------------------
+//
+// Helper to clean up IPCEvent before deleting it.
+// This must be called after an event is marshalled via code:CordbProcess::MarshalManagedEvent
+//
+// Arguments:
+// pManagedEvent - managed event to delete.
+//
+// Notes:
+// This can delete a partially marshalled event.
+//
+void DeleteIPCEventHelper(DebuggerIPCEvent *pManagedEvent)
+{
+ CONTRACTL
+ {
+ // This is backout code that shouldn't need to throw.
+ NOTHROW;
+ }
+ CONTRACTL_END;
+ if (pManagedEvent == NULL)
+ {
+ return;
+ }
+ switch (pManagedEvent->type & DB_IPCE_TYPE_MASK)
+ {
+ // so far only this event need to cleanup.
+ case DB_IPCE_MDA_NOTIFICATION:
+ pManagedEvent->MDANotification.szName.CleanUp();
+ pManagedEvent->MDANotification.szDescription.CleanUp();
+ pManagedEvent->MDANotification.szXml.CleanUp();
+ break;
+
+ case DB_IPCE_FIRST_LOG_MESSAGE:
+ pManagedEvent->FirstLogMessage.szContent.CleanUp();
+ break;
+
+ default:
+ break;
+ }
+ delete [] (BYTE *)pManagedEvent;
+}
+
+//---------------------------------------------------------------------------------------
+// Handle a CLR specific notification event.
+//
+// Arguments:
+// pManagedEvent - non-null pointer to a managed event.
+// pLockHolder - hold to process lock that gets toggled if this dispatches an event.
+// pCallback - callback to dispatch potential managed events.
+//
+// Return Value:
+// Throws on error.
+//
+// Assumptions:
+// Target is stopped. Record was already determined to be a CLR event.
+//
+// Notes:
+// This is called after caller does WaitForDebugEvent.
+// Any exception this Filter does not recognize is treated as kNotClr.
+// Currently, this includes both managed-exceptions and unmanaged ones.
+// For interop-debugging, the interop logic will handle all kNotClr and triage if
+// it's really a non-CLR exception.
+//
+//---------------------------------------------------------------------------------------
+void CordbProcess::FilterClrNotification(
+ DebuggerIPCEvent * pManagedEvent,
+ RSLockHolder * pLockHolder,
+ ICorDebugManagedCallback * pCallback)
+{
+ CONTRACTL
+ {
+ THROWS;
+ PRECONDITION(CheckPointer(pManagedEvent));
+ PRECONDITION(CheckPointer(pCallback));
+ PRECONDITION(ThreadHoldsProcessLock());
+ }
+ CONTRACTL_END;
+
+ // There are 3 types of events from the LS:
+ // 1) Replies (eg, corresponding to WaitForIPCEvent)
+ // we need to set LSEA/wait on LSER.
+ // 2) Sync-Complete (kind of like a special notification)
+ // Ping the helper
+ // 3) Notifications (eg, Module-load):
+ // these are dispatched immediately.
+ // 4) Left-side Startup event
+
+
+ // IF we're synced, then we must be getting a "Reply".
+ bool fReply = this->GetSynchronized();
+
+ LOG((LF_CORDB, LL_INFO10000, "CP::FCN - Received event %s; fReply: %d\n",
+ IPCENames::GetName(pManagedEvent->type),
+ fReply));
+
+ if (fReply)
+ {
+ //
+ _ASSERTE(m_pShim != NULL);
+ //
+ // Case 1: Reply
+ //
+
+ pLockHolder->Release();
+ _ASSERTE(!ThreadHoldsProcessLock());
+
+ // Save the IPC event and wake up the thread which is waiting for it from the LS.
+ GetEventChannel()->SaveEventFromLeftSide(pManagedEvent);
+ SetEvent(this->m_leftSideEventAvailable);
+
+ // Some other thread called code:CordbRCEventThread::WaitForIPCEventFromProcess, and
+ // that will respond here and set the event.
+
+ DWORD dwResult = WaitForSingleObject(this->m_leftSideEventRead, CordbGetWaitTimeout());
+ pLockHolder->Acquire();
+ if (dwResult != WAIT_OBJECT_0)
+ {
+ // The wait failed. This is probably WAIT_TIMEOUT which suggests a deadlock/assert on
+ // the RCEventThread.
+ CONSISTENCY_CHECK_MSGF(false, ("WaitForSingleObject failed: %d", dwResult));
+ ThrowHR(CORDBG_E_TIMEOUT);
+ }
+ }
+ else
+ {
+ if (pManagedEvent->type == DB_IPCE_LEFTSIDE_STARTUP)
+ {
+ //
+ // Case 4: Left-side startup event. We'll mark that we're attached from oop.
+ //
+
+ // Now that LS is started, we should definitely be able to instantiate DAC.
+ InitializeDac();
+
+ // @dbgtodo 'attach-bit': we don't want the debugger automatically invading the process.
+ GetDAC()->MarkDebuggerAttached(TRUE);
+ }
+ else if (pManagedEvent->type == DB_IPCE_SYNC_COMPLETE)
+ {
+ // Since V3 doesn't request syncs, it shouldn't get sync-complete.
+ // @dbgtodo sync: this changes when V3 can explicitly request an AsyncBreak.
+ _ASSERTE(m_pShim != NULL);
+
+ //
+ // Case 2: Sync Complete
+ //
+
+ HandleSyncCompleteRecieved();
+ }
+ else
+ {
+ //
+ // Case 3: Notification. This will dispatch the event immediately.
+ //
+
+ // Toggles the process-lock if it dispatches callbacks.
+ HandleRCEvent(pManagedEvent, pLockHolder, pCallback);
+
+ } // end Notification
+ }
+}
+
+
+
+//
+// If the thread has an unhandled managed exception, hijack it.
+//
+// Arguments:
+// dwThreadId - OS Thread id.
+//
+// Returns:
+// True if hijacked; false if not.
+//
+// Notes:
+// This is called from shim to emulate being synchronized at an unhandled
+// exception.
+// Other ICorDebug operations could calls this (eg, func-eval at 2nd chance).
+BOOL CordbProcess::HijackThreadForUnhandledExceptionIfNeeded(DWORD dwThreadId)
+{
+ PUBLIC_API_ENTRY(this); // from Shim
+
+ BOOL fHijacked = FALSE;
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ RSLockHolder lockHolder(GetProcessLock());
+
+ // OS will not execute the Unhandled Exception Filter under native debugger, so
+ // we need to hijack the thread to get it to execute the UEF, which will then do
+ // work for unhandled managed exceptions.
+ CordbThread * pThread = TryLookupOrCreateThreadByVolatileOSId(dwThreadId);
+ if (pThread != NULL)
+ {
+ // If the thread has a managed exception, then we should have a pThread object.
+
+ if (pThread->HasUnhandledNativeException())
+ {
+ _ASSERTE(pThread->IsThreadExceptionManaged()); // should have been marked earlier
+
+ pThread->HijackForUnhandledException();
+ fHijacked = TRUE;
+ }
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ SIMPLIFYING_ASSUMPTION(SUCCEEDED(hr));
+
+ return fHijacked;
+}
+
+//---------------------------------------------------------------------------------------
+// Validate the given exception record or throw.
+//
+// Arguments:
+// pRawRecord - non-null raw bytes of the exception
+// countBytes - number of bytes in pRawRecord buffer.
+// format - format of pRawRecord
+//
+// Returns:
+// A type-safe exception record from the raw buffer.
+//
+// Notes:
+// This is a helper for code:CordbProcess::Filter.
+// This can do consistency checks on the incoming parameters such as:
+// * verify countBytes matches the expected size for the given format.
+// * verify the format is supported.
+//
+// If we let a given ICD understand multiple formats (eg, have x86 understand both Exr32 and
+// Exr64), this would be the spot to allow the conversion.
+//
+const EXCEPTION_RECORD * CordbProcess::ValidateExceptionRecord(
+ const BYTE pRawRecord[],
+ DWORD countBytes,
+ CorDebugRecordFormat format)
+{
+ ValidateOrThrow(pRawRecord);
+
+ //
+ // Check format against expected platform.
+ //
+
+ // @dbgtodo - , cross-plat: Once we do cross-plat, these should be based off target-architecture not host's.
+#if defined(_WIN64)
+ if (format != FORMAT_WINDOWS_EXCEPTIONRECORD64)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+#else
+ if (format != FORMAT_WINDOWS_EXCEPTIONRECORD32)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+#endif
+
+ // @dbgtodo cross-plat: once we do cross-plat, need to use correct EXCEPTION_RECORD variant.
+ if (countBytes != sizeof(EXCEPTION_RECORD))
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+
+ const EXCEPTION_RECORD * pRecord = reinterpret_cast<const EXCEPTION_RECORD *> (pRawRecord);
+
+ return pRecord;
+};
+
+// Return value: S_OK or indication that no more room exists for enabled types
+HRESULT CordbProcess::SetEnableCustomNotification(ICorDebugClass * pClass, BOOL fEnable)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this); // takes the lock
+
+ ValidateOrThrow(pClass);
+
+ ((CordbClass *)pClass)->SetCustomNotifications(fEnable);
+
+ PUBLIC_API_END(hr);
+ return hr;
+} // CordbProcess::SetEnableCustomNotification
+
+//---------------------------------------------------------------------------------------
+// Public implementation of ICDProcess4::Filter
+//
+// Arguments:
+// pRawRecord - non-null raw bytes of the exception
+// countBytes - number of bytes in pRawRecord buffer.
+// format - format of pRawRecord
+// dwFlags - flags providing auxillary info for exception record.
+// dwThreadId - thread that exception occurred on.
+// pCallback - callback to dispatch potential managed events on.
+// pContinueStatus - Continuation status for exception. This dictates what
+// to pass to kernel32!ContinueDebugEvent().
+//
+// Return Value:
+// S_OK on success.
+//
+// Assumptions:
+// Target is stopped.
+//
+// Notes:
+// The exception could be anything, including:
+// - a CLR notification,
+// - a random managed exception (both from managed code or the runtime),
+// - a non-CLR exception
+//
+// This is cross-platform. The {pRawRecord, countBytes, format} describe events
+// on an arbitrary target architecture. On windows, this will be an EXCEPTION_RECORD.
+//
+HRESULT CordbProcess::Filter(
+ const BYTE pRawRecord[],
+ DWORD countBytes,
+ CorDebugRecordFormat format,
+ DWORD dwFlags,
+ DWORD dwThreadId,
+ ICorDebugManagedCallback * pCallback,
+ DWORD * pContinueStatus
+)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this); // takes the lock
+ {
+ //
+ // Validate parameters
+ //
+
+ // If we don't care about the continue status, we leave it untouched.
+ ValidateOrThrow(pContinueStatus);
+ ValidateOrThrow(pCallback);
+
+ const EXCEPTION_RECORD * pRecord = ValidateExceptionRecord(pRawRecord, countBytes, format);
+
+ DWORD dwFirstChance = (dwFlags & IS_FIRST_CHANCE);
+
+ //
+ // Deal with 2nd-chance exceptions. Don't actually hijack now (that's too invasive),
+ // but mark that we have the exception in case a future operation (eg, func-eval) needs to hijack.
+ //
+ if (!dwFirstChance)
+ {
+ CordbThread * pThread = TryLookupOrCreateThreadByVolatileOSId(dwThreadId);
+
+ // If we don't have a managed-thread object, then it certainly can't have a throwable.
+ // It's possible this is still an exception from the native portion of the runtime,
+ // but that's ok, we'll just treat it as a native exception.
+ // This could be expensive, don't want to do it often... (definitely not on every Filter).
+
+
+ // OS will not execute the Unhandled Exception Filter under native debugger, so
+ // we need to hijack the thread to get it to execute the UEF, which will then do
+ // work for unhandled managed exceptions.
+ if ((pThread != NULL) && pThread->IsThreadExceptionManaged())
+ {
+ // Copy exception record for future use in case we decide to hijack.
+ pThread->SetUnhandledNativeException(pRecord);
+ }
+ // we don't care about 2nd-chance exceptions, unless we decide to hijack it later.
+ }
+
+ //
+ // Deal with CLR notifications
+ //
+ else if (pRecord->ExceptionCode == CLRDBG_NOTIFICATION_EXCEPTION_CODE) // Special event code
+ {
+ //
+ // This may not be for us, or we may not have a managed thread object:
+ // 1. Anybody can raise an exception with this exception code, so can't assume this belongs to us yet.
+ // 2. Notifications may come on unmanaged threads if they're coming from MDAs or CLR internal events
+ // fired before the thread is created.
+ //
+ BYTE * pManagedEventBuffer = new BYTE[CorDBIPC_BUFFER_SIZE];
+ DeleteIPCEventHolder pManagedEvent(reinterpret_cast<DebuggerIPCEvent *>(pManagedEventBuffer));
+
+ bool fOwner = CopyManagedEventFromTarget(pRecord, pManagedEvent);
+ if (fOwner)
+ {
+ // This toggles the lock if it dispatches callbacks
+ FilterClrNotification(pManagedEvent, GET_PUBLIC_LOCK_HOLDER(), pCallback);
+
+ // Cancel any notification events from target. These are just supposed to notify ICD and not
+ // actually be real exceptions in the target.
+ // Canceling here also prevents a VectoredExceptionHandler in the target from picking
+ // up exceptions for the CLR.
+ *pContinueStatus = DBG_CONTINUE;
+ }
+
+ // holder will invoke DeleteIPCEventHelper(pManagedEvent).
+ }
+
+ }
+ PUBLIC_API_END(hr);
+ // we may not find the correct mscordacwks so fail gracefully
+ _ASSERTE(SUCCEEDED(hr) || (hr != HRESULT_FROM_WIN32(ERROR_MOD_NOT_FOUND)));
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+// Wrapper to invoke ICorDebugMutableDataTarget::ContinueStatusChanged
+//
+// Arguments:
+// dwContinueStatus - new continue status
+//
+// Returns:
+// None. Throw on error.
+//
+// Notes:
+// Initial continue status is returned from code:CordbProcess::Filter.
+// Some operations (mainly hijacking on a 2nd-chance exception), may need to
+// override that continue status.
+// ICorDebug operations invoke a callback on the data-target to notify the debugger
+// of a change in status. Debugger may fail the request.
+//
+void CordbProcess::ContinueStatusChanged(DWORD dwThreadId, CORDB_CONTINUE_STATUS dwContinueStatus)
+{
+ HRESULT hr = m_pMutableDataTarget->ContinueStatusChanged(dwThreadId, dwContinueStatus);
+ IfFailThrow(hr);
+}
+
+//---------------------------------------------------------------------------------------
+// Request a synchronization to occur after a debug event is dispatched.
+//
+// Note:
+// This is called in response to a managed debug event, and so we know that we have
+// a worker thread in the process (the one that just sent the event!)
+// This can not be called asynchronously.
+//---------------------------------------------------------------------------------------
+void CordbProcess::RequestSyncAtEvent()
+{
+ GetDAC()->RequestSyncAtEvent();
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Primary loop of the Win32 debug event thread.
+//
+//
+// Arguments:
+// None.
+//
+// Return Value:
+// None.
+//
+// Notes:
+// This is it, you've found it, the main guy. This function loops as long as the
+// debugger is around calling the OS WaitForDebugEvent() API. It takes the OS Debug
+// Event and filters it thru the right-side, continuing the process if not recognized.
+//
+// @dbgtodo shim: this will become part of the shim.
+//---------------------------------------------------------------------------------------
+void CordbWin32EventThread::Win32EventLoop()
+{
+ // This must be called from the win32 event thread.
+ _ASSERTE(IsWin32EventThread());
+
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::W32EL: entered win32 event loop\n"));
+
+
+ DEBUG_EVENT event;
+
+ // Allow the timeout for WFDE to be adjustable. Default to 25 ms based off perf numbers (see issue VSWhidbey 132368).
+ DWORD dwWFDETimeout = CLRConfig::GetConfigValue(CLRConfig::UNSUPPORTED_DbgWFDETimeout);
+
+ while (m_run)
+ {
+ BOOL fEventAvailable = FALSE;
+
+ // We should not have any locks right now.
+
+
+ // Have to wait on 2 sources:
+ // WaitForMultipleObjects - ping for messages (create, attach, Continue, detach) and also
+ // process exits in the managed-only case.
+ // Native Debug Events - This is a huge perf hit so we want to avoid it whenever we can.
+ // Only wait on these if we're interop debugging and if the process is not frozen.
+ // A frozen process can't send any debug events, so don't bother looking for them.
+
+
+ unsigned int cWaitCount = 1;
+
+ HANDLE rghWaitSet[2];
+
+ rghWaitSet[0] = m_threadControlEvent;
+
+ DWORD dwWaitTimeout = INFINITE;
+
+ if (m_pProcess != NULL)
+ {
+ // Process is always built on Native debugging pipeline, so it needs to always be prepared to call WFDE
+ // As an optimization, if the target is stopped, then we can avoid calling WFDE.
+ {
+#ifndef FEATURE_INTEROP_DEBUGGING
+ // Managed-only, never win32 stopped, so always check for an event.
+ dwWaitTimeout = 0;
+ fEventAvailable = m_pNativePipeline->WaitForDebugEvent(&event, dwWFDETimeout, m_pProcess);
+#else
+ // Wait for a Win32 debug event from any processes that we may be attached to as the Win32 debugger.
+ const bool fIsWin32Stopped = (m_pProcess->m_state & CordbProcess::PS_WIN32_STOPPED) != 0;
+ const bool fSkipWFDE = fIsWin32Stopped;
+
+
+ const bool fIsInteropDebugging = m_pProcess->IsInteropDebugging();
+ (void)fIsInteropDebugging; //prevent "unused variable" error from GCC
+
+ // Assert checks
+ _ASSERTE(fIsInteropDebugging == m_pShim->IsInteropDebugging());
+
+ if (!fSkipWFDE)
+ {
+ dwWaitTimeout = 0;
+ fEventAvailable = m_pNativePipeline->WaitForDebugEvent(&event, dwWFDETimeout, m_pProcess);
+ }
+ else
+ {
+ // If we're managed-only debugging, then the process should always be running,
+ // which means we always need to be calling WFDE to pump potential debug events.
+ // If we're interop-debugging, then the process can be stopped at a native-debug event,
+ // in which case we don't have to call WFDE until we resume it again.
+ // So we can only skip the WFDE when we're interop-debugging.
+ _ASSERTE(fIsInteropDebugging);
+ }
+#endif // FEATURE_INTEROP_DEBUGGING
+ }
+
+
+ } // end m_pProcess != NULL
+
+#if defined(FEATURE_INTEROP_DEBUGGING)
+ // While interop-debugging, the process may get killed rudely underneath us, even if we haven't
+ // continued the last debug event. In such cases, The process object will get signalled normally.
+ // If we didn't just get a native-exitProcess event, then listen on the process handle for exit.
+ // (this includes all managed-only debugging)
+ // It's very important to establish this before we go into the WaitForMutlipleObjects below
+ // because the debuggee may exit while we're sitting in that loop (waiting for the debugger to call Continue).
+ bool fDidNotJustGetExitProcessEvent = !fEventAvailable || (event.dwDebugEventCode != EXIT_PROCESS_DEBUG_EVENT);
+#else
+ // In non-interop scenarios, we'll never get any native debug events, let alone an ExitProcess native event.
+ bool fDidNotJustGetExitProcessEvent = true;
+#endif // FEATURE_INTEROP_DEBUGGING
+
+
+ // The m_pProcess handle will get nulled out after we process the ExitProcess event, and
+ // that will ensure that we only wait for an Exit event once.
+ if ((m_pProcess != NULL) && fDidNotJustGetExitProcessEvent)
+ {
+ rghWaitSet[1] = m_pProcess->UnsafeGetProcessHandle();
+ cWaitCount = 2;
+ }
+
+ // See if any process that we aren't attached to as the Win32 debugger have exited. (Note: this is a
+ // polling action if we are also waiting for Win32 debugger events. We're also looking at the thread
+ // control event here, too, to see if we're supposed to do something, like attach.
+ DWORD dwStatus = WaitForMultipleObjectsEx(cWaitCount, rghWaitSet, FALSE, dwWaitTimeout, FALSE);
+
+ _ASSERTE((dwStatus == WAIT_TIMEOUT) || (dwStatus < cWaitCount));
+
+ if (!m_run)
+ {
+ _ASSERTE(m_action == W32ETA_NONE);
+ break;
+ }
+
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::W32EL - got event , ret=%d, has w32 dbg event=%d\n",
+ dwStatus, fEventAvailable));
+
+ // If we haven't timed out, or if it wasn't the thread control event
+ // that was set, then a process has
+ // exited...
+ if ((dwStatus != WAIT_TIMEOUT) && (dwStatus != WAIT_OBJECT_0))
+ {
+ // Grab the process that exited.
+ _ASSERTE((dwStatus - WAIT_OBJECT_0) == 1);
+ ExitProcess(false); // not detach
+ fEventAvailable = false;
+ }
+ // Should we create a process?
+ else if (m_action == W32ETA_CREATE_PROCESS)
+ {
+ CreateProcess();
+ }
+ // Should we attach to a process?
+ else if (m_action == W32ETA_ATTACH_PROCESS)
+ {
+ AttachProcess();
+ }
+ // Should we detach from a process?
+ else if (m_action == W32ETA_DETACH)
+ {
+ ExitProcess(true); // detach case
+
+ // Once we detach, we don't need to continue any outstanding event.
+ // So act like we never got the event.
+ fEventAvailable = false;
+ PREFIX_ASSUME(m_pProcess == NULL); // W32 cleared process pointer
+ }
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ // Should we continue the process?
+ else if (m_action == W32ETA_CONTINUE)
+ {
+ HandleUnmanagedContinue();
+ }
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ // We don't need to sweep the FCH threads since we never hijack a thread in cooperative mode.
+
+
+ // Only process an event if one is available.
+ if (!fEventAvailable)
+ {
+ continue;
+ }
+
+ // The only ref we have is the one in the ProcessList hash;
+ // If we dispatch an ExitProcess event, we may even lose that.
+ // But since the CordbProcess is our parent object, we know it won't go away until
+ // it neuters us, so we can safely proceed.
+ // Find the process this event is for.
+ PREFIX_ASSUME(m_pProcess != NULL);
+ _ASSERTE(m_pProcess->m_id == GetProcessId(&event)); // should only get events for our proc
+ g_pRSDebuggingInfo->m_MRUprocess = m_pProcess;
+
+ // Must flush the dac cache since we were just running.
+ m_pProcess->ForceDacFlush();
+
+ // So we've filtered out CLR events.
+ // Let the shim handle the remaining events. This will call back into Filter() if appropriate.
+ // This will also ensure the debug event gets continued.
+ HRESULT hrShim = S_OK;
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE0_NO_LOCK(NULL);
+ hrShim = m_pShim->HandleWin32DebugEvent(&event);
+ }
+ // Any errors from the shim (eg. failure to load DAC) are unrecoverable
+ SetUnrecoverableIfFailed(m_pProcess, hrShim);
+
+ } // loop
+
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::W32EL: exiting event loop\n"));
+
+ return;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Returns if the current thread is the win32 thread.
+//
+// Return Value:
+// true iff this is the win32 event thread.
+//
+//---------------------------------------------------------------------------------------
+bool CordbProcess::IsWin32EventThread()
+{
+ _ASSERTE((m_pShim != NULL) || !"Don't check win32 event thread in V3 cases");
+ return m_pShim->IsWin32EventThread();
+}
+
+//---------------------------------------------------------------------------------------
+// Call when the sync complete event is received and can be processed.
+//
+// Notes:
+// This is called when the RS gets the sync-complete from the LS and can process it.
+//
+// This has a somewhat elaborate contract to fill between Interop-debugging, Async-Break, draining the
+// managed event-queue, and coordinating with the dispatch thread (RCET).
+//
+// @dbgtodo - this should eventually get hoisted into the shim.
+void CordbProcess::HandleSyncCompleteRecieved()
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ this->SetSyncCompleteRecv(true);
+
+ // If some thread is waiting for the process to sync, notify that it can go now.
+ if (this->m_stopRequested)
+ {
+ this->SetSynchronized(true);
+ SetEvent(this->m_stopWaitEvent);
+ }
+ else
+ {
+ // Note: we set the m_stopWaitEvent all the time and leave it high while we're stopped. This
+ // must be done after we've checked m_stopRequested.
+ SetEvent(this->m_stopWaitEvent);
+
+ // Otherwise, simply mark that the state of the process has changed and let the
+ // managed event dispatch logic take over.
+ //
+ // Note: process->m_synchronized remains false, which indicates to the RC event
+ // thread that it can dispatch the next managed event.
+ m_cordb->ProcessStateChanged();
+ }
+}
+
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+
+// Get a Thread's _user_ starting address (the real starting address may be some
+// OS shim.)
+// This may return NULL for the Async-Break thread.
+void* GetThreadUserStartAddr(const DEBUG_EVENT* pCreateThreadEvent)
+{
+ // On Win7 and above, we can trust the lpStartAddress field of the CREATE_THREAD_DEBUG_EVENT
+ // to be the user start address (the actual OS start address is an implementation detail that
+ // doesn't need to be exposed to users). Note that we are assuming that the target process
+ // is running on Win7 if mscordbi is. If we ever have some remoting scenario where the target
+ // can run on a different windows machine with a different OS version we will need a way to
+ // determine the target's OS version
+ if(RunningOnWin7())
+ {
+ return pCreateThreadEvent->u.CreateThread.lpStartAddress;
+ }
+
+ // On pre-Win7 OSes, we rely on an OS implementation detail to get the real user thread start:
+ // it exists in EAX at thread start time.
+ // Note that for a brief period of time there was a GetThreadStartInformation API in Longhorn
+ // we could use for this, but it was removed during the Longhorn reset.
+ HANDLE hThread = pCreateThreadEvent->u.CreateThread.hThread;
+#if defined(DBG_TARGET_X86)
+ // Grab the thread's context.
+ DT_CONTEXT c;
+ c.ContextFlags = DT_CONTEXT_FULL;
+ BOOL succ = DbiGetThreadContext(hThread, &c);
+
+ if (succ)
+ {
+ return (void*) c.Eax;
+ }
+#elif defined(DBG_TARGET_AMD64)
+ DT_CONTEXT c;
+ c.ContextFlags = DT_CONTEXT_FULL;
+ BOOL succ = DbiGetThreadContext(hThread, &c);
+
+ if (succ)
+ {
+ return (void*) c.Rcx;
+ }
+#else
+ PORTABILITY_ASSERT("port GetThreadUserStartAddr");
+#endif
+
+ return NULL;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Get (create if needed) the unmanaged thread for an unmanaged debug event.
+//
+// Arguments:
+// event - native debug event.
+//
+// Return Value:
+// Unmanaged thread corresponding to the native debug event.
+//
+//
+// Notes:
+// Thread may be newly allocated, or may be existing. CordbProcess holds
+// list of all CordbUnmanagedThreads, and will handle freeing memory.
+//
+//---------------------------------------------------------------------------------------
+CordbUnmanagedThread * CordbProcess::GetUnmanagedThreadFromEvent(const DEBUG_EVENT * pEvent)
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+ HRESULT hr;
+
+ CordbUnmanagedThread * pUnmanagedThread = NULL;
+
+ // Remember newly created threads.
+ if (pEvent->dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT)
+ {
+ // We absolutely should have an unmanaged callback by this point.
+ // That means that the client debugger should have called ICorDebug::SetUnmanagedHandler by now.
+ // However, we can't actually enforce that (see comment in ICorDebug::SetUnmanagedHandler for details),
+ // so we do a runtime check to check this.
+ // This is an extremely gross API misuse and an issue in the client if the callback is not set yet.
+ // Without the unmanaged callback, we absolutely can't do interop-debugging. We assert (checked builds) and
+ // dispatch unrecoverable error (retail builds) to avoid an AV.
+
+
+ if (this->m_cordb->m_unmanagedCallback == NULL)
+ {
+ CONSISTENCY_CHECK_MSGF((this->m_cordb->m_unmanagedCallback != NULL),
+ ("GROSS API misuse!!\nNo unmanaged callback set by the time we've received CreateProcess debug event for proces 0x%x.\n",
+ pEvent->dwProcessId));
+
+ CORDBSetUnrecoverableError(this, CORDBG_E_INTEROP_NOT_SUPPORTED, 0);
+
+ // Returning NULL will tell caller not to dispatch event to client. We have no callback object to dispatch upon.
+ return NULL;
+ }
+
+ pUnmanagedThread = this->HandleUnmanagedCreateThread(pEvent->dwThreadId,
+ pEvent->u.CreateProcessInfo.hThread,
+ pEvent->u.CreateProcessInfo.lpThreadLocalBase);
+
+ // Managed-attach won't start until after Cordbg continues from the loader-bp.
+ }
+ else if (pEvent->dwDebugEventCode == CREATE_THREAD_DEBUG_EVENT)
+ {
+ pUnmanagedThread = this->HandleUnmanagedCreateThread(pEvent->dwThreadId,
+ pEvent->u.CreateThread.hThread,
+ pEvent->u.CreateThread.lpThreadLocalBase);
+
+ BOOL fBlockExists = FALSE;
+ hr = S_OK;
+ EX_TRY
+ {
+ // See if we have the debugger control block yet...
+
+ this->GetEventBlock(&fBlockExists);
+
+ // If we have the debugger control block, and if that control block has the address of the thread proc for
+ // the helper thread, then we're initialized enough on the Left Side to recgonize the helper thread based on
+ // its thread proc's address.
+ if (this->GetDCB() != NULL)
+ {
+ // get the latest LS DCB information
+ UpdateRightSideDCB();
+ if ((this->GetDCB()->m_helperThreadStartAddr != NULL) && (pUnmanagedThread != NULL))
+ {
+ void * pStartAddr = GetThreadUserStartAddr(pEvent);
+
+ if (pStartAddr == this->GetDCB()->m_helperThreadStartAddr)
+ {
+ // Remember the ID of the helper thread.
+ this->m_helperThreadId = pEvent->dwThreadId;
+
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::W32EL: Left Side Helper Thread is 0x%x\n", pEvent->dwThreadId));
+ }
+ }
+ }
+ }
+ EX_CATCH_HRESULT(hr)
+ {
+ if (fBlockExists && FAILED(hr))
+ {
+ _ASSERTE(IsLegalFatalError(hr));
+ // Send up the DebuggerError event
+ this->UnrecoverableError(hr, 0, NULL, 0);
+
+ // Kill the process.
+ // RS will pump events until we LS process exits.
+ TerminateProcess(this->m_handle, hr);
+
+ return pUnmanagedThread;
+ }
+ }
+ }
+ else
+ {
+ // Find the unmanaged thread that this event is for.
+ pUnmanagedThread = this->GetUnmanagedThread(pEvent->dwThreadId);
+ }
+
+ return pUnmanagedThread;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Handle a native-debug event representing a managed sync-complete event.
+//
+//
+// Return Value:
+// Reaction telling caller how to respond to the native-debug event.
+//
+// Assumptions:
+// Called within the Triage process after receiving a native-debug event.
+//
+//---------------------------------------------------------------------------------------
+Reaction CordbProcess::TriageSyncComplete()
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::TSC: received 'sync complete' flare.\n");
+
+ _ASSERTE(IsInteropDebugging());
+
+ // Note: we really don't need to be suspending Runtime threads that we know have tripped
+ // here. If we ever end up with a nice, quick way to know that about each unmanaged thread, then
+ // we should put that to good use here.
+ this->SuspendUnmanagedThreads();
+
+ this->HandleSyncCompleteRecieved();
+
+ // Let the process run free.
+ return REACTION(cIgnore);
+
+ // At this point, all managed threads are stopped at safe places and all unmanaged
+ // threads are either suspended or hijacked. All stopped managed threads are also hard
+ // suspended (due to the call to SuspendUnmanagedThreads above) except for the thread
+ // that sent the sync complete flare.
+
+ // We've handled this exception, so skip all further processing.
+ UNREACHABLE();
+}
+
+//-----------------------------------------------------------------------------
+// Triage a breakpoint (non-flare) on a "normal" thread.
+//-----------------------------------------------------------------------------
+Reaction CordbProcess::TriageBreakpoint(CordbUnmanagedThread * pUnmanagedThread, const DEBUG_EVENT * pEvent)
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ HRESULT hr = S_OK;
+
+ DWORD dwExCode = pEvent->u.Exception.ExceptionRecord.ExceptionCode;
+ const void * pExAddress = pEvent->u.Exception.ExceptionRecord.ExceptionAddress;
+
+ _ASSERTE(dwExCode == STATUS_BREAKPOINT);
+
+ // There are three cases here:
+ //
+ // 1. The breakpoint definetly belongs to the Runtime. (I.e., a BP in our patch table that
+ // is in managed code.) In this case, we continue the process with
+ // DBG_EXCEPTION_NOT_HANDLED, which lets the in-process exception logic kick in as if we
+ // weren't here.
+ //
+ // 2. The breakpoint is definetly not ours. (I.e., a BP that is not in our patch table.) We
+ // pass these up as regular exception events, doing the can't stop check as usual.
+ //
+ // 3. We're not sure. (I.e., a BP in our patch table, but set in unmangaed code.) In this
+ // case, we hijack as usual, also with can't stop check as usual.
+
+ bool fPatchFound = false;
+ bool fPatchIsUnmanaged = false;
+
+ hr = this->FindPatchByAddress(PTR_TO_CORDB_ADDRESS(pExAddress),
+ &fPatchFound,
+ &fPatchIsUnmanaged);
+
+ if (SUCCEEDED(hr))
+ {
+ if (fPatchFound)
+ {
+#ifdef _DEBUG
+ // What if managed & native patch the same address? That could happen on a step out M --> U.
+ {
+ NativePatch * pNativePatch = GetNativePatch(pExAddress);
+ SIMPLIFYING_ASSUMPTION_MSGF(pNativePatch == NULL, ("Have Managed & native patch at 0x%p", pExAddress));
+ }
+#endif
+
+ // BP could be ours... if its unmanaged, then we still need to hijack, so fall
+ // through to that logic. Otherwise, its ours.
+ if (!fPatchIsUnmanaged)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::W32EL: breakpoint exception "
+ "belongs to runtime due to patch table match.\n"));
+
+ return REACTION(cCLR);
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::W32EL: breakpoint exception "
+ "matched in patch table, but its unmanaged so might hijack anyway.\n"));
+
+ // If we're in cooperative mode, then we must have a inproc handler, and don't need to hijack
+ // One way this can happen is the patch placed for a func-eval complete is hit in coop-mode.
+ if (pUnmanagedThread->GetEEPGCDisabled())
+ {
+ LOG((LF_CORDB, LL_INFO10000, "Already in coop-mode, don't need to hijack\n"));
+ return REACTION(cCLR);
+ }
+ else
+ {
+ return REACTION(cBreakpointRequiringHijack);
+ }
+ }
+
+ UNREACHABLE();
+ }
+ else // Patch not found
+ {
+ // If we're here, then we have a BP that's not in the managed patch table, and not
+ // in the native patch list. This should be rare. Perhaps an int3 / DebugBreak() / Assert in
+ // the native code stream.
+ // Anyway, we don't know about this patch so we can't skip it. The only thing we can do
+ // is chuck it up to Cordbg and hope they can help us. Note that this is the same case
+ // we were in w. V1.
+
+ // BP doesn't belong to CLR ... so dispatch it to Cordbg as either make it IB or OOB.
+ // @todo - make the runtime 1 giant Can't stop region.
+ bool fCantStop = pUnmanagedThread->IsCantStop();
+
+#ifdef _DEBUG
+ // We rarely expect a raw int3 here. Add a debug check that will assert.
+ // Tests that know they don't have raw int3 can enable this regkey to get
+ // extra coverage.
+ static DWORD s_fBreakOnRawInt3 = -1;
+
+ if (s_fBreakOnRawInt3 == -1)
+ s_fBreakOnRawInt3 = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgBreakOnRawInt3);
+
+ if (s_fBreakOnRawInt3)
+ {
+ CONSISTENCY_CHECK_MSGF(false, ("Unexpected Raw int3 at:%p on tid 0x%x (%d). CantStop=%d."
+ "This assert is used by specific tests to get extra checks."
+ "For normal cases it's ignorable and is enabled by setting DbgBreakOnRawInt3==1.",
+ pExAddress, pEvent->dwThreadId, pEvent->dwThreadId, fCantStop));
+ }
+#endif
+
+ if (fCantStop)
+ {
+ // If we're in a can't stop region, then its OOB no matter what at this point.
+ return REACTION(cOOB);
+ }
+ else
+ {
+ // PGC must be enabled if we're going to stop for an IB event.
+ bool PGCDisabled = pUnmanagedThread->GetEEPGCDisabled();
+ _ASSERTE(!PGCDisabled);
+
+ // Bp is definitely not ours, and PGC is not disabled, so in-band exception.
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::W32EL: breakpoint exception "
+ "does not belong to the runtime due to failed patch table match.\n"));
+
+ return REACTION(cInband);
+ }
+
+ UNREACHABLE();
+ }
+
+ UNREACHABLE();
+ }
+ else
+ {
+ // Patch table lookup failed? Only on OOM or if ReadProcessMemory fails...
+ _ASSERTE(!"Patch table lookup failed!");
+ CORDBSetUnrecoverableError(this, hr, 0);
+ return REACTION(cOOB);
+ }
+
+ UNREACHABLE();
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Triage a "normal" 1st chance exception on a "normal" thread.
+// Not hijacked, not the helper thread, not a flare, etc.. This is the common
+// case for a native exception from native code.
+//
+// Arguments:
+// pUnmanagedThread - Pointer to the CordbUnmanagedThread object that we want to hijack.
+// pEvent - Pointer to the debug event which contains the exception code and address.
+//
+// Return Value:
+// The Reaction tells if the event is in-band, out-of-band, CLR specific or ignorable.
+//
+//---------------------------------------------------------------------------------------
+Reaction CordbProcess::Triage1stChanceNonSpecial(CordbUnmanagedThread * pUnmanagedThread, const DEBUG_EVENT * pEvent)
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr = S_OK;
+
+ DWORD dwExCode = pEvent->u.Exception.ExceptionRecord.ExceptionCode;
+ const void * pExAddress = pEvent->u.Exception.ExceptionRecord.ExceptionAddress;
+
+ // This had better not be a flare. If it is, that means we have some race that unmarked
+ // the hijacks.
+ _ASSERTE(!ExceptionIsFlare(dwExCode, pExAddress));
+
+ // Any first chance exception could belong to the Runtime, so long as the Runtime has actually been
+ // initialized. Here we'll setup a first-chance hijack for this thread so that it can give us the
+ // true answer that we need.
+
+ // But none of those exceptions could possibly be ours unless we have a managed thread to go with
+ // this unmanaged thread. A non-NULL EEThreadPtr tells us that there is indeed a managed thread for
+ // this unmanaged thread, even if the Right Side hasn't received a managed ThreadCreate message yet.
+ REMOTE_PTR pEEThread;
+ hr = pUnmanagedThread->GetEEThreadPtr(&pEEThread);
+ _ASSERTE(SUCCEEDED(hr));
+
+ if (pEEThread == NULL)
+ {
+ // No managed thread, so it can't possibly belong to the runtime!
+ // But it may still be in a can't-stop region (think some goofy shutdown case).
+ if (pUnmanagedThread->IsCantStop())
+ {
+ return REACTION(cOOB);
+ }
+ else
+ {
+ return REACTION(cInband);
+ }
+ }
+
+
+
+ // We have to be careful here. A Runtime thread may be in a place where we cannot let an
+ // unmanaged exception stop it. For instance, an unmanaged user breakpoint set on
+ // WaitForSingleObject will prevent Runtime threads from sending events to the Right Side. So at
+ // various points below, we check to see if this Runtime thread is in a place were we can't let
+ // it stop, and if so then we jump over to the out-of-band dispatch logic and treat this
+ // exception as out-of-band. The debugger is supposed to continue from the out-of-band event
+ // properly and help us avoid this problem altogether.
+
+ // Grab a few flags from the thread's state...
+ bool fThreadStepping = false;
+ bool fSpecialManagedException = false;
+
+ pUnmanagedThread->GetEEState(&fThreadStepping, &fSpecialManagedException);
+
+ // If we've got a single step exception, and if the Left Side has indicated that it was
+ // stepping the thread, then the exception is ours.
+ if (dwExCode == STATUS_SINGLE_STEP)
+ {
+ if (fThreadStepping)
+ {
+ // Yup, its the Left Side that was stepping the thread...
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "W32ET::W32EL: single step exception belongs to the runtime.\n");
+
+ return REACTION(cCLR);
+ }
+
+ // Any single step that is triggered when the thread's state doesn't indicate that
+ // we were stepping the thread automatically gets passed out as an unmanged event.
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "W32ET::W32EL: single step exception "
+ "does not belong to the runtime.\n");
+
+ if (pUnmanagedThread->IsCantStop())
+ {
+ return REACTION(cOOB);
+ }
+ else
+ {
+ return REACTION(cInband);
+ }
+
+ UNREACHABLE();
+ }
+
+#ifdef CorDB_Short_Circuit_First_Chance_Ownership
+ // If the runtime indicates that this is a special exception being thrown within the runtime,
+ // then its ours no matter what.
+ else if (fSpecialManagedException)
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "W32ET::W32EL: exception belongs to the runtime due to "
+ "special managed exception marking.\n");
+
+ return REACTION(cCLR);
+ }
+ else if ((dwExCode == EXCEPTION_COMPLUS) || (dwExCode == EXCEPTION_HIJACK))
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000,
+ "W32ET::W32EL: exception belongs to Runtime due to match on built in exception code\n");
+
+ return REACTION(cCLR);
+ }
+ else if (dwExCode == EXCEPTION_MSVC)
+ {
+ // The runtime may use C++ exceptions internally. We can still report these
+ // to the debugger as long as we're outside of a can't-stop region.
+ if (pUnmanagedThread->IsCantStop())
+ {
+ return REACTION(cCLR);
+ }
+ else
+ {
+ return REACTION(cInband);
+ }
+ }
+ else if (dwExCode == STATUS_BREAKPOINT)
+ {
+ return TriageBreakpoint(pUnmanagedThread, pEvent);
+ }// end BP case
+#endif
+
+ // It's not a breakpoint or single-step. Now it just comes down to the address from where
+ // the exception is coming from. If it's managed, we give it back to the CLR. If it's
+ // from native, then we dispatch to Cordbg.
+ // We can use DAC to figure this out from Out-of-process.
+ _ASSERTE(dwExCode != STATUS_BREAKPOINT); // BP were already handled.
+
+
+ // Use DAC to decide if it's ours or not w/o going inproc.
+ CORDB_ADDRESS address = PTR_TO_CORDB_ADDRESS(pExAddress);
+
+ IDacDbiInterface::AddressType addrType;
+
+ addrType = GetDAC()->GetAddressType(address);
+ bool fIsCorCode =((addrType == IDacDbiInterface::kAddressManagedMethod) ||
+ (addrType == IDacDbiInterface::kAddressRuntimeManagedCode) ||
+ (addrType == IDacDbiInterface::kAddressRuntimeUnmanagedCode));
+
+ STRESS_LOG2(LF_CORDB, LL_INFO1000, "W32ET::W32EL: IsCorCode(0x%I64p)=%d\n", address, fIsCorCode);
+
+
+ if (fIsCorCode)
+ {
+ return REACTION(cCLR);
+ }
+ else
+ {
+ if (pUnmanagedThread->IsCantStop())
+ {
+ return REACTION(cOOB);
+ }
+ else
+ {
+ return REACTION(cInband);
+ }
+ }
+
+ UNREACHABLE();
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Triage a 1st-chance exception when the CLR is initialized.
+//
+// Arguments:
+// pUnmanagedThread - thread that the event has occurred on.
+// pEvent - native debug event for the exception that occurred that this is triaging.
+//
+// Return Value:
+// Reaction for how to handle this event.
+//
+// Assumptions:
+// Called when receiving a debug event when the process is stopped.
+//
+// Notes:
+// A 1st-chance event has a wide spectrum of possibility including:
+// - It may be unmanaged or managed.
+// - Or it may be an execution control exception for managed-exceution
+// - thread skipping an OOB event.
+//
+//---------------------------------------------------------------------------------------
+Reaction CordbProcess::TriageExcep1stChanceAndInit(CordbUnmanagedThread * pUnmanagedThread,
+ const DEBUG_EVENT * pEvent)
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+ _ASSERTE(m_runtimeOffsetsInitialized);
+
+ NativePatch * pNativePatch = NULL;
+ DebuggerIPCRuntimeOffsets * pIPCRuntimeOffsets = &(this->m_runtimeOffsets);
+
+ DWORD dwExCode = pEvent->u.Exception.ExceptionRecord.ExceptionCode;
+ const void * pExAddress = pEvent->u.Exception.ExceptionRecord.ExceptionAddress;
+
+
+#ifdef _DEBUG
+ // Some Interop bugs involve threads that land at a crazy IP. Since we're interop-debugging, we can't
+ // attach a debugger to the LS. So we have some debug mode where we enable the SS flag and thus
+ // produce a trace of where a thread is going.
+ if (pUnmanagedThread->IsDEBUGTrace() && (dwExCode == STATUS_SINGLE_STEP))
+ {
+ pUnmanagedThread->ClearState(CUTS_DEBUG_SingleStep);
+ LOG((LF_CORDB, LL_INFO10000, "DEBUG TRACE, thread %4x at IP: 0x%p\n", pUnmanagedThread->m_id, pExAddress));
+
+ // Clear the exception and pretend this never happened.
+ return REACTION(cIgnore);
+ }
+#endif
+
+ // If we were stepping for exception retrigger and got the single step and it should be hidden then just ignore it.
+ // Anything that isn't cInbandExceptionRetrigger will cause the debug event to be dequeued, stepping turned off, and
+ // it will count as not retriggering
+ // TODO: I don't think the IsSSFlagNeeded() check is needed here though it doesn't break anything
+ if (pUnmanagedThread->IsSSFlagNeeded() && pUnmanagedThread->IsSSFlagHidden() && (dwExCode == STATUS_SINGLE_STEP))
+ {
+ LOG((LF_CORDB, LL_INFO10000, "CP::TE1stCAI: ignoring hidden single step\n"));
+ return REACTION(cIgnore);
+ }
+
+ // Is this a breakpoint indicating that the Left Side is now synchronized?
+ if ((dwExCode == STATUS_BREAKPOINT) &&
+ (pExAddress == pIPCRuntimeOffsets->m_notifyRSOfSyncCompleteBPAddr))
+ {
+ return TriageSyncComplete();
+ }
+ else if ((dwExCode == STATUS_BREAKPOINT) &&
+ (pExAddress == pIPCRuntimeOffsets->m_excepForRuntimeHandoffCompleteBPAddr))
+ {
+ _ASSERTE(!"This should be unused now");
+
+ // This notification means that a thread that had been first-chance hijacked is now
+ // finally leaving the hijack.
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::TE1stCAI: received 'first chance hijack handoff complete' flare.\n");
+
+ // Let the process run.
+ return REACTION(cIgnore);
+ }
+ else if ((dwExCode == STATUS_BREAKPOINT) &&
+ (pExAddress == pIPCRuntimeOffsets->m_signalHijackCompleteBPAddr))
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::TE1stCAI: received 'hijack complete' flare.\n");
+ return REACTION(cInbandHijackComplete);
+ }
+ else if ((dwExCode == STATUS_BREAKPOINT) &&
+ (pExAddress == m_runtimeOffsets.m_signalHijackStartedBPAddr))
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::TE1stCAI: received 'hijack started' flare.\n");
+ return REACTION(cFirstChanceHijackStarted);
+ }
+ else if ((dwExCode == STATUS_BREAKPOINT) && ((pNativePatch = GetNativePatch(pExAddress)) != NULL) )
+ {
+ // We hit a native BP placed by Cordbg. This could happen on any thread (including helper)
+ bool fCantStop = pUnmanagedThread->IsCantStop();
+
+ // REVISIT_TODO: if the user also set a breakpoint here then we should dispatch to the debugger
+ // and rely on the debugger to get us past this. Should be a rare case though.
+ if (fCantStop)
+ {
+ // Need to skip it completely; never dispatch.
+ pUnmanagedThread->SetupForSkipBreakpoint(pNativePatch);
+
+ // Debuggee will single step over the patch, and fire a SS exception.
+ // We'll then call FixupForSkipBreakpoint, and continue the process.
+ return REACTION(cIgnore);
+ }
+ else
+ {
+ // Native patch in native code. A very common scenario.
+ // Dispatch as an IB event to Cordbg.
+ STRESS_LOG1(LF_CORDB, LL_INFO10000, "Native patch in native code (at %p), dispatching as IB event.\n", pExAddress);
+ return REACTION(cInband);
+ }
+
+ UNREACHABLE();
+ }
+
+ else if ((dwExCode == STATUS_BREAKPOINT) && !IsBreakOpcodeAtAddress(pExAddress))
+ {
+ // If we got an int3 exception, but there's not actually an int3 at the address, then just reset the IP
+ // to the address. This can happen if the int 3 is cleared after the thread has dispatched it (in which case
+ // WFDE will pick it up) but before we realize it's one of ours.
+ STRESS_LOG2(LF_CORDB, LL_INFO1000, "CP::TE1stCAI: Phantom Int3: Tid=0x%x, addr=%p\n", pEvent->dwThreadId, pExAddress);
+
+ DT_CONTEXT context;
+
+ context.ContextFlags = DT_CONTEXT_FULL;
+
+ BOOL fSuccess = DbiGetThreadContext(pUnmanagedThread->m_handle, &context);
+
+ _ASSERTE(fSuccess);
+
+ if (fSuccess)
+ {
+ // Backup IP to point to the instruction we need to execute. Continuing from a breakpoint exception
+ // continues execution at the instruction after the breakpoint, but we need to continue where the
+ // breakpoint was.
+ CORDbgSetIP(&context, (LPVOID) pExAddress);
+
+ fSuccess = DbiSetThreadContext(pUnmanagedThread->m_handle, &context);
+ _ASSERTE(fSuccess);
+ }
+
+ return REACTION(cIgnore);
+ }
+ else if (pUnmanagedThread->IsSkippingNativePatch())
+ {
+ // If we Single-Step over an exception, then the OS never gives us the single-step event.
+ // Thus if we're skipping a native patch, we don't care what exception event we got.
+ LOG((LF_CORDB, LL_INFO100000, "Done skipping native patch. Ex=0x%x\n, IsSS=%d",
+ dwExCode,
+ (dwExCode == STATUS_SINGLE_STEP)));
+
+ // This is the 2nd half of skipping a native patch.
+ // This could happen on any thread (including helper)
+ // We've already removed the opcode and now we just finished a single-step over it.
+ // So put the patch back in, and continue the process.
+ pUnmanagedThread->FixupForSkipBreakpoint();
+
+ return REACTION(cIgnore);
+ }
+ else if (this->IsHelperThreadWorked(pUnmanagedThread->GetOSTid()))
+ {
+ // We should never ever get a single-step event from the helper thread.
+ CONSISTENCY_CHECK_MSGF(dwExCode != STATUS_SINGLE_STEP, (
+ "Single-Step exception on helper thread (tid=0x%x/%d) in debuggee process (pid=0x%x/%d).\n"
+ "For more information, attach a debuggee non-invasively to the LS to get the callstack.\n",
+ pUnmanagedThread->m_id,
+ pUnmanagedThread->m_id,
+ this->m_id,
+ this->m_id));
+
+ // We ignore any first chance exceptions from the helper thread. There are lots of places
+ // on the left side where we attempt to dereference bad object refs and such that will be
+ // handled by exception handlers already in place.
+ //
+ // Note: we check this after checking for the sync complete notification, since that can
+ // come from the helper thread.
+ //
+ // Note: we do let single step and breakpoint exceptions go through to the debugger for processing.
+ if ((dwExCode != STATUS_BREAKPOINT) && (dwExCode != STATUS_SINGLE_STEP))
+ {
+ return REACTION(cCLR);
+ }
+ else
+ {
+ // Since the helper thread is part of the "can't stop" region, we should have already
+ // skipped any BPs on it.
+ // However, any Assert on the helper thread will hit this case.
+ CONSISTENCY_CHECK_MSGF((dwExCode != STATUS_BREAKPOINT), (
+ "Assert on helper thread (tid=0x%x/%d) in debuggee process (pid=0x%x/%d).\n"
+ "For more information, attach a debuggee non-invasively to the LS to get the callstack.\n",
+ pUnmanagedThread->m_id,
+ pUnmanagedThread->m_id,
+ this->m_id,
+ this->m_id));
+
+ // These breakpoint and single step exceptions have to be dispatched to the debugger as
+ // out-of-band events. This tells the debugger that they must continue from these events
+ // immediatly, and that no interaction with the Left Side is allowed until they do so. This
+ // makes sense, since these events are on the helper thread.
+ return REACTION(cOOB);
+ }
+ UNREACHABLE();
+ }
+ else if (pUnmanagedThread->IsFirstChanceHijacked() && this->ExceptionIsFlare(dwExCode, pExAddress))
+ {
+ _ASSERTE(!"This should be unused now");
+ }
+ else if (pUnmanagedThread->IsGenericHijacked())
+ {
+ if (this->ExceptionIsFlare(dwExCode, pExAddress))
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::TE1stCAI: fixing up from generic hijack.\n");
+
+ _ASSERTE(dwExCode == STATUS_BREAKPOINT);
+
+ // Fixup the thread from the generic hijack.
+ pUnmanagedThread->FixupFromGenericHijack();
+
+ // We force continue from this flare, since its only purpose was to notify us that we had to
+ // fixup the thread from a generic hijack.
+ return REACTION(cIgnore);
+ }
+ else
+ {
+ // We might reach here due to the stack overflow issue, due to target
+ // memory corruption, or even due to an exception thrown during hijacking
+
+ BOOL bStackOverflow = FALSE;
+
+ if (dwExCode == STATUS_ACCESS_VIOLATION || dwExCode == STATUS_STACK_OVERFLOW)
+ {
+ CORDB_ADDRESS stackLimit;
+ CORDB_ADDRESS stackBase;
+ if (pUnmanagedThread->GetStackRange(&stackBase, &stackLimit))
+ {
+ TADDR addr = pEvent->u.Exception.ExceptionRecord.ExceptionInformation[1];
+ if (stackLimit <= addr && addr < stackBase)
+ bStackOverflow = TRUE;
+ }
+ else
+ {
+ // to limit the impact of the change we'll consider failure to retrieve the stack
+ // bounds as stack overflow as well
+ bStackOverflow = TRUE;
+ }
+ }
+
+ if (!bStackOverflow)
+ {
+ // generic hijack means we're in CantStop, so return cOOB
+ return REACTION(cOOB);
+ }
+
+ // If generichijacked and its not a flare, and the address referenced is on the stack then we've
+ // got our special stack overflow case. Take off generic hijacked, mark that the helper thread
+ // is dead, throw this event on the floor, and pop anyone in SendIPCEvent out of their wait.
+ pUnmanagedThread->ClearState(CUTS_GenericHijacked);
+
+ this->m_helperThreadDead = true;
+
+ // This only works on Windows, not on Mac. We don't support interop-debugging on Mac anyway.
+ SetEvent(m_pEventChannel->GetRightSideEventAckHandle());
+
+ // Note: we remember that this was a second chance event from one of the special stack overflow
+ // cases with CUES_ExceptionUnclearable. This tells us to force the process to terminate when we
+ // continue from the event. Since for some odd reason the OS decides to re-raise this exception
+ // (first chance then second chance) infinitely.
+
+ _ASSERTE(pUnmanagedThread->HasIBEvent());
+
+ pUnmanagedThread->IBEvent()->SetState(CUES_ExceptionUnclearable);
+
+ //newEvent = false;
+ return REACTION(cInband_NotNewEvent);
+ }
+ }
+ else
+ {
+ Reaction r(REACTION(cOOB));
+ HRESULT hrCheck = S_OK;;
+ EX_TRY
+ {
+ r = Triage1stChanceNonSpecial(pUnmanagedThread, pEvent);
+ }
+ EX_CATCH_HRESULT(hrCheck);
+ SIMPLIFYING_ASSUMPTION(SUCCEEDED(hrCheck));
+ SetUnrecoverableIfFailed(this, hrCheck);
+
+ return r;
+
+ }
+
+ // At this point, any first-chance exceptions that could be special have been handled. Any
+ // first-chance exception that we're still processing at this point is destined to be
+ // dispatched as an unmanaged event.
+ UNREACHABLE();
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Triage a 2nd-chance exception when the CLR is initialized.
+//
+// Arguments:
+// pUnmanagedThread - thread that the event has occurred on.
+// pEvent - native debug event for the exception that occurred that this is triaging.
+//
+// Return Value:
+// Reaction for how to handle this event.
+//
+// Assumptions:
+// Called when receiving a debug event when the process is stopped.
+//
+// Notes:
+// We already hijacked 2nd-chance managed exceptions, so this is just handling
+// some V2 Interop corner cases.
+// @dbgtodo interop: this should eventually completely go away with the V3 design.
+//
+//---------------------------------------------------------------------------------------
+Reaction CordbProcess::TriageExcep2ndChanceAndInit(CordbUnmanagedThread * pUnmanagedThread, const DEBUG_EVENT * pEvent)
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ DWORD dwExCode = pEvent->u.Exception.ExceptionRecord.ExceptionCode;
+
+#ifdef _DEBUG
+ // For debugging, add an extra knob that let us break on any 2nd chance exceptions.
+ // Most tests don't throw 2nd-chance, so we could have this enabled most of the time and
+ // catch bogus 2nd chance exceptions
+ static DWORD dwNo2ndChance = -1;
+
+ if (dwNo2ndChance == -1)
+ {
+ dwNo2ndChance = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgNo2ndChance);
+ }
+
+ if (dwNo2ndChance)
+ {
+ CONSISTENCY_CHECK_MSGF(false, ("2nd chance exception occurred on LS thread=0x%x, code=0x%08x, address=0x%p\n"
+ "This assert is firing b/c you explicitly requested it by having the 'DbgNo2ndChance' knob enabled.\n"
+ "Disable it to avoid asserts on 2nd chance.",
+ pUnmanagedThread->m_id,
+ dwExCode,
+ pEvent->u.Exception.ExceptionRecord.ExceptionAddress));
+ }
+#endif
+
+
+ // Second chance exception, Runtime initialized. It could belong to the Runtime, so we'll check. If it
+ // does, then we'll hijack the thread. Otherwise, well just fall through and let it get
+ // dispatched. Note: we do this so that the CLR's unhandled exception logic gets a chance to run even
+ // though we've got a win32 debugger attached. But the unhandled exception logic never touches
+ // breakpoint or single step exceptions, so we ignore those here, too.
+
+ // There are strange cases with stack overflow exceptions. If a nieve application catches a stack
+ // overflow exception and handles it, without resetting the guard page, then the app will get an AV when
+ // it overflows the stack a second time. We will get the first chance AV, but when we continue from it the
+ // OS won't run any SEH handlers, so our FCH won't actually work. Instead, we'll get the AV back on
+ // second chance right away, and we'll end up right here.
+ if (this->IsSpecialStackOverflowCase(pUnmanagedThread, pEvent))
+ {
+ // IsSpecialStackOverflowCase will queue the event for us, so its no longer a "new event". Setting
+ // newEvent = false here basically prevents us from playing with the event anymore and we fall down
+ // to the dispatch logic below, which will get our already queued first chance AV dispatched for
+ // this thread.
+ //newEvent = false;
+ return REACTION(cInband_NotNewEvent);
+ }
+ else if (this->IsHelperThreadWorked(pUnmanagedThread->GetOSTid()))
+ {
+ // A second chance exception from the helper thread. This is pretty bad... we just force continue
+ // from them and hope for the best.
+ return REACTION(cCLR);
+ }
+
+ if(pUnmanagedThread->IsCantStop())
+ {
+ return REACTION(cOOB);
+ }
+ else
+ {
+ return REACTION(cInband);
+ }
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Triage a win32 Debug event to get a reaction
+//
+// Arguments:
+// pUnmanagedThread - thread that the event has occurred on.
+// pEvent - native debug event for the exception that occurred that this is triaging.
+//
+// Return Value:
+// Reaction for how to handle this event.
+//
+// Assumptions:
+// Called when receiving a debug event when the process is stopped.
+//
+// Notes:
+// This is the main triage routine for Win32 debug events, this delegates to the
+// 1st and 2nd chance routines above appropriately.
+//
+//---------------------------------------------------------------------------------------
+Reaction CordbProcess::TriageWin32DebugEvent(CordbUnmanagedThread * pUnmanagedThread, const DEBUG_EVENT * pEvent)
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ // Lots of special cases for exception events. The vast majority of hybrid debugging work that takes
+ // place is in response to exception events. The work below will consider certian exception events
+ // special cases and rather than letting them be queued and dispatched, they will be handled right
+ // here.
+ if (pEvent->dwDebugEventCode == EXCEPTION_DEBUG_EVENT)
+ {
+ STRESS_LOG4(LF_CORDB, LL_INFO1000, "CP::TW32DE: unmanaged exception on "
+ "tid 0x%x, code 0x%08x, addr 0x%08x, chance %d\n",
+ pEvent->dwThreadId,
+ pEvent->u.Exception.ExceptionRecord.ExceptionCode,
+ pEvent->u.Exception.ExceptionRecord.ExceptionAddress,
+ 2-pEvent->u.Exception.dwFirstChance);
+
+#ifdef LOGGING
+ if (pEvent->u.Exception.ExceptionRecord.ExceptionCode == STATUS_ACCESS_VIOLATION)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "\t<%s> address 0x%08x\n",
+ pEvent->u.Exception.ExceptionRecord.ExceptionInformation[0] ? "write to" : "read from",
+ pEvent->u.Exception.ExceptionRecord.ExceptionInformation[1]));
+ }
+#endif
+
+ // Mark the loader bp for kicks. We won't start managed attach until native attach is finished.
+ if (!this->m_loaderBPReceived)
+ {
+ // If its a first chance breakpoint, and its the first one, then its the loader breakpoint.
+ if (pEvent->u.Exception.dwFirstChance &&
+ (pEvent->u.Exception.ExceptionRecord.ExceptionCode == STATUS_BREAKPOINT))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::TW32DE: loader breakpoint received.\n"));
+
+ // Remember that we've received the loader BP event.
+ this->m_loaderBPReceived = true;
+
+ // We never hijack the loader BP anymore (CLR 2.0+).
+ // This is b/c w/ interop-attach, we don't start the managed-attach until _after_ Cordbg
+ // continues from the loader-bp.
+ }
+ } // end of loader bp.
+
+ // This event might be the retriggering of an event we already saw but previously had to hijack
+ if(pUnmanagedThread->HasIBEvent())
+ {
+ const EXCEPTION_RECORD* pRecord1 = &(pEvent->u.Exception.ExceptionRecord);
+ const EXCEPTION_RECORD* pRecord2 = &(pUnmanagedThread->IBEvent()->m_currentDebugEvent.u.Exception.ExceptionRecord);
+ if(pRecord1->ExceptionCode == pRecord2->ExceptionCode &&
+ pRecord1->ExceptionFlags == pRecord2->ExceptionFlags &&
+ pRecord1->ExceptionAddress == pRecord2->ExceptionAddress)
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::TW32DE: event is continuation of previously hijacked event.\n");
+ // if we continued from the hijack then we should have already dispatched this event
+ _ASSERTE(pUnmanagedThread->IBEvent()->IsDispatched());
+ return REACTION(cInbandExceptionRetrigger);
+ }
+ }
+
+ // We only care about exception events if they are first chance events and if the Runtime is
+ // initialized within the process. Otherwise, we don't do anything special with them.
+ if (pEvent->u.Exception.dwFirstChance && this->m_initialized)
+ {
+ return TriageExcep1stChanceAndInit(pUnmanagedThread, pEvent);
+ }
+ else if (!pEvent->u.Exception.dwFirstChance && this->m_initialized)
+ {
+ return TriageExcep2ndChanceAndInit(pUnmanagedThread, pEvent);
+ }
+ else
+ {
+ // An exception event, but the Runtime hasn't been initialize. I.e., its an exception event
+ // that we will never try to hijack.
+ return REACTION(cInband);
+ }
+
+ UNREACHABLE();
+ }
+ else
+ // OOB
+ {
+ return REACTION(cOOB);
+ }
+
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Top-level handler for a win32 debug event during Interop-debugging.
+//
+// Arguments:
+// event - native debug event to handle.
+//
+// Assumptions:
+// The process just got a native debug event via WaitForDebugEvent
+//
+// Notes:
+// The function will Triage the excpetion and then handle it based on the
+// appropriate reaction (see: code:Reaction).
+//
+// @dbgtodo interop: this should all go into the shim.
+//---------------------------------------------------------------------------------------
+void CordbProcess::HandleDebugEventForInteropDebugging(const DEBUG_EVENT * pEvent)
+{
+ PUBLIC_API_ENTRY_FOR_SHIM(this);
+ _ASSERTE(IsInteropDebugging() || !"Only do this in real interop handling path");
+
+
+ STRESS_LOG3(LF_CORDB, LL_INFO1000, "W32ET::W32EL: got unmanaged event %d on thread 0x%x, proc 0x%x\n",
+ pEvent->dwDebugEventCode, pEvent->dwThreadId, pEvent->dwProcessId);
+
+ // Get the Lock.
+ _ASSERTE(!this->ThreadHoldsProcessLock());
+
+ RSSmartPtr<CordbProcess> pRef(this); // make sure we're alive...
+
+ RSLockHolder processLockHolder(&this->m_processMutex);
+
+ // If we get a new Win32 Debug event, then we need to flush any cached oop data structures.
+ // This includes refreshing DAC and our patch table.
+ ForceDacFlush();
+ ClearPatchTable();
+
+#ifdef _DEBUG
+ // We want to detect if we've deadlocked. Unfortunately, w/ interop debugging, there can be a lot of
+ // deadtime since we need to wait for a debug event. Thus the CPU usage may appear to be at 0%, but
+ // we're not deadlocked b/c we're still receiving debug events.
+ // So ping every X debug events.
+ static int s_cCount = 0;
+ static int s_iPingLevel = -1;
+ if (s_iPingLevel == -1)
+ {
+ s_iPingLevel = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgPingInterop);
+ }
+ if (s_iPingLevel != 0)
+ {
+ s_cCount++;
+ if (s_cCount >= s_iPingLevel)
+ {
+ s_cCount = 0;
+ ::Beep(1000,100);
+
+ // Refresh so we can adjust ping level midstream.
+ s_iPingLevel = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgPingInterop);
+ }
+ }
+#endif
+
+ bool fNewEvent = true;
+
+ // Mark the process as stopped.
+ this->m_state |= CordbProcess::PS_WIN32_STOPPED;
+
+ CordbUnmanagedThread * pUnmanagedThread = GetUnmanagedThreadFromEvent(pEvent);
+
+ // In retail, if there is no unmanaged thread then we just continue and loop back around. UnrecoverableError has
+ // already been set in this case. Note: there is an issue in the Win32 debugging API that can cause duplicate
+ // ExitThread events. We therefore must handle not finding an unmanaged thread gracefully.
+
+ _ASSERTE((pUnmanagedThread != NULL) || (pEvent->dwDebugEventCode == EXIT_THREAD_DEBUG_EVENT));
+
+ if (pUnmanagedThread == NULL)
+ {
+ // Note: we use ContinueDebugEvent directly here since our continue is very simple and all of our other
+ // continue mechanisms rely on having an UnmanagedThread object to play with ;)
+ STRESS_LOG2(LF_CORDB, LL_INFO1000, "W32ET::W32EL: Continuing without thread on tid 0x%x, code=0x%x\n",
+ pEvent->dwThreadId,
+ pEvent->dwDebugEventCode);
+
+ this->m_state &= ~CordbProcess::PS_WIN32_STOPPED;
+
+ BOOL fOk = ContinueDebugEvent(pEvent->dwProcessId, pEvent->dwThreadId, DBG_EXCEPTION_NOT_HANDLED);
+
+ _ASSERTE(fOk || !"ContinueDebugEvent failed when he have no thread. Debuggee is likely hung");
+
+ return;
+ }
+
+ // There's an innate race such that we can get a Debug Event even after we've suspended a thread.
+ // This can happen if the thread has already dispatched the debug event but we haven't called WFDE to pick it up
+ // yet. This is sufficiently goofy that we want to stress log it.
+ if (pUnmanagedThread->IsSuspended())
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "W32ET::W32EL: Thread 0x%x is suspended\n", pEvent->dwThreadId);
+ }
+
+ // For debugging crazy races in retail, we'll keep a rolling queue of win32 debug events.
+ this->DebugRecordWin32Event(pEvent, pUnmanagedThread);
+
+
+ // Check to see if shutdown of the in-proc debugging services has begun. If it has, then we know we'll no longer
+ // be running any managed code, and we know that we can stop hijacking threads. We remember this by setting
+ // m_initialized to false, thus preventing most things from happening elsewhere.
+ // Don't even bother checking the DCB fields until it's been verified (m_initialized == true)
+ if (this->m_initialized && (this->GetDCB() != NULL))
+ {
+ UpdateRightSideDCB();
+ if (this->GetDCB()->m_shutdownBegun)
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "W32ET::W32EL: shutdown begun...\n");
+ this->m_initialized = false;
+ }
+ }
+
+#ifdef _DEBUG
+ //Verify that GetThreadContext agrees with the exception address
+ if (pEvent->dwDebugEventCode == EXCEPTION_DEBUG_EVENT)
+ {
+ DT_CONTEXT tempDebugContext;
+ tempDebugContext.ContextFlags = DT_CONTEXT_FULL;
+ DbiGetThreadContext(pUnmanagedThread->m_handle, &tempDebugContext);
+ CordbUnmanagedThread::LogContext(&tempDebugContext);
+ _ASSERTE(CORDbgGetIP(&tempDebugContext) == pEvent->u.Exception.ExceptionRecord.ExceptionAddress ||
+ (DWORD)CORDbgGetIP(&tempDebugContext) == ((DWORD)pEvent->u.Exception.ExceptionRecord.ExceptionAddress)+1);
+ }
+#endif
+
+ // This call will decide what to do w/ the the win32 event we just got. It does a lot of work.
+ Reaction reaction = TriageWin32DebugEvent(pUnmanagedThread, pEvent);
+
+
+ // Stress-log the reaction.
+#ifdef _DEBUG
+ STRESS_LOG3(LF_CORDB, LL_INFO1000, "Reaction: %d (%s), line=%d\n",
+ reaction.GetType(),
+ reaction.GetReactionName(),
+ reaction.GetLine());
+#else
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "Reaction: %d\n", reaction.GetType());
+#endif
+
+ // Make sure the lock wasn't accidentally released.
+ _ASSERTE(ThreadHoldsProcessLock());
+ CordbWin32EventThread * pW32EventThread = this->m_pShim->GetWin32EventThread();
+ _ASSERTE(pW32EventThread != NULL);
+
+ // if we were waiting for a retriggered exception but recieved any other event then turn
+ // off the single stepping and dequeue the IB event. Right now we only use the SS flag internally
+ // for stepping during possible retrigger.
+ if(reaction.GetType() != Reaction::cInbandExceptionRetrigger && pUnmanagedThread->IsSSFlagNeeded())
+ {
+ _ASSERTE(pUnmanagedThread->HasIBEvent());
+ CordbUnmanagedEvent* pUnmanagedEvent = pUnmanagedThread->IBEvent();
+ _ASSERTE(pUnmanagedEvent->IsIBEvent());
+ _ASSERTE(pUnmanagedEvent->IsEventContinuedUnhijacked());
+ _ASSERTE(pUnmanagedEvent->IsDispatched());
+ LOG((LF_CORDB, LL_INFO100000, "CP::HDEFID: IB event did not retrigger ue=0x%p\n", pUnmanagedEvent));
+
+ DequeueUnmanagedEvent(pUnmanagedThread);
+ pUnmanagedThread->EndStepping();
+ }
+
+ switch(reaction.GetType())
+ {
+ // Common for flares.
+ case Reaction::cIgnore:
+
+ // Shouldn't be suspending in the first place with outstanding flares.
+ _ASSERTE(!pUnmanagedThread->IsSuspended());
+
+ pW32EventThread->ForceDbgContinue(this, pUnmanagedThread, DBG_CONTINUE, false);
+ goto LDone;
+
+ case Reaction::cCLR:
+ // Don't care if thread is suspended here. We'll just let the thread continue whatever it's doing.
+
+ this->m_DbgSupport.m_TotalCLR++;
+
+ // If this is for the CLR, then we just continue unhandled and know that the CLR has
+ // a handler inplace to deal w/ this exception.
+ pW32EventThread->ForceDbgContinue(this, pUnmanagedThread, DBG_EXCEPTION_NOT_HANDLED, false);
+ goto LDone;
+
+
+ case Reaction::cInband_NotNewEvent:
+ fNewEvent = false;
+
+ // fall through to Inband case...
+
+ case Reaction::cInband:
+ {
+ this->m_DbgSupport.m_TotalIB++;
+
+ // Hijack in-band events (exception events, exit threads) if there is already an event at the head
+ // of the queue or if the process is currently synchronized. Of course, we only do this if the
+ // process is initialized.
+ //
+ // Note: we also hijack these left over in-band events if we're activley trying to send the
+ // managed continue message to the Left Side. This is controlled by m_specialDeferment below.
+
+ // Only exceptions can be IB events - everything else is OOB.
+ _ASSERTE(pEvent->dwDebugEventCode == EXCEPTION_DEBUG_EVENT);
+
+ // CLR internal exceptions should be sent back to the CLR and never treated as inband events.
+ // If this assert fires, the event was triaged wrong.
+ CONSISTENCY_CHECK_MSGF((pEvent->u.Exception.ExceptionRecord.ExceptionCode != EXCEPTION_COMPLUS),
+ ("Attempting to dispatch a CLR internal exception as an Inband event. Reaction line=%d\n",
+ reaction.GetLine()));
+
+
+ _ASSERTE(!pUnmanagedThread->IsCantStop());
+
+ // We need to decide whether or not to dispatch this event immediately
+ // We defer it to enforce that we only dispatch 1 IB event at a time (managed events are
+ // considered IB here).
+ // This means if:
+ // 1) there's already an outstanding unmanaged inband event (an event the user has not continued from)
+ // 2) If the process is synchronized (since that means we've already dispatched a managed event).
+ // 3) If we've received a SyncComplete event, but aren't yet Sync. This will almost always be the same as
+ // whether we're synced, but has a distict quality. It's always set by the w32 event thread in Interop,
+ // and so it's guaranteed to be serialized against this check here (also on the w32et).
+ // 4) Special deferment - This covers the region where we're sending a Stop/Continue IPC event across.
+ // We defer it here to keep the Helper thread alive so that it can handle these IPC events.
+ // Queued events will be dispatched when continue is called.
+ BOOL fHasUserUncontinuedNativeEvents = HasUserUncontinuedNativeEvents();
+ bool fDeferInbandEvent = (fHasUserUncontinuedNativeEvents ||
+ GetSynchronized() ||
+ GetSyncCompleteRecv() ||
+ m_specialDeferment);
+
+ // If we've got a new event, queue it.
+ if (fNewEvent)
+ {
+ this->QueueUnmanagedEvent(pUnmanagedThread, pEvent);
+ }
+
+ if (fNewEvent && this->m_initialized && fDeferInbandEvent)
+ {
+ STRESS_LOG4(LF_CORDB, LL_INFO1000, "W32ET::W32EL: Needed to defer dispatching event: %d %d %d %d\n",
+ fHasUserUncontinuedNativeEvents,
+ GetSynchronized(),
+ GetSyncCompleteRecv(),
+ m_specialDeferment);
+
+ // this continues the IB debug event into the hijack
+ // the process is now running again
+ pW32EventThread->DoDbgContinue(this, pUnmanagedThread->IBEvent());
+
+ // Since we've hijacked this event, we don't need to do any further processing.
+ goto LDone;
+ }
+ else
+ {
+ // No need to defer the dispatch, do it now
+ this->DispatchUnmanagedInBandEvent();
+
+ goto LDone;
+ }
+ UNREACHABLE();
+ }
+
+ case Reaction::cFirstChanceHijackStarted:
+ {
+ // determine the logical event we are handling, if any
+ CordbUnmanagedEvent* pUnmanagedEvent = NULL;
+ if(pUnmanagedThread->HasIBEvent())
+ {
+ pUnmanagedEvent = pUnmanagedThread->IBEvent();
+ }
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::W32EL: IB hijack starting, ue=0x%p\n", pUnmanagedEvent));
+
+ // fetch the LS memory set up for this hijack
+ REMOTE_PTR pDebuggerWord = NULL;
+ DebuggerIPCFirstChanceData fcd;
+ pUnmanagedThread->GetEEDebuggerWord(&pDebuggerWord);
+ SafeReadStruct(PTR_TO_CORDB_ADDRESS(pDebuggerWord), &fcd);
+
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::W32EL: old fcd DebugCounter=0x%x\n", fcd.debugCounter));
+
+ // determine what action the LS should take
+ if(pUnmanagedThread->IsBlockingForSync())
+ {
+ // there should be an event we hijacked in this case
+ _ASSERTE(pUnmanagedEvent != NULL);
+
+ // block that event
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::W32EL: blocking\n"));
+ fcd.action = HIJACK_ACTION_WAIT;
+ fcd.debugCounter = 0x2;
+ SafeWriteStruct(PTR_TO_CORDB_ADDRESS(pDebuggerWord), &fcd);
+ }
+ else
+ {
+ // we don't need to block. We want the vectored handler to just exit
+ // as if it wasn't there
+ _ASSERTE(fcd.action == HIJACK_ACTION_EXIT_UNHANDLED);
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::W32EL: not blocking\n"));
+ }
+
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::W32EL: continuing from flare\n"));
+ pW32EventThread->ForceDbgContinue(this, pUnmanagedThread, DBG_CONTINUE, false);
+ goto LDone;
+ }
+
+ case Reaction::cInbandHijackComplete:
+ {
+ // We now execute the hijack worker even when not actually hijacked
+ // so can't assert this
+ //_ASSERTE(pUnmanagedThread->IsFirstChanceHijacked());
+
+ // we should not be stepping at the end of hijacks
+ _ASSERTE(!pUnmanagedThread->IsSSFlagHidden());
+ _ASSERTE(!pUnmanagedThread->IsSSFlagNeeded());
+
+ // if we were hijacked then clean up
+ if(pUnmanagedThread->IsFirstChanceHijacked())
+ {
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::W32EL: hijack complete will restore context...\n"));
+ DT_CONTEXT tempContext = { 0 };
+ tempContext.ContextFlags = DT_CONTEXT_FULL;
+ HRESULT hr = pUnmanagedThread->GetThreadContext(&tempContext);
+ _ASSERTE(SUCCEEDED(hr));
+
+ // The sync hijack returns normally but the m2uHandoff hijack needs to have the IP
+ // deliberately restored
+ if(!pUnmanagedThread->IsBlockingForSync())
+ {
+ // restore the context to the current un-hijacked context
+ BOOL succ = DbiSetThreadContext(pUnmanagedThread->m_handle, &tempContext);
+ _ASSERTE(succ);
+
+ // Because hijacks don't return normally they might have pushed handlers without poping them
+ // back off. To take care of that we explicitly restore the old SEH chain.
+ #ifdef DBG_TARGET_X86
+ hr = pUnmanagedThread->RestoreLeafSeh();
+ _ASSERTE(SUCCEEDED(hr));
+ #endif
+ }
+ else
+ {
+ _ASSERTE(pUnmanagedThread->HasIBEvent());
+ CordbUnmanagedEvent* pUnmanagedEvent = pUnmanagedThread->IBEvent();
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::W32EL: IB hijack completing, continuing unhijacked ue=0x%p\n", pUnmanagedEvent));
+ _ASSERTE(pUnmanagedEvent->IsEventContinuedHijacked());
+ _ASSERTE(pUnmanagedEvent->IsDispatched());
+ _ASSERTE(pUnmanagedEvent->IsEventUserContinued());
+ _ASSERTE(!pUnmanagedEvent->IsEventContinuedUnhijacked());
+ pUnmanagedEvent->SetState(CUES_EventContinuedUnhijacked);
+
+ // fetch the LS memory set up for this hijack
+ REMOTE_PTR pDebuggerWord = NULL;
+ DebuggerIPCFirstChanceData fcd;
+ pUnmanagedThread->GetEEDebuggerWord(&pDebuggerWord);
+ SafeReadStruct(PTR_TO_CORDB_ADDRESS(pDebuggerWord), &fcd);
+
+ LOG((LF_CORDB, LL_INFO10000, "W32ET::W32EL: pDebuggerWord is 0x%p\n", pDebuggerWord));
+
+ //set the correct continuation action based upon the user's selection
+ if(pUnmanagedEvent->IsExceptionCleared())
+ {
+ LOG((LF_CORDB, LL_INFO10000, "W32ET::W32EL: exception cleared\n"));
+ fcd.action = HIJACK_ACTION_EXIT_HANDLED;
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO10000, "W32ET::W32EL: exception not cleared\n"));
+ fcd.action = HIJACK_ACTION_EXIT_UNHANDLED;
+ }
+
+ //
+ // LS context is restored here so that execution continues from next instruction that caused the hijack.
+ // We shouldn't always restore the LS context though.
+ // Consider the following case where this can cause issues:
+ // Debuggee process hits an exception and calls KERNELBASE!RaiseException, debugger gets the notification and
+ // prepares for first-chance hijack. Debugger(DBI) saves the current thread context (see SetupFirstChanceHijackForSync) which is restored
+ // later below (see SafeWriteThreadContext call) when the process is in VEH (CLRVectoredExceptionHandlerShim->FirstChanceSuspendHijackWorker).
+ // The thread context that got saved(by SetupFirstChanceHijackForSync) was for when the thread was executing RaiseException and when
+ // this context gets restored in VEH, the thread resumes after the exception handler with a context that is not same as one with which
+ // it entered. This inconsistency can lead to bad execution code-paths or even a debuggee crash.
+ //
+ // Example case where we should definitely update the LS context:
+ // After a DbgBreakPoint call, IP gets updated to point to the instruction after int 3 and this is the context saved by debugger.
+ // The IP in context passed to VEH still points to int 3 though and if we don't update the LS context in VEH, the breakpoint
+ // instruction will get executed again.
+ //
+ // Here's a list of cases when we update the LS context:
+ // * we know that context was explicitly updated during this hijack, OR
+ // * if single-stepping flag was set on it originally, OR
+ // * if this was a breakpoint event
+ // Note that above list is a heuristic and it is possible that we need to add more such cases in future.
+ //
+ BOOL isBreakPointEvent = (pUnmanagedEvent->m_currentDebugEvent.dwDebugEventCode == EXCEPTION_DEBUG_EVENT &&
+ pUnmanagedEvent->m_currentDebugEvent.u.Exception.ExceptionRecord.ExceptionCode == STATUS_BREAKPOINT);
+ if (pUnmanagedThread->IsContextSet() || IsSSFlagEnabled(&tempContext) || isBreakPointEvent)
+ {
+ _ASSERTE(fcd.pLeftSideContext != NULL);
+ LOG((LF_CORDB, LL_INFO10000, "W32ET::W32EL: updating LS context at 0x%p\n", fcd.pLeftSideContext));
+ // write the new context over the old one on the LS
+ SafeWriteThreadContext(fcd.pLeftSideContext, &tempContext);
+ }
+
+ // Write the new Fcd data to the LS
+ fcd.debugCounter = 0x1;
+ SafeWriteStruct(PTR_TO_CORDB_ADDRESS(pDebuggerWord), &fcd);
+
+ fcd.debugCounter = 0;
+ SafeReadStruct(PTR_TO_CORDB_ADDRESS(pDebuggerWord), &fcd);
+ _ASSERTE(fcd.debugCounter == 1);
+
+ DequeueUnmanagedEvent(pUnmanagedThread);
+ }
+
+ _ASSERTE(m_cFirstChanceHijackedThreads > 0);
+ m_cFirstChanceHijackedThreads--;
+ if(m_cFirstChanceHijackedThreads == 0)
+ {
+ m_state &= ~PS_HIJACKS_IN_PLACE;
+ }
+
+ pUnmanagedThread->ClearState(CUTS_FirstChanceHijacked);
+ pUnmanagedThread->ClearState(CUTS_BlockingForSync);
+
+ // if the user set the context it either was already applied (m2uHandoff hijack)
+ // or is about to be applied when the hijack returns (sync hijack).
+ // There may still a small window where it won't appear accurate that
+ // we just have to live with
+ pUnmanagedThread->ClearState(CUTS_HasContextSet);
+ }
+
+ pW32EventThread->ForceDbgContinue(this, pUnmanagedThread, DBG_CONTINUE, false);
+
+ // We've handled this event. Skip further processing.
+ goto LDone;
+ }
+
+ case Reaction::cBreakpointRequiringHijack:
+ {
+ HRESULT hr = pUnmanagedThread->SetupFirstChanceHijack(EHijackReason::kM2UHandoff, &(pEvent->u.Exception.ExceptionRecord));
+ _ASSERTE(SUCCEEDED(hr));
+ pW32EventThread->ForceDbgContinue(this, pUnmanagedThread, DBG_CONTINUE, false);
+ goto LDone;
+ }
+
+ case Reaction::cInbandExceptionRetrigger:
+ {
+ // this should be unused now
+ _ASSERTE(FALSE);
+ _ASSERTE(pUnmanagedThread->HasIBEvent());
+ CordbUnmanagedEvent* pUnmanagedEvent = pUnmanagedThread->IBEvent();
+ _ASSERTE(pUnmanagedEvent->IsIBEvent());
+ _ASSERTE(pUnmanagedEvent->IsEventContinuedUnhijacked());
+ _ASSERTE(pUnmanagedEvent->IsDispatched());
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::W32EL: IB event completing, continuing ue=0x%p\n", pUnmanagedEvent));
+
+ DequeueUnmanagedEvent(pUnmanagedThread);
+ // If this event came from RaiseException then flush the context to ensure we won't use it until we re-enter
+ if(pUnmanagedEvent->m_owner->IsRaiseExceptionHijacked())
+ {
+ pUnmanagedEvent->m_owner->RestoreFromRaiseExceptionHijack();
+ pUnmanagedEvent->m_owner->ClearRaiseExceptionEntryContext();
+ }
+ else // otherwise we should have been stepping
+ {
+ pUnmanagedThread->EndStepping();
+ }
+ pW32EventThread->ForceDbgContinue(this, pUnmanagedThread,
+ pUnmanagedEvent->IsExceptionCleared() ? DBG_CONTINUE : DBG_EXCEPTION_NOT_HANDLED, false);
+
+ // We've handled this event. Skip further processing.
+ goto LDone;
+ }
+
+ case Reaction::cOOB:
+ {
+ // Don't care if this thread claimed to be suspended or not. Dispatch event anyways. After all,
+ // OOB events can come at *any* time.
+
+ // This thread may be suspended. We don't care.
+ this->m_DbgSupport.m_TotalOOB++;
+
+ // Not an inband event. This includes ALL non-exception events (including EXIT_THREAD) as
+ // well as any exception that can't be hijacked (ex, an exception on the helper thread).
+
+ // If this is an exit thread or exit process event, then we need to mark the unmanaged thread as
+ // exited for later.
+ if ((pEvent->dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT) ||
+ (pEvent->dwDebugEventCode == EXIT_THREAD_DEBUG_EVENT))
+ {
+ pUnmanagedThread->SetState(CUTS_Deleted);
+ }
+
+ // If we get an exit process or exit thread event on the helper thread, then we know we're loosing
+ // the Left Side, so go ahead and remember that the helper thread has died.
+ if (this->IsHelperThreadWorked(pUnmanagedThread->GetOSTid()))
+ {
+ if ((pEvent->dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT) ||
+ (pEvent->dwDebugEventCode == EXIT_THREAD_DEBUG_EVENT))
+ {
+ this->m_helperThreadDead = true;
+ }
+ }
+
+ // Queue the current out-of-band event.
+ this->QueueOOBUnmanagedEvent(pUnmanagedThread, pEvent);
+
+ // Go ahead and dispatch the event if its the first one.
+ if (this->m_outOfBandEventQueue == pUnmanagedThread->OOBEvent())
+ {
+ // Set this to true to indicate to Continue() that we're in the unamnaged callback.
+ CordbUnmanagedEvent * pUnmanagedEvent = pUnmanagedThread->OOBEvent();
+
+ this->m_dispatchingOOBEvent = true;
+
+ pUnmanagedEvent->SetState(CUES_Dispatched);
+
+ this->Unlock();
+
+ // Handler should have been registered by now.
+ _ASSERTE(this->m_cordb->m_unmanagedCallback != NULL);
+
+ // Call the callback with fIsOutOfBand = TRUE.
+ {
+ PUBLIC_WIN32_CALLBACK_IN_THIS_SCOPE(this, pEvent, TRUE);
+ this->m_cordb->m_unmanagedCallback->DebugEvent(const_cast<DEBUG_EVENT*> (pEvent), TRUE);
+ }
+
+ this->Lock();
+
+ // If m_dispatchingOOBEvent is false, that means that the user called Continue() from within
+ // the callback. We know that we can go ahead and continue the process now.
+ if (this->m_dispatchingOOBEvent == false)
+ {
+ // Note: this call will dispatch more OOB events if necessary.
+ pW32EventThread->UnmanagedContinue(this, cOobUMContinue);
+ }
+ else
+ {
+ // We're not dispatching anymore, so set this back to false.
+ this->m_dispatchingOOBEvent = false;
+ }
+ }
+
+ // We've handled this event. Skip further processing.
+ goto LDone;
+ }
+ } // end Switch on Reaction
+
+ UNREACHABLE();
+
+LDone:
+ // Process Lock implicitly released by holder.
+
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "W32ET::W32EL: done processing event.\n");
+
+ return;
+}
+
+//
+// Returns true if the exception is a flare from the left side, false otherwise.
+//
+bool CordbProcess::ExceptionIsFlare(DWORD exceptionCode, const void *exceptionAddress)
+{
+ _ASSERTE(m_runtimeOffsetsInitialized);
+
+ // Can't have a flare if the left side isn't initialized
+ if (m_initialized)
+ {
+ DebuggerIPCRuntimeOffsets *pRO = &m_runtimeOffsets;
+
+ // All flares are breakpoints...
+ if (exceptionCode == STATUS_BREAKPOINT)
+ {
+ // Does the breakpoint address match a flare address?
+ if ((exceptionAddress == pRO->m_signalHijackStartedBPAddr) ||
+ (exceptionAddress == pRO->m_excepForRuntimeHandoffStartBPAddr) ||
+ (exceptionAddress == pRO->m_excepForRuntimeHandoffCompleteBPAddr) ||
+ (exceptionAddress == pRO->m_signalHijackCompleteBPAddr) ||
+ (exceptionAddress == pRO->m_excepNotForRuntimeBPAddr) ||
+ (exceptionAddress == pRO->m_notifyRSOfSyncCompleteBPAddr))
+ return true;
+ }
+ }
+
+ return false;
+}
+#endif // FEATURE_INTEROP_DEBUGGING
+
+// Allocate a buffer in the target and copy data into it.
+//
+// Arguments:
+// pDomain - an appdomain associated with the allocation request.
+// bufferSize - size of the buffer in bytes
+// bufferFrom - local buffer of data (bufferSize bytes) to copy data from.
+// ppRes - address into target of allocated buffer
+//
+// Returns:
+// S_OK on success, else error.
+HRESULT CordbProcess::GetAndWriteRemoteBuffer(CordbAppDomain *pDomain, unsigned int bufferSize, const void *bufferFrom, void **ppRes)
+{
+ _ASSERTE(ppRes != NULL);
+ *ppRes = NULL;
+
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+ TargetBuffer tbTarget = GetRemoteBuffer(bufferSize); // throws
+ SafeWriteBuffer(tbTarget, (const BYTE*) bufferFrom); // throws
+
+ // Succeeded.
+ *ppRes = CORDB_ADDRESS_TO_PTR(tbTarget.pAddress);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+
+//
+// Checks to see if the given second chance exception event actually signifies the death of the process due to a second
+// stack overflow special case.
+//
+// There are strange cases with stack overflow exceptions. If a nieve application catches a stack overflow exception and
+// handles it, without resetting the guard page, then the app will get an AV when it overflows the stack a second time. We
+// will get the first chance AV, but when we continue from it the OS won't run any SEH handlers, so our FCH won't
+// actually work. Instead, we'll get the AV back on second chance right away.
+//
+bool CordbProcess::IsSpecialStackOverflowCase(CordbUnmanagedThread *pUThread, const DEBUG_EVENT *pEvent)
+{
+ _ASSERTE(pEvent->dwDebugEventCode == EXCEPTION_DEBUG_EVENT);
+ _ASSERTE(pEvent->u.Exception.dwFirstChance == 0);
+
+ // If this is not an AV, it can't be our special case.
+ if (pEvent->u.Exception.ExceptionRecord.ExceptionCode != STATUS_ACCESS_VIOLATION)
+ return false;
+
+ // If the thread isn't already first chance hijacked, it can't be our special case.
+ if (!pUThread->IsFirstChanceHijacked())
+ return false;
+
+ // The first chance hijack didn't take, so we're not FCH anymore and we're not waiting for an answer
+ // anymore... Note: by leaving this thread completely unhijacked, we'll report its true context, which is correct.
+ pUThread->ClearState(CUTS_FirstChanceHijacked);
+
+ // The process is techincally dead as a door nail here, so we'll mark that the helper thread is dead so our managed
+ // API bails nicely.
+ m_helperThreadDead = true;
+
+ // Remember we're in our special case.
+ pUThread->SetState(CUTS_HasSpecialStackOverflowCase);
+
+ // Now, remember the second chance AV event in the second IB event slot for this thread and add it to the end of the
+ // IB event queue.
+ QueueUnmanagedEvent(pUThread, pEvent);
+
+ // Note: returning true will ensure that the queued first chance AV for this thread is dispatched.
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Longhorn broke ContinueDebugEvent.
+// In previous OS releases, DBG_CONTINUE would continue a non-continuable exception.
+// In longhorn, we need to pass the DBG_FORCE_CONTINUE flag to do that.
+// Note that all CLR exceptions are non-continuable.
+// Now instead of DBG_CONTINUE, we need to pass DBG_FORCE_CONTINUE.
+//-----------------------------------------------------------------------------
+
+// Currently we don't have headers for the longhorn winnt.h. So we need to privately declare
+// this here. We have a check such that if we do get headers, the value won't change underneath us.
+#define MY_DBG_FORCE_CONTINUE ((DWORD )0x00010003L)
+#ifndef DBG_FORCE_CONTINUE
+#define DBG_FORCE_CONTINUE MY_DBG_FORCE_CONTINUE
+#else
+static_assert_no_msg(DBG_FORCE_CONTINUE == MY_DBG_FORCE_CONTINUE);
+#endif
+
+DWORD GetDbgContinueFlag()
+{
+ // Currently, default to not using the new DBG_FORCE_CONTINUE flag.
+ static ConfigDWORD fNoFlagKey;
+ bool fNoFlag = fNoFlagKey.val(CLRConfig::UNSUPPORTED_DbgNoForceContinue) != 0;
+
+
+ if (!fNoFlag)
+ {
+ return DBG_FORCE_CONTINUE;
+ }
+ else
+ {
+ return DBG_CONTINUE;
+ }
+}
+
+
+// Some Interop bugs involve threads that land at a crazy IP. Since we're interop-debugging, we can't
+// attach a debugger to the LS. So we have some debug mode where we enable the SS flag and thus
+// produce a trace of where a thread is going.
+#ifdef _DEBUG
+void EnableDebugTrace(CordbUnmanagedThread *ut)
+{
+ // To enable, attach w/ a debugger and either set fTrace==true, or setip.
+ static bool fTrace = false;
+ if (!fTrace)
+ return;
+
+ // Give us a nop so that we can setip in the optimized case.
+#ifdef _TARGET_X86_
+ __asm {
+ nop
+ }
+#endif
+
+ fTrace = true;
+ CordbProcess *pProcess = ut->GetProcess();
+
+ // Get the context
+ HRESULT hr = S_OK;
+ DT_CONTEXT context;
+ context.ContextFlags = DT_CONTEXT_FULL;
+
+
+ hr = pProcess->GetThreadContext((DWORD) ut->m_id, sizeof(context), (BYTE*)&context);
+ if (FAILED(hr))
+ return;
+
+ // If the flag is already set, then don't set it again - that will just get confusing.
+ if (IsSSFlagEnabled(&context))
+ {
+ return;
+ }
+ _ASSERTE(CORDbgGetIP(&context) != 0);
+ SetSSFlag(&context);
+
+ // If SS flag not set, enable it. And remeber that it's us so we know how to handle
+ // it when we get the debug event.
+ hr = pProcess->SetThreadContext((DWORD)ut->m_id, sizeof(context), (BYTE*)&context);
+ ut->SetState(CUTS_DEBUG_SingleStep);
+}
+#endif // _DEBUG
+
+//-----------------------------------------------------------------------------
+// DoDbgContinue
+//
+// Continues from a specific Win32 DEBUG_EVENT.
+//
+// Arguments:
+// pProcess - The process to continue.
+// pUnmanagedEvent - The event to continue.
+//
+//-----------------------------------------------------------------------------
+void CordbWin32EventThread::DoDbgContinue(CordbProcess *pProcess,
+ CordbUnmanagedEvent *pUnmanagedEvent)
+{
+ _ASSERTE(pProcess->ThreadHoldsProcessLock());
+ _ASSERTE(IsWin32EventThread());
+ _ASSERTE(pUnmanagedEvent != NULL);
+ _ASSERTE(!pUnmanagedEvent->IsEventContinuedUnhijacked());
+
+ STRESS_LOG3(LF_CORDB, LL_INFO1000,
+ "W32ET::DDC: continue with ue=0x%p, thread=0x%p, tid=0x%x\n",
+ pUnmanagedEvent,
+ pUnmanagedEvent->m_owner,
+ pUnmanagedEvent->m_owner->m_id);
+
+#ifdef _DEBUG
+ EnableDebugTrace(pUnmanagedEvent->m_owner);
+#endif
+
+
+ if (pUnmanagedEvent->IsEventContinuedHijacked())
+ {
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::DDC: Skiping DoDbgContinue because event was already"
+ " continued hijacked, ue=0x%p\n", pUnmanagedEvent));
+ return;
+ }
+
+ BOOL threadIsHijacked = (pUnmanagedEvent->m_owner->IsFirstChanceHijacked() ||
+ pUnmanagedEvent->m_owner->IsGenericHijacked());
+
+ BOOL eventIsIB = (pUnmanagedEvent->m_owner->HasIBEvent() &&
+ pUnmanagedEvent->m_owner->IBEvent() == pUnmanagedEvent);
+
+ _ASSERTE((DWORD) pProcess->m_id == pUnmanagedEvent->m_currentDebugEvent.dwProcessId);
+ _ASSERTE(pProcess->m_state & CordbProcess::PS_WIN32_STOPPED);
+
+ DWORD dwContType;
+ if(eventIsIB)
+ {
+ // 3 cases here...
+ // event was already hijacked
+ if(threadIsHijacked)
+ {
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::DDC: Continuing IB, already hijacked, ue=0x%p\n", pUnmanagedEvent));
+ pUnmanagedEvent->SetState(CUES_EventContinuedHijacked);
+ dwContType = !pUnmanagedEvent->m_owner->IsBlockingForSync() ? GetDbgContinueFlag() : DBG_EXCEPTION_NOT_HANDLED;
+ }
+ // event was not hijacked but has been dispatched
+ else if(!threadIsHijacked && pUnmanagedEvent->IsDispatched())
+ {
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::DDC: Continuing IB, not hijacked, ue=0x%p\n", pUnmanagedEvent));
+ _ASSERTE(pUnmanagedEvent->IsDispatched());
+ _ASSERTE(pUnmanagedEvent->IsEventUserContinued());
+ _ASSERTE(!pUnmanagedEvent->IsEventContinuedUnhijacked());
+ pUnmanagedEvent->SetState(CUES_EventContinuedUnhijacked);
+ dwContType = pUnmanagedEvent->IsExceptionCleared() ? GetDbgContinueFlag() : DBG_EXCEPTION_NOT_HANDLED;
+
+ // The event was never hijacked and so will never need to retrigger, get rid
+ // of it right now. If it had been hijacked then we would dequeue it either after the
+ // hijack complete flare or one instruction after that when it has had a chance to retrigger
+ pProcess->DequeueUnmanagedEvent(pUnmanagedEvent->m_owner);
+ }
+ // event was not hijacked nor dispatched
+ else // if(!threadIsHijacked && !pUnmanagedEvent->IsDispatched())
+ {
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::DDC: Continuing IB, now hijacked, ue=0x%p\n", pUnmanagedEvent));
+ HRESULT hr = pProcess->HijackIBEvent(pUnmanagedEvent);
+ _ASSERTE(SUCCEEDED(hr));
+ pUnmanagedEvent->SetState(CUES_EventContinuedHijacked);
+ dwContType = !pUnmanagedEvent->m_owner->IsBlockingForSync() ? GetDbgContinueFlag() : DBG_EXCEPTION_NOT_HANDLED;
+ }
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::DDC: Continuing OB, ue=0x%p\n", pUnmanagedEvent));
+ // we might actually be hijacked here, but if we are it should be for a previous IB event
+ // we just mark all OB events as continued unhijacked
+ pUnmanagedEvent->SetState(CUES_EventContinuedUnhijacked);
+ dwContType = pUnmanagedEvent->IsExceptionCleared() ? GetDbgContinueFlag() : DBG_EXCEPTION_NOT_HANDLED;
+ }
+
+ // If the exception is marked as unclearable, then make sure the continue type is correct and force the process
+ // to terminate.
+ if (pUnmanagedEvent->IsExceptionUnclearable())
+ {
+ TerminateProcess(pProcess->UnsafeGetProcessHandle(), pUnmanagedEvent->m_currentDebugEvent.u.Exception.ExceptionRecord.ExceptionCode);
+ dwContType = DBG_EXCEPTION_NOT_HANDLED;
+ }
+
+ // If we're continuing from the loader-bp, then send the managed attach here.
+ // (Note this will only be set if the runtime was loaded when we first tried to attach).
+ // We assume that the loader-bp is the 1st BP exception. This is naive,
+ // since it's not 100% accurate (someone could CreateThread w/ a threadproc of DebugBreak).
+ // But it's the best we can do.
+ // Note that it's critical we do this BEFORE continuing the process. If this is mixed-mode, we've already
+ // told VS about this breakpoint, and so it's set the attach-complete event. As soon as we continue this debug
+ // event the process can start moving again, so the CLR needs to know to wait for a managed attach.
+ DWORD dwEventCode = pUnmanagedEvent->m_currentDebugEvent.dwDebugEventCode;
+ if (dwEventCode == EXCEPTION_DEBUG_EVENT)
+ {
+ EXCEPTION_DEBUG_INFO * pDebugInfo = &pUnmanagedEvent->m_currentDebugEvent.u.Exception;
+ if (pDebugInfo->dwFirstChance && pDebugInfo->ExceptionRecord.ExceptionCode == STATUS_BREAKPOINT)
+ {
+ HRESULT hrIgnore = S_OK;
+ EX_TRY
+ {
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::DDC: Continuing from LdrBp, doing managed attach.\n"));
+ pProcess->QueueManagedAttachIfNeededWorker();
+ }
+ EX_CATCH_HRESULT(hrIgnore);
+ SIMPLIFYING_ASSUMPTION(SUCCEEDED(hrIgnore));
+ }
+ }
+
+ STRESS_LOG4(LF_CORDB, LL_INFO1000,
+ "W32ET::DDC: calling ContinueDebugEvent(0x%x, 0x%x, 0x%x), process state=0x%x\n",
+ pProcess->m_id, pUnmanagedEvent->m_owner->m_id, dwContType, pProcess->m_state);
+
+ // Actually continue the debug event
+ pProcess->m_state &= ~CordbProcess::PS_WIN32_STOPPED;
+ BOOL fSuccess = m_pNativePipeline->ContinueDebugEvent((DWORD)pProcess->m_id, (DWORD)pUnmanagedEvent->m_owner->m_id, dwContType);
+
+ // ContinueDebugEvent may 'fail' if we force kill the debuggee while stopped at the exit-process event.
+ if (!fSuccess && (dwEventCode != EXIT_PROCESS_DEBUG_EVENT))
+ {
+ _ASSERTE(!"ContinueDebugEvent failed!");
+ CORDBSetUnrecoverableError(pProcess, HRESULT_FROM_GetLastError(), 0);
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "W32ET::DDC: Last error after ContinueDebugEvent is %d\n", GetLastError());
+ }
+
+ // If this thread is marked for deletion (exit thread or exit process event on it), then we need to delete the
+ // unmanaged thread object.
+ if ((dwEventCode == EXIT_PROCESS_DEBUG_EVENT) || (dwEventCode == EXIT_THREAD_DEBUG_EVENT))
+ {
+ CordbUnmanagedThread * pUnmanagedThread = pUnmanagedEvent->m_owner;
+ _ASSERTE(pUnmanagedThread->IsDeleted());
+
+
+ // Thread may have a hijacked inband event on it. Thus it's actually running free from the OS perspective,
+ // and fair game to be terminated. In that case, we need to auto-dequeue the event.
+ // This will just prevent the RS from making the underlying call to ContinueDebugEvent on this thread
+ // for the inband event. Since we've already lost the thread, that's actually exactly what we want.
+ if (pUnmanagedThread->HasIBEvent())
+ {
+ pProcess->DequeueUnmanagedEvent(pUnmanagedThread);
+ }
+
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "Removing thread 0x%x (%d) from process list\n", pUnmanagedThread->m_id);
+ pProcess->m_unmanagedThreads.RemoveBase((ULONG_PTR)pUnmanagedThread->m_id);
+ }
+
+
+ // If we just continued from an exit process event, then its time to do the exit processing.
+ if (dwEventCode == EXIT_PROCESS_DEBUG_EVENT)
+ {
+ pProcess->Unlock();
+ ExitProcess(false); // not detach case
+ pProcess->Lock();
+ }
+
+}
+
+//---------------------------------------------------------------------------------------
+//
+// ForceDbgContinue continues from the last Win32 DEBUG_EVENT on the given thread, no matter what it was.
+//
+// Arguments:
+// pProcess - process object to continue
+// pUnmanagedThread - unmanaged thread object (maybe null if we're doing a raw cotninue)
+// contType - continuation status (DBG_CONTINUE or DBG_EXCEPTION_NOT_HANDLED)
+// fContinueProcess - do we resume hijacks?
+//
+void CordbWin32EventThread::ForceDbgContinue(CordbProcess *pProcess, CordbUnmanagedThread *pUnmanagedThread, DWORD contType,
+ bool fContinueProcess)
+{
+ _ASSERTE(pProcess->ThreadHoldsProcessLock());
+ _ASSERTE(pUnmanagedThread != NULL);
+ STRESS_LOG4(LF_CORDB, LL_INFO1000,
+ "W32ET::FDC: force continue with 0x%x (%s), contProcess=%d, tid=0x%x\n",
+ contType,
+ (contType == DBG_CONTINUE) ? "DBG_CONTINUE" : "DBG_EXCEPTION_NOT_HANDLED",
+ fContinueProcess,
+ pUnmanagedThread->m_id);
+
+ if (fContinueProcess)
+ {
+ pProcess->ResumeHijackedThreads();
+ }
+
+ if (contType == DBG_CONTINUE)
+ {
+ contType = GetDbgContinueFlag();
+ }
+
+ _ASSERTE(pProcess->m_state & CordbProcess::PS_WIN32_STOPPED);
+
+ // Remove the Win32 stopped flag so long as the OOB event queue is empty. We're forcing a continue here, so by
+ // definition this should be the case...
+ _ASSERTE(pProcess->m_outOfBandEventQueue == NULL);
+
+ pProcess->m_state &= ~CordbProcess::PS_WIN32_STOPPED;
+
+ STRESS_LOG4(LF_CORDB, LL_INFO1000, "W32ET::FDC: calling ContinueDebugEvent(0x%x, 0x%x, 0x%x), process state=0x%x\n",
+ pProcess->m_id, pUnmanagedThread->m_id, contType, pProcess->m_state);
+
+
+ #ifdef _DEBUG
+ EnableDebugTrace(pUnmanagedThread);
+ #endif
+ BOOL ret = m_pNativePipeline->ContinueDebugEvent((DWORD)pProcess->m_id, (DWORD)pUnmanagedThread->m_id, contType);
+
+ if (!ret)
+ {
+ // This could in theory fail from Process exit, but that really would only be on the DoDbgContinue path.
+ _ASSERTE(!"ContinueDebugEvent failed #2!");
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "W32ET::DDC: Last error after ContinueDebugEvent is %d\n", GetLastError());
+ }
+}
+#endif // FEATURE_INTEROP_DEBUGGING
+
+//
+// This is the thread's real thread proc. It simply calls to the
+// thread proc on the given object.
+//
+/*static*/ DWORD WINAPI CordbWin32EventThread::ThreadProc(LPVOID parameter)
+{
+ CordbWin32EventThread* t = (CordbWin32EventThread*) parameter;
+ INTERNAL_THREAD_ENTRY(t);
+ t->ThreadProc();
+ return 0;
+}
+
+
+//
+// Send a CreateProcess event to the Win32 thread to have it create us
+// a new process.
+//
+HRESULT CordbWin32EventThread::SendCreateProcessEvent(
+ MachineInfo machineInfo,
+ LPCWSTR programName,
+ __in_z LPWSTR programArgs,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ PVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation,
+ CorDebugCreateProcessFlags corDebugFlags)
+{
+ HRESULT hr = S_OK;
+
+ LockSendToWin32EventThreadMutex();
+ LOG((LF_CORDB, LL_EVERYTHING, "CordbWin32EventThread::SCPE Called\n"));
+ m_actionData.createData.machineInfo = machineInfo;
+ m_actionData.createData.programName = programName;
+ m_actionData.createData.programArgs = programArgs;
+ m_actionData.createData.lpProcessAttributes = lpProcessAttributes;
+ m_actionData.createData.lpThreadAttributes = lpThreadAttributes;
+ m_actionData.createData.bInheritHandles = bInheritHandles;
+ m_actionData.createData.dwCreationFlags = dwCreationFlags;
+ m_actionData.createData.lpEnvironment = lpEnvironment;
+ m_actionData.createData.lpCurrentDirectory = lpCurrentDirectory;
+ m_actionData.createData.lpStartupInfo = lpStartupInfo;
+ m_actionData.createData.lpProcessInformation = lpProcessInformation;
+ m_actionData.createData.corDebugFlags = corDebugFlags;
+
+ // m_action is set last so that the win32 event thread can inspect
+ // it and take action without actually having to take any
+ // locks. The lock around this here is simply to prevent multiple
+ // threads from making requests at the same time.
+ m_action = W32ETA_CREATE_PROCESS;
+
+ BOOL succ = SetEvent(m_threadControlEvent);
+
+ if (succ)
+ {
+ DWORD ret = WaitForSingleObject(m_actionTakenEvent, INFINITE);
+
+ LOG((LF_CORDB, LL_EVERYTHING, "Process Handle is: %x, m_threadControlEvent is %x\n",
+ (UINT_PTR)m_actionData.createData.lpProcessInformation->hProcess, (UINT_PTR)m_threadControlEvent));
+
+ if (ret == WAIT_OBJECT_0)
+ hr = m_actionResult;
+ else
+ hr = HRESULT_FROM_GetLastError();
+ }
+ else
+ hr = HRESULT_FROM_GetLastError();
+
+ UnlockSendToWin32EventThreadMutex();
+
+ return hr;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Create a process
+//
+// Assumptions:
+// This occurs on the win32 event thread. It is invokved via
+// a message sent from code:CordbWin32EventThread::SendCreateProcessEvent
+//
+// Notes:
+// Create a new process. This is called in the context of the Win32
+// event thread to ensure that if we're Win32 debugging the process
+// that the same thread that waits for debugging events will be the
+// thread that creates the process.
+//
+//---------------------------------------------------------------------------------------
+void CordbWin32EventThread::CreateProcess()
+{
+ m_action = W32ETA_NONE;
+ HRESULT hr = S_OK;
+
+ DWORD dwCreationFlags = m_actionData.createData.dwCreationFlags;
+
+ // If the creation flags has DEBUG_PROCESS in them, then we're
+ // Win32 debugging this process. Otherwise, we have to create
+ // suspended to give us time to setup up our side of the IPC
+ // channel.
+ BOOL fInteropDebugging =
+#if defined(FEATURE_INTEROP_DEBUGGING)
+ (dwCreationFlags & (DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS));
+#else
+ false; // Interop not supported.
+#endif
+
+ // Have Win32 create the process...
+ hr = m_pNativePipeline->CreateProcessUnderDebugger(
+ m_actionData.createData.machineInfo,
+ m_actionData.createData.programName,
+ m_actionData.createData.programArgs,
+ m_actionData.createData.lpProcessAttributes,
+ m_actionData.createData.lpThreadAttributes,
+ m_actionData.createData.bInheritHandles,
+ dwCreationFlags,
+ m_actionData.createData.lpEnvironment,
+ m_actionData.createData.lpCurrentDirectory,
+ m_actionData.createData.lpStartupInfo,
+ m_actionData.createData.lpProcessInformation);
+
+ if (SUCCEEDED(hr))
+ {
+ // Process ID is filled in after process is succesfully created.
+ DWORD dwProcessId = m_actionData.createData.lpProcessInformation->dwProcessId;
+
+ RSUnsafeExternalSmartPtr<CordbProcess> pProcess;
+ hr = m_pShim->InitializeDataTarget(dwProcessId);
+
+ if (SUCCEEDED(hr))
+ {
+ // To emulate V2 semantics, we pass 0 for the clrInstanceID into
+ // OpenVirtualProcess. This will then connect to the first CLR
+ // loaded.
+ const ULONG64 cFirstClrLoaded = 0;
+ hr = CordbProcess::OpenVirtualProcess(cFirstClrLoaded, m_pShim->GetDataTarget(), NULL, m_cordb, dwProcessId, m_pShim, &pProcess);
+ }
+
+ // Shouldn't happen on a create, only an attach
+ _ASSERTE(hr != CORDBG_E_DEBUGGER_ALREADY_ATTACHED);
+
+ // Remember the process in the global list of processes.
+ if (SUCCEEDED(hr))
+ {
+ EX_TRY
+ {
+ // Mark if we're interop-debugging
+ if (fInteropDebugging)
+ {
+ pProcess->EnableInteropDebugging();
+ }
+
+ m_cordb->AddProcess(pProcess); // will take ref if it succeeds
+ }
+ EX_CATCH_HRESULT(hr);
+ }
+
+ // If we're Win32 attached to this process, then increment the
+ // proper count, otherwise add this process to the wait set
+ // and resume the process's main thread.
+ if (SUCCEEDED(hr))
+ {
+ _ASSERTE(m_pProcess == NULL);
+ m_pProcess.Assign(pProcess);
+ }
+ }
+
+
+ //
+ // Signal the hr to the caller.
+ //
+ m_actionResult = hr;
+ SetEvent(m_actionTakenEvent);
+}
+
+
+//
+// Send a DebugActiveProcess event to the Win32 thread to have it attach to
+// a new process.
+//
+HRESULT CordbWin32EventThread::SendDebugActiveProcessEvent(
+ MachineInfo machineInfo,
+ DWORD pid,
+ bool fWin32Attach,
+ CordbProcess *pProcess)
+{
+ HRESULT hr = S_OK;
+
+ LockSendToWin32EventThreadMutex();
+
+ m_actionData.attachData.machineInfo = machineInfo;
+ m_actionData.attachData.processId = pid;
+#if !defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ m_actionData.attachData.fWin32Attach = fWin32Attach;
+#endif
+ m_actionData.attachData.pProcess = pProcess;
+
+ // m_action is set last so that the win32 event thread can inspect
+ // it and take action without actually having to take any
+ // locks. The lock around this here is simply to prevent multiple
+ // threads from making requests at the same time.
+ m_action = W32ETA_ATTACH_PROCESS;
+
+ BOOL succ = SetEvent(m_threadControlEvent);
+
+ if (succ)
+ {
+ DWORD ret = WaitForSingleObject(m_actionTakenEvent, INFINITE);
+
+ if (ret == WAIT_OBJECT_0)
+ hr = m_actionResult;
+ else
+ hr = HRESULT_FROM_GetLastError();
+ }
+ else
+ hr = HRESULT_FROM_GetLastError();
+
+ UnlockSendToWin32EventThreadMutex();
+
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// Is the given thread id a helper thread (real or worker?)
+//-----------------------------------------------------------------------------
+bool CordbProcess::IsHelperThreadWorked(DWORD tid)
+{
+ // Check against the id gained by sniffing Thread-Create events.
+ if (tid == this->m_helperThreadId)
+ {
+ return true;
+ }
+
+ // Now check for potential datate in the IPC block. If not there,
+ // then we know it can't be the helper.
+ DebuggerIPCControlBlock * pDCB = this->GetDCB();
+
+ if (pDCB == NULL)
+ {
+ return false;
+ }
+
+ // get the latest information from the LS DCB
+ UpdateRightSideDCB();
+ return
+ (tid == pDCB->m_realHelperThreadId) ||
+ (tid == pDCB->m_temporaryHelperThreadId);
+
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Cleans up the Left Side's DCB after a failed attach attempt.
+//
+// Assumptions:
+// Called when the left-site failed initialization
+//
+// Notes:
+// This can be called multiple times.
+//---------------------------------------------------------------------------------------
+void CordbProcess::CleanupHalfBakedLeftSide()
+{
+ if (GetDCB() != NULL)
+ {
+ EX_TRY
+ {
+ GetDCB()->m_rightSideIsWin32Debugger = false;
+ UpdateLeftSideDCBField(&(GetDCB()->m_rightSideIsWin32Debugger), sizeof(GetDCB()->m_rightSideIsWin32Debugger));
+
+ if (m_pEventChannel != NULL)
+ {
+ m_pEventChannel->Delete();
+ m_pEventChannel = NULL;
+ }
+ }
+ EX_CATCH
+ {
+ _ASSERTE(!"Writing process memory failed, perhaps due to an unexpected disconnection from the target.");
+ }
+ EX_END_CATCH(SwallowAllExceptions);
+ }
+
+ // Close and null out the various handles and events, including our process handle m_handle.
+ CloseIPCHandles();
+
+ m_cordb.Clear();
+
+ // This process object is Dead-On-Arrival, so it doesn't really have anything to neuter.
+ // But for safekeeping, we'll mark it as neutered.
+ UnsafeNeuterDeadObject();
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Attach to an existing process.
+//
+//
+// Assumptions:
+// Called on W32Event Thread, in response to event sent by
+// code:CordbWin32EventThread::SendDebugActiveProcessEvent
+//
+// Notes:
+// Attach to a process. This is called in the context of the Win32
+// event thread to ensure that if we're Win32 debugging the process
+// that the same thread that waits for debugging events will be the
+// thread that attaches the process.
+//
+// @dbgtodo shim: this will be part of the shim
+//---------------------------------------------------------------------------------------
+void CordbWin32EventThread::AttachProcess()
+{
+ _ASSERTE(IsWin32EventThread());
+
+ RSUnsafeExternalSmartPtr<CordbProcess> pProcess;
+
+ m_action = W32ETA_NONE;
+
+ HRESULT hr = S_OK;
+
+ DWORD dwProcessId = m_actionData.attachData.processId;
+ bool fNativeAttachSucceeded = false;
+
+
+ // Always do OS attach to the target.
+ // By this point, the pid should be valid (because OpenProcess above), pending some race where the process just exited.
+ // The OS will enforce that only 1 debugger is attached.
+ // Common failure paths here would be: access denied, double-attach
+ {
+ hr = m_pNativePipeline->DebugActiveProcess(m_actionData.attachData.machineInfo,
+ dwProcessId);
+ if (FAILED(hr))
+ {
+ goto LExit;
+ }
+ fNativeAttachSucceeded = true;
+ }
+
+
+ hr = m_pShim->InitializeDataTarget(m_actionData.attachData.processId);
+ if (FAILED(hr))
+ {
+ goto LExit;
+ }
+
+ // To emulate V2 semantics, we pass 0 for the clrInstanceID into
+ // OpenVirtualProcess. This will then connect to the first CLR
+ // loaded.
+ {
+ const ULONG64 cFirstClrLoaded = 0;
+ hr = CordbProcess::OpenVirtualProcess(cFirstClrLoaded, m_pShim->GetDataTarget(), NULL, m_cordb, dwProcessId, m_pShim, &pProcess);
+ if (FAILED(hr))
+ {
+ goto LExit;
+ }
+ }
+
+ // Remember the process in the global list of processes.
+ // The caller back in code:Cordb::DebugActiveProcess will then get this by fetching it from the list.
+
+ EX_TRY
+ {
+ // Mark interop-debugging
+ if (m_actionData.attachData.IsInteropDebugging())
+ {
+ pProcess->EnableInteropDebugging(); // Throwing
+ }
+
+ m_cordb->AddProcess(pProcess); // will take ref if it succeeds
+
+
+ // Queue fake Attach event for CreateProcess
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE0_NO_LOCK(pProcess);
+ m_pShim->BeginQueueFakeAttachEvents();
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ if (FAILED(hr))
+ {
+ goto LExit;
+ }
+
+ _ASSERTE(m_pProcess == NULL);
+ m_pProcess.Assign(pProcess);
+ pProcess.Clear(); // ownership transfered to m_pProcess
+
+ // Should have succeeded if we got to this point.
+ _ASSERTE(SUCCEEDED(hr));
+
+
+LExit:
+ if (FAILED(hr))
+ {
+ // If we succeed to do a native-attach, but then failed elsewhere, try to native-detach.
+ if (fNativeAttachSucceeded)
+ {
+ m_pNativePipeline->DebugActiveProcessStop(dwProcessId);
+ }
+
+ if (pProcess != NULL)
+ {
+ // Safe to call this even if the process wasn't added.
+ m_cordb->RemoveProcess(pProcess);
+ pProcess->CleanupHalfBakedLeftSide();
+ pProcess.Clear();
+ }
+ m_pProcess.Clear();
+ }
+
+ //
+ // Signal the hr to the caller.
+ //
+ m_actionResult = hr;
+ SetEvent(m_actionTakenEvent);
+}
+
+
+// Note that the actual 'DetachProcess' method is really ExitProcess with CW32ET_UNKNOWN_PROCESS_SLOT ==
+// processSlot
+HRESULT CordbWin32EventThread::SendDetachProcessEvent(CordbProcess *pProcess)
+{
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::SDPE\n"));
+ HRESULT hr = S_OK;
+
+ LockSendToWin32EventThreadMutex();
+
+ m_actionData.detachData.pProcess = pProcess;
+
+ // m_action is set last so that the win32 event thread can inspect it and take action without actually
+ // having to take any locks. The lock around this here is simply to prevent multiple threads from making
+ // requests at the same time.
+ m_action = W32ETA_DETACH;
+
+ BOOL succ = SetEvent(m_threadControlEvent);
+
+ if (succ)
+ {
+ DWORD ret = WaitForSingleObject(m_actionTakenEvent, INFINITE);
+
+ if (ret == WAIT_OBJECT_0)
+ hr = m_actionResult;
+ else
+ hr = HRESULT_FROM_GetLastError();
+ }
+ else
+ hr = HRESULT_FROM_GetLastError();
+
+ UnlockSendToWin32EventThreadMutex();
+
+ return hr;
+}
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+//
+// Send a UnmanagedContinue event to the Win32 thread to have it
+// continue from an unmanged debug event.
+//
+HRESULT CordbWin32EventThread::SendUnmanagedContinue(CordbProcess *pProcess,
+ EUMContinueType eContType)
+{
+ HRESULT hr = S_OK;
+
+ // If this were being called on the win32 EventThread, we'd deadlock.
+ _ASSERTE(!IsWin32EventThread());
+
+ // This can't hold the process lock, b/c we're making a cross-thread call,
+ // and our target will need the process lock.
+ _ASSERTE(!pProcess->ThreadHoldsProcessLock());
+
+ LockSendToWin32EventThreadMutex();
+
+ m_actionData.continueData.process = pProcess;
+ m_actionData.continueData.eContType = eContType;
+
+ // m_action is set last so that the win32 event thread can inspect
+ // it and take action without actually having to take any
+ // locks. The lock around this here is simply to prevent multiple
+ // threads from making requests at the same time.
+ m_action = W32ETA_CONTINUE;
+
+ BOOL succ = SetEvent(m_threadControlEvent);
+
+ if (succ)
+ {
+ DWORD ret = WaitForSingleObject(m_actionTakenEvent, INFINITE);
+
+ if (ret == WAIT_OBJECT_0)
+ hr = m_actionResult;
+ else
+ hr = HRESULT_FROM_GetLastError();
+ }
+ else
+ hr = HRESULT_FROM_GetLastError();
+
+ UnlockSendToWin32EventThreadMutex();
+
+ return hr;
+}
+
+
+//
+// Handle unmanaged continue. Continue an unmanaged debug
+// event. Deferes to UnmanagedContinue. This is called in the context
+// of the Win32 event thread to ensure that if we're Win32 debugging
+// the process that the same thread that waits for debugging events
+// will be the thread that continues the process.
+//
+void CordbWin32EventThread::HandleUnmanagedContinue()
+{
+ _ASSERTE(IsWin32EventThread());
+
+ m_action = W32ETA_NONE;
+ HRESULT hr = S_OK;
+
+ // Continue the process
+ CordbProcess *pProcess = m_actionData.continueData.process;
+
+ // If we lost the process object, we must have exited.
+ if (m_pProcess != NULL)
+ {
+ _ASSERTE(m_pProcess != NULL);
+ _ASSERTE(pProcess == m_pProcess);
+
+ _ASSERTE(!pProcess->ThreadHoldsProcessLock());
+
+ RSSmartPtr<CordbProcess> proc(pProcess);
+ RSLockHolder ch(&pProcess->m_processMutex);
+
+ hr = UnmanagedContinue(pProcess, m_actionData.continueData.eContType);
+ }
+
+ // Signal the hr to the caller.
+ m_actionResult = hr;
+ SetEvent(m_actionTakenEvent);
+}
+
+//
+// Continue an unmanaged debug event. This is called in the context of the Win32 Event thread to ensure that the same
+// thread that waits for debug events will be the thread that continues the process.
+//
+HRESULT CordbWin32EventThread::UnmanagedContinue(CordbProcess *pProcess,
+ EUMContinueType eContType)
+{
+ _ASSERTE(pProcess->ThreadHoldsProcessLock());
+ _ASSERTE(IsWin32EventThread());
+ _ASSERTE(m_pShim != NULL);
+
+ HRESULT hr = S_OK;
+
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "UM Continue. type=%d\n", eContType);
+
+ if (eContType == cOobUMContinue)
+ {
+ _ASSERTE(pProcess->m_outOfBandEventQueue != NULL);
+
+ // Dequeue the OOB event.
+ CordbUnmanagedEvent *ue = pProcess->m_outOfBandEventQueue;
+ CordbUnmanagedThread *ut = ue->m_owner;
+ pProcess->DequeueOOBUnmanagedEvent(ut);
+
+ // Do a little extra work if that was an OOB exception event...
+ hr = ue->m_owner->FixupAfterOOBException(ue);
+ _ASSERTE(SUCCEEDED(hr));
+
+ // Continue from the event.
+ DoDbgContinue(pProcess, ue);
+
+ // If there are more queued OOB events, dispatch them now.
+ if (pProcess->m_outOfBandEventQueue != NULL)
+ pProcess->DispatchUnmanagedOOBEvent();
+
+ // Note: if we previously skipped letting the entire process go on an IB continue due to a blocking OOB event,
+ // and if the OOB event queue is now empty, then go ahead and let the process continue now...
+ if ((pProcess->m_doRealContinueAfterOOBBlock == true) &&
+ (pProcess->m_outOfBandEventQueue == NULL))
+ goto doRealContinue;
+ }
+ else if (eContType == cInternalUMContinue)
+ {
+ // We're trying to get into a synced state which means we need the process running (potentially
+ // with some threads hijacked) in order to have the helper thread do the sync.
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::UC: internal continue.\n"));
+
+ if (!pProcess->GetSynchronized())
+ {
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::UC: internal continue, !sync'd.\n"));
+ pProcess->ResumeUnmanagedThreads();
+
+ // the event we may need to hijack and continue;
+ CordbUnmanagedEvent* pEvent = pProcess->m_lastQueuedUnmanagedEvent;
+
+ // It is possible to be stopped at either an IB or an OOB event here. We only want to
+ // continue from an IB event here though
+ if(pProcess->m_state & CordbProcess::PS_WIN32_STOPPED && pEvent != NULL &&
+ pEvent->IsEventWaitingForContinue())
+ {
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::UC: internal continue, frozen on IB event.\n"));
+
+ // There should be a uncontinued IB event at the head of the queue
+ _ASSERTE(pEvent->IsIBEvent());
+ _ASSERTE(!pEvent->IsEventContinuedUnhijacked());
+ _ASSERTE(!pEvent->IsEventContinuedHijacked());
+
+ // Ensure that the event is hijacked now (it may not have been before) so that the
+ // thread does not slip forward during the sync process. After that we can safely continue
+ // it.
+ pProcess->HijackIBEvent(pEvent);
+ m_pShim->GetWin32EventThread()->DoDbgContinue(pProcess, pEvent);
+ }
+ }
+
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::UC: internal continue, done.\n"));
+ }
+ else
+ {
+ // If we're here, then we know 100% for sure that we've successfully gotten the managed continue event to the
+ // Left Side, so we can stop force hijacking left over in-band events now. Note: if we had hijacked any such
+ // events, they'll be dispatched below since they're properly queued.
+ pProcess->m_specialDeferment = false;
+
+ // We don't actually do any work if there is an outstanding out-of-band event. When we do continue from the
+ // out-of-band event, we'll do this work, too.
+ if (pProcess->m_outOfBandEventQueue != NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::UC: ignoring real continue due to block by out-of-band event(s).\n"));
+
+ _ASSERTE(pProcess->m_doRealContinueAfterOOBBlock == false);
+ pProcess->m_doRealContinueAfterOOBBlock = true;
+ }
+ else
+ {
+doRealContinue:
+ // This is either the Frozen -> Running transition or a
+ // Synced -> Running transition
+ _ASSERTE(pProcess->m_outOfBandEventQueue == NULL);
+
+
+ pProcess->m_doRealContinueAfterOOBBlock = false;
+
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::UC: continuing the process.\n"));
+ // Dispatch any more queued in-band events, or if there are none then just continue the process.
+ //
+ // Note: don't dispatch more events if we've already sent up the ExitProcess event... those events are just
+ // lost.
+ if ((pProcess->HasUndispatchedNativeEvents()) && (pProcess->m_exiting == false))
+ {
+ pProcess->DispatchUnmanagedInBandEvent();
+ }
+ else
+ {
+ // If the unmanaged event queue is empty now, and the process is synchronized, and there are queued
+ // managed events, then go ahead and get more managed events dispatched.
+ //
+ // Note: don't dispatch more events if we've already sent up the ExitProcess event... those events are
+ // just lost.
+ if (pProcess->GetSynchronized() && (!m_pShim->GetManagedEventQueue()->IsEmpty()) && (pProcess->m_exiting == false))
+ {
+ if(pProcess->m_state & CordbProcess::PS_WIN32_STOPPED)
+ {
+ DoDbgContinue(pProcess, pProcess->m_lastDispatchedIBEvent);
+
+ // This if should not be necessary, I am just being extra careful because this
+ // fix is going in late - see issue 818301
+ _ASSERTE(pProcess->m_lastDispatchedIBEvent != NULL);
+ if(pProcess->m_lastDispatchedIBEvent != NULL)
+ {
+ pProcess->m_lastDispatchedIBEvent->m_owner->InternalRelease();
+ pProcess->m_lastDispatchedIBEvent = NULL;
+ }
+ }
+
+ // Now, get more managed events dispatched.
+ pProcess->SetSynchronized(false);
+ pProcess->MarkAllThreadsDirty();
+ m_cordb->ProcessStateChanged();
+ }
+ else
+ {
+ // free all the hijacked threads that hit native debug events
+ pProcess->ResumeHijackedThreads();
+
+ // after continuing the here the process should be running completely
+ // free... no hijacks, no suspended threads, and of course not frozen
+ if(pProcess->m_state & CordbProcess::PS_WIN32_STOPPED)
+ {
+ DoDbgContinue(pProcess, pProcess->m_lastDispatchedIBEvent);
+ // This if should not be necessary, I am just being extra careful because this
+ // fix is going in late - see issue 818301
+ _ASSERTE(pProcess->m_lastDispatchedIBEvent != NULL);
+ if(pProcess->m_lastDispatchedIBEvent != NULL)
+ {
+ pProcess->m_lastDispatchedIBEvent->m_owner->InternalRelease();
+ pProcess->m_lastDispatchedIBEvent = NULL;
+ }
+ }
+ }
+ }
+
+ // Implicit Release on UT
+ }
+ }
+
+ return hr;
+}
+#endif // FEATURE_INTEROP_DEBUGGING
+
+void ExitProcessWorkItem::Do()
+{
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "ExitProcessWorkItem proc=%p\n", GetProcess());
+
+ // This is being called on the RCET.
+ // That's the thread that dispatches managed events. Since it's calling us now, we know
+ // it can't be dispatching a managed event, and so we don't need to both waiting for it
+
+ {
+ // Get the SG lock here to coordinate against any other continues.
+ RSLockHolder ch(GetProcess()->GetStopGoLock());
+ RSLockHolder ch2(&(GetProcess()->m_processMutex));
+
+ LOG((LF_CORDB, LL_INFO1000,"W32ET::EP: ExitProcess callback\n"));
+
+ // We're synchronized now, so mark the process as such.
+ GetProcess()->SetSynchronized(true);
+ GetProcess()->IncStopCount();
+
+ // By the time we release the SG + Process locks here, the process object has been
+ // marked as exiting + terminated (by the w32et which queued us). Future attemps to
+ // continue should fail, and thus we should remain synchronized.
+ }
+
+
+ // Just to be safe, neuter any children before the exit process callback.
+ {
+ RSLockHolder ch(GetProcess()->GetProcessLock());
+
+ // Release the process.
+ GetProcess()->NeuterChildren();
+ }
+
+ RSSmartPtr<Cordb> pCordb(NULL);
+
+ // There is a race condition here where the debuggee process is killed while we are processing a process
+ // detach. We queue the process exit event for the Win32 event thread before queueing the process detach
+ // event. By the time this function is executed, we may have neutered the CordbProcess already as a
+ // result of code:CordbProcess::Detach. Detect that case here under the SG lock.
+ {
+ RSLockHolder ch(GetProcess()->GetStopGoLock());
+ if (!GetProcess()->IsNeutered())
+ {
+ _ASSERTE(GetProcess()->m_cordb != NULL);
+ pCordb.Assign(GetProcess()->m_cordb);
+ }
+ }
+
+ // Move this into Shim?
+
+ // Invoke the ExitProcess callback. This is very important since the a shell
+ // may rely on it for proper shutdown and may hang if they don't get it.
+ // We don't expect Cordbg to continue from this (we're certainly not going to wait for it).
+ if ((pCordb != NULL) && (pCordb->m_managedCallback != NULL))
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE0_NO_LOCK(GetProcess());
+ pCordb->m_managedCallback->ExitProcess(GetProcess());
+ }
+
+ // This CordbProcess object now has no reservations against a client calling ICorDebug::Terminate.
+ // That call may race against the CordbProcess::Neuter below, but since we already neutered the children,
+ // that neuter call will not do anything interesting that will conflict with Terminate.
+
+ LOG((LF_CORDB, LL_INFO1000,"W32ET::EP: returned from ExitProcess callback\n"));
+
+ {
+ RSLockHolder ch(GetProcess()->GetStopGoLock());
+
+ // Release the process.
+ GetProcess()->Neuter();
+ }
+
+ // Our dtor will release the Process object.
+ // This may be the final release on the process.
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Handles process exiting and detach cases
+//
+// Arguments:
+// fDetach - true if detaching, false if process is exiting.
+//
+// Return Value:
+// The type of the next argument in the signature,
+// normalized.
+//
+// Assumptions:
+// On exit, the process has already exited and we detected this by either an EXIT_PROCESS
+// native debug event, or by waiting on the process handle.
+// On detach, the process is stil live.
+//
+// Notes:
+// ExitProcess is called when a process exits or detaches.
+// This does our final cleanup and removes the process from our wait sets.
+// We're either here because we're detaching (fDetach == TRUE), or because the process has really exited,
+// and we're doing shutdown logic.
+//
+//---------------------------------------------------------------------------------------
+void CordbWin32EventThread::ExitProcess(bool fDetach)
+{
+ INTERNAL_API_ENTRY(this);
+
+ // Consider the following when you're modifying this function:
+ // - The OS can kill the debuggee at any time.
+ // - ExitProcess can race with detach.
+
+ LOG((LF_CORDB, LL_INFO1000,"W32ET::EP: begin ExitProcess, detach=%d\n", fDetach));
+
+
+ // For the Mac remote debugging transport, DebugActiveProcessStop() is a nop. The transport will be
+ // shut down later when we neuter the CordbProcess.
+#if !defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ // @dbgtodo shim: this is a primitive workaround for interop-detach
+ // Eventually, the Debugger owns the detach pipeline, so this won't be necessary.
+ if (fDetach && (m_pProcess != NULL))
+ {
+ HRESULT hr = m_pNativePipeline->DebugActiveProcessStop(m_pProcess->GetPid());
+
+ // We don't expect detach to fail (we check earlier for common conditions that
+ // may cause it to fail)
+ SIMPLIFYING_ASSUMPTION(SUCCEEDED(hr));
+ if( FAILED(hr) )
+ {
+ m_actionResult = hr;
+ SetEvent(m_actionTakenEvent);
+ return;
+ }
+ }
+#endif // !FEATURE_DBGIPC_TRANSPORT_DI
+
+
+ // We don't really care if we're on the Win32 thread or not here. We just want to be sure that
+ // the LS Exit case and the Detach case both occur on the same thread. This makes it much easier
+ // to assert that if we exit while detaching, EP is only called once.
+ // If we ever decide to make the RCET listen on the LS process handle for EP(exit), then we should also
+ // make the EP(detach) handled on the RCET (via DoFavor() ).
+ _ASSERTE(IsWin32EventThread());
+
+ // So either the Exit case or Detach case must happen first.
+ // 1) If Detach first, then LS process is removed from wait set and so EP(Exit) will never happen
+ // because we check wait set after returning from EP(Detach).
+ // 2) If Exit is first, m_pProcess gets set=NULL. EP(detach) will still get called, so explicitly check that.
+ if (fDetach && ((m_pProcess == NULL) || m_pProcess->m_terminated))
+ {
+ // m_terminated is only set after the LS exits.
+ // So the only way (fDetach && m_terminated) is true is if the LS exited while detaching. In that case
+ // we already called EP(exit) and we don't want to call it again for EP(detach). So return here.
+ LOG((LF_CORDB, LL_INFO1000,"W32ET::EP: In EP(detach), but EP(exit) already called. Early failure\n"));
+
+ m_actionResult = CORDBG_E_PROCESS_TERMINATED;
+ SetEvent(m_actionTakenEvent);
+
+ return;
+ }
+
+ // We null m_pProcess at the end here, so
+ // Only way we could get here w/ null process is if we're called twice. We can only be called
+ // by detach or exit. Can't detach twice, can't exit twice, so must have been one of each.
+ // If exit is first, we got removed from the wait set, so 2nd call must be detach and we'd catch
+ // that above. If detach is first, we'd get removed from the wait set and so exit would never happen.
+ _ASSERTE(m_pProcess != NULL);
+ _ASSERTE(!m_pProcess->ThreadHoldsProcessLock());
+
+
+
+ // Mark the process teminated. After this, the RCET will never call FlushQueuedEvents. It will
+ // ignore all events it receives (including a SyncComplete) and the RCET also does not listen
+ // to terminated processes (so ProcessStateChange() won't cause a FQE either).
+ m_pProcess->Terminating(fDetach);
+
+ // Take care of the race where the process exits right after the user calls Continue() from the last
+ // managed event but before the handler has actually returned.
+ //
+ // Also, To get through this lock means that either:
+ // 1. FlushQueuedEvents is not currently executing and no one will call FQE.
+ // 2. FQE is exiting but is in the middle of a callback (so AreDispatchingEvent = true)
+ //
+ m_pProcess->Lock();
+
+ m_pProcess->m_exiting = true;
+
+ if (fDetach)
+ {
+ m_pProcess->SetSynchronized(false);
+ }
+
+ // If we are exiting, we *must* dispatch the ExitProcess callback, but we will delete all the events
+ // in the queue and not bother dispatching anything else. If (and only if) we are currently dispatching
+ // an event, then we will wait while that event is finished before invoking ExitProcess.
+ // (Note that a dispatched event has already been removed from the queue)
+
+ // Remove the process from the global list of processes.
+ m_cordb->RemoveProcess(m_pProcess);
+
+ if (fDetach)
+ {
+ // Signal the hr to the caller.
+ LOG((LF_CORDB, LL_INFO1000,"W32ET::EP: Detach: send result back!\n"));
+
+ m_actionResult = S_OK;
+ SetEvent(m_actionTakenEvent);
+ }
+
+ m_pProcess->Unlock();
+
+ // Delete all queued events
+ m_pProcess->DeleteQueuedEvents();
+
+
+ // If we're detaching, then the Detach already neutered everybody, so nothing here.
+ // If we're exiting, then we still need to neuter things, but we can't do that on this thread,
+ // so we queue it. We also need to dispatch an exit process callback. We'll queue that onto the RCET
+ // and dispatch it inband w/the other callbacks.
+ if (!fDetach)
+ {
+#ifdef FEATURE_PAL
+ // Cleanup the transport pipe and semaphore files that might be left by the target (LS) process.
+ m_pNativePipeline->CleanupTargetProcess();
+#endif
+ ExitProcessWorkItem * pItem = new (nothrow) ExitProcessWorkItem(m_pProcess);
+ if (pItem != NULL)
+ {
+ m_cordb->m_rcEventThread->QueueAsyncWorkItem(pItem);
+ }
+ }
+
+ // This will remove the process from our wait lists (so that we don't send multiple ExitProcess events).
+ m_pProcess.Clear();
+}
+
+
+//
+// Start actually creates and starts the thread.
+//
+HRESULT CordbWin32EventThread::Start()
+{
+ HRESULT hr = S_OK;
+ if (m_threadControlEvent == NULL)
+ return E_INVALIDARG;
+
+ // Create the thread suspended to make sure that m_threadId is set
+ // before CordbWin32EventThread::ThreadProc runs
+ // Stack size = 0x80000 = 512KB
+ m_thread = CreateThread(NULL, 0x80000, &CordbWin32EventThread::ThreadProc,
+ (LPVOID) this, CREATE_SUSPENDED | STACK_SIZE_PARAM_IS_A_RESERVATION, &m_threadId);
+
+ if (m_thread == NULL)
+ return HRESULT_FROM_GetLastError();
+
+ DWORD succ = ResumeThread(m_thread);
+ if (succ == (DWORD)-1)
+ return HRESULT_FROM_GetLastError();
+ return hr;
+}
+
+
+//
+// Stop causes the thread to stop receiving events and exit. It
+// waits for it to exit before returning.
+//
+HRESULT CordbWin32EventThread::Stop()
+{
+ HRESULT hr = S_OK;
+
+ // m_pProcess may be NULL from CordbWin32EventThread::ExitProcess
+
+ // Can't block on W32ET while holding the process-lock since the W32ET may need that to exit.
+ // But since m_pProcess may be null, we can't enforce that.
+
+ if (m_thread != NULL)
+ {
+ LockSendToWin32EventThreadMutex();
+ m_action = W32ETA_NONE;
+ m_run = FALSE;
+
+ SetEvent(m_threadControlEvent);
+ UnlockSendToWin32EventThreadMutex();
+
+ DWORD ret = WaitForSingleObject(m_thread, INFINITE);
+
+ if (ret != WAIT_OBJECT_0)
+ hr = HRESULT_FROM_GetLastError();
+ }
+
+ m_pProcess.Clear();
+ m_cordb.Clear();
+
+ return hr;
+}
+
+
+
+
+
+
+
+
+// Allocate a buffer of cbBuffer bytes in the target.
+//
+// Arguments:
+// cbBuffer - count of bytes for the buffer.
+//
+// Returns:
+// a TargetBuffer describing the new memory region in the target.
+// Throws on error.
+TargetBuffer CordbProcess::GetRemoteBuffer(ULONG cbBuffer)
+{
+ INTERNAL_SYNC_API_ENTRY(this); //
+
+ // Create and initialize the event as synchronous
+ DebuggerIPCEvent event;
+ InitIPCEvent(&event,
+ DB_IPCE_GET_BUFFER,
+ true,
+ VMPTR_AppDomain::NullPtr());
+
+ // Indicate the buffer size wanted
+ event.GetBuffer.bufSize = cbBuffer;
+
+ // Make the request, which is synchronous
+ HRESULT hr = SendIPCEvent(&event, sizeof(event));
+ IfFailThrow(hr);
+ _ASSERTE(event.type == DB_IPCE_GET_BUFFER_RESULT);
+
+ IfFailThrow(event.GetBufferResult.hr);
+
+ // The request succeeded. Return the newly allocated range.
+ return TargetBuffer(event.GetBufferResult.pBuffer, cbBuffer);
+}
+
+/*
+ * This will release a previously allocated left side buffer.
+ */
+HRESULT CordbProcess::ReleaseRemoteBuffer(void **ppBuffer)
+{
+ INTERNAL_SYNC_API_ENTRY(this); //
+
+ _ASSERTE(m_pShim != NULL);
+
+ // Create and initialize the event as synchronous
+ DebuggerIPCEvent event;
+ InitIPCEvent(&event,
+ DB_IPCE_RELEASE_BUFFER,
+ true,
+ VMPTR_AppDomain::NullPtr());
+
+ // Indicate the buffer to release
+ event.ReleaseBuffer.pBuffer = (*ppBuffer);
+
+ // Make the request, which is synchronous
+ HRESULT hr = SendIPCEvent(&event, sizeof(event));
+ TESTANDRETURNHR(hr);
+
+ (*ppBuffer) = NULL;
+
+ // Indicate success
+ return event.ReleaseBufferResult.hr;
+}
+
+HRESULT CordbProcess::SetDesiredNGENCompilerFlags(DWORD dwFlags)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+#if defined(FEATURE_PREJIT)
+ if ((dwFlags != CORDEBUG_JIT_DEFAULT) && (dwFlags != CORDEBUG_JIT_DISABLE_OPTIMIZATION))
+ {
+ return E_INVALIDARG;
+ }
+
+ CordbProcess *pProcess = GetProcess();
+ ATT_REQUIRE_STOPPED_MAY_FAIL(pProcess);
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // Left-side checks that this is a valid time to set the Ngen flags.
+ hr = pProcess->GetDAC()->SetNGENCompilerFlags(dwFlags);
+ if (!SUCCEEDED(hr) && GetShim() != NULL)
+ {
+ // Emulate V2 error semantics.
+ hr = GetShim()->FilterSetNgenHresult(hr);
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+
+#else // !FEATURE_PREJIT
+ return CORDBG_E_NGEN_NOT_SUPPORTED;
+
+#endif // FEATURE_PREJIT
+}
+
+HRESULT CordbProcess::GetDesiredNGENCompilerFlags(DWORD *pdwFlags )
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pdwFlags, DWORD*);
+ *pdwFlags = 0;
+
+ CordbProcess *pProcess = GetProcess();
+ ATT_REQUIRE_STOPPED_MAY_FAIL(pProcess);
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ hr = pProcess->GetDAC()->GetNGENCompilerFlags(pdwFlags);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// Get an ICorDebugReference Value for the GC handle.
+// handle - raw bits for the GC handle.
+// pOutHandle
+//-----------------------------------------------------------------------------
+HRESULT CordbProcess::GetReferenceValueFromGCHandle(
+ UINT_PTR gcHandle,
+ ICorDebugReferenceValue **pOutValue)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this);
+ VALIDATE_POINTER_TO_OBJECT(pOutValue, ICorDebugReferenceValue*);
+
+ *pOutValue = NULL;
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+ if (gcHandle == NULL)
+ {
+ ThrowHR(CORDBG_E_BAD_REFERENCE_VALUE);
+ }
+
+ IDacDbiInterface* pDAC = GetProcess()->GetDAC();
+ VMPTR_OBJECTHANDLE vmObjHandle = pDAC->GetVmObjectHandle(gcHandle);
+ if(!pDAC->IsVmObjectHandleValid(vmObjHandle))
+ {
+ ThrowHR(CORDBG_E_BAD_REFERENCE_VALUE);
+ }
+ ULONG appDomainId = pDAC->GetAppDomainIdFromVmObjectHandle(vmObjHandle);
+ VMPTR_AppDomain vmAppDomain = pDAC->GetAppDomainFromId(appDomainId);
+
+ RSLockHolder lockHolder(GetProcessLock());
+ CordbAppDomain * pAppDomain = LookupOrCreateAppDomain(vmAppDomain);
+ lockHolder.Release();
+
+ // Now that we finally have the AppDomain, we can go ahead and get a ReferenceValue
+ // from the ObjectHandle.
+ hr = CordbReferenceValue::BuildFromGCHandle(pAppDomain, vmObjHandle, pOutValue);
+ _ASSERTE(SUCCEEDED(hr) == (*pOutValue != NULL));
+ IfFailThrow(hr);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// Return count of outstanding GC handles held by CordbHandleValue objects
+LONG CordbProcess::OutstandingHandles()
+{
+ return m_cOutstandingHandles;
+}
+
+//-----------------------------------------------------------------------------
+// Increment the outstanding handle count for code:CordbProces::OutstandingHandles
+// This is the inverse of code:CordbProces::DecrementOutstandingHandles
+void CordbProcess::IncrementOutstandingHandles()
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+ m_cOutstandingHandles++;
+}
+
+//-----------------------------------------------------------------------------
+// Decrement the outstanding handle count for code:CordbProces::OutstandingHandles
+// This is the inverse of code:CordbProces::IncrementOutstandingHandles
+void CordbProcess::DecrementOutstandingHandles()
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+ m_cOutstandingHandles--;
+}
+
+
+/*
+ * IsReadyForDetach
+ *
+ * This method encapsulates all logic for deciding if it is ok for a debugger to
+ * detach from the process at this time.
+ *
+ * Parameters: None.
+ *
+ * Returns: S_OK if it is ok to detach, else a specific HRESULT describing why it
+ * is not ok to detach.
+ *
+ */
+HRESULT CordbProcess::IsReadyForDetach()
+{
+ INTERNAL_API_ENTRY(this);
+
+ // Always safe to detach in V3 case.
+ if (m_pShim == NULL)
+ {
+ return S_OK;
+ }
+
+ // If not initialized yet, then there are no detach liabilities.
+ if (!m_initialized)
+ {
+ return S_OK;
+ }
+
+ RSLockHolder lockHolder(&this->m_processMutex);
+
+ //
+ // If there are any outstanding func-evals then fail the detach.
+ //
+ if (OutstandingEvalCount() != 0)
+ {
+ return CORDBG_E_DETACH_FAILED_OUTSTANDING_EVALS;
+ }
+
+ // V2 didn't check outstanding handles (code:CordbProcess::OutstandingHandles)
+ // because it could automatically clean those up on detach.
+
+ //
+ // If there are any outstanding steppers then fail the detach.
+ //
+ if (m_steppers.IsInitialized() && (m_steppers.GetCount() > 0))
+ {
+ return CORDBG_E_DETACH_FAILED_OUTSTANDING_STEPPERS;
+ }
+
+ //
+ // If there are any outstanding breakpoints then fail the detach.
+ //
+ HASHFIND foundAppDomain;
+ CordbAppDomain *pAppDomain = m_appDomains.FindFirst(&foundAppDomain);
+
+ while (pAppDomain != NULL)
+ {
+ if (pAppDomain->m_breakpoints.IsInitialized() && (pAppDomain->m_breakpoints.GetCount() > 0))
+ {
+ return CORDBG_E_DETACH_FAILED_OUTSTANDING_BREAKPOINTS;
+ }
+
+ // Check for any outstanding EnC modules.
+ HASHFIND foundModule;
+ CordbModule * pModule = pAppDomain->m_modules.FindFirst(&foundModule);
+ while (pModule != NULL)
+ {
+ if (pModule->m_EnCCount > 0)
+ {
+ return CORDBG_E_DETACH_FAILED_ON_ENC;
+ }
+ pModule = pAppDomain->m_modules.FindNext(&foundModule);
+ }
+
+
+ pAppDomain = m_appDomains.FindNext(&foundAppDomain);
+ }
+
+ // If we're using the shim, give a chance to early-out if the OS doesn't support detach
+ // so that the user can continue to debug in that case.
+ // Ideally we'd just rely on the failure from DebugActiveProcessStop, but by then it's too late
+ // to recover. This function is our only chance to distinguish between graceful detach failures
+ // and hard detach failures (after which the process object is neutered).
+ if (m_pShim != NULL)
+ {
+#if !defined(FEATURE_CORESYSTEM) // CORESYSTEM TODO
+ HModuleHolder hKernel32;
+ hKernel32 = WszLoadLibrary(W("kernel32"));
+ if (hKernel32 == NULL)
+ return HRESULT_FROM_GetLastError();
+ typedef BOOL (*DebugActiveProcessStopSig) (DWORD);
+ DebugActiveProcessStopSig pDebugActiveProcessStop =
+ reinterpret_cast<DebugActiveProcessStopSig>(GetProcAddress(hKernel32, "DebugActiveProcessStop"));
+ if (pDebugActiveProcessStop == NULL)
+ return COR_E_PLATFORMNOTSUPPORTED;
+#endif
+ }
+
+ return S_OK;
+}
+
+
+/*
+ * Look for any thread which was last seen in the specified AppDomain.
+ * The CordbAppDomain object is about to be neutered due to an AD Unload
+ * So the thread must no longer be considered to be in that domain.
+ * Note that this is a workaround due to the existance of the (possibly incorrect)
+ * cached AppDomain value. Ideally we would remove the cached value entirely
+ * and there would be no need for this.
+ *
+ * @dbgtodo: , appdomain: We should remove CordbThread::m_pAppDomain in the V3 architecture.
+ * If we need the thread's current domain, we should get it accurately with DAC.
+ */
+void CordbProcess::UpdateThreadsForAdUnload(CordbAppDomain * pAppDomain)
+{
+ INTERNAL_API_ENTRY(this);
+
+ // If we're doing an AD unload then we should have already seen the ATTACH
+ // notification for the default domain.
+ //_ASSERTE( m_pDefaultAppDomain != NULL );
+ // @dbgtodo appdomain: fix Default domain invariants with DAC-izing Appdomain work.
+
+ RSLockHolder lockHolder(GetProcessLock());
+
+ CordbThread* t;
+ HASHFIND find;
+
+ // We don't need to prepopulate here (to collect LS state) because we're just updating RS state.
+ for (t = m_userThreads.FindFirst(&find);
+ t != NULL;
+ t = m_userThreads.FindNext(&find))
+ {
+ if( t->GetAppDomain() == pAppDomain )
+ {
+ // This thread cannot actually be in this AppDomain anymore (since it's being
+ // unloaded). Reset it to point to the default AppDomain
+ t->m_pAppDomain = m_pDefaultAppDomain;
+ }
+ }
+}
+
+// CordbProcess::LookupClass
+// Looks up a previously constructed CordbClass instance without creating. May return NULL if the
+// CordbClass instance doesn't exist.
+// Argument: (in) vmDomainFile - pointer to the domainfile for the module
+// (in) mdTypeDef - metadata token for the class
+// Return value: pointer to a previously created CordbClass instance or NULL in none exists
+CordbClass * CordbProcess::LookupClass(ICorDebugAppDomain * pAppDomain, VMPTR_DomainFile vmDomainFile, mdTypeDef classToken)
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ if (pAppDomain != NULL)
+ {
+ CordbModule * pModule = ((CordbAppDomain *)pAppDomain)->m_modules.GetBase(VmPtrToCookie(vmDomainFile));
+ if (pModule != NULL)
+ {
+ return pModule->LookupClass(classToken);
+ }
+ }
+ return NULL;
+} // CordbProcess::LookupClass
+
+//---------------------------------------------------------------------------------------
+// Look for a specific module in the process.
+//
+// Arguments:
+// vmDomainFile - non-null module to lookup
+//
+// Returns:
+// a CordbModule object for the given cookie. Object may be from the cache, or created
+// lazily.
+// Never returns null. Throws on error.
+//
+// Notes:
+// A VMPTR_DomainFile has appdomain affinity, but is ultimately scoped to a process.
+// So if we get a raw VMPTR_DomainFile (eg, from the stackwalker or from some other
+// lookup function), then we need to do a process wide lookup since we don't know which
+// appdomain it's in. If you know the appdomain, you can use code:CordbAppDomain::LookupOrCreateModule.
+//
+CordbModule * CordbProcess::LookupOrCreateModule(VMPTR_DomainFile vmDomainFile)
+{
+ INTERNAL_API_ENTRY(this);
+
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+ _ASSERTE(!vmDomainFile.IsNull());
+
+ DomainFileInfo data;
+ GetDAC()->GetDomainFileData(vmDomainFile, &data); // throws
+
+ CordbAppDomain * pAppDomain = LookupOrCreateAppDomain(data.vmAppDomain);
+ return pAppDomain->LookupOrCreateModule(vmDomainFile);
+}
+
+//---------------------------------------------------------------------------------------
+// Determine if the process has any in-band queued events which have not been dispatched
+//
+// Returns:
+// TRUE iff there are undispatched IB events
+//
+#ifdef FEATURE_INTEROP_DEBUGGING
+BOOL CordbProcess::HasUndispatchedNativeEvents()
+{
+ INTERNAL_API_ENTRY(this);
+
+ CordbUnmanagedEvent* pEvent = m_unmanagedEventQueue;
+ while(pEvent != NULL && pEvent->IsDispatched())
+ {
+ pEvent = pEvent->m_next;
+ }
+
+ return pEvent != NULL;
+}
+#endif
+
+//---------------------------------------------------------------------------------------
+// Determine if the process has any in-band queued events which have not been user continued
+//
+// Returns:
+// TRUE iff there are user uncontinued IB events
+//
+#ifdef FEATURE_INTEROP_DEBUGGING
+BOOL CordbProcess::HasUserUncontinuedNativeEvents()
+{
+ INTERNAL_API_ENTRY(this);
+
+ CordbUnmanagedEvent* pEvent = m_unmanagedEventQueue;
+ while(pEvent != NULL && pEvent->IsEventUserContinued())
+ {
+ pEvent = pEvent->m_next;
+ }
+
+ return pEvent != NULL;
+}
+#endif
+
+//---------------------------------------------------------------------------------------
+// Hijack the thread which had this event. If the thread is already hijacked this method
+// has no effect.
+//
+// Arguments:
+// pUnmanagedEvent - the debug event which requires us to hijack
+//
+// Returns:
+// S_OK on success, failing HRESULT if the hijack could not be set up
+//
+#ifdef FEATURE_INTEROP_DEBUGGING
+HRESULT CordbProcess::HijackIBEvent(CordbUnmanagedEvent * pUnmanagedEvent)
+{
+ // Can't hijack after the event has already been continued hijacked
+ _ASSERTE(!pUnmanagedEvent->IsEventContinuedHijacked());
+ // Can only hijack IB events
+ _ASSERTE(pUnmanagedEvent->IsIBEvent());
+
+ // If we already hijacked the event then there is nothing left to do
+ if(pUnmanagedEvent->m_owner->IsFirstChanceHijacked() ||
+ pUnmanagedEvent->m_owner->IsGenericHijacked())
+ {
+ return S_OK;
+ }
+
+ ResetEvent(this->m_leftSideUnmanagedWaitEvent);
+ if (pUnmanagedEvent->m_currentDebugEvent.u.Exception.dwFirstChance)
+ {
+ HRESULT hr = pUnmanagedEvent->m_owner->SetupFirstChanceHijackForSync();
+ SIMPLIFYING_ASSUMPTION(SUCCEEDED(hr));
+ return hr;
+ }
+ else // Second chance exceptions must be generic hijacked.
+ {
+ HRESULT hr = pUnmanagedEvent->m_owner->SetupGenericHijack(pUnmanagedEvent->m_currentDebugEvent.dwDebugEventCode, &pUnmanagedEvent->m_currentDebugEvent.u.Exception.ExceptionRecord);
+ SIMPLIFYING_ASSUMPTION(SUCCEEDED(hr));
+ return hr;
+ }
+}
+#endif
+
+// Sets a bitfield reflecting the managed debugging state at the time of
+// the jit attach.
+HRESULT CordbProcess::GetAttachStateFlags(CLR_DEBUGGING_PROCESS_FLAGS *pFlags)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_REENTRANT_API_BEGIN(this)
+ {
+ if(pFlags == NULL)
+ hr = E_POINTER;
+ else
+ *pFlags = GetDAC()->GetAttachStateFlags();
+ }
+ PUBLIC_API_END(hr);
+
+ return hr;
+}
+
+// Determine if this version of ICorDebug is compatibile with the ICorDebug in the specified major CLR version
+bool CordbProcess::IsCompatibleWith(DWORD clrMajorVersion)
+{
+ // The debugger versioning policy is that debuggers generally need to opt-in to supporting major new
+ // versions of the CLR. Often new versions of the CLR violate some invariant that previous debuggers assume
+ // (eg. hot/cold splitting in Whidbey, multiple CLRs in a process in CLR v4), and neither VS or the CLR
+ // teams generally want the support burden of forward compatibility.
+
+ //
+ // If this assert is firing for you, its probably because the major version
+ // number of the clr.dll has changed. This assert is here to remind you to do a bit of other
+ // work you may not have realized you needed to do so that our versioning works smoothly
+ // for debugging. You probably want to contact the CLR DST team if you are a
+ // non-debugger person hitting this. DON'T JUST DELETE THIS ASSERT!!!
+ //
+ // 1) You should ensure new versions of all ICorDebug users in DevDiv (VS Debugger, MDbg, etc.)
+ // are using a creation path that explicitly specifies that they support this new major
+ // version of the CLR.
+ // 2) You should file an issue to track blocking earlier debuggers from targetting this
+ // version of the CLR (i.e. update requiredVersion to the new CLR major
+ // version). To enable a smooth internal transition, this often isn't done until absolutely
+ // necessary (sometimes as late as Beta2).
+ // 3) You can consider updating the CLR_ID guid used by the shim to recognize a CLR, but only
+ // if it's important to completely hide newer CLRs from the shim. The expectation now
+ // is that we won't need to do this (i.e. we'd like VS to give a nice error message about
+ // needed a newer version of the debugger, rather than just acting as if a process has no CLR).
+ // 4) Update this assert so that it no longer fires for your new CLR version or any of
+ // the previous versions, but don't delete the assert...
+ // the next CLR version after yours will probably need the same reminder
+
+ _ASSERTE_MSG(clrMajorVersion <= 4,
+ "Found major CLR version greater than 4 in mscordbi.dll from CLRv4 - contact CLRDST");
+
+ // This knob lets us enable forward compatibility for internal scenarios, and also simulate new
+ // versions of the runtime for testing the failure user-experience in a version of the debugger
+ // before it is shipped.
+ // We don't want to risk customers getting this, so for RTM builds this must be CHK-only.
+ // To aid in internal transition, we may temporarily enable this in RET builds, but when
+ // doing so must file a bug to track making it CHK only again before RTM.
+ // For example, Dev10 Beta2 shipped with this knob, but it was made CHK-only at the start of RC.
+ // In theory we might have a point release someday where we break debugger compat, but
+ // it seems unlikely and since this knob is unsupported anyway we can always extend it
+ // then (support reading a string value, etc.). So for now we just map the number
+ // to the major CLR version number.
+ DWORD requiredVersion = 0;
+#ifdef _DEBUG
+ requiredVersion = CLRConfig::GetConfigValue(CLRConfig::UNSUPPORTED_Debugging_RequiredVersion);
+#endif
+
+ // If unset (the only supported configuration), then we require a debugger designed for CLRv4
+ // for desktop, where we do not allow forward compat.
+ // For SL, we allow forward compat. Right now, that means SLv2+ debugger requests can be
+ // honored for SLv4.
+ if (requiredVersion <= 0)
+ {
+#if defined(FEATURE_CORECLR)
+ requiredVersion = 2;
+#else
+ requiredVersion = 4;
+#endif
+ }
+
+ // Compare the version we were created for against the minimum required
+ return (clrMajorVersion >= requiredVersion);
+}
+
+bool CordbProcess::IsThreadSuspendedOrHijacked(ICorDebugThread * pICorDebugThread)
+{
+ // An RS lock can be held while this is called. Specifically,
+ // CordbThread::EnumerateChains may be on the stack, and it uses
+ // ATT_REQUIRE_STOPPED_MAY_FAIL, which holds the CordbProcess::m_StopGoLock lock for
+ // its entire duration. As a result, this needs to be considered a reentrant API. See
+ // comments above code:PrivateShimCallbackHolder for more info.
+ PUBLIC_REENTRANT_API_ENTRY_FOR_SHIM(this);
+
+ CordbThread * pCordbThread = static_cast<CordbThread *> (pICorDebugThread);
+ return GetDAC()->IsThreadSuspendedOrHijacked(pCordbThread->m_vmThreadToken);
+}
diff --git a/src/debug/di/publish.cpp b/src/debug/di/publish.cpp
new file mode 100644
index 0000000000..888988a10f
--- /dev/null
+++ b/src/debug/di/publish.cpp
@@ -0,0 +1,1282 @@
+// 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.
+//*****************************************************************************
+// File: publish.cpp
+//
+
+//
+//*****************************************************************************
+
+
+#include "stdafx.h"
+#ifdef FEATURE_DBG_PUBLISH
+
+#include "check.h"
+
+#include <tlhelp32.h>
+#include "wtsapi32.h"
+
+#ifndef SM_REMOTESESSION
+#define SM_REMOTESESSION 0x1000
+#endif
+
+#include "corpriv.h"
+#include "../../dlls/mscorrc/resource.h"
+#include <limits.h>
+
+// Publish shares header files with the rest of ICorDebug.
+// ICorDebug should not call ReadProcessMemory & other APIs directly, it should instead go through
+// the Data-target. ICD headers #define these APIs to help enforce this.
+// Since Publish is separate and doesn't use data-targets, it can access the APIs directly.
+// see code:RSDebuggingInfo#UseDataTarget
+#undef ReadProcessMemory
+
+//****************************************************************************
+//************ App Domain Publishing Service API Implementation **************
+//****************************************************************************
+
+// This function enumerates all the process in the system and returns
+// their PIDs
+BOOL GetAllProcessesInSystem(DWORD *ProcessId,
+ DWORD dwArraySize,
+ DWORD *pdwNumEntries)
+{
+ HandleHolder hSnapshotHolder;
+
+#if !defined(FEATURE_CORESYSTEM)
+ // Load the dll "kernel32.dll".
+ HModuleHolder hDll = WszLoadLibrary(W("kernel32"));
+ _ASSERTE(hDll != NULL);
+
+ if (hDll == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000,
+ "Unable to load the dll for enumerating processes. "
+ "LoadLibrary (kernel32.dll) failed.\n"));
+ return FALSE;
+ }
+#else
+ // Load the dll "api-ms-win-obsolete-kernel32-l1-1-0.dll".
+ HModuleHolder hDll = WszLoadLibrary(W("api-ms-win-obsolete-kernel32-l1-1-0.dll"));
+ _ASSERTE(hDll != NULL);
+
+ if (hDll == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000,
+ "Unable to load the dll for enumerating processes. "
+ "LoadLibrary (api-ms-win-obsolete-kernel32-l1-1-0.dll) failed.\n"));
+ return FALSE;
+ }
+#endif
+
+
+ // Create the Process' Snapshot
+ // Get the pointer to the requested function
+ FARPROC pProcAddr = GetProcAddress(hDll, "CreateToolhelp32Snapshot");
+
+ // If the proc address was not found, return error
+ if (pProcAddr == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000,
+ "Unable to enumerate processes in the system. "
+ "GetProcAddr (CreateToolhelp32Snapshot) failed.\n"));
+ return FALSE;
+ }
+
+
+
+ // Handle from CreateToolHelp32Snapshot must be freed via CloseHandle().
+ typedef HANDLE CREATETOOLHELP32SNAPSHOT(DWORD, DWORD);
+
+ HANDLE hSnapshot =
+ ((CREATETOOLHELP32SNAPSHOT *)pProcAddr)(TH32CS_SNAPPROCESS, NULL);
+
+ if (hSnapshot == INVALID_HANDLE_VALUE)
+ {
+ LOG((LF_CORDB, LL_INFO1000,
+ "Unable to create snapshot of processes in the system. "
+ "CreateToolhelp32Snapshot() failed.\n"));
+ return FALSE;
+ }
+ // HandleHolder doesn't deal with INVALID_HANDLE_VALUE, so we only assign if we have a legal value.
+ hSnapshotHolder.Assign(hSnapshot);
+
+ // Get the first process in the process list
+ // Get the pointer to the requested function
+ pProcAddr = GetProcAddress(hDll, "Process32First");
+
+ // If the proc address was not found, return error
+ if (pProcAddr == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000,
+ "Unable to enumerate processes in the system. "
+ "GetProcAddr (Process32First) failed.\n"));
+ return FALSE;
+ }
+
+ PROCESSENTRY32 PE32;
+
+ // need to initialize the dwSize field before calling Process32First
+ PE32.dwSize = sizeof (PROCESSENTRY32);
+
+ typedef BOOL PROCESS32FIRST(HANDLE, LPPROCESSENTRY32);
+
+ BOOL succ =
+ ((PROCESS32FIRST *)pProcAddr)(hSnapshot, &PE32);
+
+ if (succ != TRUE)
+ {
+ LOG((LF_CORDB, LL_INFO1000,
+ "Unable to create snapshot of processes in the system. "
+ "Process32First() returned FALSE.\n"));
+ return FALSE;
+ }
+
+
+ // Loop over and get all the remaining processes
+ // Get the pointer to the requested function
+ pProcAddr = GetProcAddress(hDll, "Process32Next");
+
+ // If the proc address was not found, return error
+ if (pProcAddr == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000,
+ "Unable to enumerate processes in the system. "
+ "GetProcAddr (Process32Next) failed.\n"));
+ return FALSE;
+ }
+
+ typedef BOOL PROCESS32NEXT(HANDLE, LPPROCESSENTRY32);
+
+ int iIndex = 0;
+
+ do
+ {
+ ProcessId [iIndex++] = PE32.th32ProcessID;
+
+ succ = ((PROCESS32NEXT *)pProcAddr)(hSnapshot, &PE32);
+
+ } while ((succ == TRUE) && (iIndex < (int)dwArraySize));
+
+ // I would like to know if we're running more than 512 processes on Win95!!
+ _ASSERTE (iIndex < (int)dwArraySize);
+
+ *pdwNumEntries = iIndex;
+
+ // If we made it this far, we succeeded
+ return TRUE;
+}
+
+
+// We never want to wait infinite on an object that we can't verify.
+// Wait with a timeout.
+const DWORD SAFETY_TIMEOUT = 2000;
+
+// ******************************************
+// CorpubPublish
+// ******************************************
+
+CorpubPublish::CorpubPublish()
+ : CordbCommonBase(0),
+ m_fpGetModuleFileNameEx(NULL)
+{
+ // Try to get psapi!GetModuleFileNameExW once, and then every process object can use it.
+ // If we can't get it, then we'll fallback to getting information from the IPC block.
+#if !defined(FEATURE_CORESYSTEM)
+ m_hPSAPIdll = WszLoadLibrary(W("psapi.dll"));
+#else
+ m_hPSAPIdll = WszLoadLibrary(W("api-ms-win-obsolete-psapi-l1-1-0.dll"));
+#endif
+
+ if (m_hPSAPIdll != NULL)
+ {
+ m_fpGetModuleFileNameEx = (FPGetModuleFileNameEx*) GetProcAddress(m_hPSAPIdll, "GetModuleFileNameExW");
+ }
+
+ CordbCommonBase::InitializeCommon();
+}
+
+CorpubPublish::~CorpubPublish()
+{
+ // m_hPSAPIdll is a module holder, so the dtor will free it automatically for us.
+}
+
+
+COM_METHOD CorpubPublish::QueryInterface(REFIID id, void **ppInterface)
+{
+ if (id == IID_ICorPublish)
+ *ppInterface = (ICorPublish*)this;
+ else if (id == IID_IUnknown)
+ *ppInterface = (IUnknown*)(ICorPublish*)this;
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+
+COM_METHOD CorpubPublish::EnumProcesses(COR_PUB_ENUMPROCESS Type,
+ ICorPublishProcessEnum **ppIEnum)
+{
+ HRESULT hr = E_FAIL;
+ CorpubProcess* pProcessList = NULL ;
+ CorpubProcessEnum* pProcEnum = NULL;
+ *ppIEnum = NULL;
+
+ if( Type != COR_PUB_MANAGEDONLY )
+ {
+ hr = E_INVALIDARG;
+ goto exit;
+ }
+
+ // call function to get PIDs for all processes in the system
+#define MAX_PROCESSES 512
+
+ DWORD ProcessId[MAX_PROCESSES];
+ DWORD dwNumProcesses = 0;
+ if( !GetAllProcessesInSystem(ProcessId, MAX_PROCESSES, &dwNumProcesses) )
+ {
+ hr = E_FAIL;
+ goto exit;
+ }
+
+ // iterate over all the processes to fetch all the managed processes
+ for (int i = 0; i < (int)dwNumProcesses; i++)
+ {
+ CorpubProcess *pProcess = NULL;
+ hr = GetProcessInternal( ProcessId[i], &pProcess );
+ if( FAILED(hr) )
+ {
+ _ASSERTE( pProcess == NULL );
+ goto exit; // a serious error has occurred, abort
+ }
+
+ if( hr == S_OK )
+ {
+ // Success, Add the process to the list.
+ _ASSERTE( pProcess != NULL );
+ pProcess->SetNext( pProcessList );
+ pProcessList = pProcess;
+ }
+ else
+ {
+ // Ignore this process (isn't managed, or shut down, etc.)
+ _ASSERTE( pProcess == NULL );
+ }
+ }
+
+ // create and return the ICorPublishProcessEnum
+ pProcEnum = new (nothrow) CorpubProcessEnum(pProcessList);
+ if (pProcEnum == NULL)
+ {
+ hr = E_OUTOFMEMORY;
+ goto exit;
+ }
+ pProcEnum->AddRef();
+
+ hr = pProcEnum->QueryInterface(IID_ICorPublishProcessEnum, (void**)ppIEnum);
+ if( FAILED(hr) )
+ {
+ goto exit;
+ }
+
+ hr = S_OK;
+
+exit:
+ // release our handle on the process objects
+ while (pProcessList != NULL)
+ {
+ CorpubProcess *pTmp = pProcessList;
+ pProcessList = pProcessList->GetNextProcess();
+ pTmp->Release();
+ }
+ if( pProcEnum != NULL )
+ {
+ pProcEnum->Release();
+ pProcEnum = NULL;
+ }
+
+ return hr;
+}
+
+
+HRESULT CorpubPublish::GetProcess(unsigned pid,
+ ICorPublishProcess **ppProcess)
+{
+ *ppProcess = NULL;
+
+ // Query for this specific process (even if we've already handed out a
+ // now-stale process object for this pid)
+ CorpubProcess * pProcess = NULL;
+ HRESULT hr = GetProcessInternal( pid, &pProcess );
+ if( hr != S_OK )
+ {
+ // Couldn't get this process (doesn't exist, or isn't managed)
+ _ASSERTE( pProcess == NULL );
+ if( FAILED(hr) )
+ {
+ return hr; // there was a serious error trying to get this process info
+ }
+ return E_INVALIDARG; // this process doesn't exist, isn't managed or is shutting down
+ }
+
+ // QI to ICorPublishProcess and return it
+ _ASSERTE( pProcess != NULL );
+ hr = pProcess->QueryInterface(IID_ICorPublishProcess, (void**)ppProcess);
+ pProcess->Release();
+ return hr;
+}
+
+
+// Attempts to create a CorpubProcess object for a specific managed process
+// On success returns S_OK and sets ppProcess to a new AddRef'd CorpubProcess
+// object. Otherwise, returns S_FALSE if the process isn't managed or if it has
+// terminated (i.e. it should be ignored), or and error code on a serious failure.
+HRESULT CorpubPublish::GetProcessInternal(
+ unsigned pid,
+ CorpubProcess **ppProcess )
+{
+#if defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ return E_NOTIMPL;
+
+#else // !FEATURE_DBGIPC_TRANSPORT_DI
+ HRESULT hr = S_OK;
+ *ppProcess = NULL;
+
+ NewHolder<IPCReaderInterface> pIPCReader( new (nothrow) IPCReaderInterface() );
+ if (pIPCReader == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO100, "CP::EP: Failed to allocate memory for IPCReaderInterface.\n"));
+ return E_OUTOFMEMORY;
+ }
+
+ // See if it is a managed process by trying to open the shared
+ // memory block.
+ hr = pIPCReader->OpenLegacyPrivateBlockTempV4OnPid(pid);
+ if (FAILED(hr))
+ {
+ return S_FALSE; // Not a managed process
+ }
+
+ // Get the AppDomainIPCBlock
+ AppDomainEnumerationIPCBlock *pAppDomainCB = pIPCReader->GetAppDomainBlock();
+ if (pAppDomainCB == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::EP: Failed to obtain AppDomainIPCBlock.\n"));
+ return S_FALSE;
+ }
+
+ // Get the process handle.
+ HANDLE hProcess = OpenProcess((PROCESS_VM_READ |
+ PROCESS_QUERY_INFORMATION |
+ PROCESS_DUP_HANDLE |
+ SYNCHRONIZE),
+ FALSE, pid);
+ if (hProcess == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::EP: OpenProcess() returned NULL handle.\n"));
+ return S_FALSE;
+ }
+
+ // If the mutex isn't filled in, the CLR is either starting up or shutting down
+ if (pAppDomainCB->m_hMutex == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::EP: IPC block isn't properly filled in.\n"));
+ return S_FALSE;
+ }
+
+ // Dup the valid mutex handle into this process.
+ HANDLE hMutex;
+ if( !pAppDomainCB->m_hMutex.DuplicateToLocalProcess(hProcess, &hMutex) )
+ {
+ return S_FALSE;
+ }
+
+ // Acquire the mutex, only waiting two seconds.
+ // We can't actually gaurantee that the target put a mutex object in here.
+ DWORD dwRetVal = WaitForSingleObject(hMutex, SAFETY_TIMEOUT);
+
+ if (dwRetVal == WAIT_OBJECT_0)
+ {
+ // Make sure the mutex handle is still valid. If
+ // its not, then we lost a shutdown race.
+ if (pAppDomainCB->m_hMutex == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::EP: lost shutdown race, skipping...\n"));
+
+ ReleaseMutex(hMutex);
+ CloseHandle(hMutex);
+ return S_FALSE;
+ }
+ }
+ else
+ {
+ // Again, landing here is most probably a shutdown race. Its okay, though...
+ LOG((LF_CORDB, LL_INFO1000, "CP::EP: failed to get IPC mutex.\n"));
+
+ if (dwRetVal == WAIT_ABANDONED)
+ {
+ ReleaseMutex(hMutex);
+ }
+ CloseHandle(hMutex);
+ return S_FALSE;
+ }
+ // Beware: if the target pid is not properly honoring the mutex, the data in the
+ // IPC block may still shift underneath us.
+
+ // If we get here, then hMutex is held by this process.
+
+ // Now create the CorpubProcess object for the ProcessID
+ CorpubProcess *pProc = new (nothrow) CorpubProcess(pid,
+ true,
+ hProcess,
+ hMutex,
+ pAppDomainCB,
+ pIPCReader,
+ m_fpGetModuleFileNameEx);
+
+ // Release our lock on the IPC block.
+ ReleaseMutex(hMutex);
+
+ if (pProc == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+ pIPCReader.SuppressRelease();
+
+ // Success, return the Process object
+ pProc->AddRef();
+ *ppProcess = pProc;
+ return S_OK;
+
+#endif // FEATURE_DBGIPC_TRANSPORT_DI
+}
+
+
+
+// ******************************************
+// CorpubProcess
+// ******************************************
+
+// Constructor
+CorpubProcess::CorpubProcess(DWORD dwProcessId,
+ bool fManaged,
+ HANDLE hProcess,
+ HANDLE hMutex,
+ AppDomainEnumerationIPCBlock *pAD,
+#if !defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ IPCReaderInterface *pIPCReader,
+#endif // !FEATURE_DBGIPC_TRANSPORT_DI
+ FPGetModuleFileNameEx * fpGetModuleFileNameEx)
+ : CordbCommonBase(0, enumCorpubProcess),
+ m_dwProcessId(dwProcessId),
+ m_fIsManaged(fManaged),
+ m_hProcess(hProcess),
+ m_hMutex(hMutex),
+ m_AppDomainCB(pAD),
+#if !defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ m_pIPCReader(pIPCReader),
+#endif // !FEATURE_DBGIPC_TRANSPORT_DI
+ m_pNext(NULL)
+{
+ {
+ // First try to get the process name from the OS. That can't be spoofed by badly formed IPC block.
+ // psapi!GetModuleFileNameExW can get that, but it's not available on all platforms so we
+ // need to load it dynamically.
+ if (fpGetModuleFileNameEx != NULL)
+ {
+ // MSDN is very confused about whether the lenght is in bytes (MSDN 2002) or chars (MSDN 2004).
+ // We err on the safe side by having buffer that's twice as large, and ignoring
+ // the units on the return value.
+ WCHAR szName[MAX_LONGPATH * sizeof(WCHAR)];
+
+ DWORD lenInCharsOrBytes = MAX_LONGPATH*sizeof(WCHAR);
+
+ // Pass NULL module handle to get "Main Module", which will give us the process name.
+ DWORD ret = (*fpGetModuleFileNameEx) (hProcess, NULL, szName, lenInCharsOrBytes);
+ if (ret > 0)
+ {
+ // Recompute string length because we don't know if 'ret' is in bytes or char.
+ SIZE_T len = wcslen(szName) + 1;
+ m_szProcessName = new (nothrow) WCHAR[len];
+ if (m_szProcessName != NULL)
+ {
+ wcscpy_s(m_szProcessName, len, szName);
+ goto exit;
+ }
+ }
+ }
+
+ // This is a security feature on WinXp + above, so make sure it worked there.
+ CONSISTENCY_CHECK_MSGF(FALSE, ("On XP/2k03 OSes + above, we should have been able to get\n"
+ "the module name from psapi!GetModuleFileNameEx. fp=0x%p\n.", fpGetModuleFileNameEx));
+ }
+ // We couldn't get it from the OS, so fallthrough to getting it from the IPC block.
+
+ // Fetch the process name from the AppDomainIPCBlock
+ _ASSERTE (pAD->m_szProcessName != NULL);
+
+ if (pAD->m_szProcessName == NULL)
+ m_szProcessName = NULL;
+ else
+ {
+ SIZE_T nBytesRead;
+
+ _ASSERTE(pAD->m_iProcessNameLengthInBytes > 0);
+
+ // Note: this assumes we're reading the null terminator from
+ // the IPC block.
+ m_szProcessName = (WCHAR*) new (nothrow) char[pAD->m_iProcessNameLengthInBytes];
+
+ if (m_szProcessName == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000,
+ "CP::CP: Failed to allocate memory for ProcessName.\n"));
+
+ goto exit;
+ }
+
+ BOOL bSucc = ReadProcessMemory(hProcess,
+ pAD->m_szProcessName,
+ m_szProcessName,
+ pAD->m_iProcessNameLengthInBytes,
+ &nBytesRead);
+
+ if ((bSucc == 0) ||
+ (nBytesRead != (SIZE_T)pAD->m_iProcessNameLengthInBytes))
+ {
+ // The EE may have done a rude exit
+ LOG((LF_CORDB, LL_INFO1000,
+ "CP::EAD: ReadProcessMemory (ProcessName) failed.\n"));
+ }
+ }
+
+exit:
+ ;
+}
+
+CorpubProcess::~CorpubProcess()
+{
+ delete [] m_szProcessName;
+#if !defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ delete m_pIPCReader;
+#endif // !FEATURE_DBGIPC_TRANSPORT_DI
+ CloseHandle(m_hProcess);
+ CloseHandle(m_hMutex);
+}
+
+
+HRESULT CorpubProcess::QueryInterface(REFIID id, void **ppInterface)
+{
+ if (id == IID_ICorPublishProcess)
+ *ppInterface = (ICorPublishProcess*)this;
+ else if (id == IID_IUnknown)
+ *ppInterface = (IUnknown*)(ICorPublishProcess*)this;
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+
+// Helper to tell if this process has exited.
+bool CorpubProcess::IsExited()
+{
+ DWORD res = WaitForSingleObject(this->m_hProcess, 0);
+ return (res == WAIT_OBJECT_0);
+}
+
+
+HRESULT CorpubProcess::IsManaged(BOOL *pbManaged)
+{
+ *pbManaged = (m_fIsManaged == true) ? TRUE : FALSE;
+
+ return S_OK;
+}
+
+// Helper.
+// Allocates a local buffer (using 'new') and fills it by copying it from remote memory.
+// Returns:
+// - on success, S_OK, *ppNewLocalBuffer points to a newly allocated buffer containing
+// the full copy from remote memoy. Caller must use 'delete []' to free this.
+// - on failure, a failing HR. No memory is allocated.
+HRESULT AllocateAndReadRemoteBuffer(
+ HANDLE hProcess,
+ void * pRemotePtr,
+ SIZE_T cbSize, // size of buffer to allocate + copy.
+ BYTE * * ppNewLocalBuffer
+)
+{
+ _ASSERTE(ppNewLocalBuffer != NULL);
+ *ppNewLocalBuffer = NULL;
+
+
+ if (pRemotePtr == NULL)
+ {
+ return E_INVALIDARG;
+ }
+
+ BYTE *pLocalBuffer = new (nothrow) BYTE[cbSize];
+
+ if (pLocalBuffer == NULL)
+ {
+ _ASSERTE(!"Failed to alloc memory. Likely size is bogusly large, perhaps from an attacker.");
+ return E_OUTOFMEMORY;
+ }
+
+ SIZE_T nBytesRead;
+
+ // Need to read in the remote process' memory
+ BOOL bSucc = ReadProcessMemory(hProcess,
+ pRemotePtr,
+ pLocalBuffer, cbSize,
+ &nBytesRead);
+
+ if ((bSucc == 0) || (nBytesRead != cbSize))
+ {
+ // The EE may have done a rude exit
+ delete [] pLocalBuffer;
+ return E_FAIL;
+ }
+
+ *ppNewLocalBuffer = pLocalBuffer;
+ return S_OK;
+}
+
+// Wrapper around AllocateAndReadRemoteBuffer,
+// to ensure that we're reading an remote-null terminated string.
+// Ensures that string is null-terminated.
+HRESULT AllocateAndReadRemoteString(
+ HANDLE hProcess,
+ void * pRemotePtr,
+ SIZE_T cbSize, // size of buffer to allocate + copy.
+ __deref_out_bcount(cbSize) WCHAR * * ppNewLocalBuffer
+ )
+{
+ // Make sure buffer has right geometry.
+ if (cbSize < 0)
+ {
+ return E_INVALIDARG;
+ }
+
+ // If it's not on a WCHAR boundary, then we may have a 1-byte buffer-overflow.
+ SIZE_T ceSize = cbSize / sizeof(WCHAR);
+ if ((ceSize * sizeof(WCHAR)) != cbSize)
+ {
+ return E_INVALIDARG;
+ }
+
+ // It should at least have 1 char for the null terminator.
+ if (ceSize < 1)
+ {
+ return E_INVALIDARG;
+ }
+
+
+ HRESULT hr = AllocateAndReadRemoteBuffer(hProcess, pRemotePtr, cbSize, (BYTE**) ppNewLocalBuffer);
+ if (SUCCEEDED(hr))
+ {
+ // Ensure that the string we just read is actually null terminated.
+ // We can't call wcslen() on it yet, since that may AV on a non-null terminated string.
+ WCHAR * pString = *ppNewLocalBuffer;
+
+ if (pString[ceSize - 1] == W('\0'))
+ {
+ // String is null terminated.
+ return S_OK;
+ }
+ pString[ceSize - 1] = W('\0');
+
+ SIZE_T ceTestLen = wcslen(pString);
+ if (ceTestLen == ceSize - 1)
+ {
+ // String was not previously null-terminated.
+ delete [] ppNewLocalBuffer;
+ return E_INVALIDARG;
+ }
+ }
+ return S_OK;
+}
+
+//
+// Enumerate the list of known application domains in the target process.
+//
+HRESULT CorpubProcess::EnumAppDomains(ICorPublishAppDomainEnum **ppIEnum)
+{
+ VALIDATE_POINTER_TO_OBJECT(ppIEnum, ICorPublishAppDomainEnum **);
+ *ppIEnum = NULL;
+
+ int i;
+
+ HRESULT hr = S_OK;
+ WCHAR *pAppDomainName = NULL;
+ CorpubAppDomain *pAppDomainHead = NULL;
+
+ // Lock the IPC block:
+ // We can't trust any of the data in the IPC block (including our own mutex handle),
+ // because we don't want bugs in the debuggee escalating into bugs in the debugger.
+ DWORD res = WaitForSingleObject(m_hMutex, SAFETY_TIMEOUT);
+
+ if (res == WAIT_TIMEOUT)
+ {
+ // This should only happen if the target process is illbehaved.
+ return CORDBG_E_TIMEOUT;
+ }
+
+ // If the process has gone away, or if it has cleared out its control block, then
+ // we've lost the race to access this process before it is terminated.
+ // Note that if the EE does a rude process exit, it won't have cleared the control block so there
+ // will be a small race window.
+ if (this->IsExited() || this->m_AppDomainCB->m_hMutex == NULL )
+ {
+ // This is the common case. A process holding the mutex shouldn't normally exit,
+ // but once it releases the mutex, it may exit asynchronously.
+ return CORDBG_E_PROCESS_TERMINATED;
+ }
+
+ if (res == WAIT_FAILED)
+ {
+ // This should be the next most common failure case
+ return HRESULT_FROM_GetLastError();
+ }
+
+ if (res != WAIT_OBJECT_0)
+ {
+ // Catch all other possible failures
+ return E_FAIL;
+ }
+
+ int iAppDomainCount = 0;
+ AppDomainInfo *pADI = NULL;
+
+ // Make a copy of the IPC block so that we can gaurantee that it's not changing on us.
+ AppDomainEnumerationIPCBlock tempBlock;
+ memcpy(&tempBlock, m_AppDomainCB, sizeof(tempBlock));
+
+ // Allocate memory to read the remote process' memory into
+ const SIZE_T cbADI = tempBlock.m_iSizeInBytes;
+
+ // It's possible the process will not have any appdomains.
+ if ((tempBlock.m_rgListOfAppDomains == NULL) != (tempBlock.m_iSizeInBytes == 0))
+ {
+ _ASSERTE(!"Inconsistent IPC block in publish.");
+ hr = E_FAIL;
+ goto exit;
+ }
+
+ // All the data in the IPC block is signed integers. They should never be negative,
+ // so check that now.
+ if ((tempBlock.m_iTotalSlots < 0) ||
+ (tempBlock.m_iNumOfUsedSlots < 0) ||
+ (tempBlock.m_iLastFreedSlot < 0) ||
+ (tempBlock.m_iSizeInBytes < 0) ||
+ (tempBlock.m_iProcessNameLengthInBytes < 0))
+ {
+ hr = E_FAIL;
+ goto exit;
+ }
+
+ // Check other invariants.
+ if (cbADI != tempBlock.m_iTotalSlots * sizeof(AppDomainInfo))
+ {
+ _ASSERTE(!"Size mismatch");
+ hr = E_FAIL;
+ goto exit;
+ }
+
+ hr = AllocateAndReadRemoteBuffer(m_hProcess, tempBlock.m_rgListOfAppDomains, cbADI, (BYTE**) &pADI);
+ if (FAILED(hr))
+ {
+ goto exit;
+ }
+ _ASSERTE(pADI != NULL);
+
+ // Collect all the AppDomain info info a list of CorpubAppDomains
+ for (i = 0; i < tempBlock.m_iTotalSlots; i++)
+ {
+ if (!pADI[i].IsEmpty())
+ {
+ // Should be positive, and at least have a null-terminator character.
+ if (pADI[i].m_iNameLengthInBytes <= 1)
+ {
+ hr = E_INVALIDARG;
+ goto exit;
+ }
+ hr = AllocateAndReadRemoteString(m_hProcess,
+ (void*) pADI[i].m_szAppDomainName, pADI[i].m_iNameLengthInBytes, // remote string + size in bytes
+ &pAppDomainName);
+ if (FAILED(hr))
+ {
+ goto exit;
+ }
+
+ // create a new AppDomainObject. This will take ownership of pAppDomainName.
+ // We know the string is a well-formed null-terminated string,
+ // but beyond that, we can't verify that the data is actually truthful.
+ CorpubAppDomain *pCurrentAppDomain = new (nothrow) CorpubAppDomain(pAppDomainName,
+ pADI[i].m_id);
+
+ if (pCurrentAppDomain == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000,
+ "CP::EAD: Failed to allocate memory for CorpubAppDomain.\n"));
+
+ hr = E_OUTOFMEMORY;
+ goto exit;
+ }
+
+ // Since CorpubAppDomain now owns pAppDomain's memory, we don't worry about freeing it.
+ pAppDomainName = NULL;
+
+ // Add the appdomain to the list.
+ pCurrentAppDomain->SetNext(pAppDomainHead);
+ pAppDomainHead = pCurrentAppDomain;
+
+ // Shortcut to opt out of reading the rest of the array if it's empty.
+ if (++iAppDomainCount >= tempBlock.m_iNumOfUsedSlots)
+ break;
+ }
+ }
+
+ {
+ _ASSERTE ((iAppDomainCount >= tempBlock.m_iNumOfUsedSlots)
+ && (i <= tempBlock.m_iTotalSlots));
+
+ // create and return the ICorPublishAppDomainEnum object, handing off the AppDomain list to it
+ CorpubAppDomainEnum *pTemp = new (nothrow) CorpubAppDomainEnum(pAppDomainHead);
+
+ if (pTemp == NULL)
+ {
+ hr = E_OUTOFMEMORY;
+ goto exit;
+ }
+
+ pAppDomainHead = NULL; // handed off AppDomain list to enum, don't delete below
+
+ hr = pTemp->QueryInterface(IID_ICorPublishAppDomainEnum,
+ (void **)ppIEnum);
+ }
+
+exit:
+ ReleaseMutex(m_hMutex);
+
+ // If we didn't hand off the AppDomain objects, delete them
+ while( pAppDomainHead != NULL )
+ {
+ CorpubAppDomain *pTemp = pAppDomainHead;
+ pAppDomainHead = pAppDomainHead->GetNextAppDomain();
+ delete pTemp;
+ }
+
+ if (pADI != NULL)
+ delete[] pADI;
+
+ if (pAppDomainName != NULL)
+ delete [] pAppDomainName;
+
+ // Either we succeeded && provided an enumerator; or we failed and didn't provide an enum.
+ _ASSERTE(SUCCEEDED(hr) == (*ppIEnum != NULL));
+ return hr;
+}
+
+/*
+ * Returns the OS ID for the process in question.
+ */
+HRESULT CorpubProcess::GetProcessID(unsigned *pid)
+{
+ *pid = m_dwProcessId;
+
+ return S_OK;
+}
+
+/*
+ * Get the display name for a process.
+ */
+HRESULT CorpubProcess::GetDisplayName(ULONG32 cchName,
+ ULONG32 *pcchName,
+ __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[])
+{
+ VALIDATE_POINTER_TO_OBJECT_ARRAY_OR_NULL(szName, WCHAR, cchName, true, true);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pcchName, ULONG32 *);
+
+ // Reasonable defaults
+ if (szName)
+ *szName = 0;
+
+ if (pcchName)
+ *pcchName = 0;
+
+ const WCHAR *szTempName = m_szProcessName;
+
+ // In case we didn't get the name (most likely out of memory on ctor).
+ if (!szTempName)
+ szTempName = W("<unknown>");
+
+ return CopyOutString(szTempName, cchName, pcchName, szName);
+}
+
+
+// ******************************************
+// CorpubAppDomain
+// ******************************************
+
+CorpubAppDomain::CorpubAppDomain (__in LPWSTR szAppDomainName, ULONG Id)
+ : CordbCommonBase (0, enumCorpubAppDomain),
+ m_pNext (NULL),
+ m_szAppDomainName (szAppDomainName),
+ m_id (Id)
+{
+ _ASSERTE(m_szAppDomainName != NULL);
+}
+
+CorpubAppDomain::~CorpubAppDomain()
+{
+ delete [] m_szAppDomainName;
+}
+
+HRESULT CorpubAppDomain::QueryInterface (REFIID id, void **ppInterface)
+{
+ if (id == IID_ICorPublishAppDomain)
+ *ppInterface = (ICorPublishAppDomain*)this;
+ else if (id == IID_IUnknown)
+ *ppInterface = (IUnknown*)(ICorPublishAppDomain*)this;
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+
+/*
+ * Get the name and ID for an application domain.
+ */
+HRESULT CorpubAppDomain::GetID (ULONG32 *pId)
+{
+ VALIDATE_POINTER_TO_OBJECT(pId, ULONG32 *);
+
+ *pId = m_id;
+
+ return S_OK;
+}
+
+/*
+ * Get the name for an application domain.
+ */
+HRESULT CorpubAppDomain::GetName(ULONG32 cchName,
+ ULONG32 *pcchName,
+ __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[])
+{
+ VALIDATE_POINTER_TO_OBJECT_ARRAY_OR_NULL(szName, WCHAR, cchName, true, true);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pcchName, ULONG32 *);
+
+ const WCHAR *szTempName = m_szAppDomainName;
+
+ // In case we didn't get the name (most likely out of memory on ctor).
+ if (!szTempName)
+ szTempName = W("<unknown>");
+
+ return CopyOutString(szTempName, cchName, pcchName, szName);
+}
+
+
+
+// ******************************************
+// CorpubProcessEnum
+// ******************************************
+
+CorpubProcessEnum::CorpubProcessEnum (CorpubProcess *pFirst)
+ : CordbCommonBase (0, enumCorpubProcessEnum),
+ m_pFirst (pFirst),
+ m_pCurrent (pFirst)
+{
+ // Increment the ref count on each process, we own the list
+ CorpubProcess * cur = pFirst;
+ while( cur != NULL )
+ {
+ cur->AddRef();
+ cur = cur->GetNextProcess();
+ }
+}
+
+CorpubProcessEnum::~CorpubProcessEnum()
+{
+ // Release each process in the list (our client may still have a reference
+ // to some of them)
+ while (m_pFirst != NULL)
+ {
+ CorpubProcess *pTmp = m_pFirst;
+ m_pFirst = m_pFirst->GetNextProcess();
+ pTmp->Release();
+ }
+}
+
+HRESULT CorpubProcessEnum::QueryInterface (REFIID id, void **ppInterface)
+{
+ if (id == IID_ICorPublishProcessEnum)
+ *ppInterface = (ICorPublishProcessEnum*)this;
+ else if (id == IID_IUnknown)
+ *ppInterface = (IUnknown*)(ICorPublishProcessEnum*)this;
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+
+HRESULT CorpubProcessEnum::Skip(ULONG celt)
+{
+ while ((m_pCurrent != NULL) && (celt-- > 0))
+ {
+ m_pCurrent = m_pCurrent->GetNextProcess();
+ }
+
+ return S_OK;
+}
+
+HRESULT CorpubProcessEnum::Reset()
+{
+ m_pCurrent = m_pFirst;
+
+ return S_OK;
+}
+
+HRESULT CorpubProcessEnum::Clone(ICorPublishEnum **ppEnum)
+{
+ VALIDATE_POINTER_TO_OBJECT(ppEnum, ICorPublishEnum **);
+ return E_NOTIMPL;
+}
+
+HRESULT CorpubProcessEnum::GetCount(ULONG *pcelt)
+{
+ VALIDATE_POINTER_TO_OBJECT(pcelt, ULONG *);
+
+ CorpubProcess *pTemp = m_pFirst;
+
+ *pcelt = 0;
+
+ while (pTemp != NULL)
+ {
+ (*pcelt)++;
+ pTemp = pTemp->GetNextProcess();
+ }
+
+ return S_OK;
+}
+
+HRESULT CorpubProcessEnum::Next(ULONG celt,
+ ICorPublishProcess *objects[],
+ ULONG *pceltFetched)
+{
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(objects, ICorPublishProcess *,
+ celt, true, true);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pceltFetched, ULONG *);
+
+ if ((pceltFetched == NULL) && (celt != 1))
+ {
+ return E_INVALIDARG;
+ }
+
+ if (celt == 0)
+ {
+ if (pceltFetched != NULL)
+ {
+ *pceltFetched = 0;
+ }
+ return S_OK;
+ }
+
+ HRESULT hr = S_OK;
+
+ ULONG count = 0;
+
+ while ((m_pCurrent != NULL) && (count < celt))
+ {
+ hr = m_pCurrent->QueryInterface (IID_ICorPublishProcess,
+ (void**)&objects[count]);
+
+ if (hr != S_OK)
+ {
+ break;
+ }
+
+ count++;
+ m_pCurrent = m_pCurrent->GetNextProcess();
+ }
+
+ if (pceltFetched != NULL)
+ {
+ *pceltFetched = count;
+ }
+
+ //
+ // If we reached the end of the enumeration, but not the end
+ // of the number of requested items, we return S_FALSE.
+ //
+ if (count < celt)
+ {
+ return S_FALSE;
+ }
+
+ return hr;
+}
+
+// ******************************************
+// CorpubAppDomainEnum
+// ******************************************
+CorpubAppDomainEnum::CorpubAppDomainEnum (CorpubAppDomain *pFirst)
+ : CordbCommonBase (0, enumCorpubAppDomainEnum),
+ m_pFirst (pFirst),
+ m_pCurrent (pFirst)
+{
+ CorpubAppDomain *pCur = pFirst;
+ while( pCur != NULL )
+ {
+ pCur->AddRef();
+ pCur = pCur->GetNextAppDomain();
+ }
+}
+
+CorpubAppDomainEnum::~CorpubAppDomainEnum()
+{
+ // Delete all the app domains
+ while (m_pFirst != NULL )
+ {
+ CorpubAppDomain *pTemp = m_pFirst;
+ m_pFirst = m_pFirst->GetNextAppDomain();
+ pTemp->Release();
+ }
+}
+
+HRESULT CorpubAppDomainEnum::QueryInterface (REFIID id, void **ppInterface)
+{
+ if (id == IID_ICorPublishAppDomainEnum)
+ *ppInterface = (ICorPublishAppDomainEnum*)this;
+ else if (id == IID_IUnknown)
+ *ppInterface = (IUnknown*)(ICorPublishAppDomainEnum*)this;
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+
+HRESULT CorpubAppDomainEnum::Skip(ULONG celt)
+{
+ while ((m_pCurrent != NULL) && (celt-- > 0))
+ {
+ m_pCurrent = m_pCurrent->GetNextAppDomain();
+ }
+
+ return S_OK;
+}
+
+HRESULT CorpubAppDomainEnum::Reset()
+{
+ m_pCurrent = m_pFirst;
+
+ return S_OK;
+}
+
+HRESULT CorpubAppDomainEnum::Clone(ICorPublishEnum **ppEnum)
+{
+ VALIDATE_POINTER_TO_OBJECT(ppEnum, ICorPublishEnum **);
+ return E_NOTIMPL;
+}
+
+HRESULT CorpubAppDomainEnum::GetCount(ULONG *pcelt)
+{
+ VALIDATE_POINTER_TO_OBJECT(pcelt, ULONG *);
+
+ CorpubAppDomain *pTemp = m_pFirst;
+
+ *pcelt = 0;
+
+ while (pTemp != NULL)
+ {
+ (*pcelt)++;
+ pTemp = pTemp->GetNextAppDomain();
+ }
+
+ return S_OK;
+}
+
+HRESULT CorpubAppDomainEnum::Next(ULONG celt,
+ ICorPublishAppDomain *objects[],
+ ULONG *pceltFetched)
+{
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(objects, ICorPublishProcess *,
+ celt, true, true);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pceltFetched, ULONG *);
+
+ if ((pceltFetched == NULL) && (celt != 1))
+ {
+ return E_INVALIDARG;
+ }
+
+ if (celt == 0)
+ {
+ if (pceltFetched != NULL)
+ {
+ *pceltFetched = 0;
+ }
+ return S_OK;
+ }
+
+ HRESULT hr = S_OK;
+
+ ULONG count = 0;
+
+ while ((m_pCurrent != NULL) && (count < celt))
+ {
+ hr = m_pCurrent->QueryInterface (IID_ICorPublishAppDomain,
+ (void **)&objects[count]);
+
+ if (hr != S_OK)
+ {
+ break;
+ }
+
+ count++;
+ m_pCurrent = m_pCurrent->GetNextAppDomain();
+ }
+
+
+ if (pceltFetched != NULL)
+ {
+ *pceltFetched = count;
+ }
+
+ //
+ // If we reached the end of the enumeration, but not the end
+ // of the number of requested items, we return S_FALSE.
+ //
+ if (count < celt)
+ {
+ return S_FALSE;
+ }
+
+ return hr;
+}
+
+#endif // defined(FEATURE_DBG_PUBLISH)
diff --git a/src/debug/di/remoteeventchannel.cpp b/src/debug/di/remoteeventchannel.cpp
new file mode 100644
index 0000000000..fc1d57a60f
--- /dev/null
+++ b/src/debug/di/remoteeventchannel.cpp
@@ -0,0 +1,342 @@
+// 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.
+//*****************************************************************************
+// File: RemoteEventChannel.cpp
+//
+
+//
+// Implements the old-style event channel between two remote processes.
+//*****************************************************************************
+
+#include "stdafx.h"
+#include "eventchannel.h"
+
+#include "dbgtransportsession.h"
+#include "dbgtransportmanager.h"
+
+
+//---------------------------------------------------------------------------------------
+// Class serves as a connector to win32 native-debugging API.
+class RemoteEventChannel : public IEventChannel
+{
+public:
+ RemoteEventChannel(DebuggerIPCControlBlock * pDCBBuffer,
+ DbgTransportTarget * pProxy,
+ DbgTransportSession * pTransport);
+
+ virtual ~RemoteEventChannel() {}
+
+ // Inititalize the event channel.
+ virtual HRESULT Init(HANDLE hTargetProc);
+
+ // Called when the debugger is detaching.
+ virtual void Detach();
+
+ // Delete the event channel and clean up all the resources it owns. This function can only be called once.
+ virtual void Delete();
+
+
+
+ // Update a single field with a value stored in the RS copy of the DCB.
+ virtual HRESULT UpdateLeftSideDCBField(void *rsFieldAddr, SIZE_T size);
+
+ // Update the entire RS copy of the debugger control block by reading the LS copy.
+ virtual HRESULT UpdateRightSideDCB();
+
+ // Get the pointer to the RS DCB.
+ virtual DebuggerIPCControlBlock * GetDCB();
+
+
+
+ // Check whether we need to wait for an acknowledgement from the LS after sending an IPC event.
+ virtual BOOL NeedToWaitForAck(DebuggerIPCEvent * pEvent);
+
+ // Get a handle to wait on after sending an IPC event to the LS. The caller should call NeedToWaitForAck()
+ virtual HANDLE GetRightSideEventAckHandle();
+
+ // Clean up the state if the wait for an acknowledgement is unsuccessful.
+ virtual void ClearEventForLeftSide();
+
+
+
+ // Send an IPC event to the LS.
+ virtual HRESULT SendEventToLeftSide(DebuggerIPCEvent * pEvent, SIZE_T eventSize);
+
+ // Get the reply from the LS for a previously sent IPC event.
+ virtual HRESULT GetReplyFromLeftSide(DebuggerIPCEvent * pReplyEvent, SIZE_T eventSize);
+
+
+
+ // Save an IPC event from the LS.
+ // Used for transferring an IPC event from the native pipeline to the IPC event channel.
+ virtual HRESULT SaveEventFromLeftSide(DebuggerIPCEvent * pEventFromLeftSide);
+
+ // Get a saved IPC event from the LS.
+ // Used for transferring an IPC event from the native pipeline to the IPC event channel.
+ virtual HRESULT GetEventFromLeftSide(DebuggerIPCEvent * pLocalManagedEvent);
+
+private:
+ DebuggerIPCControlBlock * m_pDCBBuffer; // local buffer for the DCB on the RS
+ DbgTransportTarget * m_pProxy; // connection to the debugger proxy
+ DbgTransportSession * m_pTransport; // connection to the debuggee process
+
+ // The next two fields are used for storing an IPC event from the native pipeline
+ // for the IPC event channel.
+ BYTE m_rgbLeftSideEventBuffer[CorDBIPC_BUFFER_SIZE];
+ BOOL m_fLeftSideEventAvailable;
+};
+
+// Allocate and return an old-style event channel object for this target platform.
+HRESULT NewEventChannelForThisPlatform(CORDB_ADDRESS pLeftSideDCB,
+ ICorDebugMutableDataTarget * pMutableDataTarget,
+ DWORD dwProcessId,
+ MachineInfo machineInfo,
+ IEventChannel ** ppEventChannel)
+{
+ // @dbgtodo Mac - Consider moving all of the transport logic to one place.
+ // Perhaps add a new function on DbgTransportManager.
+ HandleHolder hDummy;
+ HRESULT hr = E_FAIL;
+
+ RemoteEventChannel * pEventChannel = NULL;
+ DebuggerIPCControlBlock * pDCBBuffer = NULL;
+
+ DbgTransportTarget * pProxy = g_pDbgTransportTarget;
+ DbgTransportSession * pTransport = NULL;
+
+ hr = pProxy->GetTransportForProcess(dwProcessId, &pTransport, &hDummy);
+ if (FAILED(hr))
+ {
+ goto Label_Exit;
+ }
+
+ if (!pTransport->WaitForSessionToOpen(10000))
+ {
+ hr = CORDBG_E_TIMEOUT;
+ goto Label_Exit;
+ }
+
+ pDCBBuffer = new (nothrow) DebuggerIPCControlBlock;
+ if (pDCBBuffer == NULL)
+ {
+ hr = E_OUTOFMEMORY;
+ goto Label_Exit;
+ }
+
+ pEventChannel = new (nothrow) RemoteEventChannel(pDCBBuffer, pProxy, pTransport);
+ if (pEventChannel == NULL)
+ {
+ hr = E_OUTOFMEMORY;
+ goto Label_Exit;
+ }
+
+ _ASSERTE(SUCCEEDED(hr));
+ *ppEventChannel = pEventChannel;
+
+Label_Exit:
+ if (FAILED(hr))
+ {
+ if (pEventChannel != NULL)
+ {
+ // The IEventChannel has ownership of the proxy and the transport,
+ // so we don't need to clean them up here.
+ delete pEventChannel;
+ }
+ else
+ {
+ if (pTransport != NULL)
+ {
+ pProxy->ReleaseTransport(pTransport);
+ }
+ if (pDCBBuffer != NULL)
+ {
+ delete pDCBBuffer;
+ }
+ }
+ }
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+//
+// This is the constructor.
+//
+// Arguments:
+// pLeftSideDCB - target address of the DCB on the LS
+// pDCBBuffer - local buffer for storing the DCB on the RS; the memory is owned by this class
+// pMutableDataTarget - data target for reading from and writing to the target process's address space
+//
+
+RemoteEventChannel::RemoteEventChannel(DebuggerIPCControlBlock * pDCBBuffer,
+ DbgTransportTarget * pProxy,
+ DbgTransportSession * pTransport)
+{
+ m_pDCBBuffer = pDCBBuffer;
+ m_pProxy = pProxy;
+ m_pTransport = pTransport;
+ m_fLeftSideEventAvailable = FALSE;
+}
+
+// Inititalize the event channel.
+//
+// virtual
+HRESULT RemoteEventChannel::Init(HANDLE hTargetProc)
+{
+ return S_OK;
+}
+
+// Called when the debugger is detaching.
+//
+// virtual
+void RemoteEventChannel::Detach()
+{
+ // This is a nop for Mac debugging because we don't use RSEA/RSER.
+ return;
+}
+
+// Delete the event channel and clean up all the resources it owns. This function can only be called once.
+//
+// virtual
+void RemoteEventChannel::Delete()
+{
+ if (m_pDCBBuffer != NULL)
+ {
+ delete m_pDCBBuffer;
+ m_pDCBBuffer = NULL;
+ }
+
+ if (m_pTransport != NULL)
+ {
+ m_pProxy->ReleaseTransport(m_pTransport);
+ }
+
+ delete this;
+}
+
+// Update a single field with a value stored in the RS copy of the DCB.
+//
+// virtual
+HRESULT RemoteEventChannel::UpdateLeftSideDCBField(void * rsFieldAddr, SIZE_T size)
+{
+ _ASSERTE(m_pDCBBuffer != NULL);
+
+ // Ask the transport to update the LS DCB.
+ return m_pTransport->SetDCB(m_pDCBBuffer);
+}
+
+// Update the entire RS copy of the debugger control block by reading the LS copy.
+//
+// virtual
+HRESULT RemoteEventChannel::UpdateRightSideDCB()
+{
+ _ASSERTE(m_pDCBBuffer != NULL);
+
+ // Ask the transport to read the DCB from the Ls.
+ return m_pTransport->GetDCB(m_pDCBBuffer);
+}
+
+// Get the pointer to the RS DCB.
+//
+// virtual
+DebuggerIPCControlBlock * RemoteEventChannel::GetDCB()
+{
+ return m_pDCBBuffer;
+}
+
+// Check whether we need to wait for an acknowledgement from the LS after sending an IPC event.
+//
+// virtual
+BOOL RemoteEventChannel::NeedToWaitForAck(DebuggerIPCEvent * pEvent)
+{
+ // There are three cases to consider when sending an event over the transport:
+ //
+ // 1) asynchronous
+ // - the LS can just send the event and continue
+ //
+ // 2) synchronous, but no reply
+ // - This is different than Windows. We don't wait for an acknowledgement.
+ // Needless to say this is a semantical difference, but none of our code actually expects
+ // this type of IPC events to be synchronized.
+ //
+ // 3) synchronous, reply required:
+ // - This is the only case we need to wait for an acknowledgement in the Mac debugging case.
+ return (!pEvent->asyncSend && pEvent->replyRequired);
+}
+
+// Get a handle to wait on after sending an IPC event to the LS. The caller should call NeedToWaitForAck()
+//
+// virtual
+HANDLE RemoteEventChannel::GetRightSideEventAckHandle()
+{
+ // Delegate to the transport which does the real work.
+ return m_pTransport->GetIPCEventReadyEvent();
+}
+
+// Clean up the state if the wait for an acknowledgement is unsuccessful.
+//
+// virtual
+void RemoteEventChannel::ClearEventForLeftSide()
+{
+ // This is a nop for Mac debugging because we don't use RSEA/RSER.
+ return;
+}
+
+// Send an IPC event to the LS.
+//
+// virtual
+HRESULT RemoteEventChannel::SendEventToLeftSide(DebuggerIPCEvent * pEvent, SIZE_T eventSize)
+{
+ _ASSERTE(eventSize <= CorDBIPC_BUFFER_SIZE);
+
+ // Delegate to the transport. The event size is ignored.
+ return m_pTransport->SendEvent(pEvent);
+}
+
+// Get the reply from the LS for a previously sent IPC event.
+//
+// virtual
+HRESULT RemoteEventChannel::GetReplyFromLeftSide(DebuggerIPCEvent * pReplyEvent, SIZE_T eventSize)
+{
+ // Delegate to the transport.
+ m_pTransport->GetNextEvent(pReplyEvent, (DWORD)eventSize);
+ return S_OK;
+}
+
+// Save an IPC event from the LS.
+// Used for transferring an IPC event from the native pipeline to the IPC event channel.
+//
+// virtual
+HRESULT RemoteEventChannel::SaveEventFromLeftSide(DebuggerIPCEvent * pEventFromLeftSide)
+{
+ if (m_fLeftSideEventAvailable)
+ {
+ // We should only be saving one event at a time.
+ return E_FAIL;
+ }
+ else
+ {
+ memcpy(m_rgbLeftSideEventBuffer, reinterpret_cast<BYTE *>(pEventFromLeftSide), CorDBIPC_BUFFER_SIZE);
+ m_fLeftSideEventAvailable = TRUE;
+ return S_OK;
+ }
+}
+
+// Get a saved IPC event from the LS.
+// Used for transferring an IPC event from the native pipeline to the IPC event channel.
+//
+// virtual
+HRESULT RemoteEventChannel::GetEventFromLeftSide(DebuggerIPCEvent * pLocalManagedEvent)
+{
+ if (m_fLeftSideEventAvailable)
+ {
+ memcpy(reinterpret_cast<BYTE *>(pLocalManagedEvent), m_rgbLeftSideEventBuffer, CorDBIPC_BUFFER_SIZE);
+ m_fLeftSideEventAvailable = FALSE;
+ return S_OK;
+ }
+ else
+ {
+ // We have not saved any event.
+ return E_FAIL;
+ }
+}
diff --git a/src/debug/di/rsappdomain.cpp b/src/debug/di/rsappdomain.cpp
new file mode 100644
index 0000000000..fdfe657c57
--- /dev/null
+++ b/src/debug/di/rsappdomain.cpp
@@ -0,0 +1,1235 @@
+// 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.
+//*****************************************************************************
+// File: RsAppDomain.cpp
+//
+
+//
+//*****************************************************************************
+#include "stdafx.h"
+#include "primitives.h"
+#include "safewrap.h"
+
+#include "check.h"
+
+#include <tlhelp32.h>
+#include "wtsapi32.h"
+
+#ifndef SM_REMOTESESSION
+#define SM_REMOTESESSION 0x1000
+#endif
+
+#include "corpriv.h"
+#include "../../dlls/mscorrc/resource.h"
+#include <limits.h>
+
+
+/* ------------------------------------------------------------------------- *
+ * AppDomain class methods
+ * ------------------------------------------------------------------------- */
+
+//
+// Create a CordbAppDomain object based on a pointer to the AppDomain instance
+// in the CLR. Pre-populates some cached information about the AppDomain
+// from the CLR using DAC.
+//
+// Arguments:
+// pProcess - the CordbProcess object that this AppDomain is part of
+// vmAppDomain - the address in the CLR of the AppDomain object this corresponds to.
+// This will be used to read any additional information about the AppDomain.
+//
+// Assumptions:
+// The IMetaSig object should have been allocated by
+// IMDInternal on a valid metadata blob
+//
+//
+CordbAppDomain::CordbAppDomain(CordbProcess * pProcess, VMPTR_AppDomain vmAppDomain)
+ : CordbBase(pProcess, LsPtrToCookie(vmAppDomain.ToLsPtr()), enumCordbAppDomain),
+ m_AppDomainId(0),
+ m_breakpoints(17),
+ m_sharedtypes(3),
+ m_modules(17),
+ m_assemblies(9),
+ m_vmAppDomain(vmAppDomain)
+{
+ // This may throw out of the Ctor on error.
+
+ // @dbgtodo reliability: we should probably tolerate failures here and keep track
+ // of whether our ADID is valid or not, and requery if necessary.
+ m_AppDomainId = m_pProcess->GetDAC()->GetAppDomainId(m_vmAppDomain);
+
+ LOG((LF_CORDB,LL_INFO10000, "CAD::CAD: this:0x%x (void*)this:0x%x<%d>\n", this, (void *)this, m_AppDomainId));
+
+#ifdef _DEBUG
+ m_assemblies.DebugSetRSLock(pProcess->GetProcessLock());
+ m_modules.DebugSetRSLock(pProcess->GetProcessLock());
+ m_breakpoints.DebugSetRSLock(pProcess->GetProcessLock());
+ m_sharedtypes.DebugSetRSLock(pProcess->GetProcessLock());
+#endif
+
+}
+
+/*
+ A list of which resources owened by this object are accounted for.
+
+ RESOLVED:
+ // AddRef() in CordbHashTable::GetBase for a special InProc case
+ // AddRef() on the DB_IPCE_CREATE_APP_DOMAIN event from the LS
+ // Release()ed in Neuter
+ CordbProcess *m_pProcess;
+
+ WCHAR *m_szAppDomainName; // Deleted in ~CordbAppDomain
+
+ // Cleaned up in Neuter
+ CordbHashTable m_assemblies;
+ CordbHashTable m_sharedtypes;
+ CordbHashTable m_modules;
+ CordbHashTable m_breakpoints; // Disconnect()ed in ~CordbAppDomain
+
+ private:
+*/
+
+CordbAppDomain::~CordbAppDomain()
+{
+
+ // We expect to be Neutered before being released. Neutering will release our process ref
+ _ASSERTE(IsNeutered());
+}
+
+
+// Neutered by process. Once we're neutered, we lose our backpointer to the CordbProcess object, and
+// thus can't do things like call GetProcess() or Continue().
+void CordbAppDomain::Neuter()
+{
+ // This check prevents us from calling this twice and underflowing the internal ref count!
+ if (IsNeutered())
+ {
+ return;
+ }
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+
+ //
+ // Disconnect any active breakpoints
+ //
+ {
+ CordbBreakpoint* entry;
+ HASHFIND find;
+
+ for (entry = m_breakpoints.FindFirst(&find);
+ entry != NULL;
+ entry = m_breakpoints.FindNext(&find))
+ {
+ entry->Disconnect();
+ }
+ }
+
+ // Mark as neutered so that our children can tell the appdomain has now
+ // exited.
+ CordbBase::Neuter();
+
+ //
+ // Purge neuter lists.
+ //
+ m_TypeNeuterList.NeuterAndClear(GetProcess());
+ m_SweepableNeuterList.NeuterAndClear(GetProcess());
+
+
+ m_assemblies.NeuterAndClear(GetProcess()->GetProcessLock());
+ m_modules.NeuterAndClear(GetProcess()->GetProcessLock());
+ m_sharedtypes.NeuterAndClear(GetProcess()->GetProcessLock());
+ m_breakpoints.NeuterAndClear(GetProcess()->GetProcessLock());
+
+}
+
+
+HRESULT CordbAppDomain::QueryInterface(REFIID id, void **ppInterface)
+{
+ if (id == IID_ICorDebugAppDomain)
+ {
+ *ppInterface = (ICorDebugAppDomain*)this;
+ }
+ else if (id == IID_ICorDebugAppDomain2)
+ {
+ *ppInterface = (ICorDebugAppDomain2*)this;
+ }
+ else if (id == IID_ICorDebugAppDomain3)
+ {
+ *ppInterface = (ICorDebugAppDomain3*)this;
+ }
+ else if (id == IID_ICorDebugAppDomain4)
+ {
+ *ppInterface = (ICorDebugAppDomain4*)this;
+ }
+ else if (id == IID_ICorDebugController)
+ *ppInterface = (ICorDebugController*)(ICorDebugAppDomain*)this;
+ else if (id == IID_IUnknown)
+ *ppInterface = (IUnknown*)(ICorDebugAppDomain*)this;
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Ensure the AppDomain friendly name has been set.
+//
+// Return value:
+// S_OK on success, or a failure code if we couldn't read the name for some reason.
+// There shouldn't be any reason in practice for this to fail other than a corrupt
+// process image.
+//
+// Assumptions:
+// The AppDomain object has already been initialized to know about
+// it's corresponding VM appdomain.
+// InvalidateName is called whenever the name may have changed to prompt us to re-fetch.
+//
+//---------------------------------------------------------------------------------------
+HRESULT CordbAppDomain::RefreshName()
+{
+ if (m_strAppDomainName.IsSet())
+ {
+ // If we already have a valid name, we're done.
+ return S_OK;
+ }
+
+ // Use DAC to get the name.
+
+ _ASSERTE(!m_vmAppDomain.IsNull());
+ IDacDbiInterface * pDac = NULL;
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ pDac = m_pProcess->GetDAC();
+
+ #ifdef _DEBUG
+ // For debug, double-check the cached value against getting the AD via an AppDomainId.
+ VMPTR_AppDomain pAppDomain = pDac->GetAppDomainFromId(m_AppDomainId);
+ _ASSERTE(m_vmAppDomain == pAppDomain);
+ #endif
+
+ // Get the actual string contents.
+ pDac->GetAppDomainFullName(m_vmAppDomain, &m_strAppDomainName);
+
+ // Now that m_strAppDomainName is set, don't fail without clearing it.
+ }
+ EX_CATCH_HRESULT(hr);
+
+ _ASSERTE(SUCCEEDED(hr) == m_strAppDomainName.IsSet());
+
+ return hr;
+}
+
+
+HRESULT CordbAppDomain::Stop(DWORD dwTimeout)
+{
+ FAIL_IF_NEUTERED(this);
+ PUBLIC_API_ENTRY(this);
+ return (m_pProcess->StopInternal(dwTimeout, this->GetADToken()));
+}
+
+HRESULT CordbAppDomain::Continue(BOOL fIsOutOfBand)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ return m_pProcess->ContinueInternal(fIsOutOfBand);
+}
+
+HRESULT CordbAppDomain::IsRunning(BOOL *pbRunning)
+{
+ PUBLIC_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(pbRunning, BOOL *);
+ FAIL_IF_NEUTERED(this);
+
+ *pbRunning = !m_pProcess->GetSynchronized();
+
+ return S_OK;
+}
+
+HRESULT CordbAppDomain::HasQueuedCallbacks(ICorDebugThread *pThread, BOOL *pbQueued)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pThread,ICorDebugThread *);
+ VALIDATE_POINTER_TO_OBJECT(pbQueued,BOOL *);
+
+ return m_pProcess->HasQueuedCallbacks (pThread, pbQueued);
+}
+
+HRESULT CordbAppDomain::EnumerateThreads(ICorDebugThreadEnum **ppThreads)
+{
+ // @TODO E_NOIMPL this
+ //
+ // (use Process::EnumerateThreads and let users filter their own data)
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+ {
+ ValidateOrThrow(ppThreads);
+
+ RSInitHolder<CordbEnumFilter> pThreadEnum(
+ new CordbEnumFilter(GetProcess(), GetProcess()->GetContinueNeuterList()));
+
+ GetProcess()->PrepopulateThreadsOrThrow();
+
+ RSInitHolder<CordbHashTableEnum> pEnum;
+ GetProcess()->BuildThreadEnum(this, NULL, pEnum.GetAddr());
+
+ // This builds up auxillary list. don't need pEnum after this.
+ hr = pThreadEnum->Init(pEnum, this);
+ IfFailThrow(hr);
+
+ pThreadEnum.TransferOwnershipExternal(ppThreads);
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+
+HRESULT CordbAppDomain::SetAllThreadsDebugState(CorDebugThreadState state,
+ ICorDebugThread *pExceptThisThread)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ return m_pProcess->SetAllThreadsDebugState(state, pExceptThisThread);
+}
+
+HRESULT CordbAppDomain::Detach()
+{
+ PUBLIC_REENTRANT_API_ENTRY(this); // may be called from IMDA::Detach
+ FAIL_IF_NEUTERED(this);
+
+ return E_NOTIMPL;
+}
+
+HRESULT CordbAppDomain::Terminate(unsigned int exitCode)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ return E_NOTIMPL;
+}
+
+void CordbAppDomain::AddToTypeList(CordbBase *pObject)
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(pObject != NULL);
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+ this->m_TypeNeuterList.Add(GetProcess(), pObject);
+}
+
+
+HRESULT CordbAppDomain::CanCommitChanges(
+ ULONG cSnapshots,
+ ICorDebugEditAndContinueSnapshot *pSnapshots[],
+ ICorDebugErrorInfoEnum **pError)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT CordbAppDomain::CommitChanges(
+ ULONG cSnapshots,
+ ICorDebugEditAndContinueSnapshot *pSnapshots[],
+ ICorDebugErrorInfoEnum **pError)
+{
+ return E_NOTIMPL;
+}
+
+
+/*
+ * GetProcess returns the process containing the app domain
+ */
+HRESULT CordbAppDomain::GetProcess(ICorDebugProcess **ppProcess)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ VALIDATE_POINTER_TO_OBJECT(ppProcess,ICorDebugProcess **);
+
+ _ASSERTE (m_pProcess != NULL);
+
+ *ppProcess = static_cast<ICorDebugProcess *> (m_pProcess);
+ m_pProcess->ExternalAddRef();
+
+ return S_OK;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Callback for assembly enumeration.
+//
+// Arguments:
+// vmDomainAssembly - new assembly to add
+// pThis - user data for CordbAppDomain to add assembly too
+//
+//
+// Assumptions:
+// Invoked as callback from code:CordbAppDomain::PrepopulateAssemblies
+//
+// Notes:
+//
+
+// static
+void CordbAppDomain::AssemblyEnumerationCallback(VMPTR_DomainAssembly vmDomainAssembly, void * pThis)
+{
+ CordbAppDomain * pAppDomain = static_cast<CordbAppDomain *> (pThis);
+ INTERNAL_DAC_CALLBACK(pAppDomain->GetProcess());
+
+ // This lookup will cause the cache to be populated if we haven't seen this assembly before.
+ pAppDomain->LookupOrCreateAssembly(vmDomainAssembly);
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Cache a new assembly
+//
+// Arguments:
+// vmDomainAssembly - new assembly to add to cache
+//
+// Return Value:
+// Pointer to Assembly in cache.
+// NULL on failure, and sets unrecoverable error.
+//
+// Assumptions:
+// Caller gaurantees assembly is not already added.
+// Called under the stop-go lock.
+//
+// Notes:
+//
+CordbAssembly * CordbAppDomain::CacheAssembly(VMPTR_DomainAssembly vmDomainAssembly)
+{
+ INTERNAL_API_ENTRY(GetProcess());
+
+ VMPTR_Assembly vmAssembly;
+ GetProcess()->GetDAC()->GetAssemblyFromDomainAssembly(vmDomainAssembly, &vmAssembly);
+
+ RSInitHolder<CordbAssembly> pAssembly(new CordbAssembly(this, vmAssembly, vmDomainAssembly));
+
+ return pAssembly.TransferOwnershipToHash(&m_assemblies);
+}
+
+CordbAssembly * CordbAppDomain::CacheAssembly(VMPTR_Assembly vmAssembly)
+{
+ INTERNAL_API_ENTRY(GetProcess());
+
+ RSInitHolder<CordbAssembly> pAssembly(new CordbAssembly(this, vmAssembly, VMPTR_DomainAssembly()));
+
+ return pAssembly.TransferOwnershipToHash(&m_assemblies);
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Build up cache of assmeblies
+//
+// Arguments:
+//
+// Return Value:
+// Throws on error.
+//
+// Assumptions:
+// This is an non-invasive inspection operation called when the debuggee is stopped.
+//
+// Notes:
+// This can safely be called multiple times.
+//
+
+void CordbAppDomain::PrepopulateAssembliesOrThrow()
+{
+ INTERNAL_API_ENTRY(GetProcess());
+
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+
+ if (!GetProcess()->IsDacInitialized())
+ {
+ return;
+ }
+
+ // DD-primitive that invokes a callback.
+ GetProcess()->GetDAC()->EnumerateAssembliesInAppDomain(
+ this->m_vmAppDomain,
+ CordbAppDomain::AssemblyEnumerationCallback,
+ this); // user data
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Public API tp EnumerateAssemblies enumerates all assemblies in the app domain
+//
+// Arguments:
+// ppAssemblies - OUT: get enumerator
+//
+// Return Value:
+// S_OK on success.
+//
+//
+// Notes:
+// This will prepopulate the list of assemblies (useful for non-invasive case
+// where we don't get debug event).
+//
+
+HRESULT CordbAppDomain::EnumerateAssemblies(ICorDebugAssemblyEnum **ppAssemblies)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+ {
+ ValidateOrThrow(ppAssemblies);
+ *ppAssemblies = NULL;
+
+ PrepopulateAssembliesOrThrow();
+
+ RSInitHolder<CordbHashTableEnum> pEnum;
+ CordbHashTableEnum::BuildOrThrow(
+ this,
+ GetProcess()->GetContinueNeuterList(), // ownership
+ &m_assemblies,
+ IID_ICorDebugAssemblyEnum,
+ pEnum.GetAddr());
+ pEnum.TransferOwnershipExternal(ppAssemblies);
+
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+// Implement public interface
+HRESULT CordbAppDomain::GetModuleFromMetaDataInterface(
+ IUnknown *pIMetaData,
+ ICorDebugModule **ppModule)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pIMetaData, IUnknown *);
+ VALIDATE_POINTER_TO_OBJECT(ppModule, ICorDebugModule **);
+
+
+
+ HRESULT hr = S_OK;
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ *ppModule = NULL;
+
+ EX_TRY
+ {
+ CordbModule * pModule = GetModuleFromMetaDataInterface(pIMetaData);
+ _ASSERTE(pModule != NULL); // thrown on error
+
+ *ppModule = static_cast<ICorDebugModule*> (pModule);
+ pModule->ExternalAddRef();
+ }
+ EX_CATCH_HRESULT(hr);
+
+
+ return hr;
+}
+
+// Gets a CordbModule that has the given metadata interface
+//
+// Arguments:
+// pIMetaData - metadata interface
+//
+// Returns:
+// CordbModule whose associated metadata matches the metadata interface provided here
+// Throws on error. Returns non-null
+//
+CordbModule * CordbAppDomain::GetModuleFromMetaDataInterface(IUnknown *pIMetaData)
+{
+ HRESULT hr = S_OK;
+
+ RSExtSmartPtr<IMetaDataImport> pImport;
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock()); // need for module enumeration
+
+ // Grab the interface we need...
+ hr = pIMetaData->QueryInterface(IID_IMetaDataImport, (void**)&pImport);
+ if (FAILED(hr))
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ // Get the mvid of the given module.
+ GUID matchMVID;
+ hr = pImport->GetScopeProps(NULL, 0, 0, &matchMVID);
+ IfFailThrow(hr);
+
+ CordbModule* pModule;
+ HASHFIND findmodule;
+
+ PrepopulateModules();
+
+ for (pModule = m_modules.FindFirst(&findmodule);
+ pModule != NULL;
+ pModule = m_modules.FindNext(&findmodule))
+ {
+ IMetaDataImport * pImportCurrent = pModule->GetMetaDataImporter(); // throws
+ _ASSERTE(pImportCurrent != NULL);
+
+ // Get the mvid of this module
+ GUID MVID;
+ hr = pImportCurrent->GetScopeProps(NULL, 0, 0, &MVID);
+ IfFailThrow(hr);
+
+ if (MVID == matchMVID)
+ {
+ return pModule;
+ }
+ }
+
+ ThrowHR(E_INVALIDARG);
+}
+
+HRESULT CordbAppDomain::EnumerateBreakpoints(ICorDebugBreakpointEnum **ppBreakpoints)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ VALIDATE_POINTER_TO_OBJECT(ppBreakpoints, ICorDebugBreakpointEnum **);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ RSInitHolder<CordbHashTableEnum> pEnum;
+ CordbHashTableEnum::BuildOrThrow(
+ this,
+ GetProcess()->GetContinueNeuterList(), // ownership
+ &m_breakpoints,
+ IID_ICorDebugBreakpointEnum,
+ pEnum.GetAddr());
+
+ pEnum.TransferOwnershipExternal(ppBreakpoints);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+HRESULT CordbAppDomain::EnumerateSteppers(ICorDebugStepperEnum **ppSteppers)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ VALIDATE_POINTER_TO_OBJECT(ppSteppers,ICorDebugStepperEnum **);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ //
+ // !!! m_steppers may be modified while user is enumerating,
+ // if steppers complete (if process is running)
+ //
+
+ RSInitHolder<CordbHashTableEnum> pEnum;
+ CordbHashTableEnum::BuildOrThrow(
+ GetProcess(),
+ GetProcess()->GetContinueNeuterList(), // ownership
+ &(m_pProcess->m_steppers),
+ IID_ICorDebugStepperEnum,
+ pEnum.GetAddr());
+
+ pEnum.TransferOwnershipExternal(ppSteppers);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// CordbAppDomain::IsAttached - always returns true
+//
+// Arguments:
+// pfAttached - out parameter, will be set to TRUE
+//
+// Return Value:
+// CORDB_E_OBJECT_NEUTERED if the AppDomain has been neutered
+// E_INVALIDARG if pbAttached is null
+// Otherwise always returns S_OK.
+//
+// Notes:
+// Prior to V3, we used to keep track of a per-appdomain attached status.
+// Debuggers were required to explicitly attach to every AppDomain, so this
+// did not provide any actual functionality. In V3, there is no longer any
+// concept of per-AppDomain attach/detach. This API is provided for compatibility.
+//
+
+HRESULT CordbAppDomain::IsAttached(BOOL *pfAttached)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pfAttached, BOOL *);
+
+ *pfAttached = TRUE;
+
+ return S_OK;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// CordbAppDomain::Attach - does nothing
+//
+// Arguments:
+//
+// Return Value:
+// CORDB_E_OBJECT_NEUTERED if the AppDomain has been neutered
+// Otherwise always returns S_OK.
+//
+// Notes:
+// Prior to V3, we used to keep track of a per-appdomain attached status.
+// Debuggers were required to explicitly attach to every AppDomain, so this
+// did not provide any actual functionality. In V3, there is no longer any
+// concept of per-AppDomain attach/detach. This API is provided for compatibility.
+//
+
+HRESULT CordbAppDomain::Attach()
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(m_pProcess);
+
+ return S_OK;
+}
+
+/*
+ * GetName returns the name of the app domain.
+ */
+HRESULT CordbAppDomain::GetName(ULONG32 cchName,
+ ULONG32 *pcchName,
+ __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[])
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this)
+ {
+ // Some reasonable defaults
+ if (szName)
+ *szName = 0;
+
+ if (pcchName)
+ *pcchName = 0;
+
+
+ // Lazily refresh.
+ IfFailThrow(RefreshName());
+
+ const WCHAR * pName = m_strAppDomainName;
+ _ASSERTE(pName != NULL);
+
+ hr = CopyOutString(pName, cchName, pcchName, szName);
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+/*
+ * GetObject returns the runtime app domain object.
+ * Note: this is lazily initialized and may be NULL
+ */
+HRESULT CordbAppDomain::GetObject(ICorDebugValue **ppObject)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppObject,ICorDebugObjectValue **);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ _ASSERTE(!m_vmAppDomain.IsNull());
+ IDacDbiInterface * pDac = NULL;
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ pDac = m_pProcess->GetDAC();
+ VMPTR_OBJECTHANDLE vmObjHandle = pDac->GetAppDomainObject(m_vmAppDomain);
+ if (!vmObjHandle.IsNull())
+ {
+ ICorDebugReferenceValue * pRefValue = NULL;
+ hr = CordbReferenceValue::BuildFromGCHandle(this, vmObjHandle, &pRefValue);
+ *ppObject = pRefValue;
+ }
+ else
+ {
+ *ppObject = NULL;
+ hr = S_FALSE;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+/*
+ * Get the ID of the app domain.
+ */
+HRESULT CordbAppDomain::GetID (ULONG32 *pId)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ OK_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pId, ULONG32 *);
+
+ *pId = m_AppDomainId;
+
+ return S_OK;
+}
+
+//---------------------------------------------------------------------------------------
+// Remove an assembly from the ICorDebug cache.
+//
+// Arguments:
+// vmDomainAssembly - token to remove.
+//
+// Notes:
+// This is the opposite of code:CordbAppDomain::LookupOrCreateAssembly.
+// This only need to be called at assembly unload events.
+void CordbAppDomain::RemoveAssemblyFromCache(VMPTR_DomainAssembly vmDomainAssembly)
+{
+ // This will handle if the assembly is not in the hash.
+ // This could happen if we attach right before an assembly-unload event.
+ m_assemblies.RemoveBase(VmPtrToCookie(vmDomainAssembly));
+}
+
+//---------------------------------------------------------------------------------------
+// Lookup (or create) the CordbAssembly for the given VMPTR_DomainAssembly
+//
+// Arguments:
+// vmDomainAssembly - CLR token for the Assembly.
+//
+// Returns:
+// a CordbAssembly object for the given CLR assembly. This may be from the cache,
+// or newly created if not yet in the cache.
+// Never returns NULL. Throws on error (eg, oom).
+//
+CordbAssembly * CordbAppDomain::LookupOrCreateAssembly(VMPTR_DomainAssembly vmDomainAssembly)
+{
+ CordbAssembly * pAssembly = m_assemblies.GetBase(VmPtrToCookie(vmDomainAssembly));
+ if (pAssembly != NULL)
+ {
+ return pAssembly;
+ }
+ return CacheAssembly(vmDomainAssembly);
+}
+
+
+//
+CordbAssembly * CordbAppDomain::LookupOrCreateAssembly(VMPTR_Assembly vmAssembly)
+{
+ CordbAssembly * pAssembly = m_assemblies.GetBase(VmPtrToCookie(vmAssembly));
+ if (pAssembly != NULL)
+ {
+ return pAssembly;
+ }
+ return CacheAssembly(vmAssembly);
+}
+
+
+//---------------------------------------------------------------------------------------
+// Lookup or create a module within the appdomain
+//
+// Arguments:
+// vmDomainFile - non-null module to lookup
+//
+// Returns:
+// a CordbModule object for the given cookie. Object may be from the cache, or created
+// lazily.
+// Never returns null. Throws on error.
+//
+// Notes:
+// If you don't know which appdomain the module is in, use code:CordbProcess::LookupOrCreateModule.
+//
+CordbModule* CordbAppDomain::LookupOrCreateModule(VMPTR_Module vmModule, VMPTR_DomainFile vmDomainFile)
+{
+ INTERNAL_API_ENTRY(this);
+ CordbModule * pModule;
+
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock()); // @dbgtodo locking: push this up.
+
+ _ASSERTE(!vmDomainFile.IsNull() || !vmModule.IsNull());
+
+ // check to see if the module is present in this app domain
+ pModule = m_modules.GetBase(vmDomainFile.IsNull() ? VmPtrToCookie(vmModule) : VmPtrToCookie(vmDomainFile));
+ if (pModule != NULL)
+ {
+ return pModule;
+ }
+
+ if (vmModule.IsNull())
+ GetProcess()->GetDAC()->GetModuleForDomainFile(vmDomainFile, &vmModule);
+
+ RSInitHolder<CordbModule> pModuleInit(new CordbModule(GetProcess(), vmModule, vmDomainFile));
+ pModule = pModuleInit.TransferOwnershipToHash(&m_modules);
+
+ // The appdomains should match.
+ GetProcess()->TargetConsistencyCheck(pModule->GetAppDomain() == this);
+
+ return pModule;
+}
+
+
+CordbModule* CordbAppDomain::LookupOrCreateModule(VMPTR_DomainFile vmDomainFile)
+{
+ INTERNAL_API_ENTRY(this);
+
+ _ASSERTE(!vmDomainFile.IsNull());
+ return LookupOrCreateModule(VMPTR_Module::NullPtr(), vmDomainFile);
+}
+
+
+
+//---------------------------------------------------------------------------------------
+// Callback invoked by DAC for each module in an assembly. Used to populate RS module cache.
+//
+// Arguments:
+// vmModule - module from enumeration
+// pUserData - user data, a 'this' pointer to the CordbAssembly to add to.
+//
+// Notes:
+// This is called from code:CordbAppDomain::PrepopulateModules invoking DAC, which
+// invokes this callback.
+
+// static
+void CordbAppDomain::ModuleEnumerationCallback(VMPTR_DomainFile vmModule, void * pUserData)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ CordbAppDomain * pAppDomain = static_cast<CordbAppDomain *> (pUserData);
+ INTERNAL_DAC_CALLBACK(pAppDomain->GetProcess());
+
+ pAppDomain->LookupOrCreateModule(vmModule);
+}
+
+
+//
+// Use DAC to preopulate the list of modules for this assembly
+//
+// Notes:
+// This may pick up modules for which a load notification has not yet been dispatched.
+void CordbAppDomain::PrepopulateModules()
+{
+ INTERNAL_API_ENTRY(GetProcess());
+
+ if (!GetProcess()->IsDacInitialized())
+ {
+ return;
+ }
+
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+
+ // Want to make sure we don't double-add modules.
+ // Modules for all assemblies are stored in 1 giant hash in the AppDomain, so
+ // we don't have a good way of querying if this specific assembly needs to be prepopulated.
+ // We'll check before adding each module that it's unique.
+
+ PrepopulateAssembliesOrThrow();
+
+ HASHFIND hashfind;
+
+ for (CordbAssembly * pAssembly = m_assemblies.FindFirst(&hashfind);
+ pAssembly != NULL;
+ pAssembly = m_assemblies.FindNext(&hashfind))
+ {
+
+ // DD-primitive that invokes a callback.
+ GetProcess()->GetDAC()->EnumerateModulesInAssembly(
+ pAssembly->GetDomainAssemblyPtr(),
+ CordbAppDomain::ModuleEnumerationCallback,
+ this); // user data
+
+ }
+}
+
+//-----------------------------------------------------------------------------
+//
+// Get a type that represents an array or pointer of the given type.
+//
+// Arguments:
+// elementType - determines if this will be an array or a pointer
+// nRank - Rank of the array to make
+// pTypeArg - The type of array element or pointer.
+// ppResultType - OUT: out parameter to hold outgoing CordbType.
+//
+// Returns:
+// S_OK on success. Else failure.
+//
+HRESULT CordbAppDomain::GetArrayOrPointerType(CorElementType elementType,
+ ULONG32 nRank,
+ ICorDebugType * pTypeArg,
+ ICorDebugType ** ppResultType)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppResultType, ICorDebugType **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ CordbType * pResultType = NULL;
+
+ if (!(elementType == ELEMENT_TYPE_PTR && nRank == 0) &&
+ !(elementType == ELEMENT_TYPE_BYREF && nRank == 0) &&
+ !(elementType == ELEMENT_TYPE_SZARRAY && nRank == 1) &&
+ !(elementType == ELEMENT_TYPE_ARRAY))
+ {
+ return E_INVALIDARG;
+ }
+
+ HRESULT hr = CordbType::MkType(
+ this,
+ elementType,
+ (ULONG) nRank,
+ static_cast<CordbType *>(pTypeArg),
+ &pResultType);
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ _ASSERTE(pResultType != NULL);
+
+ pResultType->ExternalAddRef();
+
+ *ppResultType = pResultType;
+ return hr;
+
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// Get a type that represents a function pointer with signature of the types given.
+//
+// Arguments:
+// cTypeArgs - count of the number of entries in rgpTypeArgs
+// rgpTypeArgs - Array of types
+// ppResultType - OUT: out parameter to hold outgoing CordbType.
+//
+// Returns:
+// S_OK on success. Else failure.
+//
+HRESULT CordbAppDomain::GetFunctionPointerType(ULONG32 cTypeArgs,
+ ICorDebugType * rgpTypeArgs[],
+ ICorDebugType ** ppResultType)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppResultType, ICorDebugType **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ // Prefast overflow check:
+ S_UINT32 allocSize = S_UINT32(cTypeArgs) * S_UINT32(sizeof(CordbType *));
+
+ if (allocSize.IsOverflow())
+ {
+ return E_INVALIDARG;
+ }
+
+ CordbType ** ppTypeInstantiations = reinterpret_cast<CordbType **>(_alloca(allocSize.Value()));
+
+ for (unsigned int i = 0; i < cTypeArgs; i++)
+ {
+ ppTypeInstantiations[i] = (CordbType *) rgpTypeArgs[i];
+ }
+
+
+ Instantiation typeInstantiation(cTypeArgs, ppTypeInstantiations);
+
+ CordbType * pType;
+
+ HRESULT hr = CordbType::MkType(this, ELEMENT_TYPE_FNPTR, &typeInstantiation, &pType);
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ _ASSERTE(pType != NULL);
+
+ pType->ExternalAddRef();
+
+ *ppResultType = static_cast<ICorDebugType *>(pType);
+
+ return hr;
+
+}
+
+//
+// ICorDebugAppDomain3
+//
+
+HRESULT CordbAppDomain::GetCachedWinRTTypesForIIDs(
+ ULONG32 cGuids,
+ GUID * iids,
+ ICorDebugTypeEnum * * ppTypesEnum)
+{
+#if !defined(FEATURE_COMINTEROP)
+
+ return E_NOTIMPL;
+
+#else
+
+ HRESULT hr = S_OK;
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ _ASSERTE(!m_vmAppDomain.IsNull());
+
+
+ EX_TRY
+ {
+ *ppTypesEnum = NULL;
+
+ DacDbiArrayList<DebuggerIPCE_ExpandedTypeData> dacTypes;
+ DacDbiArrayList<GUID> dacGuids;
+
+ IDacDbiInterface* pDAC = GetProcess()->GetDAC();
+
+ dacGuids.Init(iids, cGuids);
+
+ // retrieve type info from LS
+ pDAC->GetCachedWinRTTypesForIIDs(m_vmAppDomain, dacGuids, &dacTypes);
+
+ // synthesize CordbType instances
+ int cItfs = dacTypes.Count();
+ NewArrayHolder<CordbType*> pTypes(NULL);
+
+ if (cItfs > 0)
+ {
+ pTypes = new CordbType*[cItfs];
+ for (int n = 0; n < cItfs; ++n)
+ {
+ hr = CordbType::TypeDataToType(this,
+ &(dacTypes[n]),
+ &pTypes[n]);
+ }
+ }
+
+ // build a type enumerator
+ CordbTypeEnum* pTypeEnum = CordbTypeEnum::Build(this, GetProcess()->GetContinueNeuterList(), cItfs, pTypes);
+ if ( pTypeEnum == NULL )
+ {
+ IfFailThrow(E_OUTOFMEMORY);
+ }
+
+ (*ppTypesEnum) = static_cast<ICorDebugTypeEnum*> (pTypeEnum);
+ pTypeEnum->ExternalAddRef();
+
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+
+#endif // !defined(FEATURE_COMINTEROP)
+}
+
+HRESULT CordbAppDomain::GetCachedWinRTTypes(
+ ICorDebugGuidToTypeEnum * * ppTypesEnum)
+{
+#if !defined(FEATURE_COMINTEROP)
+
+ return E_NOTIMPL;
+
+#else
+
+ HRESULT hr = S_OK;
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ EX_TRY
+ {
+ *ppTypesEnum = NULL;
+
+ DacDbiArrayList<GUID> guids;
+ DacDbiArrayList<DebuggerIPCE_ExpandedTypeData> types;
+
+ IDacDbiInterface* pDAC = GetProcess()->GetDAC();
+
+ // retrieve type info from LS
+ pDAC->GetCachedWinRTTypes(m_vmAppDomain, &guids, &types);
+
+ _ASSERTE(guids.Count() == types.Count());
+ if (guids.Count() != types.Count())
+ IfFailThrow(E_FAIL);
+
+ int cnt = types.Count();
+
+ RsGuidToTypeMapping* pMap = new RsGuidToTypeMapping[cnt];
+
+ for (int i = 0; i < cnt; ++i)
+ {
+ pMap[i].iid = guids[i];
+ CordbType* pType;
+ hr = CordbType::TypeDataToType(this, &(types[i]), &pType);
+ if (SUCCEEDED(hr))
+ {
+ pMap[i].spType.Assign(pType);
+ }
+ else
+ {
+ // spType stays NULL
+ }
+ }
+
+ CordbGuidToTypeEnumerator * enumerator = new CordbGuidToTypeEnumerator(GetProcess(), &pMap, cnt);
+ _ASSERTE(pMap == NULL);
+ GetProcess()->GetContinueNeuterList()->Add(GetProcess(), enumerator);
+
+ hr = enumerator->QueryInterface(IID_ICorDebugGuidToTypeEnum, reinterpret_cast<void**>(ppTypesEnum));
+ _ASSERTE(SUCCEEDED(hr));
+ IfFailThrow(hr);
+
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+
+#endif // !defined(FEATURE_COMINTEROP)
+}
+
+//-----------------------------------------------------------
+// ICorDebugAppDomain4
+//-----------------------------------------------------------
+
+HRESULT CordbAppDomain::GetObjectForCCW(CORDB_ADDRESS ccwPointer, ICorDebugValue **ppManagedObject)
+{
+#if defined(FEATURE_COMINTEROP)
+
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ VALIDATE_POINTER_TO_OBJECT(ppManagedObject, ICorDebugValue **);
+ HRESULT hr = S_OK;
+
+ *ppManagedObject = NULL;
+
+ EX_TRY
+ {
+ VMPTR_OBJECTHANDLE vmObjHandle = GetProcess()->GetDAC()->GetObjectForCCW(ccwPointer);
+ if (vmObjHandle.IsNull())
+ {
+ hr = E_INVALIDARG;
+ }
+ else
+ {
+ ICorDebugReferenceValue *pRefValue = NULL;
+ hr = CordbReferenceValue::BuildFromGCHandle(this, vmObjHandle, &pRefValue);
+ *ppManagedObject = pRefValue;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+
+#else
+
+ return E_NOTIMPL;
+
+#endif // defined(FEATURE_COMINTEROP)
+}
diff --git a/src/debug/di/rsassembly.cpp b/src/debug/di/rsassembly.cpp
new file mode 100644
index 0000000000..e390f77bc4
--- /dev/null
+++ b/src/debug/di/rsassembly.cpp
@@ -0,0 +1,320 @@
+// 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.
+//*****************************************************************************
+// File: RsAssembly.cpp
+//
+
+//
+//*****************************************************************************
+#include "stdafx.h"
+#include "primitives.h"
+#include "safewrap.h"
+
+#include "check.h"
+
+#include <tlhelp32.h>
+#include "wtsapi32.h"
+
+#ifndef SM_REMOTESESSION
+#define SM_REMOTESESSION 0x1000
+#endif
+
+#include "corpriv.h"
+#include "../../dlls/mscorrc/resource.h"
+#include <limits.h>
+
+
+/* ------------------------------------------------------------------------- *
+ * Assembly class
+ * ------------------------------------------------------------------------- */
+CordbAssembly::CordbAssembly(CordbAppDomain * pAppDomain,
+ VMPTR_Assembly vmAssembly,
+ VMPTR_DomainAssembly vmDomainAssembly)
+
+ : CordbBase(pAppDomain->GetProcess(),
+ vmDomainAssembly.IsNull() ? VmPtrToCookie(vmAssembly) : VmPtrToCookie(vmDomainAssembly),
+ enumCordbAssembly),
+ m_vmAssembly(vmAssembly),
+ m_vmDomainAssembly(vmDomainAssembly),
+ m_pAppDomain(pAppDomain)
+{
+ _ASSERTE(!vmAssembly.IsNull());
+}
+
+/*
+ A list of which resources owned by this object are accounted for.
+
+ public:
+ CordbAppDomain *m_pAppDomain; // Assigned w/o addRef(), Deleted in ~CordbAssembly
+*/
+
+CordbAssembly::~CordbAssembly()
+{
+}
+
+HRESULT CordbAssembly::QueryInterface(REFIID id, void **ppInterface)
+{
+ if (id == IID_ICorDebugAssembly)
+ *ppInterface = static_cast<ICorDebugAssembly*>(this);
+ else if (id == IID_ICorDebugAssembly2)
+ *ppInterface = static_cast<ICorDebugAssembly2*>(this);
+ else if (id == IID_IUnknown)
+ *ppInterface = static_cast<IUnknown*>( static_cast<ICorDebugAssembly*>(this) );
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+// Neutered by AppDomain
+void CordbAssembly::Neuter()
+{
+ m_pAppDomain = NULL;
+ CordbBase::Neuter();
+}
+
+
+#ifdef _DEBUG
+//---------------------------------------------------------------------------------------
+// Callback helper for code:CordbAssembly::DbgAssertAssemblyDeleted
+//
+// Arguments
+// vmDomainAssembly - domain file in the enumeration
+// pUserData - pointer to the CordbAssembly that we just got an exit event for.
+//
+
+// static
+void CordbAssembly::DbgAssertAssemblyDeletedCallback(VMPTR_DomainAssembly vmDomainAssembly, void * pUserData)
+{
+ CordbAssembly * pThis = reinterpret_cast<CordbAssembly * >(pUserData);
+ INTERNAL_DAC_CALLBACK(pThis->GetProcess());
+
+ VMPTR_DomainAssembly vmAssemblyDeleted = pThis->m_vmDomainAssembly;
+
+ CONSISTENCY_CHECK_MSGF((vmAssemblyDeleted != vmDomainAssembly),
+ ("An Assembly Unload event was sent, but the assembly still shows up in the enumeration.\n vmAssemblyDeleted=%p\n",
+ VmPtrToCookie(vmAssemblyDeleted)));
+}
+
+//---------------------------------------------------------------------------------------
+// Assert that a assembly is no longer discoverable via enumeration.
+//
+// Notes:
+// See code:IDacDbiInterface#Enumeration for rules that we're asserting.
+// This is a debug only method. It's conceptually similar to
+// code:CordbProcess::DbgAssertAppDomainDeleted.
+//
+void CordbAssembly::DbgAssertAssemblyDeleted()
+{
+ GetProcess()->GetDAC()->EnumerateAssembliesInAppDomain(
+ GetAppDomain()->GetADToken(),
+ CordbAssembly::DbgAssertAssemblyDeletedCallback,
+ this);
+}
+#endif // _DEBUG
+
+/*
+ * GetProcess returns the process containing the assembly
+ */
+HRESULT CordbAssembly::GetProcess(ICorDebugProcess **ppProcess)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppProcess, ICorDebugProcess **);
+
+ return (m_pAppDomain->GetProcess (ppProcess));
+}
+
+//
+// Returns the AppDomain that this assembly belongs to.
+//
+// Arguments:
+// ppAppDomain - a non-NULL pointer to store the AppDomain in.
+//
+// Return Value:
+// S_OK
+//
+// Notes:
+// On the debugger right-side we currently consider every assembly to belong
+// to a single AppDomain, and create multiple CordbAssembly instances (one
+// per AppDomain) to represent domain-neutral assemblies.
+//
+HRESULT CordbAssembly::GetAppDomain(ICorDebugAppDomain **ppAppDomain)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppAppDomain, ICorDebugAppDomain **);
+
+ _ASSERTE(m_pAppDomain != NULL);
+
+ *ppAppDomain = static_cast<ICorDebugAppDomain *> (m_pAppDomain);
+ m_pAppDomain->ExternalAddRef();
+
+ return S_OK;
+}
+
+
+
+/*
+ * EnumerateModules enumerates all modules in the assembly
+ */
+HRESULT CordbAssembly::EnumerateModules(ICorDebugModuleEnum **ppModules)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+ {
+ ValidateOrThrow(ppModules);
+ *ppModules = NULL;
+
+ m_pAppDomain->PrepopulateModules();
+
+ RSInitHolder<CordbEnumFilter> pModEnum(
+ new CordbEnumFilter(GetProcess(), GetProcess()->GetContinueNeuterList()));
+
+ RSInitHolder<CordbHashTableEnum> pEnum;
+
+ CordbHashTableEnum::BuildOrThrow(
+ this,
+ NULL, // ownership
+ &m_pAppDomain->m_modules,
+ IID_ICorDebugModuleEnum,
+ pEnum.GetAddr());
+
+ // this will build up an auxillary list. Don't need pEnum after this.
+ hr = pModEnum->Init(pEnum, this);
+ IfFailThrow(hr);
+
+ pModEnum.TransferOwnershipExternal(ppModules);
+
+ }
+ PUBLIC_API_END(hr);
+
+ return hr;
+}
+
+
+/*
+ * GetCodeBase returns the code base used to load the assembly
+ */
+HRESULT CordbAssembly::GetCodeBase(ULONG32 cchName,
+ ULONG32 *pcchName,
+ __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[])
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(szName, WCHAR, cchName, true, true);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pcchName, ULONG32 *);
+
+ return E_NOTIMPL;
+}
+
+//
+// Gets the filename of the assembly
+//
+// Arguments:
+// cchName - number of characters available in szName, or 0 to query length
+// pcchName - optional pointer to store the real length of the filename
+// szName - buffer in which to copy the filename, or NULL if cchName is 0.
+//
+// Return value:
+// S_OK on success (even if there is no filename).
+// An error code if the filename could not be read for the assembly. This should
+// not happen unless the target is corrupt.
+//
+// Notes:
+// In-memory assemblies do not have a filename. In that case, for compatibility
+// this returns success and the string "<unknown>". We may want to change this
+// behavior in the future.
+//
+HRESULT CordbAssembly::GetName(ULONG32 cchName,
+ ULONG32 *pcchName,
+ __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[])
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY_OR_NULL(szName, WCHAR, cchName, true, true);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pcchName, ULONG32 *);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // Lazily initialize our cache of the assembly filename.
+ // Note that if this fails, we'll try again next time this is called.
+ // This can be convenient for transient errors and debugging purposes, but could cause a
+ // performance problem if failure was common (it should not be).
+ if (!m_strAssemblyFileName.IsSet())
+ {
+ IDacDbiInterface * pDac = m_pProcess->GetDAC(); // throws
+ BOOL fNonEmpty = pDac->GetAssemblyPath(m_vmAssembly, &m_strAssemblyFileName); // throws
+ _ASSERTE(m_strAssemblyFileName.IsSet());
+
+
+ if (!fNonEmpty)
+ {
+ // File name is empty (eg. for an in-memory assembly)
+ _ASSERTE(m_strAssemblyFileName.IsEmpty());
+
+ // Construct a fake name
+ // This seems unwise - the assembly doesn't have a filename, we should probably just return
+ // an empty string and S_FALSE. This is a common case (in-memory assemblies), I don't see any reason to
+ // fake up a filename to pretend that it has a disk location when it doesn't.
+ // But I don't want to break tests at the moment that expect this.
+ // Note that all assemblies have a simple metadata name - perhaps we should have an additional API for that.
+ m_strAssemblyFileName.AssignCopy(W("<unknown>"));
+ }
+ }
+
+ // We should now have a non-empty string
+ _ASSERTE(m_strAssemblyFileName.IsSet());
+ _ASSERTE(!m_strAssemblyFileName.IsEmpty());
+
+ // Copy it out to our caller
+ }
+ EX_CATCH_HRESULT(hr);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+ return CopyOutString(m_strAssemblyFileName, cchName, pcchName, szName);
+}
+
+HRESULT CordbAssembly::IsFullyTrusted( BOOL *pbFullyTrusted )
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ VALIDATE_POINTER_TO_OBJECT(pbFullyTrusted, BOOL*);
+
+ if (m_vmDomainAssembly.IsNull())
+ return E_UNEXPECTED;
+
+ // Check for cached result
+ if( m_foptIsFullTrust.HasValue() )
+ {
+ *pbFullyTrusted = m_foptIsFullTrust.GetValue();
+ return S_OK;
+ }
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+
+ CordbProcess * pProcess = m_pAppDomain->GetProcess();
+ IDacDbiInterface * pDac = pProcess->GetDAC();
+
+ BOOL fIsFullTrust = pDac->IsAssemblyFullyTrusted(m_vmDomainAssembly);
+
+ // Once the trust level of an assembly is known, it cannot change.
+ m_foptIsFullTrust = fIsFullTrust;
+
+ *pbFullyTrusted = fIsFullTrust;
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
diff --git a/src/debug/di/rsclass.cpp b/src/debug/di/rsclass.cpp
new file mode 100644
index 0000000000..f0cdda4a0b
--- /dev/null
+++ b/src/debug/di/rsclass.cpp
@@ -0,0 +1,1194 @@
+// 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.
+//*****************************************************************************
+
+//
+// File: class.cpp
+//
+//*****************************************************************************
+#include "stdafx.h"
+
+// We have an assert in ceemain.cpp that validates this assumption
+#define FIELD_OFFSET_NEW_ENC_DB 0x07FFFFFB
+
+#include "winbase.h"
+#include "corpriv.h"
+
+
+
+//-----------------------------------------------------------------------------
+// class CordbClass
+// Represents a IL-Class in the debuggee, eg: List<T>, System.Console, etc
+//
+// Parameters:
+// m - module that the class is contained in.
+// classMetadataToken - metadata token for the class, scoped to module m.
+//-----------------------------------------------------------------------------
+CordbClass::CordbClass(CordbModule *m, mdTypeDef classMetadataToken)
+ : CordbBase(m->GetProcess(), classMetadataToken, enumCordbClass),
+ m_loadLevel(Constructed),
+ m_fLoadEventSent(FALSE),
+ m_fHasBeenUnloaded(false),
+ m_pModule(m),
+ m_token(classMetadataToken),
+ m_fIsValueClassKnown(false),
+ m_fIsValueClass(false),
+ m_fHasTypeParams(false),
+ m_continueCounterLastSync(0),
+ m_fCustomNotificationsEnabled(false)
+{
+ m_classInfo.Clear();
+}
+
+
+/*
+ A list of which resources owned by this object are accounted for.
+
+ HANDLED:
+ CordbModule* m_module; // Assigned w/o AddRef()
+ FieldData *m_fields; // Deleted in ~CordbClass
+ CordbHangingFieldTable m_hangingFieldsStatic; // by value, ~CHashTableAndData frees
+*/
+
+
+//-----------------------------------------------------------------------------
+// Destructor for CordbClass
+//-----------------------------------------------------------------------------
+CordbClass::~CordbClass()
+{
+ // We should have been explicitly neutered before our internal ref went to 0.
+ _ASSERTE(IsNeutered());
+}
+
+//-----------------------------------------------------------------------------
+// Neutered by CordbModule
+// See CordbBase::Neuter for semantics.
+//-----------------------------------------------------------------------------
+void CordbClass::Neuter()
+{
+ // Reduce the reference count on the type object for this class
+ m_type.Clear();
+ CordbBase::Neuter();
+}
+
+
+
+
+//-----------------------------------------------------------------------------
+// Standard IUnknown::QI implementation.
+// See IUnknown::QI for standard semantics.
+//-----------------------------------------------------------------------------
+HRESULT CordbClass::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugClass)
+ {
+ *pInterface = static_cast<ICorDebugClass*>(this);
+ }
+ else if (id == IID_ICorDebugClass2)
+ {
+ *pInterface = static_cast<ICorDebugClass2*>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugClass*>(this));
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Get a ICorDebugValue for a static field on this class.
+//
+// Parameters:
+// fieldDef - metadata token for field on this class. Can not be from an
+// inherited class.
+// pFrame - frame used to resolve Thread-static, AppDomain-static, etc.
+// ppValue - OUT: gets value of the field.
+//
+// Returns:
+// S_OK on success.
+// CORDBG_E_STATIC_VAR_NOT_AVAILABLE
+//-----------------------------------------------------------------------------
+HRESULT CordbClass::GetStaticFieldValue(mdFieldDef fieldDef,
+ ICorDebugFrame *pFrame,
+ ICorDebugValue **ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ *ppValue = NULL;
+ BOOL fEnCHangingField = FALSE;
+
+
+ IMetaDataImport * pImport = NULL;
+ EX_TRY
+ {
+ pImport = GetModule()->GetMetaDataImporter(); // throws
+
+ // Validate the token.
+ if (!pImport->IsValidToken(fieldDef) || (TypeFromToken(fieldDef) != mdtFieldDef))
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ // Make sure we have enough info about the class.
+ Init();
+
+ // Uninstantiated generics (eg, Foo<T>) don't have static data. Must use instantiated (eg Foo<int>)
+ // But all CordbClass instances are uninstantiated. So this should fail for all generic types.
+ // Normally, debuggers should be using ICorDebugType instead.
+ // Though in the forward compat case, they'll hit this.
+ if (HasTypeParams())
+ {
+ ThrowHR(CORDBG_E_STATIC_VAR_NOT_AVAILABLE);
+ }
+
+
+ // Lookup the field given its metadata token.
+ FieldData *pFieldData;
+
+ hr = GetFieldInfo(fieldDef, &pFieldData);
+
+ // This field was added by EnC, need to use EnC specific code path
+ if (hr == CORDBG_E_ENC_HANGING_FIELD)
+ {
+ // Static fields added with EnC hang off the EnCFieldDesc
+ hr = GetEnCHangingField(fieldDef,
+ &pFieldData,
+ NULL);
+
+ if (SUCCEEDED(hr))
+ {
+ fEnCHangingField = TRUE;
+ }
+ // Note: the FieldOffset in pFieldData has been cooked to produce
+ // the correct address of the field in the syncBlock.
+ // @todo: extend Debugger_IPCEFieldData so we don't have to cook the offset here
+ }
+
+ IfFailThrow(hr);
+
+ {
+ Instantiation emptyInst;
+
+ hr = CordbClass::GetStaticFieldValue2(GetModule(),
+ pFieldData,
+ fEnCHangingField,
+ &emptyInst,
+ pFrame,
+ ppValue);
+ // Let hr fall through
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ // Translate Failure HRs.
+ if (pImport != NULL)
+ {
+ hr = CordbClass::PostProcessUnavailableHRESULT(hr, pImport, fieldDef);
+ }
+
+ return hr;
+
+}
+
+//-----------------------------------------------------------------------------
+// Common helper for accessing statics from both CordbClass and CordbType.
+//
+// Arguments:
+// pModule - module containing the class
+// pFieldData - field data describing the field (this is correlated to a
+// mdFieldDef, but has more specific data)
+// fEnCHangingField - field storage hangs off the FieldDesc for EnC
+// pInst - generic instantiation.
+// pFrame - frame used for context for Thread-static, AD-static, etc.
+// ppValue - OUT: out parameter to get value.
+//
+// Returns:
+// S_OK on success.
+// CORDBG_E_FIELD_NOT_STATIC - if field isn't static.
+// CORDBG_E_STATIC_VAR_NOT_AVAILABLE - if field storage is not available.
+// Else some other failure.
+/* static */
+HRESULT CordbClass::GetStaticFieldValue2(CordbModule * pModule,
+ FieldData * pFieldData,
+ BOOL fEnCHangingField,
+ const Instantiation * pInst,
+ ICorDebugFrame * pFrame,
+ ICorDebugValue ** ppValue)
+{
+ FAIL_IF_NEUTERED(pModule);
+ INTERNAL_SYNC_API_ENTRY(pModule->GetProcess());
+ _ASSERTE((pModule->GetProcess()->GetShim() == NULL) || pModule->GetProcess()->GetSynchronized());
+ HRESULT hr = S_OK;
+
+ if (!pFieldData->m_fFldIsStatic)
+ {
+ return CORDBG_E_FIELD_NOT_STATIC;
+ }
+
+ CORDB_ADDRESS pRmtStaticValue = NULL;
+ CordbProcess * pProcess = pModule->GetProcess();
+
+ if (pFieldData->m_fFldIsCollectibleStatic)
+ {
+ EX_TRY
+ {
+ pRmtStaticValue = pProcess->GetDAC()->GetCollectibleTypeStaticAddress(pFieldData->m_vmFieldDesc,
+ pModule->GetAppDomain()->GetADToken());
+ }
+ EX_CATCH_HRESULT(hr);
+ if(FAILED(hr))
+ {
+ return hr;
+ }
+ }
+ else if (!pFieldData->m_fFldIsTLS && !pFieldData->m_fFldIsContextStatic)
+ {
+ // Statics never move, so we always address them using their absolute address.
+ _ASSERTE(pFieldData->OkToGetOrSetStaticAddress());
+ pRmtStaticValue = pFieldData->GetStaticAddress();
+ }
+ else
+ {
+ // We've got a thread or context local static
+
+ if( fEnCHangingField )
+ {
+ // fEnCHangingField is set for fields added with EnC which hang off the FieldDesc.
+ // Thread-local and context-local statics cannot be added with EnC, so we shouldn't be here
+ // if this is an EnC field is thread- or context-local.
+ _ASSERTE(!pFieldData->m_fFldIsTLS );
+ _ASSERTE(!pFieldData->m_fFldIsContextStatic );
+ }
+ else
+ {
+ if (pFrame == NULL)
+ {
+ return E_INVALIDARG;
+ }
+
+ CordbFrame * pRealFrame = CordbFrame::GetCordbFrameFromInterface(pFrame);
+ _ASSERTE(pRealFrame != NULL);
+
+ // Get the thread we are working on
+ CordbThread * pThread = pRealFrame->m_pThread;
+
+ EX_TRY
+ {
+ pRmtStaticValue = pProcess->GetDAC()->GetThreadOrContextStaticAddress(pFieldData->m_vmFieldDesc,
+ pThread->m_vmThreadToken);
+ }
+ EX_CATCH_HRESULT(hr);
+ if(FAILED(hr))
+ {
+ return hr;
+ }
+
+ }
+ }
+
+ if (pRmtStaticValue == NULL)
+ {
+ // type probably wasn't loaded yet.
+ // The debugger may chose to func-eval the creation of an instance of this type and try again.
+ return CORDBG_E_STATIC_VAR_NOT_AVAILABLE;
+ }
+
+ SigParser sigParser;
+ hr = S_OK;
+ EX_TRY
+ {
+ hr = pFieldData->GetFieldSignature(pModule, &sigParser);
+ }
+ EX_CATCH_HRESULT(hr);
+ IfFailRet(hr);
+
+ CordbType * pType;
+ IfFailRet (CordbType::SigToType(pModule, &sigParser, pInst, &pType));
+
+ bool fIsValueClass = false;
+ EX_TRY
+ {
+ fIsValueClass = pType->IsValueType(); // throws
+ }
+ EX_CATCH_HRESULT(hr);
+ IfFailRet(hr);
+
+ // Static value classes are stored as handles so that GC can deal with them properly. Thus, we need to follow the
+ // handle like an objectref. Do this by forcing CreateValueByType to think this is an objectref. Note: we don't do
+ // this for value classes that have an RVA, since they're layed out at the RVA with no handle.
+ bool fIsBoxed = (fIsValueClass &&
+ !pFieldData->m_fFldIsRVA &&
+ !pFieldData->m_fFldIsPrimitive &&
+ !pFieldData->m_fFldIsTLS &&
+ !pFieldData->m_fFldIsContextStatic);
+
+ TargetBuffer remoteValue(pRmtStaticValue, CordbValue::GetSizeForType(pType, fIsBoxed ? kBoxed : kUnboxed));
+ ICorDebugValue * pValue;
+
+ EX_TRY
+ {
+ CordbValue::CreateValueByType(pModule->GetAppDomain(),
+ pType,
+ fIsBoxed,
+ remoteValue,
+ MemoryRange(NULL, 0),
+ NULL,
+ &pValue); // throws
+ }
+ EX_CATCH_HRESULT(hr);
+
+ if (SUCCEEDED(hr))
+ {
+ *ppValue = pValue;
+ }
+
+ return hr;
+}
+
+
+//-----------------------------------------------------------------------------
+// Public method to build a CordbType from a CordbClass.
+// This is used to build up generic types. Eg, build:
+// List<T> + { int } --> List<int>
+//
+// Arguments:
+// elementType - element type. Either ELEMENT_TYPE_CLASS, or ELEMENT_TYPE_VALUETYPE.
+// We could technically figure this out from the metadata (by looking if it derives
+// from System.ValueType).
+// cTypeArgs - number of elements in rgpTypeArgs array
+// rgpTypeArgs - array for type args.
+// ppType - OUT: out parameter to hold resulting type.
+//
+// Returns:
+// S_OK on success. Else false.
+//
+HRESULT CordbClass::GetParameterizedType(CorElementType elementType,
+ ULONG32 cTypeArgs,
+ ICorDebugType * rgpTypeArgs[],
+ ICorDebugType ** ppType)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppType, ICorDebugType **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ // Note: Do not call Init() to find out if its a VC or not.
+ // Rather expect the client to tell us. This means the debug client
+ // can describe type instantiations not yet seen in the EE.
+
+ if ((elementType != ELEMENT_TYPE_CLASS) && (elementType != ELEMENT_TYPE_VALUETYPE))
+ {
+ return E_INVALIDARG;
+ }
+
+ // Prefast overflow check:
+ S_UINT32 allocSize = S_UINT32( cTypeArgs ) * S_UINT32( sizeof(CordbType *) );
+
+ if (allocSize.IsOverflow())
+ {
+ return E_INVALIDARG;
+ }
+
+ CordbAppDomain * pClassAppDomain = GetAppDomain();
+
+ // Note: casting from (ICorDebugType **) to (CordbType **) is not valid.
+ // Offsets may differ. Copy and validate the type array.
+ CordbType ** ppArgTypes = reinterpret_cast<CordbType **>(_alloca( allocSize.Value()));
+
+ for (unsigned int i = 0; i < cTypeArgs; i++)
+ {
+ ppArgTypes[i] = static_cast<CordbType *>( rgpTypeArgs[i] );
+
+ CordbAppDomain * pArgAppDomain = ppArgTypes[i]->GetAppDomain();
+
+ if ((pArgAppDomain != NULL) && (pArgAppDomain != pClassAppDomain))
+ {
+ return CORDBG_E_APPDOMAIN_MISMATCH;
+ }
+ }
+
+ {
+ CordbType * pResultType;
+
+ Instantiation typeInstantiation(cTypeArgs, ppArgTypes);
+
+ HRESULT hr = CordbType::MkType(pClassAppDomain, elementType, this, &typeInstantiation, &pResultType);
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ *ppType = pResultType;
+ }
+
+ _ASSERTE(*ppType);
+
+ if (*ppType)
+ {
+ (*ppType)->AddRef();
+ }
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Returns true if the field is a static literal.
+// In this case, the debugger should get the value from the metadata.
+//-----------------------------------------------------------------------------
+bool IsFieldStaticLiteral(IMetaDataImport *pImport, mdFieldDef fieldDef)
+{
+ DWORD dwFieldAttr;
+ HRESULT hr2 = pImport->GetFieldProps(
+ fieldDef,
+ NULL,
+ NULL,
+ 0,
+ NULL,
+ &dwFieldAttr,
+ NULL,
+ 0,
+ NULL,
+ NULL,
+ 0);
+
+ if (SUCCEEDED(hr2) && IsFdLiteral(dwFieldAttr))
+ {
+ return true;
+ }
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Filter to determine a more descriptive failing HResult for a field lookup.
+//
+// Parameters:
+// hr - incoming ambiguous HR.
+// pImport - metadata importer for this class.
+// feildDef - field being looked up.
+//
+// Returns:
+// hr - the incoming HR if no further HR can be determined.
+// else another failing HR that it judged to be more specific that the incoming HR.
+//-----------------------------------------------------------------------------
+HRESULT CordbClass::PostProcessUnavailableHRESULT(HRESULT hr,
+ IMetaDataImport *pImport,
+ mdFieldDef fieldDef)
+{
+ CONTRACTL
+ {
+ NOTHROW; // just translates an HR. shouldn't need to throw.
+ }
+ CONTRACTL_END;
+
+ if (hr == CORDBG_E_FIELD_NOT_AVAILABLE)
+ {
+ if (IsFieldStaticLiteral(pImport, fieldDef))
+ {
+ return CORDBG_E_VARIABLE_IS_ACTUALLY_LITERAL;
+ }
+ }
+
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// Public method to get the Module that this class lives in.
+//
+// Parameters:
+// ppModule - OUT: holds module that this class gets in.
+//
+// Returns:
+// S_OK on success.
+//-----------------------------------------------------------------------------
+HRESULT CordbClass::GetModule(ICorDebugModule **ppModule)
+{
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppModule, ICorDebugModule **);
+
+ *ppModule = static_cast<ICorDebugModule*> (m_pModule);
+ m_pModule->ExternalAddRef();
+
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Get the mdTypeDef token that this class corresponds to.
+//
+// Parameters:
+// pTypeDef - OUT: out param to get typedef token.
+//
+// Returns:
+// S_OK - on success.
+//-----------------------------------------------------------------------------
+HRESULT CordbClass::GetToken(mdTypeDef *pTypeDef)
+{
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pTypeDef, mdTypeDef *);
+
+ _ASSERTE(TypeFromToken(m_token) == mdtTypeDef);
+
+ *pTypeDef = m_token;
+
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Set the JMC status on all of our member functions.
+// The current implementation just uses the metadata to enumerate all
+// methods and then calls SetJMCStatus on each method.
+// This isn't great perf, but this should never be needed in a
+// perf-critical situation.
+//
+// Parameters:
+// fIsUserCode - true to set entire class to user code. False to set to
+// non-user code.
+//
+// Returns:
+// S_OK on success. On failure, the user-code status of the methods in the
+// class is random.
+//-----------------------------------------------------------------------------
+HRESULT CordbClass::SetJMCStatus(BOOL fIsUserCode)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ // Get the member functions via a meta data interface
+ CordbModule * pModule = GetModule();
+
+ // Ensure that our process is in a sane state.
+ CordbProcess * pProcess = pModule->GetProcess();
+ _ASSERTE(pProcess != NULL);
+
+ IMetaDataImport * pImport = NULL;
+ HCORENUM phEnum = 0;
+
+ HRESULT hr = S_OK;
+
+ mdMethodDef rTokens[100];
+ ULONG i;
+ ULONG count;
+
+ EX_TRY
+ {
+ pImport = pModule->GetMetaDataImporter();
+ do
+ {
+ hr = pImport->EnumMethods(&phEnum, m_token, rTokens, NumItems(rTokens), &count);
+ IfFailThrow(hr);
+
+ for (i = 0; i < count; i++)
+ {
+ RSLockHolder lockHolder(pProcess->GetProcessLock());
+ // Need the ICorDebugFunction to query for JMC status.
+ CordbFunction * pFunction = pModule->LookupOrCreateFunctionLatestVersion(rTokens[i]);
+
+ lockHolder.Release(); // Must release before sending an IPC event
+ hr = pFunction->SetJMCStatus(fIsUserCode);
+ IfFailThrow(hr);
+ }
+ }
+ while (count > 0);
+
+ _ASSERTE(SUCCEEDED(hr));
+ }
+ EX_CATCH_HRESULT(hr);
+
+ if ((pImport != NULL) && (phEnum != 0))
+ {
+ pImport->CloseEnum(phEnum);
+ }
+
+ return hr;
+
+}
+
+//-----------------------------------------------------------------------------
+// We have to go the the EE to find out if a class is a value
+// class or not. This is because there is no flag for this, but rather
+// it depends on whether the class subclasses System.ValueType (apart
+// from System.Enum...). Replicating all that resoultion logic
+// does not seem like a good plan.
+//
+// We also accept other "evidence" that the class is or isn't a VC, in
+// particular:
+// - It is definitely a VC if it has been used after a
+// E_T_VALUETYPE in a signature.
+// - It is definitely not a VC if it has been used after a
+// E_T_CLASS in a signature.
+// - It is definitely a VC if it has been used in combination with
+// E_T_VALUETYPE in one of COM API operations that take both
+// a ICorDebugClass and a CorElementType (e.g. GetParameterizedType)
+//
+// !!!Note the following!!!!
+// - A class may still be a VC even if it has been
+// used in combination with E_T_CLASS in one of COM API operations that take both
+// a ICorDebugClass and a CorElementType (e.g. GetParameterizedType).
+// We allow the user of the API to specify E_T_CLASS when the VC status
+// is not known or is not important.
+//
+// Return Value:
+// indicates whether this is a value-class
+//
+// Notes:
+// Throws CORDBG_E_CLASS_NOT_LOADED or synchronization errors on failure
+//-----------------------------------------------------------------------------
+bool CordbClass::IsValueClass()
+{
+ INTERNAL_API_ENTRY(this);
+ THROW_IF_NEUTERED(this);
+
+ if (!m_fIsValueClassKnown)
+ {
+ ATT_REQUIRE_STOPPED_MAY_FAIL_OR_THROW(GetProcess(), ThrowHR);
+ Init();
+ }
+ return m_fIsValueClass;
+}
+
+//-----------------------------------------------------------------------------
+// Get a CordbType for the 'this' pointer of a method in a CordbClass.
+// The 'this' pointer is argument #0 in an instance method.
+//
+// For ReferenceTypes (ELEMENT_TYPE_CLASS), the 'this' pointer is just a
+// normal reference, and so GetThisType() behaves like GetParameterizedType().
+// For ValueTypes, the 'this' pointer is a byref.
+//
+// Arguments:
+// pInst - instantiation info (eg, the type parameters) to produce CordbType
+// ppResultType - OUT: out parameter to hold outgoing CordbType.
+//
+// Returns:
+// S_OK on success. Else failure.
+//
+HRESULT CordbClass::GetThisType(const Instantiation * pInst, CordbType ** ppResultType)
+{
+ FAIL_IF_NEUTERED(this);
+
+ HRESULT hr = S_OK;
+ // Note: We have to call Init() here to find out if it really a VC or not.
+ bool fIsValueClass = false;
+ EX_TRY
+ {
+ fIsValueClass = IsValueClass();
+ }
+ EX_CATCH_HRESULT(hr);
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ if (fIsValueClass)
+ {
+ CordbType *pType;
+
+ hr = CordbType::MkType(GetAppDomain(), // OK: this E_T_VALUETYPE will be normalized by MkType
+ ELEMENT_TYPE_VALUETYPE,
+ this,
+ pInst,
+ &pType);
+
+ if (!SUCCEEDED(hr))
+ {
+ return hr;
+ }
+
+ hr = CordbType::MkType(GetAppDomain(), ELEMENT_TYPE_BYREF, 0, pType, ppResultType);
+
+ if (!SUCCEEDED(hr))
+ {
+ return hr;
+ }
+ }
+ else
+ {
+ hr = CordbType::MkType(GetAppDomain(), ELEMENT_TYPE_CLASS, this, pInst, ppResultType);
+
+ if (!SUCCEEDED(hr))
+ {
+ return hr;
+ }
+ }
+
+ return hr;
+}
+
+
+//-----------------------------------------------------------------------------
+// Initialize the CordbClass.
+// This will collect all the field information via the DAC, and also determine
+// whether this Type is a ReferenceType or ValueType.
+//
+// Parameters:
+// fForceInit - if true, always reinitialize. If false, may skip
+// initialization if we believe we already have the info.
+//
+// Note:
+// Throws CORDBG_E_CLASS_NOT_LOADED on failure
+//-----------------------------------------------------------------------------
+void CordbClass::Init(ClassLoadLevel desiredLoadLevel)
+{
+ INTERNAL_SYNC_API_ENTRY(this->GetProcess());
+
+ CordbProcess * pProcess = GetProcess();
+ IDacDbiInterface* pDac = pProcess->GetDAC();
+
+ // If we've done a continue since the last time we got hanging static fields,
+ // we should clear out our cache, since everything may have moved.
+ if (m_continueCounterLastSync < GetProcess()->m_continueCounter)
+ {
+ m_hangingFieldsStatic.Clear();
+ m_continueCounterLastSync = GetProcess()->m_continueCounter;
+ }
+
+ if (m_loadLevel < desiredLoadLevel)
+ {
+ // reset everything
+ m_loadLevel = Constructed;
+ m_fIsValueClass = false;
+ m_fIsValueClassKnown = false;
+ m_fHasTypeParams = false;
+ m_classInfo.Clear();
+ // @dbgtodo Microsoft inspection: declare a constant to replace badbad
+ m_classInfo.m_objectSize = 0xbadbad;
+ VMPTR_TypeHandle vmTypeHandle = VMPTR_TypeHandle::NullPtr();
+
+ // basic info load level
+ if(desiredLoadLevel >= BasicInfo)
+ {
+ vmTypeHandle = pDac->GetTypeHandle(m_pModule->GetRuntimeModule(), GetToken());
+ SetIsValueClass(pDac->IsValueType(vmTypeHandle));
+ m_fHasTypeParams = !!pDac->HasTypeParams(vmTypeHandle);
+ m_loadLevel = BasicInfo;
+ }
+
+ // full info load level
+ if(desiredLoadLevel == FullInfo)
+ {
+ VMPTR_AppDomain vmAppDomain = VMPTR_AppDomain::NullPtr();
+ VMPTR_DomainFile vmDomainFile = m_pModule->GetRuntimeDomainFile();
+ if (!vmDomainFile.IsNull())
+ {
+ DomainFileInfo info;
+ pDac->GetDomainFileData(vmDomainFile, &info);
+ vmAppDomain = info.vmAppDomain;
+ }
+ pDac->GetClassInfo(vmAppDomain, vmTypeHandle, &m_classInfo);
+
+ BOOL fGotUnallocatedStatic = GotUnallocatedStatic(&m_classInfo.m_fieldList);
+
+ // if we have an unallocated static don't record that we reached FullInfo stage
+ // this seems pretty ugly but I don't want to bite off cleaning this up just yet
+ // Not saving the FullInfo stage effectively means future calls to Init() will
+ // re-init everything and some parts of DBI may be depending on that re-initialization
+ // with alternate data in order to operate correctly
+ if(!fGotUnallocatedStatic)
+ m_loadLevel = FullInfo;
+ }
+ }
+} // CordbClass::Init
+
+// determine if any fields for a type are unallocated statics
+BOOL CordbClass::GotUnallocatedStatic(DacDbiArrayList<FieldData> * pFieldList)
+{
+ BOOL fGotUnallocatedStatic = FALSE;
+ int count = 0;
+ while ((count < pFieldList->Count()) && !fGotUnallocatedStatic )
+ {
+ if ((*pFieldList)[count].OkToGetOrSetStaticAddress() &&
+ (*pFieldList)[count].GetStaticAddress() == NULL )
+ {
+ // The address for a regular static field isn't available yet
+ // How can this happen? Statics appear to get allocated during domain load.
+ // There may be some lazieness or a race-condition involved.
+ fGotUnallocatedStatic = TRUE;
+ }
+ ++count;
+ }
+ return fGotUnallocatedStatic;
+} // CordbClass::GotUnallocatedStatic
+
+/*
+ * FieldData::GetFieldSignature
+ *
+ * Get the field's full metadata signature. This may be cached, but for dynamic modules we'll always read it from
+ * the metadata.
+ *
+ * Parameters:
+ * pModule - pointer to the module that contains the field
+ *
+ * pSigParser - OUT: the full signature for the field.
+ *
+ * Returns:
+ * HRESULT for success or failure.
+ *
+ */
+HRESULT FieldData::GetFieldSignature(CordbModule *pModule,
+ SigParser *pSigParser)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ INTERNAL_SYNC_API_ENTRY(pModule->GetProcess());
+
+ HRESULT hr = S_OK;
+
+ IMetaDataImport * pImport = pModule->GetMetaDataImporter(); // throws;
+
+ PCCOR_SIGNATURE fieldSignature = NULL;
+ ULONG size = ((ULONG) -1);
+
+ _ASSERTE(pSigParser != NULL);
+
+ // If the module is dynamic, there had better not be a cached field signature.
+ _ASSERTE(!pModule->IsDynamic() || (m_fldSignatureCache == NULL));
+
+ // If the field signature cache is null, or if this is a dynamic module, then go read the signature from the
+ // matadata. We always read from the metadata for dynamic modules because our metadata blob is constantly
+ // getting deleted and re-allocated. If we kept a pointer to the signature, we'd end up pointing to bad data.
+ if (m_fldSignatureCache == NULL)
+ {
+ // Go to the metadata for all fields: previously the left-side tranferred over
+ // single-byte signatures as part of the field info. Since the left-side
+ // goes to the metadata anyway, and we already fetch plenty of other metadata,
+ // I don't believe that fetching it here instead of transferring it over
+ // is going to slow things down at all, and
+ // in any case will not be where the primary optimizations lie...
+
+ IfFailRet(pImport->GetFieldProps(m_fldMetadataToken, NULL, NULL, 0, NULL, NULL,
+ &fieldSignature,
+ &size,
+ NULL, NULL, NULL));
+
+ // Point past the calling convention
+ CorCallingConvention conv;
+
+ // Move pointer,
+ BYTE * pOldPtr = (BYTE*) fieldSignature;
+ conv = (CorCallingConvention) CorSigUncompressData(fieldSignature);
+ _ASSERTE(conv == IMAGE_CEE_CS_CALLCONV_FIELD);
+ size -= (ULONG) (((BYTE*) fieldSignature) - pOldPtr); // since we updated filedSignature, adjust size
+
+ // Although the pointer will keep updating, the size should be the same. So we assert that.
+ _ASSERTE((m_fldSignatureCacheSize == 0) || (m_fldSignatureCacheSize == size));
+
+ // Cache the value for non-dynamic modules, so this is faster later.
+ // Since we're caching in a FieldData, we can't store the actual SigParser object.
+ if (!pModule->IsDynamic())
+ {
+ m_fldSignatureCache = fieldSignature;
+ m_fldSignatureCacheSize = size;
+ }
+ }
+ else
+ {
+ // We have a cached value, so return it. Note: we should never have a cached value for a field in a dynamic
+ // module.
+ CONSISTENCY_CHECK_MSGF((!pModule->IsDynamic()),
+ ("We should never cache a field signature in a dynamic module! Module=%p This=%p",
+ pModule, this));
+
+ fieldSignature = m_fldSignatureCache;
+ size = m_fldSignatureCacheSize;
+ }
+
+ _ASSERTE(fieldSignature != NULL);
+ _ASSERTE(size != ((ULONG) -1));
+ *pSigParser = SigParser(fieldSignature, size);
+ return hr;
+}
+
+// CordbClass::InitEnCFieldInfo
+// Initializes an instance of EnCHangingFieldInfo.
+// Arguments:
+// input: fStatic - flag to indicate whether the EnC field is static
+// pObject - For instance fields, the Object instance containing the the sync-block.
+// For static fields (if this is being called from GetStaticFieldValue) object is NULL.
+// fieldToken - token for the EnC field
+// metadataToken - metadata token for this instance of CordbClass
+// output: pEncField - the fields of this class will be appropriately initialized
+void CordbClass::InitEnCFieldInfo(EnCHangingFieldInfo * pEncField,
+ BOOL fStatic,
+ CordbObjectValue * pObject,
+ mdFieldDef fieldToken,
+ mdTypeDef classToken)
+{
+ IDacDbiInterface * pInterface = GetProcess()->GetDAC();
+
+ if (fStatic)
+ {
+ // the field is static, we don't need any additional data
+ pEncField->Init(VMPTR_Object::NullPtr(), /* vmObject */
+ NULL, /* offsetToVars */
+ fieldToken,
+ ELEMENT_TYPE_MAX,
+ classToken,
+ m_pModule->GetRuntimeDomainFile());
+ }
+ else
+ {
+ // This is an instance field, we need to pass a bunch of type information back
+ _ASSERTE(pObject != NULL);
+
+ pEncField->Init(pInterface->GetObject(pObject->m_id), // VMPTR to the object instance of interest.
+ pObject->GetInfo().objOffsetToVars, // The offset from the beginning of the object
+ // to the beginning of the fields. Fields added
+ // with EnC don't actually reside in the object
+ // (they hang off the sync block instead), so
+ // this is used to compute the returned field
+ // offset (fieldData.m_fldInstanceOffset). This
+ // makes it appear to be an offset from the object.
+ // Ideally we wouldn't do any of this, and just
+ // explicitly deal with absolute addresses (instead
+ // of "offsets") for EnC hanging instance fields.
+ fieldToken, // Field token for the added field.
+ pObject->GetInfo().objTypeData.elementType, // An indication of the type of object to which
+ // we're adding a field (specifically,
+ // whether it's a value type or a class).
+ // This is used only for log messages, and could
+ // be removed.
+ classToken, // metadata token for the class
+ m_pModule->GetRuntimeDomainFile()); // Domain file for the class
+ }
+} // CordbClass::InitFieldData
+
+// CordbClass::GetEnCFieldFromDac
+// Get information via the DAC about a field added with Edit and Continue.
+// Arguments:
+// input: fStatic - flag to indicate whether the EnC field is static
+// pObject - For instance fields, the Object instance containing the the sync-block.
+// For static fields (if this is being called from GetStaticFieldValue) object is NULL.
+// fieldToken - token for the EnC field
+// output: pointer to an initialized instance of FieldData that has been added to the appropriate table
+// in our cache
+FieldData * CordbClass::GetEnCFieldFromDac(BOOL fStatic,
+ CordbObjectValue * pObject,
+ mdFieldDef fieldToken)
+{
+ EnCHangingFieldInfo encField;
+ mdTypeDef metadataToken;
+ FieldData fieldData,
+ * pInfo = NULL;
+ BOOL fDacStatic;
+ CordbProcess * pProcess = GetModule()->GetProcess();
+
+ _ASSERTE(pProcess != NULL);
+ IfFailThrow(GetToken(&metadataToken));
+ InitEnCFieldInfo(&encField, fStatic, pObject, fieldToken, metadataToken);
+
+ // Go get this particular field.
+ pProcess->GetDAC()->GetEnCHangingFieldInfo(&encField, &fieldData, &fDacStatic);
+ _ASSERTE(fStatic == fDacStatic);
+
+ // Save the field results in our cache and get a stable pointer to the data
+ if (fStatic)
+ {
+ pInfo = m_hangingFieldsStatic.AddFieldInfo(&fieldData);
+ }
+ else
+ {
+ pInfo = pObject->GetHangingFieldTable()->AddFieldInfo(&fieldData);
+ }
+
+ // We should have a fresh copy of the data (don't want to return a pointer to data on our stack)
+ _ASSERTE((void *)pInfo != (void *)&fieldData);
+ _ASSERTE(pInfo->m_fFldIsStatic == (fStatic == TRUE));
+ _ASSERTE(pInfo->m_fldMetadataToken == fieldToken);
+
+ // Pass a pointer to the data out.
+ return pInfo;
+} // CordbClass::GetEnCFieldFromDac
+
+//-----------------------------------------------------------------------------
+// Internal helper to get a FieldData for fields added by EnC after the type
+// was loaded. Since object and MethodTable layout has already been fixed,
+// such added fields are "hanging" off some other data structure. For instance
+// fields, they're stored in a syncblock off the object. For static fields
+// they're stored off the EnCFieldDesc.
+//
+// The caller must have already determined this is a hanging field (i.e.
+// GetFieldInfo returned CORDBG_E_ENC_HANGING_FIELDF).
+//
+// Arguments:
+// input: fldToken - field of interest to get.
+// pObject - For instance fields, the Object instance containing the the sync-block.
+// For static fields (if this is being called from GetStaticFieldValue) object is NULL.
+// output: ppFieldData - the FieldData matching the fldToken.
+//
+// Returns:
+// S_OK on success, failure code otherwise.
+//-----------------------------------------------------------------------------
+HRESULT CordbClass::GetEnCHangingField(mdFieldDef fldToken,
+ FieldData **ppFieldData,
+ CordbObjectValue * pObject)
+{
+ FAIL_IF_NEUTERED(this);
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+
+ HRESULT hr = S_OK;
+ _ASSERTE(pObject == NULL || !pObject->IsNeutered() );
+
+ if (HasTypeParams())
+ {
+ _ASSERTE(!"EnC hanging field not yet implemented on constructed types!");
+ return E_FAIL;
+ }
+
+ // This must be a static field if no object was supplied
+ BOOL fStatic = (pObject == NULL);
+
+ // Look for cached field information
+ FieldData *pInfo = NULL;
+ if (fStatic)
+ {
+ // Static fields should _NOT_ be cleared, since they stick around. Thus
+ // the separate tables.
+ pInfo = m_hangingFieldsStatic.GetFieldInfo(fldToken);
+ }
+ else
+ {
+ // We must get new copies each time we call continue b/c we get the
+ // actual Object ptr from the left side, which can move during a GC.
+ pInfo = pObject->GetHangingFieldTable()->GetFieldInfo(fldToken);
+ }
+
+ // We've found a previously located entry
+ if (pInfo != NULL)
+ {
+ *ppFieldData = pInfo;
+ return S_OK;
+ }
+
+ // Field information not already available - go get it
+ EX_TRY
+ {
+
+ // We're not going to be able to get the instance-specific field
+ // if we can't get the instance.
+ if (!fStatic && pObject->GetInfo().objRefBad)
+ {
+ ThrowHR(CORDBG_E_INVALID_OBJECT);
+ }
+
+ *ppFieldData = GetEnCFieldFromDac(fStatic, pObject, fldToken);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// Get a FieldData (which rich information, including details about storage)
+// from a metadata token.
+//
+// Parameters:
+// fldToken - incoming metadata token specifying the field.
+// ppFieldData - OUT: resulting FieldData structure.
+//
+// Returns:
+// S_OK on success. else failure.
+//-----------------------------------------------------------------------------
+HRESULT CordbClass::GetFieldInfo(mdFieldDef fldToken, FieldData **ppFieldData)
+{
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+
+ Init();
+ return SearchFieldInfo(GetModule(), &m_classInfo.m_fieldList, m_token, fldToken, ppFieldData);
+}
+
+
+//-----------------------------------------------------------------------------
+// Search an array of FieldData (pFieldList) for a field (fldToken).
+// The FieldData array must match the class supplied by classToken, and live
+// in the supplied module.
+//
+// Internal helper used by CordbType::GetFieldInfo, CordbClass::GetFieldInfo
+//
+// Parameters:
+// module - module containing the class that the FieldData array matches.
+// pFieldList - array of fields to search through and the number of elements in
+// the array.
+// classToken - class that the data array matches. class must live in
+// the supplied moudle.
+// fldToken - metadata token of the field to search for. This field should be
+// on the class supplied by classToken.
+//
+// Returns:
+// CORDBG_E_ENC_HANGING_FIELD for "hanging fields" (fields added via Enc) (common error).
+// Returns S_OK, set ppFieldData = pointer into data array for matching field. (*retval)->m_fldMetadataToken == fldToken)
+// Throws on other errors.
+//-----------------------------------------------------------------------------
+/* static */
+HRESULT CordbClass::SearchFieldInfo(
+ CordbModule * pModule,
+ DacDbiArrayList<FieldData> * pFieldList,
+ mdTypeDef classToken,
+ mdFieldDef fldToken,
+ FieldData **ppFieldData
+)
+{
+ int i;
+
+ IMetaDataImport * pImport = pModule->GetMetaDataImporter(); // throws
+
+ HRESULT hr = S_OK;
+ for (i = 0; i < pFieldList->Count(); i++)
+ {
+ if ((*pFieldList)[i].m_fldMetadataToken == fldToken)
+ {
+ // If the storage for this field isn't yet available (i.e. it is newly added with EnC)
+ if (!(*pFieldList)[i].m_fFldStorageAvailable)
+ {
+ // If we're a static literal, then return special HR to let
+ // debugger know that it should look it up via the metadata.
+ // Check m_fFldIsStatic first b/c that's fast.
+ if ((*pFieldList)[i].m_fFldIsStatic)
+ {
+ if (IsFieldStaticLiteral(pImport, fldToken))
+ {
+ ThrowHR(CORDBG_E_VARIABLE_IS_ACTUALLY_LITERAL);
+ }
+ }
+
+ // This is a field added by EnC, caller needs to get instance-specific info.
+ return CORDBG_E_ENC_HANGING_FIELD;
+ }
+
+ *ppFieldData = &((*pFieldList)[i]);
+ return S_OK;
+ }
+ }
+
+ // Hmmm... we didn't find the field on this class. See if the field really belongs to this class or not.
+ mdTypeDef classTok;
+
+ hr = pImport->GetFieldProps(fldToken, &classTok, NULL, 0, NULL, NULL, NULL, 0, NULL, NULL, NULL);
+ IfFailThrow(hr);
+
+ if (classTok == (mdTypeDef) classToken)
+ {
+ // Well, the field belongs in this class. The assumption is that the Runtime optimized the field away.
+ ThrowHR(CORDBG_E_FIELD_NOT_AVAILABLE);
+ }
+
+ // Well, the field doesn't even belong to this class...
+ ThrowHR(E_INVALIDARG);
+}
+
diff --git a/src/debug/di/rsenumerator.hpp b/src/debug/di/rsenumerator.hpp
new file mode 100644
index 0000000000..eb7a225a5d
--- /dev/null
+++ b/src/debug/di/rsenumerator.hpp
@@ -0,0 +1,361 @@
+// 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.
+//*****************************************************************************
+//
+
+//
+// Implementation of CordbEnumerator, a templated COM enumeration pattern on Rs
+// types
+//*****************************************************************************
+
+#include "rspriv.h"
+
+// This CordbEnumerator is a templated enumerator from which COM enumerators for RS types can quickly be fashioned.
+// It uses a private array to store the items it enumerates over so by default it does not reference any
+// other RS type except the process it is associated with. The internal storage type does not need to match the
+// the item type exposed publically so that you can easily create an enumeration that holds RsSmartPtr<CordbThread>
+// but enumerates ICorDebugThread objects as an example. The enumerator has 4 templated parameters which must be
+// defined:
+// ElemType: this is the item type used for storage internal to the enumerator. For most Rs objects you will want
+// to use an RsSmartPtr<T> type to ensure the enumerator holds references to the objects it is
+// containing. The enumerator does not do any explicit Add/Release, it just copies items.
+// ElemPublicType: this is the item type exposed publically via the Next enumeration method. Typically this is
+// an ICorDebugX interface type but it can be anything.
+// EnumInterfaceType: this is the COM interface that the instantiated template will implement. It is expected that
+// this interface type follows the standard ICorDebug COM enumerator pattern, that the interface inherits
+// ICorDebugEnum and defines a strongly typed Next, enumerating over the ElemPublicType.
+// GetPublicType: this is a function which converts from ElemType -> ElemPublicType. It is used to produce the
+// elements that Next actually enumerates from the internal data the enumerator stores. Two conversion
+// functions are already defined here for convenience: QueryInterfaceConvert and IdentityConvert. If
+// neither of those suits your needs then you can define your own.
+//
+// Note: As of right now (10/13/08) most of the ICorDebug enumerators are not implemented using this base class,
+// however it might be good if we converged on this solution. There seems to be quite a bit of redundant and
+// one-off enumeration code that could be eliminated.
+//
+
+// A conversion function that converts from T to U by performing COM QueryInterface
+template<typename T, typename U>
+U * QueryInterfaceConvert(T obj)
+{
+ U* pPublic;
+ obj->QueryInterface(__uuidof(U), (void**) &pPublic);
+ return pPublic;
+}
+
+// A conversion identity function that just returns its argument
+template<typename T>
+T IdentityConvert(T obj)
+{
+ return obj;
+}
+
+// Constructor for an CordbEnumerator.
+// Arguments:
+// pProcess - the CordbProcess with which to associate this enumerator
+// items - the set of items which should be enumerated
+// countItems - the number of items in the array pointed to by items
+//
+// Note that the items are copied into an internal array, and no reference is kept to the users array.
+// Use RsSmartPtr types instead of Rs types directly to keep accurate ref counting for types which need it
+template< typename ElemType,
+ typename ElemPublicType,
+ typename EnumInterfaceType,
+ ElemPublicType (*GetPublicType)(ElemType)>
+CordbEnumerator<ElemType,
+ ElemPublicType,
+ EnumInterfaceType,
+ GetPublicType>::CordbEnumerator(CordbProcess *pProcess,
+ ElemType *items,
+ DWORD countItems) :
+CordbBase(pProcess, 0, enumCordbEnumerator),
+m_countItems(countItems),
+m_nextIndex(0)
+{
+ m_items = new ElemType[countItems];
+ for(UINT i = 0; i < countItems; i++)
+ {
+ m_items[i] = items[i];
+ }
+}
+
+// Constructor for an CordbEnumerator.
+// Arguments:
+// pProcess - the CordbProcess with which to associate this enumerator
+// items - the address of an array of items which should be enumerated
+// countItems - the number of items in the array pointed to by items
+//
+// Note that the items array is simply taken over, setting *items to NULL.
+// Use RsSmartPtr types instead of Rs types directly to keep accurate ref counting for types which need it
+template< typename ElemType,
+ typename ElemPublicType,
+ typename EnumInterfaceType,
+ ElemPublicType (*GetPublicType)(ElemType)>
+CordbEnumerator<ElemType,
+ ElemPublicType,
+ EnumInterfaceType,
+ GetPublicType>::CordbEnumerator(CordbProcess *pProcess,
+ ElemType **items,
+ DWORD countItems) :
+CordbBase(pProcess, 0, enumCordbEnumerator),
+m_nextIndex(0),
+m_countItems(countItems)
+{
+ _ASSERTE(items != NULL);
+ m_items = *items;
+ *items = NULL;
+}
+
+// Destructor
+template< typename ElemType,
+ typename ElemPublicType,
+ typename EnumInterfaceType,
+ ElemPublicType (*GetPublicType)(ElemType)>
+CordbEnumerator<ElemType,
+ ElemPublicType,
+ EnumInterfaceType,
+ GetPublicType>::~CordbEnumerator()
+{
+ // for now at least all of these enumerators should be in neuter lists and get neutered prior to destruction
+ _ASSERTE(IsNeutered());
+}
+
+// COM IUnknown::QueryInterface - provides ICorDebugEnum, IUnknown, and templated EnumInterfaceType
+//
+// Arguments:
+// riid - IID of the interface to query for
+// ppInterface - on output set to a pointer to the desired interface
+//
+// Return:
+// S_OK for the supported interfaces and E_NOINTERFACE otherwise
+template< typename ElemType,
+ typename ElemPublicType,
+ typename EnumInterfaceType,
+ ElemPublicType (*GetPublicType)(ElemType)>
+HRESULT CordbEnumerator<ElemType,
+ ElemPublicType,
+ EnumInterfaceType,
+ GetPublicType>::QueryInterface(REFIID riid, VOID** ppInterface)
+{
+ if(riid == __uuidof(ICorDebugEnum))
+ {
+ *ppInterface = static_cast<ICorDebugEnum*>(this);
+ AddRef();
+ return S_OK;
+ }
+ else if(riid == __uuidof(IUnknown))
+ {
+ *ppInterface = static_cast<IUnknown*>(static_cast<CordbBase*>(this));
+ AddRef();
+ return S_OK;
+ }
+ else if(riid == __uuidof(EnumInterfaceType))
+ {
+ *ppInterface = static_cast<EnumInterfaceType*>(this);
+ AddRef();
+ return S_OK;
+ }
+ else
+ {
+ return E_NOINTERFACE;
+ }
+}
+
+// COM IUnknown::AddRef()
+template< typename ElemType,
+ typename ElemPublicType,
+ typename EnumInterfaceType,
+ ElemPublicType (*GetPublicType)(ElemType)>
+ULONG CordbEnumerator<ElemType,
+ ElemPublicType,
+ EnumInterfaceType,
+ GetPublicType>::AddRef()
+{
+ return BaseAddRef();
+}
+
+// COM IUnknown::Release()
+template< typename ElemType,
+ typename ElemPublicType,
+ typename EnumInterfaceType,
+ ElemPublicType (*GetPublicType)(ElemType)>
+ULONG CordbEnumerator<ElemType,
+ ElemPublicType,
+ EnumInterfaceType,
+ GetPublicType>::Release()
+{
+ return BaseRelease();
+}
+
+// ICorDebugEnum::Clone
+// Makes a duplicate of the enumeration. The internal items are copied by value and there is no explicit reference
+// between the new and old enumerations.
+//
+// Arguments:
+// ppEnum - on output filled with a duplicate enumeration
+//
+// Return:
+// S_OK if the clone was created succesfully, otherwise some appropriate failing HRESULT
+template< typename ElemType,
+ typename ElemPublicType,
+ typename EnumInterfaceType,
+ ElemPublicType (*GetPublicType)(ElemType)>
+HRESULT CordbEnumerator<ElemType,
+ ElemPublicType,
+ EnumInterfaceType,
+ GetPublicType>::Clone(ICorDebugEnum **ppEnum)
+{
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppEnum, ICorDebugEnum **);
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ CordbEnumerator<ElemType, ElemPublicType, EnumInterfaceType, GetPublicType>* clone =
+ new CordbEnumerator<ElemType, ElemPublicType, EnumInterfaceType,GetPublicType>(
+ GetProcess(), m_items, m_countItems);
+ clone->QueryInterface(__uuidof(ICorDebugEnum), (void**)ppEnum);
+ }
+ EX_CATCH_HRESULT(hr)
+ {
+ }
+ return hr;
+}
+
+// ICorDebugEnum::GetCount
+// Gets the number of items in the the list that is being enumerated
+//
+// Arguments:
+// pcelt - on return the number of items being enumerated
+//
+// Return:
+// S_OK or failing HRESULTS for other error conditions
+template< typename ElemType,
+ typename ElemPublicType,
+ typename EnumInterfaceType,
+ ElemPublicType (*GetPublicType)(ElemType)>
+HRESULT CordbEnumerator<ElemType,
+ ElemPublicType,
+ EnumInterfaceType,
+ GetPublicType>::GetCount(ULONG *pcelt)
+{
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pcelt, ULONG *);
+
+ *pcelt = m_countItems;
+ return S_OK;
+}
+
+// ICorDebugEnum::Reset
+// Restarts the enumeration at the beginning of the list
+//
+// Return:
+// S_OK or failing HRESULTS for other error conditions
+template< typename ElemType,
+ typename ElemPublicType,
+ typename EnumInterfaceType,
+ ElemPublicType (*GetPublicType)(ElemType)>
+HRESULT CordbEnumerator<ElemType,
+ ElemPublicType,
+ EnumInterfaceType,
+ GetPublicType>::Reset()
+{
+ FAIL_IF_NEUTERED(this);
+
+ m_nextIndex = 0;
+ return S_OK;
+}
+
+// ICorDebugEnum::Skip
+// Skips over celt items in the enumeration, if celt is greater than the number of remaining items then all
+// remaining items are skipped.
+//
+// Arguments:
+// celt - number of items to be skipped
+//
+// Return:
+// S_OK or failing HRESULTS for other error conditions
+template< typename ElemType,
+ typename ElemPublicType,
+ typename EnumInterfaceType,
+ ElemPublicType (*GetPublicType)(ElemType)>
+HRESULT CordbEnumerator<ElemType,
+ ElemPublicType,
+ EnumInterfaceType,
+ GetPublicType>::Skip(ULONG celt)
+{
+ FAIL_IF_NEUTERED(this);
+
+ m_nextIndex += celt;
+ if(m_nextIndex > m_countItems)
+ {
+ m_nextIndex = m_countItems;
+ }
+ return S_OK;
+}
+
+// EnumInterfaceType::Next
+// Attempts to enumerate the next celt items by copying them in the items array. If fewer than celt
+// items remain all remaining items are enumerated. In either case pceltFetched indicates the number
+// of items actually fetched.
+//
+// Arguments:
+// celt - the number of enumerated items requested
+// items - an array of size celt where the enumerated items will be copied
+// pceltFetched - on return, the actual number of items enumerated
+//
+// Return:
+// S_OK if all items could be enumerated, S_FALSE if not all the requested items were enumerated,
+// failing HRESULTS for other error conditions
+template< typename ElemType,
+ typename ElemPublicType,
+ typename EnumInterfaceType,
+ ElemPublicType (*GetPublicType)(ElemType)>
+HRESULT CordbEnumerator<ElemType,
+ ElemPublicType,
+ EnumInterfaceType,
+ GetPublicType>::Next(ULONG celt,
+ ElemPublicType items[],
+ ULONG *pceltFetched)
+{
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(items, ElemInterfaceType *,
+ celt, true, true);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pceltFetched, ULONG *);
+
+ if ((pceltFetched == NULL) && (celt != 1))
+ {
+ return E_INVALIDARG;
+ }
+
+ ULONG countFetched;
+ for(countFetched = 0; countFetched < celt && m_nextIndex < m_countItems; countFetched++, m_nextIndex++)
+ {
+ items[countFetched] = GetPublicType(m_items[m_nextIndex]);
+ }
+
+ if(pceltFetched != NULL)
+ {
+ *pceltFetched = countFetched;
+ }
+
+ return countFetched == celt ? S_OK : S_FALSE;
+}
+
+// Neuter
+// neuters the enumerator and deletes the contents (the contents are not explicitly neutered though)
+template< typename ElemType,
+ typename ElemPublicType,
+ typename EnumInterfaceType,
+ ElemPublicType (*GetPublicType)(ElemType)>
+VOID CordbEnumerator<ElemType,
+ ElemPublicType,
+ EnumInterfaceType,
+ GetPublicType>::Neuter()
+{
+ delete [] m_items;
+ m_items = NULL;
+ m_countItems = 0;
+ m_nextIndex = 0;
+ CordbBase::Neuter();
+}
diff --git a/src/debug/di/rsfunction.cpp b/src/debug/di/rsfunction.cpp
new file mode 100644
index 0000000000..8621edcedc
--- /dev/null
+++ b/src/debug/di/rsfunction.cpp
@@ -0,0 +1,1191 @@
+// 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.
+//*****************************************************************************
+// File: rsfunction.cpp
+//
+
+//
+//*****************************************************************************
+#include "stdafx.h"
+
+// We have an assert in ceemain.cpp that validates this assumption
+#define FIELD_OFFSET_NEW_ENC_DB 0x07FFFFFB
+
+#include "winbase.h"
+#include "corpriv.h"
+
+/* ------------------------------------------------------------------------- *
+ * Function class
+ * ------------------------------------------------------------------------- */
+
+//-----------------------------------------------------------------------------
+// Constructor for CordbFunction class.
+// This represents an IL Function in the debuggee.
+// CordbFunction is 1:1 with IL method bodies.
+//
+// Parameters:
+// m - module containing this function. All functions live in a single module.
+// funcMetadataToken - the metadata token for this function (scoped to the module).
+// enCVersion - Enc Version number of this function (in sync with the module's
+// EnC version). Each edit to a function means a whole new IL method body,
+// and since CordbFunction is 1:1 with IL, that means a new CordbFunction instance.
+//-----------------------------------------------------------------------------
+CordbFunction::CordbFunction(CordbModule * m,
+ mdMethodDef funcMetadataToken,
+ SIZE_T enCVersion)
+ : CordbBase(m->GetProcess(), funcMetadataToken, enumCordbFunction), m_pModule(m), m_pClass(NULL),
+ m_pILCode(NULL),
+ m_nativeCode(NULL),
+ m_MDToken(funcMetadataToken),
+ m_dwEnCVersionNumber(enCVersion),
+ m_pPrevVersion(NULL),
+ m_fIsNativeImpl(kUnknownImpl),
+ m_fCachedMethodValuesValid(FALSE),
+ m_argCountCached(0),
+ m_fIsStaticCached(FALSE),
+ m_reJitILCodes(1)
+{
+ m_methodSigParserCached = SigParser(NULL, 0);
+
+ _ASSERTE(enCVersion >= CorDB_DEFAULT_ENC_FUNCTION_VERSION);
+}
+
+
+
+/*
+ A list of which resources owned by this object are accounted for.
+
+ UNKNOWN:
+ ICorDebugInfo::NativeVarInfo *m_nativeInfo;
+
+ HANDLED:
+ CordbModule *m_module; // Assigned w/o AddRef()
+ CordbClass *m_class; // Assigned w/o AddRef()
+*/
+
+//-----------------------------------------------------------------------------
+// CordbFunction destructor
+// All external resources, including references counts, should have been
+// released in Neuter(), so this should literally just delete memory or
+// or check that the object is already dead.
+//-----------------------------------------------------------------------------
+CordbFunction::~CordbFunction()
+{
+ // We should have been explicitly neutered before our internal ref went to 0.
+ _ASSERTE(IsNeutered());
+
+ // Since we've been neutered, we shouldn't have any References to release and
+ // our hash of JitInfos should be empty.
+ _ASSERTE(m_pILCode == NULL);
+ _ASSERTE(m_pPrevVersion == NULL);
+}
+
+//-----------------------------------------------------------------------------
+// CordbFunction::Neuter
+// Neuter releases all of the resources this object holds. CordbFunction
+// lives in a CordbModule, so Module neuter will neuter this.
+// See CordbBase::Neuter for further semantics.
+//
+//-----------------------------------------------------------------------------
+void CordbFunction::Neuter()
+{
+ // Neuter any/all CordbNativeCode & CordbILCode objects
+ if (m_pILCode != NULL)
+ {
+ m_pILCode->Neuter();
+ m_pILCode.Clear(); // this will internal release.
+ }
+
+ // Neuter & Release the Prev-Function list.
+ if (m_pPrevVersion != NULL)
+ {
+ m_pPrevVersion->Neuter();
+ m_pPrevVersion.Clear(); // this will internal release.
+ }
+
+ m_pModule = NULL;
+ m_pClass = NULL;
+
+ m_nativeCode.Clear();
+ m_reJitILCodes.NeuterAndClear(GetProcess()->GetProcessLock());
+
+ CordbBase::Neuter();
+}
+
+//-----------------------------------------------------------------------------
+// CordbFunction::QueryInterface
+// Public method to implement IUnknown::QueryInterface.
+// Has standard QI semantics.
+//-----------------------------------------------------------------------------
+HRESULT CordbFunction::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugFunction)
+ {
+ *pInterface = static_cast<ICorDebugFunction*>(this);
+ }
+ else if (id == IID_ICorDebugFunction2)
+ {
+ *pInterface = static_cast<ICorDebugFunction2*>(this);
+ }
+ else if (id == IID_ICorDebugFunction3)
+ {
+ *pInterface = static_cast<ICorDebugFunction3*>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugFunction*>(this));
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// CordbFunction::GetModule
+// Public method (implements ICorDebugFunction::GetModule).
+// Get the ICorDebugModule (external representation of a module) that this
+// Function is contained in. All functions live in exactly 1 module.
+// This is related to the 'CordbModule* GetModule()' method which returns the
+// internal module representation for the containing module.
+//
+// Parameters:
+// ppModule - out parameter to hold module.
+//
+// Return values:
+// S_OK iff *ppModule is set.
+//-----------------------------------------------------------------------------
+HRESULT CordbFunction::GetModule(ICorDebugModule **ppModule)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppModule, ICorDebugModule **);
+
+ HRESULT hr = S_OK;
+
+ // Module is set on creation, so just return it.
+ *ppModule = static_cast<ICorDebugModule*> (m_pModule);
+ m_pModule->ExternalAddRef();
+
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// CordbFunction::GetClass
+// Public function to get ICorDebugClass that this function is in.
+//
+// Parameters:
+// ppClass - out parameter holding which class this function lives in.
+//
+// Return value:
+// S_OK iff *ppClass is set.
+//-----------------------------------------------------------------------------
+HRESULT CordbFunction::GetClass(ICorDebugClass **ppClass)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppClass, ICorDebugClass **);
+ ATT_ALLOW_LIVE_DO_STOPGO(GetProcess());
+ *ppClass = NULL;
+
+ HRESULT hr = S_OK;
+
+ if (m_pClass == NULL)
+ {
+ // We're not looking for any particular version, just
+ // the class info. This seems like the best version to request
+ hr = InitParentClassOfFunction();
+
+ if (FAILED(hr))
+ goto LExit;
+ }
+
+ *ppClass = static_cast<ICorDebugClass*> (m_pClass);
+
+LExit:
+ if (FAILED(hr))
+ return hr;
+
+ if (*ppClass)
+ {
+ m_pClass->ExternalAddRef();
+ return S_OK;
+ }
+ else
+ return S_FALSE;
+}
+
+//-----------------------------------------------------------------------------
+// CordbFunction::GetToken
+// Public function to get the metadata token for this function.
+// This is a MethodDef, which is scoped to a module.
+//
+// Parameters:
+// pMemberDef - out parameter to hold token.
+//
+// Return values:
+// S_OK if pMemberDef is set.
+//-----------------------------------------------------------------------------
+HRESULT CordbFunction::GetToken(mdMethodDef *pMemberDef)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pMemberDef, mdMethodDef *);
+
+
+ // Token is set on creation, so no updating needed.
+ CONSISTENCY_CHECK_MSGF((TypeFromToken(m_MDToken) == mdtMethodDef),
+ ("CordbFunction token (%08x) is not a mdtMethodDef. This=%p", m_MDToken, this));
+
+ *pMemberDef = m_MDToken;
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// CordbFunction::GetILCode
+// Public function to get an ICorDebugCode object for the IL code in
+// this function.
+// If we EnC, we get a new ICorDebugFunction, so the IL code & function
+// should be 1:1.
+//
+// Parameters:
+// ppCode - out parameter to hold the code object.
+//
+// Return value:
+// S_OK iff *ppCode != NULL. Else error.
+//-----------------------------------------------------------------------------
+HRESULT CordbFunction::GetILCode(ICorDebugCode ** ppCode)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppCode, ICorDebugCode **);
+ ATT_ALLOW_LIVE_DO_STOPGO(GetProcess());
+
+ *ppCode = NULL;
+ HRESULT hr = S_OK;
+
+ // Get the code object.
+ CordbILCode * pCode = NULL;
+ hr = GetILCode(&pCode);
+ _ASSERTE((pCode == NULL) == FAILED(hr));
+
+ if (FAILED(hr))
+ return hr;
+
+ *ppCode = (ICorDebugCode *)pCode;
+
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// CordbFunction::GetNativeCode
+// Public API (ICorDebugFunction::GetNativeCode) to get the native code for
+// this function.
+// Note that this gets a pretty much random version of the native code when the
+// function is a generic method that gets JITted more than once, e.g. for generics.
+// Use EnumerateNativeCode instead in that case.
+//
+// Parameters:
+// ppCode - out parameter yeilding the native code object.
+//
+// Returns:
+// S_OK iff *ppCode is set.
+// CORDBG_E_CODE_NOT_AVAILABLE if there is no native code. This is common
+// if the function is not yet jitted.
+//-----------------------------------------------------------------------------
+HRESULT CordbFunction::GetNativeCode(ICorDebugCode **ppCode)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppCode, ICorDebugCode **);
+ ATT_ALLOW_LIVE_DO_STOPGO(GetProcess());
+
+ HRESULT hr = S_OK;
+
+ // Make sure native code is updated before we go searching it.
+ hr = InitNativeCodeInfo();
+ if (FAILED(hr))
+ return hr;
+
+ // Generic methods may be jitted multiple times for different native instantiations,
+ // and so have 1:n relationship between IL:Native. CordbFunction is 1:1 with IL,
+ // CordbNativeCode is 1:1 with native.
+ // The interface here only lets us return 1 CordbNativeCode object, so we are
+ // returning an arbitrary one
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+ _ASSERTE(m_nativeCode == NULL || m_nativeCode->GetVersion() == m_dwEnCVersionNumber);
+
+ if (m_nativeCode == NULL)
+ {
+ hr = CORDBG_E_CODE_NOT_AVAILABLE; // This is the case for an unjitted function,
+ // and so it will be very common.
+ }
+ else
+ {
+ m_nativeCode->ExternalAddRef();
+ *ppCode = m_nativeCode;
+ hr = S_OK;
+ }
+
+ return hr;
+}
+
+
+//-----------------------------------------------------------------------------
+// CordbFunction::GetCode
+// Internal method to get the IL code for this function. Each CordbFunction is
+// 1:1 with IL, so there is a unique IL Code object to hand out.
+//
+// Parameters:
+// ppCode - out parameter, the IL code object for this function. This should
+// be set to NULL on entry.
+// Return value:
+// S_OK iff *ppCode is set. Else error.
+//-----------------------------------------------------------------------------
+HRESULT CordbFunction::GetILCode(CordbILCode ** ppCode)
+{
+ FAIL_IF_NEUTERED(this);
+ INTERNAL_SYNC_API_ENTRY(GetProcess()); //
+ VALIDATE_POINTER_TO_OBJECT(ppCode, ICorDebugCode **);
+
+ _ASSERTE(*ppCode == NULL && "Common source of errors is getting addref'd copy here and never Release()ing it");
+ *ppCode = NULL;
+
+ // Its okay to do this if the process is not sync'd.
+ CORDBRequireProcessStateOK(GetProcess());
+
+ // Fetch all information about this function.
+ HRESULT hr = S_OK;
+ CordbILCode * pCode = NULL;
+
+ hr = GetILCodeAndSigToken();
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ // It's possible that m_ILCode will still be NULL.
+ pCode = m_pILCode;
+
+ if (pCode != NULL)
+ {
+ pCode->ExternalAddRef();
+ *ppCode = pCode;
+
+ return hr;
+ }
+ else
+ {
+ return CORDBG_E_CODE_NOT_AVAILABLE;
+ }
+} // CordbFunction::GetCode
+
+//-----------------------------------------------------------------------------
+// CordbFunction::CreateBreakpoint
+// Implements ICorDebugFunction::CreateBreakpoint
+// Creates a breakpoint at IL offset 0 (which is after the prolog) of the function.
+// The function does not need to be jitted yet.
+//
+// Parameters:
+// ppBreakpoint - out parameter for newly created breakpoint object.
+//
+// Return:
+// S_OK - on success. Else error.
+//----------------------------------------------------------------------------
+HRESULT CordbFunction::CreateBreakpoint(ICorDebugFunctionBreakpoint **ppBreakpoint)
+{
+ HRESULT hr = S_OK;
+
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppBreakpoint, ICorDebugFunctionBreakpoint **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ RSExtSmartPtr<ICorDebugCode> pCode;
+
+ // Use the IL code so that we stop after the prolog
+ hr = GetILCode(&pCode);
+
+ if (SUCCEEDED(hr))
+ {
+ hr = pCode->CreateBreakpoint(0, ppBreakpoint);
+ }
+
+ return hr;
+}
+
+#ifdef EnC_SUPPORTED
+//-----------------------------------------------------------------------------
+// CordbFunction::MakeOld
+// Internal method to do any cleanup necessary when a Function is no longer
+// the most current.
+//-----------------------------------------------------------------------------
+void CordbFunction::MakeOld()
+{
+ if (m_pILCode != NULL)
+ {
+ m_pILCode->MakeOld();
+ }
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// CordbFunction::GetLocalVarSigToken
+// Public function (implements ICorDebugFunction::GetLocalVarSigToken) to
+// get signature token.
+//
+// Parameters:
+// pmdSig - out parameter to hold signature token, which is scoped to the
+// function's module.
+//
+// Return value:
+// S_OK if pmdSig is set.
+//-----------------------------------------------------------------------------
+HRESULT CordbFunction::GetLocalVarSigToken(mdSignature *pmdSig)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pmdSig, mdSignature *);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ // This will initialize the token.
+ HRESULT hr = GetILCodeAndSigToken();
+ if (FAILED(hr))
+ return hr;
+
+ *pmdSig = GetILCode()->GetLocalVarSigToken();
+
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// CordbFunction::GetCurrentVersionNumber
+// Public method for ICorDebugFunction::GetCurrentVersionNumber.
+// Gets the most recent (highest) EnC version number of this Function.
+// See CordbModule for EnC version number semantics.
+//
+// Parameters
+// pnCurrentVersion - out parameter to hold the version number.
+//
+// Returns:
+// S_OK on success.
+//-----------------------------------------------------------------------------
+HRESULT CordbFunction::GetCurrentVersionNumber(ULONG32 *pnCurrentVersion)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pnCurrentVersion, ULONG32 *);
+
+ HRESULT hr = S_OK;
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+
+ // the most current version will always be the one found.
+ CordbFunction* curFunc = m_pModule->LookupFunctionLatestVersion(m_MDToken);
+
+ // will always find at least ourself
+ PREFIX_ASSUME(curFunc != NULL);
+
+ *pnCurrentVersion = (ULONG32)(curFunc->m_dwEnCVersionNumber);
+
+#ifdef EnC_SUPPORTED
+ _ASSERTE( *pnCurrentVersion >= this->m_dwEnCVersionNumber );
+#else
+ _ASSERTE(*pnCurrentVersion == CorDB_DEFAULT_ENC_FUNCTION_VERSION);
+#endif
+
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// CordbFunction::GetVersionNumber
+// Public method for ICorDebugFunction2::GetVersionNumber.
+// Gets the EnC version number of this specific Function instance.
+// See CordbModule for EnC version number semantics.
+//
+// Parameters
+// pnVersion - out parameter to hold the version number.
+//
+// Returns:
+// S_OK on success.
+//-----------------------------------------------------------------------------
+HRESULT CordbFunction::GetVersionNumber(ULONG32 *pnVersion)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pnVersion, ULONG32 *);
+
+ // This API existed in V1.0 but wasn't implemented. It needs V2 support to work.
+ if (! this->GetProcess()->SupportsVersion(ver_ICorDebugFunction2))
+ {
+ return E_NOTIMPL;
+ }
+
+ *pnVersion = (ULONG32)m_dwEnCVersionNumber;
+
+#ifdef EnC_SUPPORTED
+ _ASSERTE(*pnVersion >= CorDB_DEFAULT_ENC_FUNCTION_VERSION);
+#else
+ _ASSERTE(*pnVersion == CorDB_DEFAULT_ENC_FUNCTION_VERSION);
+#endif
+
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// CordbFunction::GetVersionNumber
+// Public method for ICorDebugFunction2::GetVersionNumber.
+// Gets the EnC version number of this specific Function instance.
+// See CordbModule for EnC version number semantics.
+//
+// Parameters
+// pnVersion - out parameter to hold the version number.
+//
+// Returns:
+// S_OK on success.
+//-----------------------------------------------------------------------------
+HRESULT CordbFunction::GetActiveReJitRequestILCode(ICorDebugILCode **ppReJitedILCode)
+{
+ HRESULT hr = S_OK;
+ VALIDATE_POINTER_TO_OBJECT(ppReJitedILCode, ICorDebugILCode **);
+ PUBLIC_API_BEGIN(this);
+ {
+ *ppReJitedILCode = NULL;
+
+ VMPTR_ReJitInfo vmReJitInfo = VMPTR_ReJitInfo::NullPtr();
+ GetProcess()->GetDAC()->GetReJitInfo(GetModule()->m_vmModule, m_MDToken, &vmReJitInfo);
+ if (!vmReJitInfo.IsNull())
+ {
+ VMPTR_SharedReJitInfo vmSharedReJitInfo = VMPTR_SharedReJitInfo::NullPtr();
+ GetProcess()->GetDAC()->GetSharedReJitInfo(vmReJitInfo, &vmSharedReJitInfo);
+ RSSmartPtr<CordbReJitILCode> pILCode;
+ IfFailThrow(LookupOrCreateReJitILCode(vmSharedReJitInfo, &pILCode));
+ IfFailThrow(pILCode->QueryInterface(IID_ICorDebugILCode, (void**)ppReJitedILCode));
+ }
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+// determine whether we have a native-only implementation
+// Arguments:
+// Input: none (we use information in various data members of this instance of CordbFunction: m_isNativeImpl,
+// m_pIMImport, m_EnCCount)
+// Output none, although we will set m_isNativeImpl to true iff the function has a native-only implementation
+
+void CordbFunction::InitNativeImpl()
+{
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+
+ // Bail now if we've already discovered that this function is implemented natively as part of the Runtime.
+ if (m_fIsNativeImpl != kUnknownImpl)
+ {
+ return;
+ }
+
+ // If we don't have a methodToken then we can't figure out what kind of function this is. This includes functions
+ // such as LCG and ILStubs. In the past we created codepaths that avoided ever calling in here in the common case
+ // and there would have been asserts and exceptions in the uncommon cases. Now I have just officially let the
+ // function handle staying as an 'unknown' impl. In such a state it provides no IL, no sigtoken, no native code, and
+ // no parent class.
+ if (m_MDToken == mdMethodDefNil)
+ {
+ return;
+ }
+
+ // Figure out if this function is implemented as a native part of the Runtime. If it is, then this ICorDebugFunction
+ // is just a container for certain Right Side bits of info, i.e., module, class, token, etc.
+ DWORD attrs;
+ DWORD implAttrs;
+ ULONG ulRVA;
+ BOOL isDynamic;
+
+ IfFailThrow(GetModule()->GetMetaDataImporter()->GetMethodProps(m_MDToken, NULL, NULL, 0, NULL,
+ &attrs, NULL, NULL, &ulRVA, &implAttrs));
+ isDynamic = GetModule()->IsDynamic();
+
+ // A method has associated IL if its RVA is non-zero, unless it is a dynamic module
+ // @todo : if RVA is 0 and function has been EnC'd then it isn't native. Remove isEnC
+ // condition when the compilers stop generating 0 for an RVA.
+ BOOL isEnC = (GetModule()->m_EnCCount != 0);
+ if (IsMiNative(implAttrs) || ((isDynamic == FALSE) && (isEnC == FALSE) && (ulRVA == 0)))
+ {
+
+ m_fIsNativeImpl = kNativeOnly;
+ }
+ else
+ {
+ m_fIsNativeImpl = kHasIL;
+ }
+
+} // CordbFunction::GetProcessAndCheckForNativeImpl
+
+// Returns the function's ILCode and SigToken
+// Arguments:
+// Input: none (required info comes from various data members of this instance of CordbFunction
+// Output (required):
+// none explicit, but this will:
+// construct a new instance of CordbILCode and assign it to m_pILCode
+
+HRESULT CordbFunction::GetILCodeAndSigToken()
+{
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+
+ CordbProcess * pProcess = m_pModule->GetProcess();
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+
+ // ensure that we're not trying to get information about a native-only function
+ InitNativeImpl();
+ if (m_fIsNativeImpl == kNativeOnly || m_fIsNativeImpl == kUnknownImpl)
+ {
+ ThrowHR(CORDBG_E_FUNCTION_NOT_IL);
+ }
+
+ if (m_pILCode == NULL)
+ {
+ // we haven't gotten the information previously
+
+ _ASSERTE(pProcess != NULL);
+
+ // This target buffer and mdSignature might never have their values changed from the
+ // initial ones if the dump target is missing memory. TargetBuffer has a default
+ // constructor to zero its data and localVarSigToken is explicitly inited.
+ TargetBuffer codeInfo;
+ mdSignature localVarSigToken = mdSignatureNil;
+ SIZE_T currentEnCVersion;
+
+ {
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+
+ // In the dump case we may not have the backing memory for this. In such a case
+ // we construct an empty ILCode object and leave the signatureToken as mdSignatureNil.
+ // It may also be the case that the memory we read from the dump be inconsistent (huge method size)
+ // and we also fallback on creating an empty ILCode object.
+ // See issue DD 273199 for cases where IL and NGEN metadata mismatch (different RVAs).
+ ALLOW_DATATARGET_MISSING_OR_INCONSISTENT_MEMORY(
+ pProcess->GetDAC()->GetILCodeAndSig(m_pModule->GetRuntimeDomainFile(),
+ m_MDToken,
+ &codeInfo,
+ &localVarSigToken);
+ );
+
+ currentEnCVersion = m_pModule->LookupFunctionLatestVersion(m_MDToken)->m_dwEnCVersionNumber;
+ }
+
+ LOG((LF_CORDB,LL_INFO10000,"R:CF::GICAST: looking for IL code, version 0x%x\n", currentEnCVersion));
+
+ if (m_pILCode == NULL)
+ {
+ LOG((LF_CORDB,LL_INFO10000,"R:CF::GICAST: not found, creating...\n"));
+ if(codeInfo.pAddress == 0)
+ {
+ LOG((LF_CORDB,LL_INFO10000,"R:CF::GICAST: memory was missing - empty ILCode being created\n"));
+ }
+
+ // If everything succeeded, we set the IL code object (it's an outparam here).
+ _ASSERTE(m_pILCode == NULL);
+ m_pILCode.Assign(new(nothrow)CordbILCode(this,
+ codeInfo,
+ currentEnCVersion,
+ localVarSigToken));
+
+ if (m_pILCode == NULL)
+ {
+ ThrowHR(E_OUTOFMEMORY);
+ }
+ }
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+} // CordbFunction::GetILCodeAndSigToken
+
+
+// Get the metadata token for the class to which a function belongs.
+// Arguments:
+// Input:
+// funcMetadataToken - the metadata token for the method
+// Output (required):
+// classMetadataToken - the metadata token for the class to which the method belongs
+mdTypeDef CordbFunction::InitParentClassOfFunctionHelper(mdToken funcMetadataToken)
+{
+ // Get the class this method is in.
+ mdToken tkParent = mdTypeDefNil;
+ IfFailThrow(GetModule()->GetInternalMD()->GetParentToken(funcMetadataToken, &tkParent));
+ _ASSERTE(TypeFromToken(tkParent) == mdtTypeDef);
+
+ return tkParent;
+} // CordbFunction::InitParentClassOfFunctionHelper
+
+// Get the class to which a given function belongs
+// Arguments:
+// Input: none (required information comes from data members of this instance of CordbFunction)
+// Output (required): none, but sets m_pClass
+HRESULT CordbFunction::InitParentClassOfFunction()
+{
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+
+ CordbProcess * pProcess = m_pModule->GetProcess();
+ (void)pProcess; //prevent "unused variable" error from GCC
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+
+ // ensure that we're not trying to get information about a native-only function
+ InitNativeImpl();
+ if (m_fIsNativeImpl == kNativeOnly || m_fIsNativeImpl == kUnknownImpl)
+ {
+ ThrowHR(CORDBG_E_FUNCTION_NOT_IL);
+ }
+
+ mdTypeDef classMetadataToken;
+ VMPTR_DomainFile vmDomainFile = m_pModule->GetRuntimeDomainFile();
+
+ classMetadataToken = InitParentClassOfFunctionHelper(m_MDToken);
+
+ if ((m_pClass == NULL) && (classMetadataToken != mdTypeDefNil))
+ {
+ // we haven't gotten the information previously but we have it now
+
+ _ASSERTE(pProcess != NULL);
+
+ CordbAssembly *pAssembly = m_pModule->GetCordbAssembly();
+ PREFIX_ASSUME(pAssembly != NULL);
+
+ CordbModule* pClassModule = pAssembly->GetAppDomain()->LookupOrCreateModule(vmDomainFile);
+ PREFIX_ASSUME(pClassModule != NULL);
+
+ CordbClass *pClass;
+ hr = pClassModule->LookupOrCreateClass(classMetadataToken, &pClass);
+
+ IfFailThrow(hr);
+
+ _ASSERTE(pClass != NULL);
+ m_pClass = pClass;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+
+} // CordbFunction::InitParentClassOfFunction
+
+// Get information about the native code blob for a function and add it to m_nativeCodeTable
+// Arguments:
+// Input: none, but we use some data members of this instance of CordbFunction
+// Output: standard HRESULT value
+// Notes: Apart from the HRESULT, this function will build a new instance of CordbNativeCode and
+// add it to the hash table of CordbNativeCodes for this function, unless we have done that
+// previously
+
+HRESULT CordbFunction::InitNativeCodeInfo()
+{
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+
+ CordbProcess * pProcess = m_pModule->GetProcess();
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+
+ // ensure that we're not trying to get information about a native-only function
+ InitNativeImpl();
+ if (m_fIsNativeImpl == kNativeOnly || m_fIsNativeImpl == kUnknownImpl)
+ {
+ ThrowHR(CORDBG_E_FUNCTION_NOT_IL);
+ }
+
+ _ASSERTE(pProcess != NULL);
+
+ // storage for information retrieved from the DAC. This is cleared in the constructor, so it
+ // won't contain garbage if we don't use the DAC to retrieve information we already got before.
+ NativeCodeFunctionData codeInfo;
+
+ if (m_nativeCode == NULL)
+ {
+ // Get the native code information from the DAC
+ // PERF: this call is potentially more costly than it needs to be
+ // All we actually need is the start address and method desc which are cheap to get relative
+ // to some of the other members. So far this doesn't appear to be a perf hotspot, but if it
+ // shows up in some scenario it wouldn't be too hard to improve it
+ pProcess->GetDAC()->GetNativeCodeInfo(m_pModule->GetRuntimeDomainFile(), m_MDToken, &codeInfo);
+ }
+
+ // populate the m_nativeCode pointer with the code info we found
+ if (codeInfo.IsValid())
+ {
+ m_nativeCode.Assign(m_pModule->LookupOrCreateNativeCode(m_MDToken, codeInfo.vmNativeCodeMethodDescToken,
+ codeInfo.m_rgCodeRegions[kHot].pAddress));
+ }
+
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+} // CordbFunction::InitNativeCodeInfo
+
+//-----------------------------------------------------------------------------
+// CordbFunction::SetJMCStatus
+// Public method (implements ICorDebugFunction2::SetJMCStatus).
+// Set the JMC (eg, "User code" vs. "Non-user code") status of this function.
+//
+// Parameters:
+// fIsUserCode - true to set this Function to JMC, else False.
+//
+// Returns:
+// S_OK if successfully updated JMC status.
+//-----------------------------------------------------------------------------
+HRESULT CordbFunction::SetJMCStatus(BOOL fIsUserCode)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ HRESULT hr = S_OK;
+
+ LOG((LF_CORDB,LL_INFO10000,"CordbFunction::SetJMCStatus to %d, (token=0x%08x, module=%p)\n",
+ fIsUserCode, m_MDToken, m_pModule));
+
+ // Make sure the Left-Side is in a good state.
+ FAIL_IF_NEUTERED(this);
+ CordbProcess* pProcess = m_pModule->GetProcess();
+ ATT_REQUIRE_STOPPED_MAY_FAIL(pProcess);
+
+
+
+ // Send an event to the LS to keep it updated.
+
+ // Validation - JMC Steppers don't have defined behavior if
+ // JMC method status gets toggled underneath them. However, we don't have
+ // a good way of verifying which methods are of interest to a JMC stepper.
+ // Having outstanding JMC steppers is dangerous here, but still can be
+ // done safely.
+ // Furthermore, debuggers may want to lazily set JMC status (such as when
+ // code is loaded), which may happen while we have outstanding steppers.
+
+
+ DebuggerIPCEvent event;
+ pProcess->InitIPCEvent(&event, DB_IPCE_SET_METHOD_JMC_STATUS, true, m_pModule->GetAppDomain()->GetADToken());
+ event.SetJMCFunctionStatus.vmDomainFile = m_pModule->GetRuntimeDomainFile();
+ event.SetJMCFunctionStatus.funcMetadataToken = m_MDToken;
+ event.SetJMCFunctionStatus.dwStatus = fIsUserCode;
+
+
+ // Note: two-way event here...
+ hr = pProcess->m_cordb->SendIPCEvent(pProcess, &event, sizeof(DebuggerIPCEvent));
+
+ // Stop now if we can't even send the event.
+ if (!SUCCEEDED(hr))
+ return hr;
+
+ _ASSERTE(event.type == DB_IPCE_SET_METHOD_JMC_STATUS_RESULT);
+
+ return event.hr;
+}
+
+//-----------------------------------------------------------------------------
+// CordbFunction::GetJMCStatus
+// Public function (implements ICorDebugFunction2::GetJMCStatus)
+// Get the JMC status of this function.
+//
+// Parameters:
+// pfIsUserCode - out parameter describing whether this method is user code.
+// true iff this function is user code, else false.
+//
+// Return:
+// returns S_OK if *pfIsUserCode is set.
+//-----------------------------------------------------------------------------
+HRESULT CordbFunction::GetJMCStatus(BOOL * pfIsUserCode)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ VALIDATE_POINTER_TO_OBJECT(pfIsUserCode, BOOL*);
+
+
+ _ASSERTE(pfIsUserCode != NULL);
+ if (pfIsUserCode == NULL)
+ return E_INVALIDARG;
+
+ // <TODO> @perf - If we know that we haven't updated the JMC status on anything
+ // in this module (we could keep a dirty flag), then we can just cache
+ // the jmc status and not send an event to query each time. </TODO>
+
+ // Make sure the process is in a sane state.
+ CordbProcess* pProcess = m_pModule->GetProcess();
+ _ASSERTE(pProcess != NULL);
+
+ // Ask the left-side if a method is user code or not.
+ DebuggerIPCEvent event;
+ pProcess->InitIPCEvent(&event, DB_IPCE_GET_METHOD_JMC_STATUS, true, m_pModule->GetAppDomain()->GetADToken());
+ event.SetJMCFunctionStatus.vmDomainFile = m_pModule->GetRuntimeDomainFile();
+ event.SetJMCFunctionStatus.funcMetadataToken = m_MDToken;
+
+
+ // Note: two-way event here...
+ HRESULT hr = pProcess->m_cordb->SendIPCEvent(pProcess, &event, sizeof(DebuggerIPCEvent));
+
+ // Stop now if we can't even send the event.
+ if (!SUCCEEDED(hr))
+ return hr;
+
+ _ASSERTE(event.type == DB_IPCE_GET_METHOD_JMC_STATUS_RESULT);
+
+ // update our internal copy of the status.
+ BOOL fIsUserCode = event.SetJMCFunctionStatus.dwStatus;
+
+ *pfIsUserCode = fIsUserCode;
+
+ return event.hr;
+}
+
+
+/*
+ * CordbFunction::GetSig
+ *
+ * Get the method's full metadata signature. This may be cached, but for dynamic modules we'll always read it from
+ * the metadata. This function also returns the argument count and whether or not the method is static.
+ *
+ * Parameters:
+ * pMethodSigParser - OUT: the signature parser class to use for all signature parsing.
+ * pFunctionArgCount - OUT: the number of arguments the method takes.
+ * pFunctionIsStatic - OUT: TRUE if the method is static, FALSE if it is not..
+ *
+ * Returns:
+ * HRESULT for success or failure.
+ *
+ */
+HRESULT CordbFunction::GetSig(SigParser *pMethodSigParser,
+ ULONG *pFunctionArgCount,
+ BOOL *pFunctionIsStatic)
+{
+ INTERNAL_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ HRESULT hr = S_OK;
+
+ // If the module is dynamic, there had better not be a cached locals signature.
+ _ASSERTE(!GetModule()->IsDynamic() || !m_fCachedMethodValuesValid);
+
+ // If the method signature cache is null, then go read the signature from the
+ // matadata. For dynamic methods we never cache the parser because the method
+ // may change and the cached value will not match.
+ if (!m_fCachedMethodValuesValid)
+ {
+ PCCOR_SIGNATURE functionSignature;
+ ULONG size;
+ DWORD methodAttr = 0;
+ ULONG argCount;
+
+ EX_TRY // @dbgtotod - push this up
+ {
+ hr = GetModule()->GetMetaDataImporter()->GetMethodProps(m_MDToken, NULL, NULL, 0, NULL,
+ &methodAttr, &functionSignature, &size, NULL, NULL);
+ }
+ EX_CATCH_HRESULT(hr);
+ IfFailRet(hr);
+
+ SigParser sigParser = SigParser(functionSignature, size);
+
+ IfFailRet(sigParser.SkipMethodHeaderSignature(&argCount));
+
+ // If this function is not static, then we've got one extra arg.
+ BOOL isStatic = (methodAttr & mdStatic) != 0;
+
+ if (!isStatic)
+ {
+ argCount++;
+ }
+
+ // Cache the value for non-dynamic modules, so this is faster later.
+ if (!GetModule()->IsDynamic())
+ {
+ m_methodSigParserCached = sigParser;
+ m_argCountCached = argCount;
+ m_fIsStaticCached = isStatic;
+ m_fCachedMethodValuesValid = TRUE;
+ }
+ else
+ {
+ // This is the Dynamic method case, so we can't cache. Just leave fields blank
+ // and set out-parameters based off locals.
+ if (pMethodSigParser != NULL)
+ {
+ *pMethodSigParser = sigParser;
+ }
+
+ if (pFunctionArgCount != NULL)
+ {
+ *pFunctionArgCount = argCount;
+ }
+
+ if (pFunctionIsStatic != NULL)
+ {
+ *pFunctionIsStatic = isStatic;
+ }
+ }
+ }
+
+ if (m_fCachedMethodValuesValid)
+ {
+ //
+ // Retrieve values from cache
+ //
+
+ if (pMethodSigParser != NULL)
+ {
+ //
+ // Give them a new instance of the cached value
+ //
+ *pMethodSigParser = m_methodSigParserCached;
+ }
+
+ if (pFunctionArgCount != NULL)
+ {
+ *pFunctionArgCount = m_argCountCached;
+ }
+
+ if (pFunctionIsStatic != NULL)
+ {
+ *pFunctionIsStatic = m_fIsStaticCached;
+ }
+
+ }
+
+ //
+ // We should never have a cached value for in a dynamic module.
+ //
+ CONSISTENCY_CHECK_MSGF(((GetModule()->IsDynamic() && !m_fCachedMethodValuesValid) ||
+ (!GetModule()->IsDynamic() && m_fCachedMethodValuesValid)),
+ ("No dynamic modules should be cached! Module=%p This=%p", GetModule(), this));
+
+ return hr;
+}
+
+
+//-----------------------------------------------------------------------------
+// CordbFunction::GetArgumentType
+// Internal method. Given an 0-based IL argument number, return its type.
+// This can't access hidden parameters.
+//
+// Parameters:
+// dwIndex - 0-based index for IL argument number. For instance types,
+// 'this' argument is #0. For static types, first argument is #0.
+// pInst - instantiation information if this is a generic function. Eg,
+// if function is List<T>, inst describes T.
+// ppResultType - out parameter, yields to CordbType of the argument.
+//
+// Return:
+// S_OK on success.
+//
+HRESULT CordbFunction::GetArgumentType(DWORD dwIndex,
+ const Instantiation * pInst,
+ CordbType ** ppResultType)
+{
+ FAIL_IF_NEUTERED(this);
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+
+ HRESULT hr = S_OK;
+
+ // Get the method's signature, which contains the types for all the arguments.
+ SigParser sigParser;
+ ULONG cMethodArgs;
+ BOOL fMethodIsStatic;
+
+ IfFailRet(GetSig(&sigParser, &cMethodArgs, &fMethodIsStatic));
+
+ // Check the index
+ if (dwIndex >= cMethodArgs)
+ {
+ return E_INVALIDARG;
+ }
+
+ if (!fMethodIsStatic)
+ {
+ if (dwIndex == 0)
+ {
+ // Return the signature for the 'this' pointer for the
+ // class this method is in.
+ return m_pClass->GetThisType(pInst, ppResultType);
+ }
+ else
+ {
+ dwIndex--;
+ }
+ }
+
+ // Run the signature and find the required argument.
+ for (unsigned int i = 0; i < dwIndex; i++)
+ {
+ IfFailRet(sigParser.SkipExactlyOne());
+ }
+
+ hr = CordbType::SigToType(m_pModule, &sigParser, pInst, ppResultType);
+
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// CordbFunction::NotifyCodeCreated
+// Internal method. Allows CordbFunctions to get access to a canonical native code entry
+// that they will return when asked for native code. The 1:1 mapping between
+// function and code was invalidated by generics but debuggers continue to use
+// the old API. When they do we need to have some code to hand them back even
+// though it is an arbitrary instantiation. Note that that the cannonical code
+// here is merely the first one that a user inspects... it is not guaranteed to
+// be the same in each debugging session but once set it will never change. It is
+// also definately NOT guaranteed to be the instantation over the runtime type
+// __Canon.
+//
+// Parameters:
+// nativeCode - the code which corresponds to this function
+//
+VOID CordbFunction::NotifyCodeCreated(CordbNativeCode* nativeCode)
+{
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+ CONTRACTL
+ {
+ NOTHROW;
+ }
+ CONTRACTL_END;
+
+ // Grab this native code as the canonical one if we don't already
+ // have a canonical entry
+ if(m_nativeCode == NULL)
+ m_nativeCode.Assign(nativeCode);
+}
+
+
+//-----------------------------------------------------------------------------
+// LookupOrCreateReJitILCode finds an existing version of CordbReJitILCode in the given function.
+// If the CordbReJitILCode doesn't exist, it creates it.
+//
+//
+HRESULT CordbFunction::LookupOrCreateReJitILCode(VMPTR_SharedReJitInfo vmSharedReJitInfo, CordbReJitILCode** ppILCode)
+{
+ INTERNAL_API_ENTRY(this);
+
+ HRESULT hr = S_OK;
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+
+ CordbReJitILCode * pILCode = m_reJitILCodes.GetBase(VmPtrToCookie(vmSharedReJitInfo));
+
+ // special case non-existance as need to add to the hash table too
+ if (pILCode == NULL)
+ {
+ // we don't yet support ENC and ReJIT together, so the version should be 1
+ _ASSERTE(m_dwEnCVersionNumber == 1);
+ RSInitHolder<CordbReJitILCode> pILCodeHolder(new CordbReJitILCode(this, 1, vmSharedReJitInfo));
+ IfFailRet(m_reJitILCodes.AddBase(pILCodeHolder));
+ pILCode = pILCodeHolder;
+ pILCodeHolder.ClearAndMarkDontNeuter();
+ }
+
+ pILCode->InternalAddRef();
+ *ppILCode = pILCode;
+ return S_OK;
+}
diff --git a/src/debug/di/rsmain.cpp b/src/debug/di/rsmain.cpp
new file mode 100644
index 0000000000..b5685750db
--- /dev/null
+++ b/src/debug/di/rsmain.cpp
@@ -0,0 +1,2536 @@
+// 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.
+//*****************************************************************************
+// File: RsMain.cpp
+//
+
+// Random RS utility stuff, plus root ICorCordbug implementation
+//
+//*****************************************************************************
+#include "stdafx.h"
+#include "primitives.h"
+#include "safewrap.h"
+
+#include "check.h"
+
+#include <tlhelp32.h>
+#include "wtsapi32.h"
+
+#ifndef SM_REMOTESESSION
+#define SM_REMOTESESSION 0x1000
+#endif
+
+#include "corpriv.h"
+#include "../../dlls/mscorrc/resource.h"
+#include <limits.h>
+
+
+// The top level Cordb object is built around the Shim
+#include "shimpriv.h"
+
+//-----------------------------------------------------------------------------
+// For debugging ease, cache some global values.
+// Include these in retail & free because that's where we need them the most!!
+// Optimized builds may not let us view locals & parameters. So Having these
+// cached as global values should let us inspect almost all of
+// the interesting parts of the RS even in a Retail build!
+//-----------------------------------------------------------------------------
+
+RSDebuggingInfo g_RSDebuggingInfo_OutOfProc = {0 }; // set to NULL
+RSDebuggingInfo * g_pRSDebuggingInfo = &g_RSDebuggingInfo_OutOfProc;
+
+
+#ifdef _DEBUG
+// For logs, we can print the string name for the debug codes.
+const char * GetDebugCodeName(DWORD dwCode)
+{
+ if (dwCode < 1 || dwCode > 9)
+ {
+ return "!Invalid Debug Event Code!";
+ }
+
+ static const char * const szNames[] = {
+ "(1) EXCEPTION_DEBUG_EVENT",
+ "(2) CREATE_THREAD_DEBUG_EVENT",
+ "(3) CREATE_PROCESS_DEBUG_EVENT",
+ "(4) EXIT_THREAD_DEBUG_EVENT",
+ "(5) EXIT_PROCESS_DEBUG_EVENT",
+ "(6) LOAD_DLL_DEBUG_EVENT",
+ "(7) UNLOAD_DLL_DEBUG_EVENT",
+ "(8) OUTPUT_DEBUG_STRING_EVENT"
+ "(9) RIP_EVENT",// <-- only on Win9X
+ };
+
+ return szNames[dwCode - 1];
+}
+
+#endif
+
+
+//-----------------------------------------------------------------------------
+// Per-thread state for Debug builds...
+//-----------------------------------------------------------------------------
+#ifdef RSCONTRACTS
+DWORD DbgRSThread::s_TlsSlot = TLS_OUT_OF_INDEXES;
+LONG DbgRSThread::s_Total = 0;
+
+DbgRSThread::DbgRSThread()
+{
+ m_cInsideRS = 0;
+ m_fIsInCallback = false;
+ m_fIsUnrecoverableErrorCallback = false;
+
+ m_cTotalDbgApiLocks = 0;
+ for(int i = 0; i < RSLock::LL_MAX; i++)
+ {
+ m_cLocks[i] = 0;
+ }
+
+ // Initialize Identity info
+ m_Cookie = COOKIE_VALUE;
+ m_tid = GetCurrentThreadId();
+}
+
+// NotifyTakeLock & NotifyReleaseLock are called by RSLock to update the per-thread locking context.
+// This will assert if the operation is unsafe (ie, violates lock order).
+void DbgRSThread::NotifyTakeLock(RSLock * pLock)
+{
+ if (pLock->HasLock())
+ {
+ return;
+ }
+
+ int iLevel = pLock->GetLevel();
+
+ // Is it safe to take this lock?
+ // Must take "bigger" locks first. We shouldn't hold any locks at our current level either.
+ // If this lock is re-entrant and we're double-taking it, we would have returned already.
+ // And the locking model on the RS forbids taking multiple locks at the same level.
+ for(int i = iLevel; i >= 0; i --)
+ {
+ bool fHasLowerLock = m_cLocks[i] > 0;
+ CONSISTENCY_CHECK_MSGF(!fHasLowerLock, (
+ "RSLock violation. Trying to take lock '%s (%d)', but already have smaller lock at level %d'\n",
+ pLock->Name(), iLevel,
+ i));
+ }
+
+ // Update the counts
+ _ASSERTE(m_cLocks[iLevel] == 0);
+ m_cLocks[iLevel]++;
+
+ if (pLock->IsDbgApiLock())
+ m_cTotalDbgApiLocks++;
+}
+
+void DbgRSThread::NotifyReleaseLock(RSLock * pLock)
+{
+ if (pLock->HasLock())
+ {
+ return;
+ }
+
+ int iLevel = pLock->GetLevel();
+ m_cLocks[iLevel]--;
+ _ASSERTE(m_cLocks[iLevel] == 0);
+
+ if (pLock->IsDbgApiLock())
+ m_cTotalDbgApiLocks--;
+
+ _ASSERTE(m_cTotalDbgApiLocks >= 0);
+}
+
+void DbgRSThread::TakeVirtualLock(RSLock::ERSLockLevel level)
+{
+ m_cLocks[level]++;
+}
+
+void DbgRSThread::ReleaseVirtualLock(RSLock::ERSLockLevel level)
+{
+ m_cLocks[level]--;
+ _ASSERTE(m_cLocks[level] >= 0);
+}
+
+
+// Get a DbgRSThread for the current OS thread id; lazily create if needed.
+DbgRSThread * DbgRSThread::GetThread()
+{
+ _ASSERTE(DbgRSThread::s_TlsSlot != TLS_OUT_OF_INDEXES);
+
+ void * p2 = TlsGetValue(DbgRSThread::s_TlsSlot);
+ if (p2 == NULL)
+ {
+ // We lazily create for threads that haven't gone through DllMain
+ // Since this is per-thread, we don't need to lock.
+ p2 = DbgRSThread::Create();
+ }
+ DbgRSThread * p = reinterpret_cast<DbgRSThread*> (p2);
+
+ _ASSERTE(p->m_Cookie == COOKIE_VALUE);
+
+ return p;
+}
+
+
+
+#endif // RSCONTRACTS
+
+
+
+
+
+
+#ifdef _DEBUG
+LONG CordbCommonBase::s_TotalObjectCount = 0;
+LONG CordbCommonBase::s_CordbObjectUID = 0;
+
+
+LONG CordbCommonBase::m_saDwInstance[enumMaxDerived];
+LONG CordbCommonBase::m_saDwAlive[enumMaxDerived];
+PVOID CordbCommonBase::m_sdThis[enumMaxDerived][enumMaxThis];
+
+#endif
+
+#ifdef _DEBUG_IMPL
+// Mem tracking
+LONG Cordb::s_DbgMemTotalOutstandingCordb = 0;
+LONG Cordb::s_DbgMemTotalOutstandingInternalRefs = 0;
+#endif
+
+#ifdef TRACK_OUTSTANDING_OBJECTS
+void *Cordb::s_DbgMemOutstandingObjects[MAX_TRACKED_OUTSTANDING_OBJECTS] = { NULL };
+LONG Cordb::s_DbgMemOutstandingObjectMax = 0;
+#endif
+
+// Default implementation for neutering left-side resources.
+void CordbBase::NeuterLeftSideResources()
+{
+ LIMITED_METHOD_CONTRACT;
+
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+ Neuter();
+}
+
+// Default implementation for neutering.
+// All derived objects should eventually chain to this.
+void CordbBase::Neuter()
+{
+ // Neutering occurs under the process lock. Neuter can be called twice
+ // and so locking protects against races in double-delete.
+ // @dbgtodo - , some CordbBase objects (Cordb, CordbProcessEnum),
+ // don't have process affinity these should eventually be hoisted to the shim,
+ // and then we can enforce.
+ CordbProcess * pProcess = GetProcess();
+ if (pProcess != NULL)
+ {
+ _ASSERTE(pProcess->ThreadHoldsProcessLock());
+ }
+ CordbCommonBase::Neuter();
+}
+
+//-----------------------------------------------------------------------------
+// NeuterLists
+//-----------------------------------------------------------------------------
+
+NeuterList::NeuterList()
+{
+ m_pHead = NULL;
+}
+
+NeuterList::~NeuterList()
+{
+ // Our owner should have neutered us before deleting us.
+ // Thus we should be empty.
+ CONSISTENCY_CHECK_MSGF(m_pHead == NULL, ("NeuterList not empty on shutdown. this=0x%p", this));
+}
+
+// Wrapper around code:NeuterList::UnsafeAdd
+void NeuterList::Add(CordbProcess * pProcess, CordbBase * pObject)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ UnsafeAdd(pProcess, pObject);
+}
+
+//
+// Add an object to be neutered.
+//
+// Arguments:
+// pProcess - process that holds lock that will protect the neuter list
+// pObject - object to add
+//
+// Returns:
+// Throws on error.
+//
+// Notes:
+// This will add it to the list and maintain an internal reference to it.
+// This will take the process lock.
+//
+void NeuterList::UnsafeAdd(CordbProcess * pProcess, CordbBase * pObject)
+{
+ _ASSERTE(pObject != NULL);
+
+ // Lock if needed.
+ RSLock * pLock = (pProcess != NULL) ? pProcess->GetProcessLock() : NULL;
+ RSLockHolder lockHolder(pLock, FALSE);
+ if (pLock != NULL) lockHolder.Acquire();
+
+
+ Node * pNode = new Node(); // throws on error.
+ pNode->m_pObject.Assign(pObject);
+ pNode->m_pNext = m_pHead;
+
+ m_pHead = pNode;
+}
+
+// Neuter everything on the list and clear it
+//
+// Arguments:
+// pProcess - process tree that this neuterlist belongs in
+// ticket - neuter ticket proving caller ensured we're safe to neuter.
+//
+// Assumptions:
+// Caller ensures we're safe to neuter (required to obtain NeuterTicket)
+//
+// Notes:
+// This will release all internal references and empty the list.
+void NeuterList::NeuterAndClear(CordbProcess * pProcess)
+{
+ RSLock * pLock = (pProcess != NULL) ? pProcess->GetProcessLock() : NULL;
+ (void)pLock; //prevent "unused variable" error from GCC
+ _ASSERTE((pLock == NULL) || pLock->HasLock());
+
+ while (m_pHead != NULL)
+ {
+ Node * pTemp = m_pHead;
+ m_pHead = m_pHead->m_pNext;
+
+ pTemp->m_pObject->Neuter();
+ delete pTemp; // will implicitly release
+ }
+}
+
+// Only neuter objects that are marked.
+// Removes neutered objects from the list.
+void NeuterList::SweepAllNeuterAtWillObjects(CordbProcess * pProcess)
+{
+ _ASSERTE(pProcess != NULL);
+ RSLock * pLock = pProcess->GetProcessLock();
+ RSLockHolder lockHolder(pLock);
+
+ Node ** ppLast = &m_pHead;
+ Node * pCur = m_pHead;
+
+ while (pCur != NULL)
+ {
+ CordbBase * pObject = pCur->m_pObject;
+ if (pObject->IsNeuterAtWill() || pObject->IsNeutered())
+ {
+ // Delete
+ pObject->Neuter();
+
+ Node * pNext = pCur->m_pNext;
+ delete pCur; // dtor will implicitly release the internal ref to pObject
+ pCur = *ppLast = pNext;
+ }
+ else
+ {
+ // Move to next.
+ ppLast = &pCur->m_pNext;
+ pCur = pCur->m_pNext;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Neuters all objects in the list and empties the list.
+//
+// Notes:
+// See also code:LeftSideResourceCleanupList::SweepNeuterLeftSideResources,
+// which only neuters objects that have been marked as NeuterAtWill (external
+// ref count has gone to 0).
+void LeftSideResourceCleanupList::NeuterLeftSideResourcesAndClear(CordbProcess * pProcess)
+{
+ // Traversal protected under Process-lock.
+ // SG-lock must already be held to do neutering.
+ // Stop-Go lock is bigger than Process-lock.
+ // Neutering requires the Stop-Go lock (until we get rid of IPC events)
+ // But we want to be able to add to the Neuter list under the Process-lock.
+ // So we just need to protected m_pHead under process-lock.
+
+ // "Privatize" the list under the lock.
+ _ASSERTE(pProcess != NULL);
+ RSLock * pLock = pProcess->GetProcessLock();
+
+ Node * pCur = NULL;
+ {
+ RSLockHolder lockHolder(pLock); // only acquire lock if we have one
+ pCur = m_pHead;
+ m_pHead = NULL;
+ }
+
+ // @dbgtodo - eventually everything can be under the process lock.
+ _ASSERTE(!pLock->HasLock()); // Can't hold Process lock while calling NeuterLeftSideResources
+
+ // Now we're operating on local data, so traversing doesn't need to be under the lock.
+ while (pCur != NULL)
+ {
+ Node * pTemp = pCur;
+ pCur = pCur->m_pNext;
+
+ pTemp->m_pObject->NeuterLeftSideResources();
+ delete pTemp; // will implicitly release
+ }
+
+}
+
+//-----------------------------------------------------------------------------
+// Only neuter objects that are marked. Removes neutered objects from the list.
+//
+// Arguments:
+// pProcess - non-null process owning the objects in the list
+//
+// Notes:
+// this cleans up left-side resources held by objects in the list.
+// It may send IPC events to do this.
+void LeftSideResourceCleanupList::SweepNeuterLeftSideResources(CordbProcess * pProcess)
+{
+ _ASSERTE(pProcess != NULL);
+
+ // Must be safe to send IPC events.
+ _ASSERTE(pProcess->GetStopGoLock()->HasLock()); // holds this for neutering
+ _ASSERTE(pProcess->GetSynchronized());
+
+ RSLock * pLock = pProcess->GetProcessLock();
+
+ // Lock while we "privatize" the head.
+ RSLockHolder lockHolder(pLock);
+ Node * pHead = m_pHead;
+ m_pHead = NULL;
+ lockHolder.Release();
+
+ Node ** ppLast = &pHead;
+ Node * pCur = pHead;
+
+ // Can't hold the process-lock while calling Neuter.
+ while (pCur != NULL)
+ {
+ CordbBase * pObject = pCur->m_pObject;
+ if (pObject->IsNeuterAtWill() || pObject->IsNeutered())
+ {
+ // HeavyNueter can not be done under the process-lock because
+ // it may take the Stop-Go lock and send events.
+ pObject->NeuterLeftSideResources();
+
+ // Delete
+ Node * pNext = pCur->m_pNext;
+ delete pCur; // dtor will implicitly release the internal ref to pObject
+ pCur = *ppLast = pNext;
+ }
+ else
+ {
+ // Move to next.
+ ppLast = &pCur->m_pNext;
+ pCur = pCur->m_pNext;
+ }
+ }
+
+ // Now link back in. m_pHead may have changed while we were unlocked.
+ // The list does not need to be ordered.
+
+ lockHolder.Acquire();
+ *ppLast = m_pHead;
+ m_pHead = pHead;
+}
+
+
+
+/* ------------------------------------------------------------------------- *
+ * CordbBase class
+ * ------------------------------------------------------------------------- */
+
+// Do any initialization necessary for both CorPublish and CorDebug
+// This includes enabling logging and adding the SEDebug priv.
+void CordbCommonBase::InitializeCommon()
+{
+ static bool IsInitialized = false;
+ if( IsInitialized )
+ {
+ return;
+ }
+
+#ifdef STRESS_LOG
+ {
+ bool fStressLog = false;
+
+#ifdef _DEBUG
+ // default for stress log is on debug build
+ fStressLog = true;
+#endif // DEBUG
+
+ // StressLog will turn on stress logging for the entire runtime.
+ // RSStressLog is only used here and only effects just the RS.
+ fStressLog =
+ (REGUTIL::GetConfigDWORD_DontUse_(CLRConfig::UNSUPPORTED_StressLog, fStressLog) != 0) ||
+ (CLRConfig::GetConfigValue(CLRConfig::UNSUPPORTED_RSStressLog) != 0);
+
+ if (fStressLog == true)
+ {
+ unsigned facilities = REGUTIL::GetConfigDWORD_DontUse_(CLRConfig::INTERNAL_LogFacility, LF_ALL);
+ unsigned level = REGUTIL::GetConfigDWORD_DontUse_(CLRConfig::EXTERNAL_LogLevel, LL_INFO1000);
+ unsigned bytesPerThread = REGUTIL::GetConfigDWORD_DontUse_(CLRConfig::UNSUPPORTED_StressLogSize, STRESSLOG_CHUNK_SIZE * 2);
+ unsigned totalBytes = REGUTIL::GetConfigDWORD_DontUse_(CLRConfig::UNSUPPORTED_TotalStressLogSize, STRESSLOG_CHUNK_SIZE * 1024);
+#ifndef FEATURE_PAL
+ StressLog::Initialize(facilities, level, bytesPerThread, totalBytes, GetModuleInst());
+#else
+ StressLog::Initialize(facilities, level, bytesPerThread, totalBytes, NULL);
+#endif
+ }
+ }
+
+#endif // STRESS_LOG
+
+#ifdef LOGGING
+ InitializeLogging();
+#endif
+
+ // Add debug privilege. This will let us call OpenProcess() on anything, regardless of ACL.
+ AddDebugPrivilege();
+
+ IsInitialized = true;
+}
+
+// Adjust the permissions of this process to ensure that we have
+// the debugging priviledge. If we can't make the adjustment, it
+// only means that we won't be able to attach to a service under
+// NT, so we won't treat that as a critical failure.
+// This also will let us call OpenProcess() on anything, regardless of DACL. This allows an
+// Admin debugger to attach to a debuggee in the guest account.
+// Ideally, the debugger would set this (and we wouldn't mess with privileges at all). However, we've been
+// setting this since V1.0 and removing it may be a breaking change.
+void CordbCommonBase::AddDebugPrivilege()
+{
+#ifndef FEATURE_PAL
+ HANDLE hToken;
+ TOKEN_PRIVILEGES Privileges;
+ BOOL fSucc;
+
+ LUID SeDebugLuid = {0, 0};
+
+ fSucc = LookupPrivilegeValueW(NULL, SE_DEBUG_NAME, &SeDebugLuid);
+ DWORD err = GetLastError();
+
+ if (!fSucc)
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "Unable to adjust permissions of this process to include SE_DEBUG. Lookup failed %d\n", err);
+ return;
+ }
+
+
+ // Retrieve a handle of the access token
+ fSucc = OpenProcessToken(GetCurrentProcess(),
+ TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
+ &hToken);
+
+ if (fSucc)
+ {
+ Privileges.PrivilegeCount = 1;
+ Privileges.Privileges[0].Luid = SeDebugLuid;
+ Privileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
+
+ AdjustTokenPrivileges(hToken,
+ FALSE,
+ &Privileges,
+ sizeof(TOKEN_PRIVILEGES),
+ (PTOKEN_PRIVILEGES) NULL,
+ (PDWORD) NULL);
+ err = GetLastError();
+ // The return value of AdjustTokenPrivileges cannot be tested.
+ if (err != ERROR_SUCCESS)
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO1000,
+ "Unable to adjust permissions of this process to include SE_DEBUG. Adjust failed %d\n", err);
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO1000, "Adjusted process permissions to include SE_DEBUG.\n"));
+ }
+ CloseHandle(hToken);
+ }
+#endif
+}
+
+
+namespace
+{
+
+ //
+ // DefaultManagedCallback2
+ //
+ // In the event that the debugger is of an older version than the Right Side & Left Side, the Right Side may issue
+ // new callbacks that the debugger is not expecting. In this case, we need to provide a default behavior for those
+ // new callbacks, if for nothing else than to force the debugger to Continue().
+ //
+ class DefaultManagedCallback2 : public ICorDebugManagedCallback2
+ {
+ public:
+ DefaultManagedCallback2(ICorDebug* pDebug);
+ virtual ~DefaultManagedCallback2() { }
+ virtual HRESULT __stdcall QueryInterface(REFIID iid, void** pInterface);
+ virtual ULONG __stdcall AddRef();
+ virtual ULONG __stdcall Release();
+ COM_METHOD FunctionRemapOpportunity(ICorDebugAppDomain* pAppDomain,
+ ICorDebugThread* pThread,
+ ICorDebugFunction* pOldFunction,
+ ICorDebugFunction* pNewFunction,
+ ULONG32 oldILOffset);
+ COM_METHOD FunctionRemapComplete(ICorDebugAppDomain *pAppDomain,
+ ICorDebugThread *pThread,
+ ICorDebugFunction *pFunction);
+
+ COM_METHOD CreateConnection(ICorDebugProcess *pProcess,
+ CONNID dwConnectionId,
+ __in_z WCHAR* pConnectionName);
+ COM_METHOD ChangeConnection(ICorDebugProcess *pProcess, CONNID dwConnectionId);
+ COM_METHOD DestroyConnection(ICorDebugProcess *pProcess, CONNID dwConnectionId);
+
+ COM_METHOD Exception(ICorDebugAppDomain *pAddDomain,
+ ICorDebugThread *pThread,
+ ICorDebugFrame *pFrame,
+ ULONG32 nOffset,
+ CorDebugExceptionCallbackType eventType,
+ DWORD dwFlags );
+
+ COM_METHOD ExceptionUnwind(ICorDebugAppDomain *pAddDomain,
+ ICorDebugThread *pThread,
+ CorDebugExceptionUnwindCallbackType eventType,
+ DWORD dwFlags );
+ COM_METHOD MDANotification(
+ ICorDebugController * pController,
+ ICorDebugThread *pThread,
+ ICorDebugMDA * pMDA
+ ) { return E_NOTIMPL; }
+
+ private:
+ // not implemented
+ DefaultManagedCallback2(const DefaultManagedCallback2&);
+ DefaultManagedCallback2& operator=(const DefaultManagedCallback2&);
+
+ ICorDebug* m_pDebug;
+ LONG m_refCount;
+ };
+
+
+
+
+ DefaultManagedCallback2::DefaultManagedCallback2(ICorDebug* pDebug) : m_pDebug(pDebug), m_refCount(0)
+ {
+ }
+
+ HRESULT
+ DefaultManagedCallback2::QueryInterface(REFIID iid, void** pInterface)
+ {
+ if (IID_ICorDebugManagedCallback2 == iid)
+ {
+ *pInterface = static_cast<ICorDebugManagedCallback2*>(this);
+ }
+ else if (IID_IUnknown == iid)
+ {
+ *pInterface = static_cast<IUnknown*>(this);
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ this->AddRef();
+ return S_OK;
+ }
+
+ ULONG
+ DefaultManagedCallback2::AddRef()
+ {
+ return InterlockedIncrement(&m_refCount);
+ }
+
+ ULONG
+ DefaultManagedCallback2::Release()
+ {
+ ULONG ulRef = InterlockedDecrement(&m_refCount);
+ if (0 == ulRef)
+ {
+ delete this;
+ }
+
+ return ulRef;
+ }
+
+ HRESULT
+ DefaultManagedCallback2::FunctionRemapOpportunity(ICorDebugAppDomain* pAppDomain,
+ ICorDebugThread* pThread,
+ ICorDebugFunction* pOldFunction,
+ ICorDebugFunction* pNewFunction,
+ ULONG32 oldILOffset)
+ {
+
+ //
+ // In theory, this function should never be reached. To get here, we'd have to have a debugger which doesn't
+ // support edit and continue somehow turn on edit & continue features.
+ //
+ _ASSERTE(!"Edit & Continue callback reached when debugger doesn't support Edit And Continue");
+
+
+ // If you ignore this assertion, or you're in a retail build, there are two options as far as how to proceed
+ // from this point
+ // o We can do nothing, and let the debugee process hang, or
+ // o We can silently ignore the FunctionRemapOpportunity, and tell the debugee to Continue running.
+ //
+ // For now, we'll silently ignore the function remapping.
+ pAppDomain->Continue(false);
+ pAppDomain->Release();
+
+ return S_OK;
+ }
+
+
+ HRESULT
+ DefaultManagedCallback2::FunctionRemapComplete(ICorDebugAppDomain *pAppDomain,
+ ICorDebugThread *pThread,
+ ICorDebugFunction *pFunction)
+ {
+ //
+ // In theory, this function should never be reached. To get here, we'd have to have a debugger which doesn't
+ // support edit and continue somehow turn on edit & continue features.
+ //
+ _ASSERTE(!"Edit & Continue callback reached when debugger doesn't support Edit And Continue");
+ return E_NOTIMPL;
+ }
+
+ //
+ // <TODO>
+ // These methods are current left unimplemented.
+ //
+ // Create/Change/Destroy Connection *should* force the Process/AppDomain/Thread to Continue(). Currently the
+ // arguments to these functions don't provide the relevant Process/AppDomain/Thread, so there is no way to figure
+ // out which Threads should be forced to Continue().
+ //
+ // </TODO>
+ //
+ HRESULT
+ DefaultManagedCallback2::CreateConnection(ICorDebugProcess *pProcess,
+ CONNID dwConnectionId,
+ __in_z WCHAR* pConnectionName)
+ {
+ _ASSERTE(!"DefaultManagedCallback2::CreateConnection not implemented");
+ return E_NOTIMPL;
+ }
+
+ HRESULT
+ DefaultManagedCallback2::ChangeConnection(ICorDebugProcess *pProcess, CONNID dwConnectionId)
+ {
+ _ASSERTE(!"DefaultManagedCallback2::ChangeConnection not implemented");
+ return E_NOTIMPL;
+ }
+
+ HRESULT
+ DefaultManagedCallback2::DestroyConnection(ICorDebugProcess *pProcess, CONNID dwConnectionId)
+ {
+ _ASSERTE(!"DefaultManagedCallback2::DestroyConnection not implemented");
+ return E_NOTIMPL;
+ }
+
+ HRESULT
+ DefaultManagedCallback2::Exception(ICorDebugAppDomain *pAppDomain,
+ ICorDebugThread *pThread,
+ ICorDebugFrame *pFrame,
+ ULONG32 nOffset,
+ CorDebugExceptionCallbackType eventType,
+ DWORD dwFlags )
+ {
+ //
+ // Just ignore and continue the process.
+ //
+ pAppDomain->Continue(false);
+ return S_OK;
+ }
+
+ HRESULT
+ DefaultManagedCallback2::ExceptionUnwind(ICorDebugAppDomain *pAppDomain,
+ ICorDebugThread *pThread,
+ CorDebugExceptionUnwindCallbackType eventType,
+ DWORD dwFlags )
+ {
+ //
+ // Just ignore and continue the process.
+ //
+ pAppDomain->Continue(false);
+ return S_OK;
+ }
+
+ //
+ // DefaultManagedCallback3
+ //
+ // In the event that the debugger is of an older version than the Right Side & Left Side, the Right Side may issue
+ // new callbacks that the debugger is not expecting. In this case, we need to provide a default behavior for those
+ // new callbacks, if for nothing else than to force the debugger to Continue().
+ //
+ class DefaultManagedCallback3 : public ICorDebugManagedCallback3
+ {
+ public:
+ DefaultManagedCallback3(ICorDebug* pDebug);
+ virtual ~DefaultManagedCallback3() { }
+ virtual HRESULT __stdcall QueryInterface(REFIID iid, void** pInterface);
+ virtual ULONG __stdcall AddRef();
+ virtual ULONG __stdcall Release();
+ COM_METHOD CustomNotification(ICorDebugThread * pThread, ICorDebugAppDomain * pAppDomain);
+ private:
+ // not implemented
+ DefaultManagedCallback3(const DefaultManagedCallback3&);
+ DefaultManagedCallback3& operator=(const DefaultManagedCallback3&);
+
+ ICorDebug* m_pDebug;
+ LONG m_refCount;
+ };
+
+ DefaultManagedCallback3::DefaultManagedCallback3(ICorDebug* pDebug) : m_pDebug(pDebug), m_refCount(0)
+ {
+ }
+
+ HRESULT
+ DefaultManagedCallback3::QueryInterface(REFIID iid, void** pInterface)
+ {
+ if (IID_ICorDebugManagedCallback3 == iid)
+ {
+ *pInterface = static_cast<ICorDebugManagedCallback3*>(this);
+ }
+ else if (IID_IUnknown == iid)
+ {
+ *pInterface = static_cast<IUnknown*>(this);
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ this->AddRef();
+ return S_OK;
+ }
+
+ ULONG
+ DefaultManagedCallback3::AddRef()
+ {
+ return InterlockedIncrement(&m_refCount);
+ }
+
+ ULONG
+ DefaultManagedCallback3::Release()
+ {
+ ULONG ulRef = InterlockedDecrement(&m_refCount);
+ if (0 == ulRef)
+ {
+ delete this;
+ }
+
+ return ulRef;
+ }
+
+ HRESULT
+ DefaultManagedCallback3::CustomNotification(ICorDebugThread * pThread, ICorDebugAppDomain * pAppDomain)
+ {
+ //
+ // Just ignore and continue the process.
+ //
+ pAppDomain->Continue(false);
+ return S_OK;
+ }
+}
+
+/* ------------------------------------------------------------------------- *
+ * Cordb class
+ * ------------------------------------------------------------------------- */
+
+
+Cordb::Cordb(CorDebugInterfaceVersion iDebuggerVersion)
+ : CordbBase(NULL, 0, enumCordb),
+ m_processes(11),
+ m_initialized(false),
+ m_debuggerSpecifiedVersion(iDebuggerVersion)
+#ifdef FEATURE_CORESYSTEM
+ ,
+ m_targetCLR(0)
+#endif
+{
+ g_pRSDebuggingInfo->m_Cordb = this;
+
+#ifdef _DEBUG_IMPL
+ // Memory leak detection
+ InterlockedIncrement(&s_DbgMemTotalOutstandingCordb);
+#endif
+}
+
+Cordb::~Cordb()
+{
+ LOG((LF_CORDB, LL_INFO10, "C::~C Terminating Cordb object.\n"));
+ g_pRSDebuggingInfo->m_Cordb = NULL;
+}
+
+void Cordb::Neuter()
+{
+ if (this->IsNeutered())
+ {
+ return;
+ }
+
+
+ RSLockHolder lockHolder(&m_processListMutex);
+ m_pProcessEnumList.NeuterAndClear(NULL);
+
+
+ HRESULT hr = S_OK;
+ EX_TRY // @dbgtodo push this up.
+ {
+ // Iterating needs to be done under the processList lock (small), while neutering
+ // needs to be able to take the process lock (big).
+ RSPtrArray<CordbProcess> list;
+ m_processes.TransferToArray(&list); // throws
+
+ // can't hold list lock while calling CordbProcess::Neuter (which
+ // will take the Process-lock).
+ lockHolder.Release();
+
+ list.NeuterAndClear();
+ // List dtor calls release on each element
+ }
+ EX_CATCH_HRESULT(hr);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+
+ CordbCommonBase::Neuter();
+
+ // Implicit release from smart ptr.
+}
+
+#ifdef _DEBUG_IMPL
+void CheckMemLeaks()
+{
+ // Memory leak detection.
+ LONG l = InterlockedDecrement(&Cordb::s_DbgMemTotalOutstandingCordb);
+ if (l == 0)
+ {
+ // If we just released our final Cordb root object, then we expect no internal references at all.
+ // Note that there may still be external references (and thus not all objects may have been
+ // deleted yet).
+ bool fLeakedInternal = (Cordb::s_DbgMemTotalOutstandingInternalRefs > 0);
+
+ // Some Cordb objects (such as CordbValues) may not be rooted, and thus we can't neuter
+ // them and thus an external ref may keep them alive. Since these objects may have internal refs,
+ // This means that external refs can keep internal refs.
+ // Thus this assert must be tempered if unrooted objects are leaked. (But that means we can always
+ // assert the tempered version; regardless of bugs in Cordbg).
+ CONSISTENCY_CHECK_MSGF(!fLeakedInternal,
+ ("'%d' Outstanding internal references at final Cordb::Terminate\n",
+ Cordb::s_DbgMemTotalOutstandingInternalRefs));
+
+ DWORD dLeakCheck = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgLeakCheck);
+ if (dLeakCheck > 0)
+ {
+ // We have 1 ref for this Cordb root object. All other refs should have been deleted.
+ CONSISTENCY_CHECK_MSGF(Cordb::s_TotalObjectCount == 1, ("'%d' total cordbBase objects are leaked.\n",
+ Cordb::s_TotalObjectCount-1));
+ }
+ }
+}
+#endif
+
+// This shuts down ICorDebug.
+// All CordbProcess objects owned by this Cordb object must have either:
+// - returned for a Detach() call
+// - returned from dispatching the ExitProcess() callback.
+// In both cases, CordbProcess::NeuterChildren has been called, although the Process object itself
+// may not yet be neutered. This condition will ensure that the CordbProcess objects don't need
+// any resources that we're about to release.
+HRESULT Cordb::Terminate()
+{
+ LOG((LF_CORDB, LL_INFO10000, "[%x] Terminating Cordb\n", GetCurrentThreadId()));
+
+ if (!m_initialized)
+ return E_FAIL;
+
+ FAIL_IF_NEUTERED(this);
+
+ // We can't terminate the debugging services from within a callback.
+ // Caller is supposed to be out of all callbacks when they call this.
+ // This also avoids a deadlock because we'll shutdown the RCET, which would block if we're
+ // in the RCET.
+ if (m_rcEventThread->IsRCEventThread())
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO10, "C::T: failed on RCET\n");
+ _ASSERTE(!"Gross API Misuse: Debugger shouldn't call ICorDebug::Terminate from within a managed callback.");
+ return CORDBG_E_CANT_CALL_ON_THIS_THREAD;
+ }
+
+ // @todo - do we need to throw some switch to prevent new processes from being added now?
+
+ // VS must stop all debugging before terminating. Fail if we have any non-neutered processes
+ // (b/c those processes should have been either shutdown or detached).
+ // We are in an undefined state if this check fails.
+ // Process are removed from this list before Process::Detach() returns and before the ExitProcess callback is dispatched.
+ // Thus any processes in this list should be live or have an unrecoverable error.
+ {
+ RSLockHolder ch(&m_processListMutex);
+
+ HASHFIND hfDT;
+ CordbProcess * pProcess;
+
+ for (pProcess= (CordbProcess*) m_processes.FindFirst(&hfDT);
+ pProcess != NULL;
+ pProcess = (CordbProcess*) m_processes.FindNext(&hfDT))
+ {
+ _ASSERTE(pProcess->IsSafeToSendEvents() || pProcess->m_unrecoverableError);
+ if (pProcess->IsSafeToSendEvents() && !pProcess->m_unrecoverableError)
+ {
+ CONSISTENCY_CHECK_MSGF(false, ("Gross API misuses. Callling terminate with live process:0x%p\n", pProcess));
+ STRESS_LOG1(LF_CORDB, LL_INFO10, "Cordb::Terminate b/c of non-neutered process '%p'\n", pProcess);
+ // This is very bad.
+ // GROSS API MISUSES - Debugger is calling ICorDebug::Terminate while there
+ // are still outstanding (non-neutered) ICorDebugProcess.
+ // ICorDebug is now in an undefined state.
+ // We will also leak memory b/c we're leaving the EventThreads up (which will in turn
+ // keep a reference to this Cordb object).
+ return ErrWrapper(CORDBG_E_ILLEGAL_SHUTDOWN_ORDER);
+ }
+ }
+ }
+
+ // @todo- ideally, we'd wait for all threads to get outside of ICorDebug before we proceed.
+ // That's tough to implement in practice; but we at least wait for both ET to exit. As these
+ // guys dispatch callbacks, that means at least we'll wait until VS is outside of any callback.
+ //
+ // Stop the event handling threads.
+ //
+ if (m_rcEventThread != NULL)
+ {
+ // Stop may do significant work b/c if it drains the worker queue.
+ m_rcEventThread->Stop();
+ delete m_rcEventThread;
+ m_rcEventThread = NULL;
+ }
+
+
+#ifdef _DEBUG
+ // @todo - this disables thread-safety asserts on the process-list-hash. We clearly
+ // can't hold the lock while neutering it. (lock violation since who knows what neuter may do)
+ // @todo- we may have races beteen Cordb::Terminate and Cordb::CreateProcess as both
+ // modify the process list. This is mitigated since Terminate is supposed to be the last method called.
+ m_processes.DebugSetRSLock(NULL);
+#endif
+
+ //
+ // We expect the debugger to neuter all processes before calling Terminate(), so do not neuter them here.
+ //
+
+#ifdef _DEBUG
+ {
+ HASHFIND find;
+ _ASSERTE(m_processes.FindFirst(&find) == NULL); // should be emptied by neuter
+ }
+#endif //_DEBUG
+
+ // Officially mark us as neutered.
+ this->Neuter();
+
+ m_processListMutex.Destroy();
+
+ //
+ // Release the callbacks
+ //
+ m_managedCallback.Clear();
+ m_managedCallback2.Clear();
+ m_managedCallback3.Clear();
+ m_unmanagedCallback.Clear();
+
+ // The Shell may still have outstanding references, so we don't want to shutdown logging yet.
+ // But everything should be neutered anyways.
+
+ m_initialized = FALSE;
+
+
+ // After this, all outstanding Cordb objects should be neutered.
+ LOG((LF_CORDB, LL_EVERYTHING, "Cordb finished terminating.\n"));
+
+#if defined(_DEBUG)
+ //
+ // Assert that there are no outstanding object references within the debugging
+ // API itself.
+ //
+ CheckMemLeaks();
+#endif
+
+ return S_OK;
+}
+
+HRESULT Cordb::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebug)
+ *pInterface = static_cast<ICorDebug*>(this);
+ else if (id == IID_IUnknown)
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebug*>(this));
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+
+
+//
+// Initialize -- setup the ICorDebug object by creating any objects
+// that the object needs to operate and starting the two needed IPC
+// threads.
+//
+HRESULT Cordb::Initialize(void)
+{
+ HRESULT hr = S_OK;
+
+ FAIL_IF_NEUTERED(this);
+
+ if (!m_initialized)
+ {
+ CordbCommonBase::InitializeCommon();
+
+ // Since logging wasn't active when we called CordbBase, do it now.
+ LOG((LF_CORDB, LL_EVERYTHING, "Memory: CordbBase object allocated: this=%p, count=%d, RootObject\n", this, s_TotalObjectCount));
+ LOG((LF_CORDB, LL_INFO10, "Initializing ICorDebug...\n"));
+
+ // Ensure someone hasn't messed up the IPC buffer size
+ _ASSERTE(sizeof(DebuggerIPCEvent) <= CorDBIPC_BUFFER_SIZE);
+
+ //
+ // Init things that the Cordb will need to operate
+ //
+ m_processListMutex.Init("Process-List Lock", RSLock::cLockReentrant, RSLock::LL_PROCESS_LIST_LOCK);
+
+#ifdef _DEBUG
+ m_processes.DebugSetRSLock(&m_processListMutex);
+#endif
+
+ //
+ // Create the runtime controller event listening thread
+ //
+ m_rcEventThread = new (nothrow) CordbRCEventThread(this);
+
+ if (m_rcEventThread == NULL)
+ {
+ hr = E_OUTOFMEMORY;
+ }
+ else
+ {
+ // This stuff only creates events & starts the thread
+ hr = m_rcEventThread->Init();
+
+ if (SUCCEEDED(hr))
+ hr = m_rcEventThread->Start();
+
+ if (FAILED(hr))
+ {
+ delete m_rcEventThread;
+ m_rcEventThread = NULL;
+ }
+ }
+
+ if (FAILED(hr))
+ goto exit;
+
+ m_initialized = TRUE;
+ }
+
+exit:
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Throw if no more process can be debugged with this Cordb object.
+//
+// Notes:
+// This is highly dependent on the wait sets in the Win32 & RCET threads.
+// @dbgtodo- this will end up in the shim.
+
+void Cordb::EnsureAllowAnotherProcess()
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ RSLockHolder ch(&m_processListMutex);
+
+ // Cordb, Win32, and RCET all have process sets, but Cordb's is the
+ // best count of total debuggees. The RCET set is volatile (processes
+ // are added / removed when they become synchronized), and Win32's set
+ // doesn't include all processes.
+ int cCurProcess = GetProcessList()->GetCount();
+
+ // In order to accept another debuggee, we must have a free slot in all
+ // wait sets. Currently, we don't expose the size of those sets, but
+ // we know they're MAXIMUM_WAIT_OBJECTS. Note that we lose one slot
+ // to the control event.
+ if (cCurProcess >= MAXIMUM_WAIT_OBJECTS - 1)
+ {
+ ThrowHR(CORDBG_E_TOO_MANY_PROCESSES);
+ }
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Add process to the list.
+//
+// Notes:
+// AddProcess -- add a process object to this ICorDebug's hash of processes.
+// This also tells this ICorDebug's runtime controller thread that the
+// process set has changed so it can update its list of wait events.
+//
+void Cordb::AddProcess(CordbProcess* process)
+{
+ // At this point, we should have already checked that we
+ // can have another debuggee.
+ STRESS_LOG1(LF_CORDB, LL_INFO10, "Cordb::AddProcess %08x...\n", process);
+
+ if ((m_managedCallback == NULL) || (m_managedCallback2 == NULL) || (m_managedCallback3 == NULL))
+ {
+ ThrowHR(E_FAIL);
+ }
+
+
+
+ RSLockHolder lockHolder(&m_processListMutex);
+
+ // Once we add another process, all outstanding process-enumerators become invalid.
+ m_pProcessEnumList.NeuterAndClear(NULL);
+
+ GetProcessList()->AddBaseOrThrow(process);
+ m_rcEventThread->ProcessStateChanged();
+}
+
+//
+// RemoveProcess -- remove a process object from this ICorDebug's hash of
+// processes. This also tells this ICorDebug's runtime controller thread
+// that the process set has changed so it can update its list of wait events.
+//
+void Cordb::RemoveProcess(CordbProcess* process)
+{
+ STRESS_LOG1(LF_CORDB, LL_INFO10, "Cordb::RemoveProcess %08x...\n", process);
+
+ LockProcessList();
+ GetProcessList()->RemoveBase((ULONG_PTR)process->m_id);
+
+ m_rcEventThread->ProcessStateChanged();
+
+ UnlockProcessList();
+}
+
+//
+// LockProcessList -- Lock the process list.
+//
+void Cordb::LockProcessList(void)
+{
+ m_processListMutex.Lock();
+}
+
+//
+// UnlockProcessList -- Unlock the process list.
+//
+void Cordb::UnlockProcessList(void)
+{
+ m_processListMutex.Unlock();
+}
+
+#ifdef _DEBUG
+// Return true iff this thread owns the ProcessList lock
+bool Cordb::ThreadHasProcessListLock()
+{
+ return m_processListMutex.HasLock();
+}
+#endif
+
+
+// Get the hash that has the process.
+CordbSafeHashTable<CordbProcess> *Cordb::GetProcessList()
+{
+ // If we're accessing the hash, we'd better be locked.
+ _ASSERTE(ThreadHasProcessListLock());
+
+ return &m_processes;
+}
+
+
+HRESULT Cordb::SendIPCEvent(CordbProcess * pProcess,
+ DebuggerIPCEvent * pEvent,
+ SIZE_T eventSize)
+{
+ HRESULT hr = S_OK;
+
+ LOG((LF_CORDB, LL_EVERYTHING, "SendIPCEvent in Cordb called\n"));
+ EX_TRY
+ {
+ hr = m_rcEventThread->SendIPCEvent(pProcess, pEvent, eventSize);
+ }
+ EX_CATCH_HRESULT(hr)
+ return hr;
+}
+
+
+void Cordb::ProcessStateChanged(void)
+{
+ m_rcEventThread->ProcessStateChanged();
+}
+
+
+HRESULT Cordb::WaitForIPCEventFromProcess(CordbProcess* process,
+ CordbAppDomain *pAppDomain,
+ DebuggerIPCEvent* event)
+{
+ return m_rcEventThread->WaitForIPCEventFromProcess(process,
+ pAppDomain,
+ event);
+}
+
+#ifdef FEATURE_CORECLR
+HRESULT Cordb::SetTargetCLR(HMODULE hmodTargetCLR)
+{
+ if (m_initialized)
+ return E_FAIL;
+
+#ifdef FEATURE_CORESYSTEM
+ m_targetCLR = hmodTargetCLR;
+#endif
+
+ // @REVIEW: are we happy with this workaround? It allows us to use the existing
+ // infrastructure for instance name decoration, but it really doesn't fit
+ // the same model because coreclr.dll isn't in this process and hmodTargetCLR
+ // is the debuggee target, not the coreclr.dll to bind utilcode to..
+
+ CoreClrCallbacks cccallbacks;
+ cccallbacks.m_hmodCoreCLR = hmodTargetCLR;
+ cccallbacks.m_pfnIEE = NULL;
+ cccallbacks.m_pfnGetCORSystemDirectory = NULL;
+ cccallbacks.m_pfnGetCLRFunction = NULL;
+ InitUtilcode(cccallbacks);
+
+ return S_OK;
+}
+#endif // FEATURE_CORECLR
+
+//-----------------------------------------------------------
+// ICorDebug
+//-----------------------------------------------------------
+
+// Set the handler for callbacks on managed events
+// This can not be NULL.
+// If we're debugging V2.0 apps, pCallback must implement ICDManagedCallback2
+// @todo- what if somebody calls this after we've already initialized? (eg, changes
+// the callback underneath us)
+HRESULT Cordb::SetManagedHandler(ICorDebugManagedCallback *pCallback)
+{
+ if (!m_initialized)
+ return E_FAIL;
+
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pCallback, ICorDebugManagedCallback*);
+
+ m_managedCallback.Clear();
+ m_managedCallback2.Clear();
+ m_managedCallback3.Clear();
+
+ // For SxS, V2.0 debuggers must implement ManagedCallback2 to handle v2.0 debug events.
+ // For Single-CLR, A v1.0 debugger may actually geta V2.0 debuggee.
+ pCallback->QueryInterface(IID_ICorDebugManagedCallback2, (void **)&m_managedCallback2);
+ if (m_managedCallback2 == NULL)
+ {
+ if (GetDebuggerVersion() >= CorDebugVersion_2_0)
+ {
+ // This will leave our internal callbacks null, which future operations (Create/Attach) will
+ // use to know that we're not sufficiently initialized.
+ return E_NOINTERFACE;
+ }
+ else
+ {
+ // This should only be used in a single-CLR shimming scenario.
+ m_managedCallback2.Assign(new (nothrow) DefaultManagedCallback2(this));
+
+ if (m_managedCallback2 == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+ }
+ }
+
+
+ pCallback->QueryInterface(IID_ICorDebugManagedCallback3, (void **)&m_managedCallback3);
+ if (m_managedCallback3 == NULL)
+ {
+ m_managedCallback3.Assign(new (nothrow) DefaultManagedCallback3(this));
+ }
+
+ if (m_managedCallback3 == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ m_managedCallback.Assign(pCallback);
+ return S_OK;
+}
+
+HRESULT Cordb::SetUnmanagedHandler(ICorDebugUnmanagedCallback *pCallback)
+{
+ if (!m_initialized)
+ return E_FAIL;
+
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pCallback, ICorDebugUnmanagedCallback*);
+
+ m_unmanagedCallback.Assign(pCallback);
+
+ return S_OK;
+}
+
+// CreateProcess() isn't supported on Windows CoreCLR.
+// It is currently supported on Mac CoreCLR, but that may change.
+bool Cordb::IsCreateProcessSupported()
+{
+#if defined(FEATURE_CORECLR) && !defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ return false;
+#else
+ return true;
+#endif
+}
+
+// Given everything we know about our configuration, can we support interop-debugging
+bool Cordb::IsInteropDebuggingSupported()
+{
+ // We explicitly refrain from checking the unmanaged callback. See comment in
+ // ICorDebug::SetUnmanagedHandler for details.
+#ifdef FEATURE_INTEROP_DEBUGGING
+
+#if defined(FEATURE_CORECLR) && !defined(FEATURE_CORESYSTEM)
+ // Interop debugging is only supported internally on CoreCLR.
+ // Check if the special reg key is set. If not, then we don't allow interop debugging.
+ if (CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgEnableMixedModeDebugging) == 0)
+ {
+ return false;
+ }
+#endif // FEATURE_CORECLR
+
+ return true;
+#else
+ return false;
+#endif
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Implementation of ICorDebug::CreateProcess.
+// Creates a process.
+//
+// Arguments:
+// The following arguments are passed thru unmodified to the OS CreateProcess API and
+// are defined by that API.
+// lpApplicationName
+// lpCommandLine
+// lpProcessAttributes
+// lpThreadAttributes
+// bInheritHandles
+// dwCreationFlags
+// lpCurrentDirectory
+// lpStartupInfo
+// lpProcessInformation
+// debuggingFlags
+//
+// ppProcess - Space to fill in for the resulting process, returned as a valid pointer
+// on any success HRESULT.
+//
+// Return Value:
+// Normal HRESULT semantics.
+//
+//---------------------------------------------------------------------------------------
+HRESULT Cordb::CreateProcess(LPCWSTR lpApplicationName,
+ __in_z LPWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ PVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation,
+ CorDebugCreateProcessFlags debuggingFlags,
+ ICorDebugProcess **ppProcess)
+{
+ return CreateProcessCommon(NULL,
+ lpApplicationName,
+ lpCommandLine,
+ lpProcessAttributes,
+ lpThreadAttributes,
+ bInheritHandles,
+ dwCreationFlags,
+ lpEnvironment,
+ lpCurrentDirectory,
+ lpStartupInfo,
+ lpProcessInformation,
+ debuggingFlags,
+ ppProcess);
+}
+
+HRESULT Cordb::CreateProcessCommon(ICorDebugRemoteTarget * pRemoteTarget,
+ LPCWSTR lpApplicationName,
+ __in_z LPWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ PVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation,
+ CorDebugCreateProcessFlags debuggingFlags,
+ ICorDebugProcess ** ppProcess)
+{
+ // If you hit this assert, it means that you are attempting to create a process without specifying the version
+ // number.
+ _ASSERTE(CorDebugInvalidVersion != m_debuggerSpecifiedVersion);
+
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppProcess, ICorDebugProcess**);
+
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+ if (!m_initialized)
+ {
+ ThrowHR(E_FAIL);
+ }
+
+ // Check that we support the debugger version
+ CheckCompatibility();
+
+ #ifdef FEATURE_INTEROP_DEBUGGING
+ // DEBUG_PROCESS (=0x1) means debug this process & all future children.
+ // DEBUG_ONLY_THIS_PROCESS =(0x2) means just debug the immediate process.
+ // If we want to support DEBUG_PROCESS, then we need to have the RS sniff for new CREATE_PROCESS
+ // events and spawn new CordbProcess for them.
+ switch(dwCreationFlags & (DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS))
+ {
+ // 1) managed-only debugging
+ case 0:
+ break;
+
+ // 2) failure - returns E_NOTIMPL. (as this would involve debugging all of our children processes).
+ case DEBUG_PROCESS:
+ ThrowHR(E_NOTIMPL);
+
+ // 3) Interop-debugging.
+ // Note that MSDN (at least as of Jan 2003) is wrong about this flag. MSDN claims
+ // DEBUG_ONLY_THIS_PROCESS w/o DEBUG_PROCESS should be ignored.
+ // But it really should do launch as a debuggee (but not auto-attach to child processes).
+ case DEBUG_ONLY_THIS_PROCESS:
+ // Emprically, this is the common case for native / interop-debugging.
+ break;
+
+ // 4) Interop.
+ // The spec for ICorDebug::CreateProcess says this is the one to use for interop-debugging.
+ case DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS:
+ // Win2k does not honor these flags properly. So we just use
+ // It treats (DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS) as if it were DEBUG_PROCESS.
+ // We'll just always touch up the flags, even though WinXP and above is fine here.
+ // Per win2k issue, strip off DEBUG_PROCESS, so that we're just left w/ DEBUG_ONLY_THIS_PROCESS.
+ dwCreationFlags &= ~(DEBUG_PROCESS);
+ break;
+
+ default:
+ __assume(0);
+ }
+
+ #endif // FEATURE_INTEROP_DEBUGGING
+
+ // Must have a managed-callback by now.
+ if ((m_managedCallback == NULL) || (m_managedCallback2 == NULL) || (m_managedCallback3 == NULL))
+ {
+ ThrowHR(E_FAIL);
+ }
+
+ if (!IsCreateProcessSupported())
+ {
+ ThrowHR(E_NOTIMPL);
+ }
+
+ if (!IsInteropDebuggingSupported() &&
+ ((dwCreationFlags & (DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS)) != 0))
+ {
+ ThrowHR(CORDBG_E_INTEROP_NOT_SUPPORTED);
+ }
+
+ // Check that we can even accept another debuggee before trying anything.
+ EnsureAllowAnotherProcess();
+
+ } EX_CATCH_HRESULT(hr);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ hr = ShimProcess::CreateProcess(this,
+ pRemoteTarget,
+ lpApplicationName,
+ lpCommandLine,
+ lpProcessAttributes,
+ lpThreadAttributes,
+ bInheritHandles,
+ dwCreationFlags,
+ lpEnvironment,
+ lpCurrentDirectory,
+ lpStartupInfo,
+ lpProcessInformation,
+ debuggingFlags
+ );
+
+ LOG((LF_CORDB, LL_EVERYTHING, "Handle in Cordb::CreateProcess is: %.I64x\n", lpProcessInformation->hProcess));
+
+ if (SUCCEEDED(hr))
+ {
+ LockProcessList();
+
+ CordbProcess * pProcess = GetProcessList()->GetBase(lpProcessInformation->dwProcessId);
+
+ UnlockProcessList();
+
+ PREFIX_ASSUME(pProcess != NULL);
+
+ pProcess->ExternalAddRef();
+ *ppProcess = (ICorDebugProcess *)pProcess;
+ }
+
+ return hr;
+}
+
+
+HRESULT Cordb::CreateProcessEx(ICorDebugRemoteTarget * pRemoteTarget,
+ LPCWSTR lpApplicationName,
+ __in_z LPWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ PVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation,
+ CorDebugCreateProcessFlags debuggingFlags,
+ ICorDebugProcess ** ppProcess)
+{
+ if (pRemoteTarget == NULL)
+ {
+ return E_INVALIDARG;
+ }
+
+ return CreateProcessCommon(pRemoteTarget,
+ lpApplicationName,
+ lpCommandLine,
+ lpProcessAttributes,
+ lpThreadAttributes,
+ bInheritHandles,
+ dwCreationFlags,
+ lpEnvironment,
+ lpCurrentDirectory,
+ lpStartupInfo,
+ lpProcessInformation,
+ debuggingFlags,
+ ppProcess);
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Attachs to an existing process.
+//
+// Arguments:
+// dwProcessID - The PID to attach to
+// fWin32Attach - Flag to tell whether to attach as the Win32 debugger or not.
+// ppProcess - Space to fill in for the resulting process, returned as a valid pointer
+// on any success HRESULT.
+//
+// Return Value:
+// Normal HRESULT semantics.
+//
+//---------------------------------------------------------------------------------------
+HRESULT Cordb::DebugActiveProcess(DWORD dwProcessId,
+ BOOL fWin32Attach,
+ ICorDebugProcess **ppProcess)
+{
+ return DebugActiveProcessCommon(NULL, dwProcessId, fWin32Attach, ppProcess);
+}
+
+HRESULT Cordb::DebugActiveProcessCommon(ICorDebugRemoteTarget * pRemoteTarget,
+ DWORD dwProcessId,
+ BOOL fWin32Attach,
+ ICorDebugProcess ** ppProcess)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppProcess, ICorDebugProcess **);
+
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+ if (!m_initialized)
+ {
+ ThrowHR(E_FAIL);
+ }
+
+ // Must have a managed-callback by now.
+ if ((m_managedCallback == NULL) || (m_managedCallback2 == NULL) || (m_managedCallback3 == NULL))
+ {
+ ThrowHR(E_FAIL);
+ }
+
+ // See the comment in Cordb::CreateProcess
+ _ASSERTE(CorDebugInvalidVersion != m_debuggerSpecifiedVersion);
+
+ // Check that we support the debugger version
+ CheckCompatibility();
+
+ // Check that we can even accept another debuggee before trying anything.
+ EnsureAllowAnotherProcess();
+
+ // Check if we're allowed to do interop.
+ bool fAllowInterop = IsInteropDebuggingSupported();
+
+ if (!fAllowInterop && fWin32Attach)
+ {
+ ThrowHR(CORDBG_E_INTEROP_NOT_SUPPORTED);
+ }
+
+ } EX_CATCH_HRESULT(hr)
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ hr = ShimProcess::DebugActiveProcess(
+ this,
+ pRemoteTarget,
+ dwProcessId,
+ fWin32Attach == TRUE);
+
+ // If that worked, then there will be a process object...
+ if (SUCCEEDED(hr))
+ {
+ LockProcessList();
+ CordbProcess * pProcess = GetProcessList()->GetBase(dwProcessId);
+
+ if (pProcess != NULL)
+ {
+ // Add a reference now so process won't go away
+ pProcess->ExternalAddRef();
+ }
+ UnlockProcessList();
+
+ if (pProcess == NULL)
+ {
+ // This can happen if we add the process into process hash in
+ // SendDebugActiveProcessEvent and then process exit
+ // before we attemp to retrieve it again from GetBase.
+ //
+ *ppProcess = NULL;
+ return S_FALSE;
+ }
+
+#if defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ // This is where we queue the managed attach event in Whidbey. In the new architecture, the Windows
+ // pipeline gets a loader breakpoint when native attach is completed, and that's where we queue the
+ // managed attach event. See how we handle the loader breakpoint in code:ShimProcess::DefaultEventHandler.
+ // However, the Mac debugging transport gets no such breakpoint, and so we need to do this here.
+ //
+ // @dbgtodo Mac - Ideally we should hide this in our pipeline implementation, or at least move
+ // this to the shim.
+ _ASSERTE(!fWin32Attach);
+ {
+ pProcess->Lock();
+ hr = pProcess->QueueManagedAttach();
+ pProcess->Unlock();
+ }
+#endif // FEATURE_DBGIPC_TRANSPORT_DI
+
+ *ppProcess = (ICorDebugProcess*) pProcess;
+ }
+
+ return hr;
+}
+
+// Make sure we want to support the debugger that's using us
+void Cordb::CheckCompatibility()
+{
+ // Get the debugger version specified by the startup APIs and convert it to a CLR major version number
+ CorDebugInterfaceVersion debuggerVersion = GetDebuggerVersion();
+ DWORD clrMajor;
+ if (debuggerVersion <= CorDebugVersion_1_0 || debuggerVersion == CorDebugVersion_1_1)
+ clrMajor = 1;
+ else if (debuggerVersion <= CorDebugVersion_2_0)
+ clrMajor = 2;
+ else if (debuggerVersion <= CorDebugVersion_4_0)
+ clrMajor = 4;
+ else
+ clrMajor = 5; // some unrecognized future version
+
+ if(!CordbProcess::IsCompatibleWith(clrMajor))
+ {
+ // Carefully choose our error-code to get an appropriate error-message from VS 2008
+ // If GetDebuggerVersion is >= 4, we could consider using the more-appropriate (but not
+ // added until V4) HRESULT CORDBG_E_UNSUPPORTED_FORWARD_COMPAT that is used by
+ // OpenVirtualProcess, but it's probably simpler to keep ICorDebug APIs returning
+ // consistent error codes.
+ ThrowHR(CORDBG_E_INCOMPATIBLE_PROTOCOL);
+ }
+}
+
+HRESULT Cordb::DebugActiveProcessEx(ICorDebugRemoteTarget * pRemoteTarget,
+ DWORD dwProcessId,
+ BOOL fWin32Attach,
+ ICorDebugProcess ** ppProcess)
+{
+ if (pRemoteTarget == NULL)
+ {
+ return E_INVALIDARG;
+ }
+
+ return DebugActiveProcessCommon(pRemoteTarget, dwProcessId, fWin32Attach, ppProcess);
+}
+
+
+HRESULT Cordb::GetProcess(DWORD dwProcessId, ICorDebugProcess **ppProcess)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppProcess, ICorDebugProcess**);
+
+ if (!m_initialized)
+ {
+ return E_FAIL;
+ }
+
+ LockProcessList();
+ CordbProcess *p = GetProcessList()->GetBase(dwProcessId);
+ UnlockProcessList();
+
+ if (p == NULL)
+ return E_INVALIDARG;
+
+ p->ExternalAddRef();
+ *ppProcess = static_cast<ICorDebugProcess*> (p);
+
+ return S_OK;
+}
+
+HRESULT Cordb::EnumerateProcesses(ICorDebugProcessEnum **ppProcesses)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppProcesses, ICorDebugProcessEnum **);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ if (!m_initialized)
+ {
+ ThrowHR(E_FAIL);
+ }
+
+ // Locking here just means that the enumerator gets initialized against a consistent
+ // process-list. If we add/remove processes w/ an outstanding enumerator, things
+ // could still get out of sync.
+ RSLockHolder lockHolder(&this->m_processListMutex);
+
+ RSInitHolder<CordbHashTableEnum> pEnum;
+ CordbHashTableEnum::BuildOrThrow(
+ this,
+ &m_pProcessEnumList,
+ GetProcessList(),
+ IID_ICorDebugProcessEnum,
+ pEnum.GetAddr());
+
+
+ pEnum.TransferOwnershipExternal(ppProcesses);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+
+//
+// Note: the following defs and structs are copied from various NT headers. I wasn't able to include those headers (like
+// ntexapi.h) due to loads of redef problems and other conflicts with headers that we already pull in.
+//
+typedef LONG NTSTATUS;
+
+#ifndef FEATURE_PAL
+typedef BOOL (*NTQUERYSYSTEMINFORMATION)(SYSTEM_INFORMATION_CLASS SystemInformationClass,
+ PVOID SystemInformation,
+ ULONG SystemInformationLength,
+ PULONG ReturnLength);
+#endif
+
+// Implementation of ICorDebug::CanLaunchOrAttach
+// @dbgtodo- this all goes away in V3.
+// @dbgtodo- this should go away in Dev11.
+HRESULT Cordb::CanLaunchOrAttach(DWORD dwProcessId, BOOL fWin32DebuggingEnabled)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ EnsureCanLaunchOrAttach(fWin32DebuggingEnabled);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Throw an expcetion if we can't launch/attach.
+//
+// Arguments:
+// fWin32DebuggingEnabled - true if interop-debugging, else false
+//
+// Return Value:
+// None. If this returns, then it's safe to launch/attach.
+// Else this throws an exception on failure.
+//
+// Assumptions:
+//
+// Notes:
+// It should always be safe to launch/attach except in exceptional cases.
+// @dbgtodo- this all goes away in V3.
+// @dbgtodo- this should go away in Dev11.
+//
+void Cordb::EnsureCanLaunchOrAttach(BOOL fWin32DebuggingEnabled)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+ if (!m_initialized)
+ {
+ ThrowHR(E_FAIL);
+ }
+
+ EnsureAllowAnotherProcess();
+
+ if (!IsInteropDebuggingSupported() && fWin32DebuggingEnabled)
+ {
+ ThrowHR(CORDBG_E_INTEROP_NOT_SUPPORTED);
+ }
+
+ // Made it this far, we succeeded.
+}
+
+HRESULT Cordb::CreateObjectV1(REFIID id, void **object)
+{
+ return CreateObject(CorDebugVersion_1_0, id, object);
+}
+
+#if defined(FEATURE_DBGIPC_TRANSPORT_DI)
+// CoreCLR activates debugger objects via direct COM rather than the shim (just like V1). For now we share the
+// same debug engine version as V2, though this may change in the future.
+HRESULT Cordb::CreateObjectTelesto(REFIID id, void ** pObject)
+{
+ return CreateObject(CorDebugVersion_2_0, id, pObject);
+}
+#endif // FEATURE_DBGIPC_TRANSPORT_DI
+
+// Static
+// Used to create an instance for a ClassFactory (thus an external ref).
+HRESULT Cordb::CreateObject(CorDebugInterfaceVersion iDebuggerVersion, REFIID id, void **object)
+{
+ if (id != IID_IUnknown && id != IID_ICorDebug)
+ return (E_NOINTERFACE);
+
+ Cordb *db = new (nothrow) Cordb(iDebuggerVersion);
+
+ if (db == NULL)
+ return (E_OUTOFMEMORY);
+
+ *object = static_cast<ICorDebug*> (db);
+ db->ExternalAddRef();
+
+ return (S_OK);
+}
+
+
+// This is the version of the ICorDebug APIs that the debugger believes it's consuming.
+// If this is a different version than that of the debuggee, we have the option of shimming
+// behavior.
+CorDebugInterfaceVersion
+Cordb::GetDebuggerVersion() const
+{
+ return m_debuggerSpecifiedVersion;
+}
+
+//***********************************************************************
+// ICorDebugTMEnum (Thread and Module enumerator)
+//***********************************************************************
+CordbEnumFilter::CordbEnumFilter(CordbBase * pOwnerObj, NeuterList * pOwnerList)
+ : CordbBase (pOwnerObj->GetProcess(), 0),
+ m_pOwnerObj(pOwnerObj),
+ m_pOwnerNeuterList(pOwnerList),
+ m_pFirst (NULL),
+ m_pCurrent (NULL),
+ m_iCount (0)
+{
+ _ASSERTE(m_pOwnerNeuterList != NULL);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ m_pOwnerNeuterList->Add(pOwnerObj->GetProcess(), this);
+ }
+ EX_CATCH_HRESULT(hr);
+ SetUnrecoverableIfFailed(GetProcess(), hr);
+
+}
+
+CordbEnumFilter::CordbEnumFilter(CordbEnumFilter *src)
+ : CordbBase (src->GetProcess(), 0),
+ m_pOwnerObj(src->m_pOwnerObj),
+ m_pOwnerNeuterList(src->m_pOwnerNeuterList),
+ m_pFirst (NULL),
+ m_pCurrent (NULL)
+{
+ _ASSERTE(m_pOwnerNeuterList != NULL);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ m_pOwnerNeuterList->Add(src->GetProcess(), this);
+ }
+ EX_CATCH_HRESULT(hr);
+ SetUnrecoverableIfFailed(GetProcess(), hr);
+
+
+
+ int iCountSanityCheck = 0;
+ EnumElement *pElementCur = NULL;
+ EnumElement *pElementNew = NULL;
+ EnumElement *pElementNewPrev = NULL;
+
+ m_iCount = src->m_iCount;
+
+ pElementCur = src->m_pFirst;
+
+ while (pElementCur != NULL)
+ {
+ pElementNew = new (nothrow) EnumElement;
+ if (pElementNew == NULL)
+ {
+ // Out of memory. Clean up and bail out.
+ goto Error;
+ }
+
+ if (pElementNewPrev == NULL)
+ {
+ m_pFirst = pElementNew;
+ }
+ else
+ {
+ pElementNewPrev->SetNext(pElementNew);
+ }
+
+ pElementNewPrev = pElementNew;
+
+ // Copy the element, including the AddRef part
+ pElementNew->SetData(pElementCur->GetData());
+ IUnknown *iu = (IUnknown *)pElementCur->GetData();
+ iu->AddRef();
+
+ if (pElementCur == src->m_pCurrent)
+ m_pCurrent = pElementNew;
+
+ pElementCur = pElementCur->GetNext();
+ iCountSanityCheck++;
+ }
+
+ _ASSERTE(iCountSanityCheck == m_iCount);
+
+ return;
+Error:
+ // release all the allocated memory before returning
+ pElementCur = m_pFirst;
+
+ while (pElementCur != NULL)
+ {
+ pElementNewPrev = pElementCur;
+ pElementCur = pElementCur->GetNext();
+
+ ((ICorDebugModule *)pElementNewPrev->GetData())->Release();
+ delete pElementNewPrev;
+ }
+}
+
+CordbEnumFilter::~CordbEnumFilter()
+{
+ _ASSERTE(this->IsNeutered());
+
+ _ASSERTE(m_pFirst == NULL);
+}
+
+void CordbEnumFilter::Neuter()
+{
+ EnumElement *pElement = m_pFirst;
+ EnumElement *pPrevious = NULL;
+
+ while (pElement != NULL)
+ {
+ pPrevious = pElement;
+ pElement = pElement->GetNext();
+ delete pPrevious;
+ }
+
+ // Null out the head in case we get neutered again.
+ m_pFirst = NULL;
+ m_pCurrent = NULL;
+
+ CordbBase::Neuter();
+}
+
+
+
+HRESULT CordbEnumFilter::QueryInterface(REFIID id, void **ppInterface)
+{
+ // if we QI with the IID of the base type, we can't just return a pointer ICorDebugEnum directly, because
+ // the cast is ambiguous. This happens because CordbEnumFilter implements both ICorDebugModuleEnum and
+ // ICorDebugThreadEnum, both of which derive in turn from ICorDebugEnum. This produces a diamond inheritance
+ // graph. Thus we need a double cast. It doesn't really matter whether we pick ICorDebugThreadEnum or
+ // ICorDebugModuleEnum, because it will be backed by the same object regardless.
+ if (id == IID_ICorDebugEnum)
+ *ppInterface = static_cast<ICorDebugEnum *>(static_cast<ICorDebugThreadEnum *>(this));
+ else if (id == IID_ICorDebugModuleEnum)
+ *ppInterface = (ICorDebugModuleEnum*)this;
+ else if (id == IID_ICorDebugThreadEnum)
+ *ppInterface = (ICorDebugThreadEnum*)this;
+ else if (id == IID_IUnknown)
+ *ppInterface = this;
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+HRESULT CordbEnumFilter::Skip(ULONG celt)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+ {
+ while ((celt-- > 0) && (m_pCurrent != NULL))
+ {
+ m_pCurrent = m_pCurrent->GetNext();
+ }
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+HRESULT CordbEnumFilter::Reset()
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+ {
+ m_pCurrent = m_pFirst;
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+HRESULT CordbEnumFilter::Clone(ICorDebugEnum **ppEnum)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+ {
+ ValidateOrThrow(ppEnum);
+
+ CordbEnumFilter * pClone = new CordbEnumFilter(this);
+
+ // Ambigous conversion from CordbEnumFilter to ICorDebugEnum, so
+ // we explicitly convert it through ICorDebugThreadEnum.
+ pClone->ExternalAddRef();
+ (*ppEnum) = static_cast<ICorDebugThreadEnum *> (pClone);
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+HRESULT CordbEnumFilter::GetCount(ULONG *pcelt)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+ {
+ ValidateOrThrow(pcelt);
+ *pcelt = (ULONG)m_iCount;
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+HRESULT CordbEnumFilter::Next(ULONG celt,
+ ICorDebugModule *objects[],
+ ULONG *pceltFetched)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+ {
+ hr = NextWorker(celt, objects, pceltFetched);
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+HRESULT CordbEnumFilter::NextWorker(ULONG celt, ICorDebugModule *objects[], ULONG *pceltFetched)
+{
+ // <TODO>
+ //
+ // nickbe 11/20/2002 10:43:39
+ // This function allows you to enumerate threads that "belong" to a
+ // particular AppDomain. While this operation makes some sense, it makes
+ // very little sense to
+ // (a) enumerate the list of threads in the enter process
+ // (b) build up a hand-rolled singly linked list (grrr)
+ // </TODO>
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(objects, ICorDebugModule *,
+ celt, true, true);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pceltFetched, ULONG *);
+
+ if ((pceltFetched == NULL) && (celt != 1))
+ {
+ return E_INVALIDARG;
+ }
+
+ if (celt == 0)
+ {
+ if (pceltFetched != NULL)
+ {
+ *pceltFetched = 0;
+ }
+ return S_OK;
+ }
+
+ HRESULT hr = S_OK;
+
+ ULONG count = 0;
+
+ while ((m_pCurrent != NULL) && (count < celt))
+ {
+ objects[count] = (ICorDebugModule *)m_pCurrent->GetData();
+ m_pCurrent = m_pCurrent->GetNext();
+ count++;
+ }
+
+ if (pceltFetched != NULL)
+ {
+ *pceltFetched = count;
+ }
+
+ //
+ // If we reached the end of the enumeration, but not the end
+ // of the number of requested items, we return S_FALSE.
+ //
+ if (count < celt)
+ {
+ return S_FALSE;
+ }
+
+ return hr;
+}
+
+
+HRESULT CordbEnumFilter::Next(ULONG celt,
+ ICorDebugThread *objects[],
+ ULONG *pceltFetched)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+ {
+ hr = NextWorker(celt, objects, pceltFetched);
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+HRESULT CordbEnumFilter::NextWorker(ULONG celt, ICorDebugThread *objects[], ULONG *pceltFetched)
+{
+ // @TODO remove this class
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(objects, ICorDebugThread *, celt, true, true);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pceltFetched, ULONG *);
+
+ if ((pceltFetched == NULL) && (celt != 1))
+ {
+ return E_INVALIDARG;
+ }
+
+ if (celt == 0)
+ {
+ if (pceltFetched != NULL)
+ {
+ *pceltFetched = 0;
+ }
+ return S_OK;
+ }
+
+ HRESULT hr = S_OK;
+
+ ULONG count = 0;
+
+ while ((m_pCurrent != NULL) && (count < celt))
+ {
+ objects[count] = (ICorDebugThread *)m_pCurrent->GetData();
+ m_pCurrent = m_pCurrent->GetNext();
+ count++;
+ }
+
+ if (pceltFetched != NULL)
+ {
+ *pceltFetched = count;
+ }
+
+ //
+ // If we reached the end of the enumeration, but not the end
+ // of the number of requested items, we return S_FALSE.
+ //
+ if (count < celt)
+ {
+ return S_FALSE;
+ }
+
+ return hr;
+}
+
+
+
+HRESULT CordbEnumFilter::Init (ICorDebugModuleEnum * pModEnum, CordbAssembly *pAssembly)
+{
+ INTERNAL_API_ENTRY(GetProcess());
+
+ ICorDebugModule *pCorModule = NULL;
+ CordbModule *pModule = NULL;
+ ULONG ulDummy = 0;
+
+ HRESULT hr = pModEnum->Next(1, &pCorModule, &ulDummy);
+
+ //
+ // Next returns E_FAIL if there is no next item, along with
+ // the count being 0. Convert that to just being S_OK.
+ //
+ if ((hr == E_FAIL) && (ulDummy == 0))
+ {
+ hr = S_OK;
+ }
+
+ if (FAILED (hr))
+ return hr;
+
+ EnumElement *pPrevious = NULL;
+ EnumElement *pElement = NULL;
+
+ while (ulDummy != 0)
+ {
+ pModule = (CordbModule *)(ICorDebugModule *)pCorModule;
+ // Is this module part of the assembly for which we're enumerating?
+ if (pModule->m_pAssembly == pAssembly)
+ {
+ pElement = new (nothrow) EnumElement;
+ if (pElement == NULL)
+ {
+ // Out of memory. Clean up and bail out.
+ hr = E_OUTOFMEMORY;
+ goto Error;
+ }
+
+ pElement->SetData ((void *)pCorModule);
+ m_iCount++;
+
+ if (m_pFirst == NULL)
+ {
+ m_pFirst = pElement;
+ }
+ else
+ {
+ PREFIX_ASSUME(pPrevious != NULL);
+ pPrevious->SetNext (pElement);
+ }
+ pPrevious = pElement;
+ }
+ else
+ ((ICorDebugModule *)pModule)->Release();
+
+ hr = pModEnum->Next(1, &pCorModule, &ulDummy);
+
+ //
+ // Next returns E_FAIL if there is no next item, along with
+ // the count being 0. Convert that to just being S_OK.
+ //
+ if ((hr == E_FAIL) && (ulDummy == 0))
+ {
+ hr = S_OK;
+ }
+
+ if (FAILED (hr))
+ goto Error;
+ }
+
+ m_pCurrent = m_pFirst;
+
+ return S_OK;
+
+Error:
+ // release all the allocated memory before returning
+ pElement = m_pFirst;
+
+ while (pElement != NULL)
+ {
+ pPrevious = pElement;
+ pElement = pElement->GetNext();
+
+ ((ICorDebugModule *)pPrevious->GetData())->Release();
+ delete pPrevious;
+ }
+
+ return hr;
+}
+
+HRESULT CordbEnumFilter::Init (ICorDebugThreadEnum *pThreadEnum, CordbAppDomain *pAppDomain)
+{
+ INTERNAL_API_ENTRY(GetProcess());
+
+ ICorDebugThread *pCorThread = NULL;
+ CordbThread *pThread = NULL;
+ ULONG ulDummy = 0;
+
+ HRESULT hr = pThreadEnum->Next(1, &pCorThread, &ulDummy);
+
+ //
+ // Next returns E_FAIL if there is no next item, but we want to consider this
+ // ok in this context.
+ //
+ if ((hr == E_FAIL) && (ulDummy == 0))
+ {
+ hr = S_OK;
+ }
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ EnumElement *pPrevious = NULL;
+ EnumElement *pElement = NULL;
+
+ while (ulDummy > 0)
+ {
+ pThread = (CordbThread *)(ICorDebugThread *) pCorThread;
+
+ // Is this module part of the appdomain for which we're enumerating?
+ // Note that this is rather inefficient (we call into the left side for every AppDomain),
+ // but the whole idea of enumerating the threads of an AppDomain is pretty bad,
+ // and we don't expect this to be used much if at all.
+ CordbAppDomain* pThreadDomain;
+ hr = pThread->GetCurrentAppDomain( &pThreadDomain );
+ if( FAILED(hr) )
+ {
+ goto Error;
+ }
+
+ if (pThreadDomain == pAppDomain)
+ {
+ pElement = new (nothrow) EnumElement;
+ if (pElement == NULL)
+ {
+ // Out of memory. Clean up and bail out.
+ hr = E_OUTOFMEMORY;
+ goto Error;
+ }
+
+ pElement->SetData ((void *)pCorThread);
+ m_iCount++;
+
+ if (m_pFirst == NULL)
+ {
+ m_pFirst = pElement;
+ }
+ else
+ {
+ PREFIX_ASSUME(pPrevious != NULL);
+ pPrevious->SetNext (pElement);
+ }
+
+ pPrevious = pElement;
+ }
+ else
+ {
+ ((ICorDebugThread *)pThread)->Release();
+ }
+
+ // get the next thread in the thread list
+ hr = pThreadEnum->Next(1, &pCorThread, &ulDummy);
+
+ //
+ // Next returns E_FAIL if there is no next item, along with
+ // the count being 0. Convert that to just being S_OK.
+ //
+ if ((hr == E_FAIL) && (ulDummy == 0))
+ {
+ hr = S_OK;
+ }
+
+ if (FAILED (hr))
+ goto Error;
+ }
+
+ m_pCurrent = m_pFirst;
+
+ return S_OK;
+
+Error:
+ // release all the allocated memory before returning
+ pElement = m_pFirst;
+
+ while (pElement != NULL)
+ {
+ pPrevious = pElement;
+ pElement = pElement->GetNext();
+
+ ((ICorDebugThread *)pPrevious->GetData())->Release();
+ delete pPrevious;
+ }
+
+ return hr;
+}
+
diff --git a/src/debug/di/rsmda.cpp b/src/debug/di/rsmda.cpp
new file mode 100644
index 0000000000..d69b448309
--- /dev/null
+++ b/src/debug/di/rsmda.cpp
@@ -0,0 +1,243 @@
+// 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.
+//*****************************************************************************
+// File: RsMda.cpp
+//
+
+// Manage Debug Assistant support in the Right-Side
+//
+//*****************************************************************************
+#include "stdafx.h"
+
+#include "winbase.h"
+#include "corpriv.h"
+
+//-----------------------------------------------------------------------------
+// Cordb MDA notification
+//-----------------------------------------------------------------------------
+CordbMDA::CordbMDA(CordbProcess * pProc, DebuggerMDANotification * pData)
+: CordbBase(pProc, 0, enumCordbMDA)
+{
+ _ASSERTE(pData != NULL);
+
+ // Owning Parent process should add us to process'es neuter list.
+
+ // Pick up ownership of strings
+ m_szName = pData->szName.TransferStringData();
+ m_szDescription = pData->szDescription.TransferStringData();
+ m_szXml = pData->szXml.TransferStringData();
+
+ m_dwOSTID = pData->dwOSThreadId;
+ m_flags = pData->flags;
+}
+
+//-----------------------------------------------------------------------------
+// Destructor for CordbMDA object. Not much to do here since neutering should
+// have taken care of it all.
+//-----------------------------------------------------------------------------
+CordbMDA::~CordbMDA()
+{
+ // Strings protected w/ holders that will automatically free them.
+ _ASSERTE(IsNeutered());
+}
+
+//-----------------------------------------------------------------------------
+// Neuter the CordbMDA object.
+//-----------------------------------------------------------------------------
+void CordbMDA::Neuter()
+{
+ // Release buffers. Once we're neutered, these can no longer be accessed anyways,
+ // so may as well free them now.
+ // This is being done under the process-lock, and our accessors are also done
+ // under that lock, so we don't have to worry about any races here. :)
+ m_szName.Clear();
+ m_szDescription.Clear();
+ m_szXml.Clear();
+
+ CordbBase::Neuter();
+};
+
+//-----------------------------------------------------------------------------
+// Implement IUnknown::QueryInterface.
+//-----------------------------------------------------------------------------
+HRESULT CordbMDA::QueryInterface(REFIID riid, void **ppInterface)
+{
+ if (riid == IID_ICorDebugMDA)
+ *ppInterface = static_cast<ICorDebugMDA*>(this);
+ else if (riid == IID_IUnknown)
+ *ppInterface = static_cast<IUnknown*>(static_cast<ICorDebugMDA*>(this));
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Helper to marshal a string object out through the ICorDebug interfaces
+// *GetName() functions using the common triple design pattern.
+//
+// parameters:
+// pInputString - the string that we want to marshal out via the triple
+// cchName, pcchName, szName - triple used to marshal out a string.
+// Same usage as CordbModule::GetName and other string getters on the API.
+//
+// *pcchName is always set to the length of pInputString (including NULL). This lets
+// callers know the full size of buffer they'd need to allocate to get the full string.
+//
+// if (cchName == 0) then we're in "query" mode:
+// szName must be null. pcchName must be non-null and this function will just set
+// *pcchName to let the caller know how large of a buffer to allocate.
+
+// if (cchName != 0) then we copy as much as can fit into szName. We will always
+// null terminate szName.
+// pcchName can be null. If it's non-null, we set it.
+//
+//
+// Expected usage is that caller calls us twice, once in query mode to allocate
+// buffer, then a 2nd time to fill the buffer.
+//
+// Returns: S_OK on success.
+//-----------------------------------------------------------------------------
+HRESULT CopyOutString(LPCWSTR pInputString, ULONG32 cchName, ULONG32 * pcchName, __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[])
+{
+ _ASSERTE(pInputString != NULL);
+ ULONG32 len = (ULONG32) wcslen(pInputString) + 1;
+
+ if (cchName == 0)
+ {
+ // Query length
+ if ((szName != NULL) || (pcchName == NULL))
+ {
+ return E_INVALIDARG;
+ }
+ *pcchName = len;
+ return S_OK;
+ }
+ else
+ {
+ // Get data
+ if (szName == NULL)
+ {
+ return E_INVALIDARG;
+ }
+
+ // Just copy whatever we can fit into the buffer. If we truncate, that's ok.
+ // This will also guarantee that we null terminate.
+ wcsncpy_s(szName, cchName, pInputString, _TRUNCATE);
+
+ if (pcchName != 0)
+ {
+ *pcchName = len;
+ }
+
+ return S_OK;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Get the string for the type of the MDA. Never empty.
+// This is a convenient performant alternative to getting the XML stream and extracting
+// the type from that based off the schema.
+// See CopyOutString for parameter details.
+//-----------------------------------------------------------------------------
+HRESULT CordbMDA::GetName(ULONG32 cchName, ULONG32 * pcchName, __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[])
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this)
+ {
+#if defined(FEATURE_CORECLR)
+ hr = E_NOTIMPL;
+#else // !FEATURE_CORECLR
+ hr = CopyOutString(m_szName, cchName, pcchName, szName);
+#endif // FEATURE_CORECLR
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// Get a string description of the MDA. This may be empty (0-length).
+// See CopyOutString for parameter details.
+//-----------------------------------------------------------------------------
+HRESULT CordbMDA::GetDescription(ULONG32 cchName, ULONG32 * pcchName, __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[])
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this)
+ {
+#if defined(FEATURE_CORECLR)
+ hr = E_NOTIMPL;
+#else // !FEATURE_CORECLR
+ hr = CopyOutString(m_szDescription, cchName, pcchName, szName);
+#endif // FEATURE_CORECLR
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// Get the full associated XML for the MDA. This may be empty.
+// This could be a potentially expensive operation if the xml stream is large.
+// See the MDA documentation for the schema for this XML stream.
+// See CopyOutString for parameter details.
+//-----------------------------------------------------------------------------
+HRESULT CordbMDA::GetXML(ULONG32 cchName, ULONG32 * pcchName, __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[])
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this)
+ {
+#if defined(FEATURE_CORECLR)
+ hr = E_NOTIMPL;
+#else // !FEATURE_CORECLR
+ hr = CopyOutString(m_szXml, cchName, pcchName, szName);
+#endif // FEATURE_CORECLR
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// Get flags for this MDA object.
+//-----------------------------------------------------------------------------
+HRESULT CordbMDA::GetFlags(CorDebugMDAFlags * pFlags)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this)
+ {
+#if defined(FEATURE_CORECLR)
+ hr = E_NOTIMPL;
+#else // !FEATURE_CORECLR
+ ValidateOrThrow(pFlags);
+ *pFlags = this->m_flags;
+#endif // FEATURE_CORECLR
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// Thread that the MDA is fired on. We use the os tid instead of an ICDThread in case an MDA is fired on a
+// native thread (or a managed thread that hasn't yet entered managed code and so we don't have a ICDThread
+// object for it yet)
+//-----------------------------------------------------------------------------
+HRESULT CordbMDA::GetOSThreadId(DWORD * pOsTid)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this)
+ {
+#if defined(FEATURE_CORECLR)
+ hr = E_NOTIMPL;
+#else // !FEATURE_CORECLR
+ ValidateOrThrow(pOsTid);
+
+ *pOsTid = this->m_dwOSTID;
+#endif // FEATURE_CORECLR
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
diff --git a/src/debug/di/rspriv.h b/src/debug/di/rspriv.h
new file mode 100644
index 0000000000..853767804a
--- /dev/null
+++ b/src/debug/di/rspriv.h
@@ -0,0 +1,11756 @@
+// 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.
+//*****************************************************************************
+// rspriv.
+//
+
+//
+// Common include file for right-side of debugger.
+//*****************************************************************************
+
+#ifndef RSPRIV_H
+#define RSPRIV_H
+
+#include <winwrap.h>
+#include <windows.h>
+
+#include <utilcode.h>
+
+
+#ifdef _DEBUG
+#define LOGGING
+#endif
+
+#include <log.h>
+#include <corerror.h>
+
+#include "cor.h"
+
+#include "cordebug.h"
+#include "xcordebug.h"
+#include "cordbpriv.h"
+#include "mscoree.h"
+
+#include <cordbpriv.h>
+#include <dbgipcevents.h>
+
+#if !defined(FEATURE_DBGIPC_TRANSPORT_DI)
+#include <ipcmanagerinterface.h>
+#endif // !FEATURE_DBGIPC_TRANSPORT_DI
+
+#include "common.h"
+#include "primitives.h"
+
+#include "dacdbiinterface.h"
+
+#include "helpers.h"
+
+struct MachineInfo;
+
+#include "nativepipeline.h"
+#include "stringcopyholder.h"
+
+
+#include "eventchannel.h"
+
+#undef ASSERT
+#define CRASH(x) _ASSERTE(!x)
+#define ASSERT(x) _ASSERTE(x)
+
+// We want to keep the 'worst' HRESULT - if one has failed (..._E_...) & the
+// other hasn't, take the failing one. If they've both/neither failed, then
+// it doesn't matter which we take.
+// Note that this macro favors retaining the first argument
+#define WORST_HR(hr1,hr2) (FAILED(hr1)?hr1:hr2)
+
+// #UseDataTarget
+// Forbid usage of OS APIs that we should be using the data-target for
+#define ReadProcessMemory DONT_USE_READPROCESS_MEMORY
+#define WriteProcessMemory DONT_USE_WRITEPROCESS_MEMORY
+
+
+/* ------------------------------------------------------------------------- *
+ * Forward class declarations
+ * ------------------------------------------------------------------------- */
+
+class CordbBase;
+class CordbValue;
+class CordbModule;
+class CordbClass;
+class CordbFunction;
+class CordbCode;
+class CordbFrame;
+class CordbJITILFrame;
+class CordbInternalFrame;
+class CordbContext;
+class CordbThread;
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+class CordbUnmanagedThread;
+struct CordbUnmanagedEvent;
+#endif
+
+class CordbProcess;
+class CordbAppDomain;
+class CordbAssembly;
+class CordbBreakpoint;
+class CordbStepper;
+class Cordb;
+class CordbEnCSnapshot;
+class CordbWin32EventThread;
+class CordbRCEventThread;
+class CordbRegisterSet;
+class CordbNativeFrame;
+class CordbObjectValue;
+class CordbEnCErrorInfo;
+class CordbEnCErrorInfoEnum;
+class Instantiation;
+class CordbType;
+class CordbNativeCode;
+class CordbILCode;
+class CordbReJitILCode;
+class CordbEval;
+
+class CordbMDA;
+
+class CorpubPublish;
+class CorpubProcess;
+class CorpubAppDomain;
+class CorpubProcessEnum;
+class CorpubAppDomainEnum;
+
+
+class RSLock;
+class NeuterList;
+
+class IDacDbiInterface;
+
+#if defined(FEATURE_DBGIPC_TRANSPORT_DI)
+class DbgTransportTarget;
+class DbgTransportSession;
+#endif // FEATURE_DBGIPC_TRANSPORT_DI
+
+// @dbgtodo private shim hook - the RS has private hooks into the shim to help bridge the V2/V3 gap.
+// This helps provide a working dogfooding story throughout our transition.
+// These hooks must be removed before shipping.
+class ShimProcess;
+
+
+#ifndef FEATURE_PAL
+extern HINSTANCE GetModuleInst();
+#endif
+
+
+template <class T>
+class CordbSafeHashTable;
+
+
+//---------------------------------------------------------------------------------------
+//
+// This is an encapsulation of the information necessary to connect to the debugger proxy on a remote machine.
+// It includes the IP address and the port number. The IP address can be set via the env var
+// COMPlus_DbgTransportProxyAddress, and the port number is fixed when Mac debugging is configured.
+//
+
+struct MachineInfo
+{
+public:
+ void Init(DWORD dwIPAddress, USHORT usPort)
+ {
+ m_dwIPAddress = dwIPAddress;
+ m_usPort = usPort;
+ }
+
+ void Clear()
+ {
+ m_dwIPAddress = 0;
+ m_usPort = 0;
+ }
+
+ DWORD GetIPAddress() {return m_dwIPAddress;};
+ USHORT GetPort() {return m_usPort;};
+
+private:
+ DWORD m_dwIPAddress;
+ USHORT m_usPort;
+};
+
+#define forDbi (*(forDbiWorker *)NULL)
+
+// for dbi we just default to new, but we need to have these defined for both dac and dbi
+inline void * operator new(size_t lenBytes, const forDbiWorker &)
+{
+ void * result = new BYTE[lenBytes];
+ if (result == NULL)
+ {
+ ThrowOutOfMemory();
+ }
+ return result;
+}
+
+inline void * operator new[](size_t lenBytes, const forDbiWorker &)
+{
+ void * result = new BYTE[lenBytes];
+ if (result == NULL)
+ {
+ ThrowOutOfMemory();
+ }
+ return result;
+}
+
+// Helper to delete memory used with the IDacDbiInterface::IAllocator interface.
+template<class T> inline
+void DeleteDbiMemory(T *p)
+{
+ delete p;
+}
+
+
+
+//---------------------------------------------------------------------------------------
+//
+// Simple array of holders (either RSSmartPtrs or RSExtSmartPtrs).
+// Holds a reference to each element.
+//
+// Notes:
+// T is the base type and HOLDER_T is the type of the holder. All functions implemented on this base
+// class must work for both RSSmartPtrs and RSExtSmartPtrs. For example, there is no concept of neutering
+// for RSExtSmartPtrs.
+//
+
+template<typename T, typename HOLDER_T>
+class BaseRSPtrArray
+{
+public:
+ BaseRSPtrArray()
+ {
+ m_pArray = NULL;
+ m_cElements = 0;
+ }
+
+ // Is the array emtpy?
+ bool IsEmpty() const
+ {
+ return (m_pArray == NULL);
+ }
+
+ // Allocate an array of ptrs.
+ // Returns false if not enough memory; else true.
+ bool Alloc(unsigned int cElements)
+ {
+ // Caller should have already Neutered
+ _ASSERTE(IsEmpty());
+
+ // It's legal to allocate 0 items. We'll succeed the allocation, but still claim that IsEmpty() == true.
+ if (cElements == 0)
+ {
+ return true;
+ }
+
+ // RSSmartPtr ctor will ensure all elements are null initialized.
+ m_pArray = new (nothrow) HOLDER_T [cElements];
+ if (m_pArray == NULL)
+ {
+ return false;
+ }
+
+ m_cElements = cElements;
+ return true;
+ }
+
+ // Allocate an array of ptrs.
+ // Throw on failure
+ void AllocOrThrow(unsigned int cElements)
+ {
+ if (!Alloc(cElements))
+ {
+ ThrowOutOfMemory();
+ }
+ }
+
+ // Release each element and empty the array.
+ void Clear()
+ {
+ // this Invoke dtors on each element which will release each element
+ delete [] m_pArray;
+
+ m_pArray = NULL;
+ m_cElements = 0;
+ }
+
+ // Array lookup. Caller gaurantees this is in range.
+ // Used for reading
+ T* operator [] (unsigned int index) const
+ {
+ _ASSERTE(m_pArray != NULL);
+ CONSISTENCY_CHECK_MSGF((index <= m_cElements), ("Index out of range. Index=%u, Max=%u\n", index, m_cElements));
+
+ return m_pArray[index];
+ }
+
+ // Assign a given index to the given value. The array holder will increment the internal reference on the value.
+ void Assign(unsigned int index, T* pValue)
+ {
+ _ASSERTE(m_pArray != NULL);
+ CONSISTENCY_CHECK_MSGF((index <= m_cElements), ("Index out of range. Index=%u, Max=%u\n", index, m_cElements));
+
+ m_pArray[index].Assign(pValue);
+ }
+
+ // Get lenght of array in elements.
+ unsigned int Length() const
+ {
+ return m_cElements;
+ }
+
+ // Some things need to get the address of an element in the table.
+ // For example, CordbThreads have an array of CordbFrame objects, and then CordbChains describe a range
+ // or frames via pointers into the CordbThread's array.
+ // This is a dangerous operation because it lets us side-step reference counting and protection.
+ T ** UnsafeGetAddrOfIndex(unsigned int index)
+ {
+ return m_pArray[index].UnsafeGetAddr();
+ }
+
+protected:
+ // Raw array of values.
+ HOLDER_T * m_pArray;
+
+ // Number of elements in m_pArray. Note the following is always true: (m_cElements == 0) == (m_pArray == NULL);
+ unsigned int m_cElements;
+};
+
+
+//-----------------------------------------------------------------------------
+//
+// Simple array holder of RSSmartPtrs (internal pointers).
+// Holds a reference to each element.
+//
+// Notes:
+// This derived class adds the concept of neutering to the base pointer array.
+// Allows automatic Clear()ing; do not use this unless it is safe to do so in
+// all cases - e.g. you're holding a local.
+//
+
+template< typename T, typename HOLDER_T = RSSmartPtr<T> > // We need to use HOLDER_T to make gcc happy.
+class RSPtrArray : public BaseRSPtrArray<T, HOLDER_T>
+{
+private:
+ typedef BaseRSPtrArray<T, HOLDER_T> Super;
+ BOOL m_autoClear;
+
+public:
+ RSPtrArray() : m_autoClear(FALSE)
+ {
+ }
+
+ ~RSPtrArray()
+ {
+ if (m_autoClear)
+ {
+ Super::Clear();
+ }
+ else
+ {
+ // Caller should have already Neutered
+ _ASSERTE(Super::IsEmpty());
+ }
+ }
+
+ void EnableAutoClear()
+ {
+ m_autoClear = TRUE;
+ }
+
+ // Neuter all elements in the array.
+ void NeuterAndClear()
+ {
+ for(unsigned int i = 0; i < Super::m_cElements; i++)
+ {
+ if (Super::m_pArray[i] != NULL)
+ {
+ Super::m_pArray[i]->Neuter();
+ }
+ }
+
+ Super::Clear();
+ }
+};
+
+
+//-----------------------------------------------------------------------------
+//
+// Simple array holder of RSExtSmartPtrs (external pointers).
+// Holds a reference to each element.
+//
+// Notes:
+// This derived class clears the array in its destructor.
+//
+
+template< typename T, typename HOLDER_T = RSExtSmartPtr<T> > // We need to use HOLDER_T to make gcc happy.
+class RSExtPtrArray : public BaseRSPtrArray<T, HOLDER_T>
+{
+private:
+ typedef BaseRSPtrArray<T, HOLDER_T> Super;
+
+public:
+ ~RSExtPtrArray()
+ {
+ Super::Clear();
+ }
+};
+
+
+
+//-----------------------------------------------------------------------------
+// Table for RSptrs
+// This lets us map cookies <--> RSPTR_*,
+// Then we just put the cookie in the IPC block instead of the raw RSPTR.
+// This will also adjust the internal-reference count on the T* object.
+// This isolates the RS from bugs in the LS.
+// We templatize by type for type safety.
+// Caller must syncrhonize all access (preferably w/ the stop-go lock).
+//-----------------------------------------------------------------------------
+template <class T>
+class RsPtrTable
+{
+public:
+ RsPtrTable()
+ {
+ m_pTable = NULL;
+ m_cEntries = 0;
+ }
+ ~RsPtrTable()
+ {
+ Clear();
+ }
+ void Clear()
+ {
+ for(UINT i = 0; i < m_cEntries; i++)
+ {
+ if (m_pTable[i])
+ {
+ m_pTable[i]->InternalRelease();
+ }
+ }
+ delete [] m_pTable;
+ m_pTable = NULL;
+ m_cEntries = 0;
+ }
+
+ // Add a value into table. Value can't be NULL.
+ // Returns 0 on failure (such as oom),
+ // Returns a non-zero cookie on success.
+ UINT Add(T* pValue)
+ {
+ _ASSERTE(pValue != NULL);
+ // skip 0 because it's an invalid handle.
+ for(UINT i = 1; ; i++)
+ {
+ // If we've run out of space, allocate new space
+ if( i >= m_cEntries )
+ {
+ if( !Grow() )
+ {
+ return 0; // failed to grow
+ }
+ _ASSERTE( i < m_cEntries );
+ _ASSERTE( m_pTable[i] == NULL );
+ // Since we grew, the next slot should now be open.
+ }
+
+ if (m_pTable[i] == NULL)
+ {
+ m_pTable[i] = pValue;
+ pValue->InternalAddRef();
+ return i;
+ }
+ }
+ UNREACHABLE();
+ }
+
+ // Lookup the value based off the cookie, which was obtained via "Add".
+ // return NULL on error.
+ T* Lookup(UINT cookie)
+ {
+ _ASSERTE(cookie != 0);
+ if (cookie >= m_cEntries)
+ {
+ CONSISTENCY_CHECK_MSGF(false, ("Cookie out of range.Cookie=0x%x. Size=0x%x.\n", cookie, m_cEntries));
+ return NULL;
+ }
+ T* p = m_pTable[cookie];
+ if (p == NULL)
+ {
+ CONSISTENCY_CHECK_MSGF(false, ("Cookie is for empty slot.Cookie=0x%x.\n", cookie));
+ return NULL; // empty!
+ }
+ return p;
+ }
+
+ T* LookupAndRemove(UINT cookie)
+ {
+ _ASSERTE(cookie != 0);
+ T* p = Lookup(cookie);
+ if (p != NULL)
+ {
+ m_pTable[cookie] = NULL;
+ p->InternalRelease();
+ }
+ return p;
+ }
+
+protected:
+ // Resize the m_pTable array.
+ bool Grow()
+ {
+ if (m_pTable == NULL)
+ {
+ _ASSERTE(m_cEntries == 0);
+ size_t cSize = 10;
+ m_pTable = new (nothrow) T*[cSize];
+ if (m_pTable == NULL)
+ {
+ return false;
+ }
+ m_cEntries = cSize;
+ ZeroMemory(m_pTable, sizeof(T*) * m_cEntries);
+ return true;
+ }
+ size_t cNewSize = (m_cEntries * 3 / 2) + 1;
+ _ASSERTE(cNewSize > m_cEntries);
+ T** p = new (nothrow) T*[cNewSize];
+ if (p == NULL)
+ {
+ return false;
+ }
+ ZeroMemory(p, sizeof(T*) * cNewSize);
+
+
+ // Copy over old stuff
+ memcpy(p, m_pTable, sizeof(T*) * m_cEntries);
+ delete [] m_pTable;
+
+ m_pTable = p;
+ m_cEntries = cNewSize;
+ return true;
+ }
+
+ T** m_pTable;
+ size_t m_cEntries;
+};
+
+
+
+//-----------------------------------------------------------------------------
+// Simple Holder for RS object intialization to cooperate with Neutering
+// semantics.
+// The ctor will do an addref.
+// The dtor (invoked in exception) will neuter and release the object. This
+// release will likely be the final release to cause a delete.
+// If the object is created successfully, caller should do a SuppressRelease()
+// to avoid it getting neutered.
+//
+// Example:
+// RSInitHolder<CordbFoo> pFoo(new CordbFoo(x,y,z));
+// pFoo->InitMore(a,b,c);
+// GiveOwnershipToSomebodyElse(pFoo); // now somebody else owns and will clean up
+// pFoo.ClearAndMarkDontNeuter(); // we no longer need to
+//
+// So if an exception is thrown before ClearAndMarkDontNeuter(), the dtor is invoked
+// and the object is properly destroyed (deleted and neutered).
+//
+// Another common pattern is when initializing an object to hand off to an external:
+// RSInitHolder<CordbFoo> pFoo(new CordbFoo(x,y,z));
+// pFoo->InitMore(a,b,c);
+// pFoo.TransferOwnershipExternal(ppOutParameter);
+// TransferOwnershipExternal will assign to ppOutParameter, inc external ref, and
+// call ClearAndMarkDontNeuter()
+//-----------------------------------------------------------------------------
+template<class T>
+class RSInitHolder
+{
+public:
+ // Default ctor. Must call Assign() later.
+ RSInitHolder()
+ {
+ };
+ RSInitHolder(T * pObject)
+ {
+ Assign(pObject);
+ }
+
+ void Assign(T * pObject)
+ {
+ _ASSERTE(m_pObject == NULL); // only assign once.
+ m_pObject.Assign(pObject);
+ }
+ ~RSInitHolder();
+
+ FORCEINLINE operator T *() const
+ {
+ return m_pObject;
+
+ }
+ FORCEINLINE T * operator->()
+ {
+ return m_pObject;
+ }
+
+ // This will null out m_pObject such that the dtor will not neuter it.
+ // This will also release the ref we took in the ctor.
+ // This will clear the current pointer.
+ void ClearAndMarkDontNeuter()
+ {
+ m_pObject.Clear();
+ }
+
+ //
+ // Transfer ownership to a pointer
+ //
+ // Arguments:
+ // ppOutParam - pointer to get ownership. External Reference is incremented.
+ // this pointer should do an external release.
+ //
+ // Notes:
+ // This calls ClearAndMarkDontNeuter(). This holder is Empty after this.
+ template <class TOther>
+ void TransferOwnershipExternal(TOther ** ppOutParam)
+ {
+ *ppOutParam = static_cast<TOther*> (m_pObject);
+ m_pObject->ExternalAddRef();
+
+ ClearAndMarkDontNeuter();
+ }
+
+
+ //
+ // Transfer the ownership of the wrapped object to the given hash table.
+ //
+ // Arguments:
+ // pHashTable - hash table to take ownership.
+ //
+ // Returns:
+ // the contianing object for convenience. Throws on error (particularly
+ // if it fails adding to the hash).
+ //
+ // Notes:
+ // This calls ClearAndMarkDontNeuter(). This holder is Empty after this.
+ T* TransferOwnershipToHash(CordbSafeHashTable<T> * pHashtable)
+ {
+ T* pObject = m_pObject;
+ pHashtable->AddBaseOrThrow(m_pObject);
+ ClearAndMarkDontNeuter();
+ return pObject;
+ }
+
+ //
+ // Used to pass into a function that will assign to us.
+ //
+ // Returns:
+ // Address of this holder. This is like the & operator.
+ // This is provided for consistency with other holders which
+ // override the &operator.
+ RSInitHolder<T> * GetAddr()
+ {
+ return this;
+ }
+
+
+protected:
+ RSSmartPtr<T> m_pObject;
+};
+
+
+
+//-----------------------------------------------------------------------------
+// Have the extra level of indirection is useful for catching Cordbg errors.
+//-----------------------------------------------------------------------------
+#ifdef _DEBUG
+ // On debug, we have an opportunity to catch failing hresults during reproes.
+ #define ErrWrapper(hr) ErrWrapperHelper(hr, __FILE__, __LINE__)
+
+ inline HRESULT ErrWrapperHelper(HRESULT hr, const char * szFile, int line)
+ {
+ if (FAILED(hr))
+ {
+ DWORD dwErr = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgBreakOnErr);
+ if (dwErr)
+ {
+ CONSISTENCY_CHECK_MSGF(false, ("Dbg Error break, hr=0x%08x, '%s':%d", hr, szFile, line));
+ }
+ }
+ return hr;
+ }
+#else
+ // On release, it's just an identity function
+ #define ErrWrapper(hr) (hr)
+#endif
+
+//-----------------------------------------------------------------------------
+// Quick helpers for threading semantics
+//-----------------------------------------------------------------------------
+
+bool IsWin32EventThread(CordbProcess* p);
+bool IsRCEventThread(Cordb* p);
+
+/* ------------------------------------------------------------------------- *
+ * Typedefs
+ * ------------------------------------------------------------------------- */
+
+typedef void* REMOTE_PTR;
+
+
+//-----------------------------------------------------------------------------
+// Wrapper class for locks. This is like Crst on the LS
+//-----------------------------------------------------------------------------
+
+class RSLock
+{
+public:
+ // Attrs, can be bitwise-or together.
+ enum ELockAttr
+ {
+ cLockUninit = 0x00000000,
+ cLockReentrant = 0x00000001,
+ cLockFlat = 0x00000002,
+
+ // (unusual). Not considered a debug API lock, for purposes of deciding whether
+ // to count this lock in m_cTotalDbgApiLocks, which is asserted to be 0 on entry
+ // to public APIs. Example of such a lock: LL_SHIM_PROCESS_DISPOSE_LOCK
+ cLockNonDbgApi = 0x00000004,
+ };
+
+ // To prevent deadlocks, we order all locks.
+ // A thread must acquire higher-numbered locks before lower numbered locks.
+ // These are used as indices into an array, so number them accordingly!
+ enum ERSLockLevel
+ {
+ // Size of the array..
+ LL_MAX = 6,
+
+ // The Stop-Go lock is used to make Stop + Continue be atomic operations.
+ // These methods will toggle the Process-lock b/c they go between multiple threads.
+ // This lock can never be taken on the Win32 ET.
+ LL_STOP_GO_LOCK = 5,
+
+ // The win32-event-thread behaves as if it held a lock at this level.
+ LL_WIN32_EVENT_THREAD = 4,
+
+ // This held for the duration of ShimProcess::Dispose(), and protects
+ // ShimProcess::m_fIsDisposed, so that other ShimProcess functions can
+ // safely execute serially with ShimProcess::Dispose(). This needs to be
+ // a high-level lock, since ShimProcess methods that take this lock also
+ // call into CorDb* objects which take many of the other locks. In contrast,
+ // LL_SHIM_LOCK must remain low-level, as there exists at least one place where
+ // LL_SHIM_LOCK is taken while the CorDbProcess lock is also held (see
+ // CordbThread::GetActiveFunctions which takes the CorDbProcess lock while
+ // calling GetProcess()->GetShim()->LookupOrCreateShimStackWalk(this), which
+ // takes LL_SHIM_LOCK).
+ LL_SHIM_PROCESS_DISPOSE_LOCK = 3,
+
+ // The process lock is the primary lock for a CordbProcess object. It synchronizes
+ // between RCET, W32ET, and user threads.
+ LL_PROCESS_LOCK = 2,
+
+#if defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ LL_DBG_TRANSPORT_MANAGER_LOCK = 1,
+
+ LL_DBG_TRANSPORT_TARGET_LOCK = 0,
+
+ LL_DD_MARSHAL_LOCK = 0,
+#endif // FEATURE_DBGIPC_TRANSPORT_DI
+
+ // These are all leaf locks (they don't take any other lock once they're held).
+ LL_PROCESS_LIST_LOCK = 0,
+
+ // Win32 send lock is shared by all processes accessing a single w32et.
+ LL_WIN32_SEND_LOCK = 0,
+
+ // Small lock around sending IPC events to support workarounds in func-eval abort.
+ // See code:CordbEval::Abort for details.
+ LL_FUNC_EVAL_ABORT_HACK_LOCK = 0,
+
+ // Leaf-level lock used in the shim.
+ LL_SHIM_LOCK = 0
+ };
+
+ // Initialize a lock w/ debugging info. szTag must be a string literal.
+ void Init(const char * szTag, int eAttr, ERSLockLevel level);
+ void Destroy();
+
+ void Lock();
+ void Unlock();
+
+protected:
+ // Accessors for holders.
+ static void HolderEnter(RSLock * pLock)
+ {
+ pLock->Lock();
+ }
+ static void HolderLeave(RSLock * pLock)
+ {
+ pLock->Unlock();
+ }
+
+
+ CRITICAL_SECTION m_lock;
+
+#ifdef _DEBUG
+public:
+ RSLock();
+ ~RSLock();
+
+ const char * Name() { return m_szTag; }
+
+ // Returns true if this thread has the lock.
+ bool HasLock();
+
+ // Returns true if this is safe to take on this thread (ie, this thread
+ // doesn't already hold bigger locks).
+ // bool IsSafeToTake();
+
+ ERSLockLevel GetLevel() { return m_level; }
+
+ // If we're inited, we must have either cLockReentrant or cLockFlat specified.
+ bool IsInit() { return m_eAttr != 0; }
+ bool IsReentrant() { return (m_eAttr & cLockReentrant) == cLockReentrant; }
+ bool IsDbgApiLock() { return ((m_eAttr & cLockNonDbgApi) == 0); }
+
+protected:
+ ERSLockLevel m_level;
+ int m_eAttr; // Bitwise combination of ELockAttr values
+ int m_count;
+ DWORD m_tidOwner;
+ const char * m_szTag;
+
+#endif // #if debug
+
+public:
+ typedef Holder<RSLock *, RSLock::HolderEnter, RSLock::HolderLeave> RSLockHolder;
+ typedef Holder<RSLock *, RSLock::HolderLeave, RSLock::HolderEnter> RSInverseLockHolder;
+
+};
+
+typedef RSLock::RSLockHolder RSLockHolder;
+typedef RSLock::RSInverseLockHolder RSInverseLockHolder;
+
+// In the RS, we should be using RSLocks instead of raw critical sections.
+#define CRITICAL_SECTION USE_RSLOCK_INSTEAD_OF_CRITICAL_SECTION
+
+
+/* ------------------------------------------------------------------------- *
+ * Helper macros. Use the ATT_* macros below instead of these.
+ * ------------------------------------------------------------------------- */
+
+// This serves as glue for exceptions. Eventually, we shouldn't have unrecoverable
+// error, and instead, errors should just propogate up.
+#define SetUnrecoverableIfFailed(__p, __hr) \
+ if (FAILED(__hr)) \
+ { \
+ CORDBSetUnrecoverableError(__p, __hr, 0); \
+ }
+
+#define CORDBSetUnrecoverableError(__p, __hr, __code) \
+ ((__p)->UnrecoverableError((__hr), (__code), __FILE__, __LINE__))
+
+#define _CORDBCheckProcessStateOK(__p) \
+ (!((__p)->m_unrecoverableError) && !((__p)->m_terminated) && !((__p)->m_detached))
+
+#define _CORDBCheckProcessStateOKAndSync(__p, __c) \
+ (!((__p)->m_unrecoverableError) && !((__p)->m_terminated) && !((__p)->m_detached) && \
+ (__p)->GetSynchronized())
+
+// Worker to get failure HR from given state. If not in a failure state, it yields __defaultHR.
+// If a caller knows that we're in a failure state, it can pass in a failure value for __defaultHR.
+#define CORDBHRFromProcessStateWorker(__p, __c, __defaultHR) \
+ ((__p)->m_unrecoverableError ? CORDBG_E_UNRECOVERABLE_ERROR : \
+ ((__p)->m_detached ? CORDBG_E_PROCESS_DETACHED : \
+ ((__p)->m_terminated ? CORDBG_E_PROCESS_TERMINATED : \
+ (!(__p)->GetSynchronized() ? CORDBG_E_PROCESS_NOT_SYNCHRONIZED \
+ : (__defaultHR)))))
+
+#define CORDBHRFromProcessState(__p, __c) \
+ CORDBHRFromProcessStateWorker(__p, __c, S_OK) \
+
+
+// Have a set of helper macros to check the process state and return a failure code.
+// These only should be used at public interface boundaries, in which case we should
+// not be holding the process lock. But we have enough places where we use them internally,
+// so we can't really assert that we're not holding the lock.
+
+// We're very restricted in what APIs we can call on the w32et. Have
+// a convenient check for this.
+// If we have no shim, then nop this check because everything becomes like the w32-event-thread.
+#define CORDBFailOrThrowIfOnWin32EventThread(__p, errorAction) \
+ { \
+ if (((__p)->GetShim() != NULL) && (__p)->IsWin32EventThread()) \
+ { \
+ _ASSERTE(!"Don't call on this thread"); \
+ errorAction(ErrWrapper(CORDBG_E_CANT_CALL_ON_THIS_THREAD)); \
+ } \
+ }
+
+#define CORDBFailIfOnWin32EventThread(__p) CORDBFailOrThrowIfOnWin32EventThread(__p, return)
+
+#define CORDBRequireProcessStateOK(__p) { \
+ if (!_CORDBCheckProcessStateOK(__p)) \
+ return ErrWrapper(CORDBHRFromProcessState(__p, NULL)); }
+
+// If we need to be synced, then we shouldn't be on the win32 Event-Thread.
+#define CORDBRequireProcessStateOKAndSync(__p,__c) { \
+ CORDBFailIfOnWin32EventThread(__p); \
+ if (!_CORDBCheckProcessStateOKAndSync(__p, __c)) \
+ return ErrWrapper(CORDBHRFromProcessState(__p, __c)); }
+
+#define CORDBRequireProcessSynchronized(__p, __c) { \
+ CORDBFailIfOnWin32EventThread(__p); \
+ if (!(__p)->GetSynchronized()) return ErrWrapper(CORDBG_E_PROCESS_NOT_SYNCHRONIZED);}
+
+
+
+
+//-----------------------------------------------------------------------------
+// All public APIS fall into 2 categories regarding their API Threading Type (ATT)
+// We use a standard set of macros to define & enforce each type.
+//
+// (1) ATT_REQUIRE_STOPPED
+// We must be stopped (either synced or at a win32 event) to call this API.
+// - We'll fail if we're not stopped.
+// - If we're stopped, we'll sync. Thus after this API, we're always synced,
+// and Cordbg must call Continue to resume the process.
+// - We'll take the Stop-Go-lock. This prevents another thread from continuing underneath us.
+// - We may send IPC events.
+// Common for APIs like Stacktracing
+//
+// (2) ATT_ALLOW_LIVE
+// We do not have to be stopped to call this API.
+// - We can be live, thus we can not take the stop-go lock (unless it's from a SC-holder).
+// - If we're going to send IPC events, we must use a Stop-Continue holder.
+// - Our stop-status is the same after this API as it was before.
+// Common usage: read-only APIs.
+//
+// (2b) ATT_ALLOW_LIVE_DO_STOPGO.
+// - shortcut macro to do #2, but throw in a stop-continue holder. These really
+// should be in camp #1, but that would require an interface change.
+//-----------------------------------------------------------------------------
+
+// Helper macros for the ATT stuff
+
+// Do checks that need to be done before we take the SG lock. These include checks
+// where if we fail them, taking the SG lock could deadlock (such as being on win32 thread).
+#define DO_PRE_STOP_GO_CHECKS(errorAction) \
+ CORDBFailOrThrowIfOnWin32EventThread(__proc_for_ATT, errorAction) \
+ if ((__proc_for_ATT)->m_unrecoverableError) { errorAction(CORDBG_E_UNRECOVERABLE_ERROR); } \
+
+// Do checks after we take the SG lock. These include checks that rely on state protected
+// by the SG lock.
+#define DO_POST_STOP_GO_CHECKS(errorAction) \
+ _ASSERTE((this->GetProcess() == __proc_for_ATT) || this->IsNeutered()); \
+ if (this->IsNeutered()) { errorAction(CORDBG_E_OBJECT_NEUTERED); } \
+
+// #1
+// The exact details here are rocket-science.
+// We cache the __proc value to a local variable (__proc_for_ATT) so that we don't re-evaluate __proc. (It also forces type-safety).
+// This is essential in case __proc is something like "this->GetProcess()" and which can start returning NULL if 'this'
+// gets neutered underneath us. Caching guarantees that we'll be able to make it to the StopGo-lock.
+//
+// We explicitily check some things before taking the Stop-Go lock:
+// - CORDBG_E_UNRECOVERABLE_ERROR before the lock because if that's set,
+// we may have leaked locks to the outside world, so taking the StopGo lock later could fail.
+// - Are we on the W32et - can't take sg lock if on W32et
+// Then we immediately take the stop-go lock to prevent another thread from continuing underneath us.
+// Then, if we're stopped, we ensure that we're also synced.
+// Stopped includes:
+// - Win32-stopped
+// - fake win32-stopped. Eg, between SuspendUnmanagedThreads & ResumeUnmanagedThreads
+// (one way to get here is getting debug events during the special-deferment region)
+// - synchronized
+// If we're not stopped, then we fail. This macro must never return S_OK.
+//
+// If not-shimmed (using V3 pipeline), then skip all checks about stop-state.
+#define ATT_REQUIRE_STOPPED_MAY_FAIL_OR_THROW(__proc, errorAction) \
+ CordbProcess * __proc_for_ATT = (__proc); \
+ DO_PRE_STOP_GO_CHECKS(errorAction); \
+ RSLockHolder __ch(__proc_for_ATT->GetStopGoLock()); \
+ DO_POST_STOP_GO_CHECKS(errorAction); \
+ if ((__proc_for_ATT)->GetShim() != NULL) { \
+ if (!__proc_for_ATT->m_initialized) { errorAction(CORDBG_E_NOTREADY); } \
+ if ((__proc_for_ATT)->IsStopped()) { \
+ HRESULT _hr2 = (__proc_for_ATT)->StartSyncFromWin32Stop(NULL); \
+ if (FAILED(_hr2)) errorAction(_hr2); \
+ } \
+ if (!_CORDBCheckProcessStateOKAndSync(__proc_for_ATT, NULL)) \
+ errorAction(CORDBHRFromProcessStateWorker(__proc_for_ATT, NULL, E_FAIL)); \
+ }
+
+#define ATT_REQUIRE_STOPPED_MAY_FAIL(__proc)ATT_REQUIRE_STOPPED_MAY_FAIL_OR_THROW(__proc, return)
+
+// #1b - allows it to be non-inited. This should look just like ATT_REQUIRE_STOPPED_MAY_FAIL_OR_THROW
+// except it doesn't do SSFW32Stop and doesn't have the m_initialized check.
+#define ATT_REQUIRE_SYNCED_OR_NONINIT_MAY_FAIL(__proc) \
+ CordbProcess * __proc_for_ATT = (__proc); \
+ DO_PRE_STOP_GO_CHECKS(return); \
+ RSLockHolder __ch(__proc_for_ATT->GetStopGoLock()); \
+ DO_POST_STOP_GO_CHECKS(return); \
+ if ((__proc_for_ATT)->GetShim() != NULL) { \
+ if (!_CORDBCheckProcessStateOKAndSync(__proc_for_ATT, NULL)) \
+ return CORDBHRFromProcessStateWorker(__proc_for_ATT, NULL, E_FAIL); \
+ }
+
+
+
+// Gross variant on #1.
+// This is a very dangerous ATT contract; but we need to support it for backwards compat.
+// Some APIs, like ICDProcess:EnumerateThreads can be used before the process is actually
+// initialized (kind of for interop-debugging).
+// These can't check the m_initialized flag b/c that may not be set yet.
+// They also can't sync the runtime.
+// This should only be used for non-blocking leaf activity.
+#define ATT_EVERETT_HACK_REQUIRE_STOPPED_ALLOW_NONINIT(__proc) \
+ CordbProcess * __proc_for_ATT = (__proc); \
+ DO_PRE_STOP_GO_CHECKS(return); \
+ RSLockHolder __ch(__proc_for_ATT->GetStopGoLock()); \
+ DO_POST_STOP_GO_CHECKS(return); \
+ if (((__proc_for_ATT)->GetShim() != NULL) && !(__proc_for_ATT)->IsStopped()) { return CORDBG_E_PROCESS_NOT_SYNCHRONIZED; } \
+
+
+// #2 - caller may think debuggee is live, but throw in a Stop-Continue holder.
+#define ATT_ALLOW_LIVE_DO_STOPGO(__proc) \
+ CordbProcess * __proc_for_ATT = (__proc); \
+ DO_PRE_STOP_GO_CHECKS(return); \
+ CORDBRequireProcessStateOK(__proc_for_ATT); \
+ RSLockHolder __ch(__proc_for_ATT->GetStopGoLock()); \
+ DO_POST_STOP_GO_CHECKS(return); \
+ StopContinueHolder __hStopGo; \
+ if ((__proc_for_ATT)->GetShim() != NULL) \
+ { \
+ HRESULT _hr2 = __hStopGo.Init(__proc_for_ATT); \
+ if (FAILED(_hr2)) return _hr2; \
+ _ASSERTE((__proc_for_ATT)->GetSynchronized()); \
+ } \
+
+
+
+
+//-----------------------------------------------------------------------------
+// StopContinueHolder. Ensure that we're synced during a certain region.
+// (Particularly when sending an IPCEvent)
+// Calls ICorDebugProcess::Stop & IMDArocess::Continue.
+// Example usage:
+//
+// {
+// StopContinueHolder h;
+// IfFailRet(h.Init(process))
+// SendIPCEvent
+// } // continue automatically called.
+//-----------------------------------------------------------------------------
+
+class CordbProcess;
+class StopContinueHolder
+{
+public:
+ StopContinueHolder() : m_p(NULL) { };
+
+ HRESULT Init(CordbProcess * p);
+ ~StopContinueHolder();
+
+protected:
+ CordbProcess * m_p;
+};
+
+
+/* ------------------------------------------------------------------------- *
+ * Base class
+ * ------------------------------------------------------------------------- */
+
+#define COM_METHOD HRESULT STDMETHODCALLTYPE
+
+typedef enum {
+ enumCordbUnknown, // 0
+ enumCordb, // 1 1 [1]x1
+ enumCordbProcess, // 2 1 [1]x1
+ enumCordbAppDomain, // 3 1 [1]x1
+ enumCordbAssembly, // 4
+ enumCordbModule, // 5 15 [27-38,55-57]x1
+ enumCordbClass, // 6
+ enumCordbFunction, // 7
+ enumCordbThread, // 8 2 [4,7]x1
+ enumCordbCode, // 9
+ enumCordbChain, // 10
+ enumCordbChainEnum, // 11
+ enumCordbContext, // 12
+ enumCordbFrame, // 13
+ enumCordbFrameEnum, // 14
+ enumCordbValueEnum, // 15
+ enumCordbRegisterSet, // 16
+ enumCordbJITILFrame, // 17
+ enumCordbBreakpoint, // 18
+ enumCordbStepper, // 19
+ enumCordbValue, // 20
+ enumCordbEnCSnapshot, // 21
+ enumCordbEval, // 22
+ enumCordbUnmanagedThread,// 23
+ enumCorpubPublish, // 24
+ enumCorpubProcess, // 25
+ enumCorpubAppDomain, // 26
+ enumCorpubProcessEnum, // 27
+ enumCorpubAppDomainEnum,// 28
+ enumCordbEnumFilter, // 29
+ enumCordbEnCErrorInfo, // 30
+ enumCordbEnCErrorInfoEnum,//31
+ enumCordbUnmanagedEvent,// 32
+ enumCordbWin32EventThread,//33
+ enumCordbRCEventThread, // 34
+ enumCordbNativeFrame, // 35
+ enumCordbObjectValue, // 36
+ enumCordbType, // 37
+ enumCordbNativeCode, // 38
+ enumCordbILCode, // 39
+ enumCordbEval2, // 40
+ enumCordbMDA, // 41
+ enumCordbHashTableEnum, // 42
+ enumCordbCodeEnum, // 43
+ enumCordbStackWalk, // 44
+ enumCordbEnumerator, // 45
+ enumCordbHeap, // 48
+ enumCordbHeapSegments, // 47
+ enumMaxDerived, //
+ enumMaxThis = 1024
+} enumCordbDerived;
+
+
+
+//-----------------------------------------------------------------------------
+// Support for Native Breakpoints
+//-----------------------------------------------------------------------------
+struct NativePatch
+{
+ void * pAddress; // pointer into the LS address space.
+ PRD_TYPE opcode; // opcode to restore with.
+
+ inline bool operator==(NativePatch p2)
+ {
+ return memcmp(this, &p2, sizeof(p2)) == 0;
+ }
+};
+
+//-----------------------------------------------------------------------------
+// Cross-platform patch operations
+//-----------------------------------------------------------------------------
+
+// Remove the int3 from the remote address
+HRESULT RemoveRemotePatch(CordbProcess * pProcess, const void * pRemoteAddress, PRD_TYPE opcode);
+
+// This flavor is assuming our caller already knows the opcode.
+HRESULT ApplyRemotePatch(CordbProcess * pProcess, const void * pRemoteAddress);
+
+// Apply the patch and get the opcode that we're replacing.
+HRESULT ApplyRemotePatch(CordbProcess * pProcess, const void * pRemoteAddress, PRD_TYPE * pOpcode);
+
+
+class CordbHashTable;
+
+#define CORDB_COMMON_BASE_SIGNATURE 0x0d00d96a
+#define CORDB_COMMON_BASE_SIGNATURE_DEAD 0x0dead0b1
+
+// Common base for both CorPublish + CorDebug objects.
+class CordbCommonBase : public IUnknown
+{
+public:
+ // GENERIC: made this private as I'm changing the use of m_id for CordbClass, and
+ // I want to make sure I catch all the places where m_id is used directly and cast
+ // to/from tokens and/or (void*).
+ UINT_PTR m_id;
+
+#ifdef _DEBUG
+ static LONG m_saDwInstance[enumMaxDerived]; // instance x this
+ static LONG m_saDwAlive[enumMaxDerived];
+ static PVOID m_sdThis[enumMaxDerived][enumMaxThis];
+ DWORD m_dwInstance;
+ enumCordbDerived m_type;
+#endif
+
+
+
+private:
+ DWORD m_signature : 30;
+
+ // Sticky bit set when we neuter an object. All methods (besides AddRef,Release,QI)
+ // should check this bit and fail via the FAIL_IF_NEUTERED macro.
+ DWORD m_fIsNeutered : 1;
+
+ // Mark that this object can be "neutered at will". NeuterList::SweepAllNeuterAtWillObjects
+ // looks at this bit.
+ // For some objects, we don't explicitly mark when the lifetime is up. The only way
+ // we know is when external count goes to 0. This avoids forcing us to do cleanup
+ // in the dtor (which may come at a bad time). Sticky bit set in BaseRelease().
+ DWORD m_fNeuterAtWill : 1;
+public:
+
+ static LONG s_CordbObjectUID; // Unique ID for each object.
+ static LONG s_TotalObjectCount; // total number of outstanding objects.
+
+
+ void ValidateObject()
+ {
+ if( !IsValidObject() )
+ {
+ STRESS_LOG1(LF_ASSERT, LL_ALWAYS, "CordbCommonBase::IsValidObject() failed: %x\n", this);
+ _ASSERTE(!"CordbCommonBase::IsValidObject() failed");
+ FreeBuildDebugBreak();
+ }
+ }
+
+ bool IsValidObject()
+ {
+ return (m_signature == CORDB_COMMON_BASE_SIGNATURE);
+ }
+
+ CordbCommonBase(UINT_PTR id, enumCordbDerived type)
+ {
+ init(id, type);
+ }
+
+ CordbCommonBase(UINT_PTR id)
+ {
+ init(id, enumCordbUnknown);
+ }
+
+ void init(UINT_PTR id, enumCordbDerived type)
+ {
+ // To help us track object leaks, we want to log when we create & destory CordbBase objects.
+#ifdef _DEBUG
+ InterlockedIncrement(&s_TotalObjectCount);
+ InterlockedIncrement(&s_CordbObjectUID);
+
+ LOG((LF_CORDB, LL_EVERYTHING, "Memory: CordbBase object allocated: this=%p, count=%d, id=%p, Type=%d\n", this, s_CordbObjectUID, id, type));
+#endif
+
+ m_signature = CORDB_COMMON_BASE_SIGNATURE;
+ m_fNeuterAtWill = 0;
+ m_fIsNeutered = 0;
+
+ m_id = id;
+ m_RefCount = 0;
+
+#ifdef _DEBUG
+ m_type = type;
+ //m_dwInstance = CordbBase::m_saDwInstance[m_type];
+ //InterlockedIncrement(&CordbBase::m_saDwInstance[m_type]);
+ //InterlockedIncrement(&CordbBase::m_saDwAlive[m_type]);
+ //if (m_dwInstance < enumMaxThis)
+ //{
+ // m_sdThis[m_type][m_dwInstance] = this;
+ //}
+#endif
+ }
+
+ virtual ~CordbCommonBase()
+ {
+ // If we're deleting, we really should have released any outstanding reference.
+ // If we call Release() on a deleted object, we'll av (especially b/c Release
+ // may call delete again).
+ CONSISTENCY_CHECK_MSGF(m_RefCount == 0, ("Deleting w/ non-zero ref count. 0x%08x", m_RefCount));
+
+#ifdef _DEBUG
+ //InterlockedDecrement(&CordbBase::m_saDwAlive[m_type]);
+ //if (m_dwInstance < enumMaxThis)
+ //{
+ // m_sdThis[m_type][m_dwInstance] = NULL;
+ //}
+#endif
+ // To help us track object leaks, we want to log when we create & destory CordbBase objects.
+ LOG((LF_CORDB, LL_EVERYTHING, "Memory: CordbBase object deleted: this=%p, id=%p, Refcount=0x%x\n", this, m_id, m_RefCount));
+
+#ifdef _DEBUG
+ LONG newTotalObjectsCount = InterlockedDecrement(&s_TotalObjectCount);
+ _ASSERTE(newTotalObjectsCount >= 0);
+#endif
+
+ // Don't shutdown logic until everybody is done with it.
+ // If we leak objects, this may mean that we never shutdown logging at all!
+#if defined(_DEBUG) && defined(LOGGING)
+ if (newTotalObjectsCount == 0)
+ {
+ ShutdownLogging();
+ }
+#endif
+ }
+
+ /*
+ Member function behavior of a neutered COM object:
+
+ 1. AddRef(), Release(), QueryInterface() work as normal.
+ a. This gives folks who are responsible for pairing a Release() with
+ an AddRef() a chance to dereference their pointer and call Release()
+ when they are informed, explicitly or implicitly, that the object is neutered.
+
+ 2. Any other member function will return an error code unless documented.
+ a. If a member function returns information when the COM object is
+ neutered then the semantics of that function need to be documented.
+ (ie. If an AppDomain is unloaded and you have a reference to the COM
+ object representing the AppDomain, how _should_ it behave? That behavior
+ should be documented)
+
+
+ Postcondions of Neuter():
+
+ 1. All circular references (aka back-pointers) are "broken". They are broken
+ by calling Release() on all "Weak References" to the object. If you're a purist,
+ these pointers should also be NULLed out.
+ a. Weak References/Strong References:
+ i. If any objects are not "reachable" from the root (ie. stack or from global pointers)
+ they should be reclaimed. If they are not, they are leaked and there is an issue.
+ ii. There must be a partial order on the objects such that if A < B then:
+ 1. A has a reference to B. This reference is a "strong reference"
+ 2. A, and thus B, is reachable from the root
+ iii. If a reference belongs in the partial order then it is a "strong reference" else
+ it is a weak reference.
+ *** 2. Sufficient conditions to ensure no COM objects are leaked: ***
+ a. When Neuter() is invoked:
+ i. Calles Release on all its weak references.
+ ii. Then, for each strong reference:
+ 1. invoke Neuter()
+ 2. invoke Release()
+ iii. If it's derived from a CordbXXX class, call Neuter() on the base class.
+ 1. Sense Neuter() is virtual, use the scope specifier Cordb[BaseClass]::Neuter().
+ 3. All members return error codes, except:
+ a. Members of IUknown, AddRef(), Release(), QueryInterfac()
+ b. Those documented to have functionality when the object is neutered.
+ i. Neuter() still works w/o error. If it is invoke a second time it will have already
+ released all its strong and weak references so it could just return.
+
+
+ Alternate design ideas:
+
+ DESIGN: Note that it's possible for object B to have two parents in the partial order
+ and it must be documented which one is responsible for calling Neuter() on B.
+ 1. For example, CordbCode could reasonably be a sibling of CordbFunction and CordbNativeFrame.
+ Which one should call Release()? For now we have CordbFunction call Release() on CordbCode.
+
+ DESIGN: It is not a necessary condition in that Neuter() invoke Release() on all
+ it's strong references. Instead, it would be sufficient to ensure all object are released, that
+ each object call Release() on all its strong pointers in its destructor.
+ 1. This might be done if its necessary for some member to return "tombstone"
+ information after the object has been netuered() which involves the siblings (wrt poset)
+ of the object. However, no sibling could access a parent (wrt poset) because
+ Neuter called Release() on all its weak pointers.
+
+ DESIGN: Rename Neuter() to some name that more accurately reflect the semantics.
+ 1. The three operations are:
+ a. ReleaseWeakPointers()
+ b. NeuterStrongPointers()
+ c. ReleaseStrongPointers()
+ 1. Assert that it's done after NeuterStrongPointers()
+ 2. That would introduce a bunch of functions... but it would be clear.
+
+ DESIGN: CordbBase could provide a function to register strong and weak references. That way CordbBase
+ could implement a general version of ReleaseWeak/ReleaseStrong/NeuterStrongPointers(). This
+ would provide a very error resistant framework for extending the object model plus it would
+ be very explicit about what is going on.
+ One thing that might trip this is idea up is that if an object has two parents,
+ like the CordbCode might, then either both objects call Neuter or one is reference
+ is made weak.
+
+
+ Our implementation:
+
+ The graph formed by the strong references must remain acyclic.
+ It's up to the developer (YOU!) to ensure that each Neuter
+ function maintains that invariant.
+
+ Here is the current Partial Order on CordbXXX objects. (All these classes
+ eventually chain to CordbBase.Neuter() for completeness.)
+
+ Cordb
+ CordbProcess
+ CordbAppDomain
+ CordbBreakPoints
+ CordbAssembly
+ CordbModule
+ CordbClass
+ CordbFunction
+ CordbCode (Can we assert a thread will not reference
+ the same CordbCode as a CordbFunction?)
+ CordbThread
+ CordbChains
+ CordbNativeFrame -> CordbFrame (Chain to baseClass)
+ CordbJITILFrame
+
+
+ <TODO>TODO: Some Neuter functions have not yet been implemented due to time restrictions.</TODO>
+
+ <TODO>TODO: Some weak references never have AddRef() called on them. If that's cool then
+ it should be stated in the documentation. Else it should be changed.</TODO>
+*/
+
+ virtual void Neuter();
+
+ // Unsafe neuter for an object that's already dead.
+ void UnsafeNeuterDeadObject();
+
+
+#ifdef _DEBUG
+ // For debugging (asserts, logging, etc) provide a pretty name (this is 1:1 w/ the VTable)
+ // We provide a default impl in the base object in case this gets called from a dtor (virtuals
+ // called from dtors use the base version, not the derived). A pure call would AV in that case.
+ virtual const char * DbgGetName() { return "CordbBase"; };
+#endif
+
+ bool IsNeutered() const {LIMITED_METHOD_CONTRACT; return m_fIsNeutered == 1; }
+ bool IsNeuterAtWill() const { LIMITED_METHOD_CONTRACT; return m_fNeuterAtWill == 1; }
+ void MarkNeuterAtWill() { LIMITED_METHOD_CONTRACT; m_fNeuterAtWill = 1; }
+
+ //-----------------------------------------------------------
+ // IUnknown support
+ //----------------------------------------------------------
+
+private:
+ // We maintain both an internal + external refcount. This allows us to catch
+ // if an external caller has too many releases.
+ // low bits are internal count, high bits are external count
+ // so Total count = (m_RefCount & CordbBase_InternalRefCountMask) + (m_RefCount >> CordbBase_ExternalRefCountShift);
+ typedef LONGLONG MixedRefCountSigned;
+ typedef ULONGLONG MixedRefCountUnsigned;
+ typedef LONG ExternalRefCount;
+ MixedRefCountUnsigned m_RefCount;
+public:
+
+ // Adjust the internal ref count.
+ // These aren't available to the external world, so only internal code can manipulate the internal count.
+ void InternalAddRef();
+ void InternalRelease();
+
+ // Derived versions of AddRef / Release will call these.
+ // External AddRef & Release
+ // These do not have any additional Asserts to enforce that we're not manipulating the external count
+ // from internal.
+ ULONG STDMETHODCALLTYPE BaseAddRef();
+ ULONG STDMETHODCALLTYPE BaseRelease();
+
+ // External ref count versions, with extra debug count to enforce that this is done externally.
+ // When derive classes use these versions, it Asserts that we're not adjusting external counts from inside.
+ // Thus we can be confident that we're *never* leaking external refs to these objects.
+ // @todo - eventually everything should use these.
+ ULONG STDMETHODCALLTYPE BaseAddRefEnforceExternal();
+ ULONG STDMETHODCALLTYPE BaseReleaseEnforceExternal();
+
+ // Do an AddRef against the External count. This is a semantics issue.
+ // We use this when an internal component Addrefs out-parameters (which Cordbg will call Release on).
+ // This just does a regular external AddRef().
+ void ExternalAddRef();
+
+protected:
+
+ static void InitializeCommon();
+
+private:
+ static void AddDebugPrivilege();
+};
+
+#define CordbBase_ExternalRefCountShift 32
+#define CordbBase_InternalRefCountMask 0xFFFFFFFF
+#define CordbBase_InternalRefCountMax 0x7FFFFFFF
+
+#ifdef _DEBUG
+// Does the given Cordb object type have affinity to a CordbProcess object?
+// This is only used for certain asserts.
+inline bool DoesCordbObjectTypeHaveProcessPtr(enumCordbDerived type)
+{
+ return
+ (type != enumCordbCodeEnum) &&
+ (type != enumCordb) &&
+ (type != enumCordbHashTableEnum);
+}
+#endif
+
+// Base class specifically for CorDebug objects
+class CordbBase : public CordbCommonBase
+{
+public:
+ CordbBase(CordbProcess * pProcess, UINT_PTR id, enumCordbDerived type) : CordbCommonBase(id, type)
+ {
+ // CordbProcess can't pass 'this' to base class, per error C4355. So we pass null and set later.
+ _ASSERTE((pProcess != NULL) ||
+ ((type) == enumCordbProcess) ||
+ !DoesCordbObjectTypeHaveProcessPtr(type));
+
+ m_pProcess.Assign(pProcess);
+ }
+
+ CordbBase(CordbProcess * pProcess, UINT_PTR id) : CordbCommonBase(id)
+ {
+ _ASSERTE(pProcess != NULL);
+ m_pProcess.Assign(pProcess);
+ }
+
+ virtual ~CordbBase()
+ {
+ // Derived classes should not have cleared out our pointer.
+ // CordbProcess's Neuter explicitly nulls out its pointer to avoid circular reference.
+ _ASSERTE(m_pProcess!= NULL ||
+ (CordbCommonBase::m_type == enumCordbProcess) ||
+ !DoesCordbObjectTypeHaveProcessPtr(CordbCommonBase::m_type));
+
+ // Ideally, all CorDebug objects to be neutered by the time their dtor is called.
+ // @todo - we're still working out neutering semantics for a few remaining objects, so we exclude
+ // those from the assert.
+ _ASSERTE(IsNeutered() ||
+ (m_type == enumCordbBreakpoint) ||
+ (m_type == enumCordbStepper));
+ }
+
+ // Neuter just the right-side state.
+ virtual void Neuter();
+
+ // Neuter both left-side state and right-side state.
+ virtual void NeuterLeftSideResources();
+
+ // Get the CordbProcess object that this CordbBase object is associated with (or NULL if there's none).
+ CordbProcess * GetProcess() const
+ {
+ return m_pProcess;
+ }
+protected:
+ // All objects need a strong pointer back to the process so that they can get access to key locks
+ // held by the process (StopGo lock) so that they can synchronize their operations against neutering.
+ // This pointer is cleared in our dtor, and not when we're neutered. Since we can't control when the
+ // dtor is called (it's controlled by external references), we classify this as an external reference too.
+ //
+ // This is the only "strong" reference backpointer that objects need have. All other backpointers can be weak references
+ // because when a parent object is neutered, it will null out all weak reference pointers in all of its children.
+ // That will also break any potential cycles.
+ RSUnsafeExternalSmartPtr<CordbProcess> m_pProcess;
+
+};
+
+
+
+
+
+//-----------------------------------------------------------------------------
+// Macro to check if a CordbXXX object is neutered, and return a standard
+// error code if it is.
+// We pass the 'this' pointer of the object in because it gives us some extra
+// flexibility and lets us log debug info.
+// It is an API breach to access a neutered object.
+//-----------------------------------------------------------------------------
+#define FAIL_IF_NEUTERED(pThis) \
+int _____Neuter_Status_Already_Marked; \
+_____Neuter_Status_Already_Marked = 0; \
+{\
+ if (pThis->IsNeutered()) { \
+ LOG((LF_CORDB, LL_ALWAYS, "Accessing a neutered object at %p\n", pThis)); \
+ return ErrWrapper(CORDBG_E_OBJECT_NEUTERED); \
+ } \
+}
+
+//-----------------------------------------------------------------------------
+// Macro to check if a CordbXXX object is neutered, and return a standard
+// error code if it is.
+// We pass the 'this' pointer of the object in because it gives us some extra
+// flexibility and lets us log debug info.
+// It is an API breach to access a neutered object.
+//-----------------------------------------------------------------------------
+#define THROW_IF_NEUTERED(pThis) \
+int _____Neuter_Status_Already_Marked; \
+_____Neuter_Status_Already_Marked = 0; \
+{\
+ if (pThis->IsNeutered()) { \
+ LOG((LF_CORDB, LL_ALWAYS, "Accessing a neutered object at %p\n", pThis)); \
+ ThrowHR(CORDBG_E_OBJECT_NEUTERED); \
+ } \
+}
+
+// We have an OK_IF_NEUTERED macro to say that this method can be safely
+// called if we're neutered. Mostly for semantic benefits.
+// Also, if a method is marked OK, then somebody won't go and add a 'fail'
+// This is an extremely dangerous quality because:
+// 1) it means that we have no synchronization (can't take the Stop-Go lock)
+// 2) none of our backpointers are usable (they may be nulled out at anytime by another thread).
+// - this also means we absolutely can't send IPC events (since that requires a CordbProcess)
+// 3) The only safe data are blittalbe embedded fields (eg, a pid or stack range)
+//
+// Any usage of this macro should clearly specify why this is safe.
+#define OK_IF_NEUTERED(pThis) \
+int _____Neuter_Status_Already_Marked; \
+_____Neuter_Status_Already_Marked = 0;
+
+
+//-------------------------------------------------------------------------------
+// Simple COM enumerator pattern on a fixed list of items
+//--------------------------------------------------------------------------------
+template< typename ElemType,
+ typename ElemPublicType,
+ typename EnumInterfaceType,
+ ElemPublicType (*GetPublicType)(ElemType)>
+class CordbEnumerator : public CordbBase, EnumInterfaceType
+{
+private:
+ // the list of items being enumerated over
+ ElemType *m_items;
+ // the number of items in the list
+ DWORD m_countItems;
+ // the index of the next item to be returned in the enumeration
+ DWORD m_nextIndex;
+
+public:
+ // makes a copy of the elements in the "items" array
+ CordbEnumerator(CordbProcess* pProcess, ElemType *items, DWORD elemCount);
+ // assumes ownership of the elements in the "*items" array.
+ // this avoids an extra allocation + copy
+ CordbEnumerator(CordbProcess* pProcess, ElemType **items, DWORD elemCount);
+ ~CordbEnumerator();
+
+// IUnknown interface
+ virtual COM_METHOD QueryInterface(REFIID riid, VOID** ppInterface);
+ virtual ULONG __stdcall AddRef();
+ virtual ULONG __stdcall Release();
+
+// ICorDebugEnum interface
+ virtual COM_METHOD Clone(ICorDebugEnum **ppEnum);
+ virtual COM_METHOD GetCount(ULONG *pcelt);
+ virtual COM_METHOD Reset();
+ virtual COM_METHOD Skip(ULONG celt);
+
+// ICorDebugXXXEnum interface
+ virtual COM_METHOD Next(ULONG celt, ElemPublicType items[], ULONG *pceltFetched);
+
+// CordbBase overrides
+ virtual VOID Neuter();
+};
+
+// Converts T to U* by using QueryInterface
+template<typename T, typename U>
+U* QueryInterfaceConvert(T obj);
+
+// No conversion, just returns the argument
+template<typename T>
+T IdentityConvert(T obj);
+
+// CorDebugGuidToTypeMapping-adapter used by CordbGuidToTypeEnumerator
+// in the CordbEnumerator pattern
+struct RsGuidToTypeMapping
+{
+ GUID iid;
+ RSSmartPtr<CordbType> spType;
+};
+
+inline
+CorDebugGuidToTypeMapping GuidToTypeMappingConvert(RsGuidToTypeMapping m)
+{
+ CorDebugGuidToTypeMapping result;
+ result.iid = m.iid;
+ result.pType = (ICorDebugType*)(m.spType.GetValue());
+ result.pType->AddRef();
+ return result;
+}
+
+//
+// Some useful enumerators
+//
+typedef CordbEnumerator<RSSmartPtr<CordbThread>,
+ ICorDebugThread*,
+ ICorDebugThreadEnum,
+ QueryInterfaceConvert<RSSmartPtr<CordbThread>, ICorDebugThread> > CordbThreadEnumerator;
+
+typedef CordbEnumerator<CorDebugBlockingObject,
+ CorDebugBlockingObject,
+ ICorDebugBlockingObjectEnum,
+ IdentityConvert<CorDebugBlockingObject> > CordbBlockingObjectEnumerator;
+
+// Template classes must be fully defined rather than just declared in the header
+#include "rsenumerator.hpp"
+
+
+typedef CordbEnumerator<COR_SEGMENT,
+ COR_SEGMENT,
+ ICorDebugHeapSegmentEnum,
+ IdentityConvert<COR_SEGMENT> > CordbHeapSegmentEnumerator;
+
+typedef CordbEnumerator<CorDebugExceptionObjectStackFrame,
+ CorDebugExceptionObjectStackFrame,
+ ICorDebugExceptionObjectCallStackEnum,
+ IdentityConvert<CorDebugExceptionObjectStackFrame> > CordbExceptionObjectCallStackEnumerator;
+
+typedef CordbEnumerator<RsGuidToTypeMapping,
+ CorDebugGuidToTypeMapping,
+ ICorDebugGuidToTypeEnum,
+ GuidToTypeMappingConvert > CordbGuidToTypeEnumerator;
+
+// ----------------------------------------------------------------------------
+// Hash table for CordbBase objects.
+// - Uses Internal AddRef/Release (not external)
+// - Templatize for type-safety w/ Cordb objects
+// - Many hashtables are implicitly protected by a lock. For debug-only, we
+// explicitly associate w/ an optional RSLock and assert that lock is held on access.
+// ----------------------------------------------------------------------------
+
+struct CordbHashEntry
+{
+ FREEHASHENTRY entry;
+ CordbBase *pBase;
+};
+
+class CordbHashTable : private CHashTableAndData<CNewDataNoThrow>
+{
+private:
+ bool m_initialized;
+ SIZE_T m_count;
+
+ BOOL Cmp(SIZE_T k1, const HASHENTRY * pc2)
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ return ((ULONG_PTR)k1) != (reinterpret_cast<const CordbHashEntry *>(pc2))->pBase->m_id;
+ }
+
+ ULONG HASH(ULONG_PTR id)
+ {
+ return (ULONG)(id);
+ }
+
+ SIZE_T KEY(UINT_PTR id)
+ {
+ return (SIZE_T)id;
+ }
+
+public:
+ bool IsInitialized();
+
+#ifndef DACCESS_COMPILE
+ CordbHashTable(ULONG size)
+ : CHashTableAndData<CNewDataNoThrow>(size), m_initialized(false), m_count(0)
+ {
+#ifdef _DEBUG
+ m_pDbgLock = NULL;
+ m_dbgChangeCount = 0;
+#endif
+ }
+ virtual ~CordbHashTable();
+
+#ifdef _DEBUG
+ // CordbHashTables may be protected by a lock. For debug-builds, we can associate
+ // the hash w/ that lock and then assert if it's not held.
+ void DebugSetRSLock(RSLock * pLock)
+ {
+ m_pDbgLock = pLock;
+ }
+ int GetChangeCount() { return m_dbgChangeCount; }
+private:
+ void AssertIsProtected();
+
+ // Increment the Change count. This can be used to check if the hashtable changes while being enumerated.
+ void DbgIncChangeCount() { m_dbgChangeCount++; }
+
+ int m_dbgChangeCount;
+ RSLock * m_pDbgLock;
+#else
+ // RSLock association is a no-op on free builds.
+ void AssertIsProtected() { };
+ void DbgIncChangeCount() { };
+#endif // _DEBUG
+
+public:
+
+
+#endif
+
+ ULONG32 GetCount()
+ {
+ return ((ULONG32)m_count);
+ }
+
+ // These operators are unsafe b/c they have no typesafety.
+ // Use a derived CordbSafeHashTable<T> instead.
+ HRESULT UnsafeAddBase(CordbBase *pBase);
+ HRESULT UnsafeSwapBase(CordbBase* pBaseOld, CordbBase* pBaseNew);
+ CordbBase *UnsafeGetBase(ULONG_PTR id, BOOL fFab = TRUE);
+ CordbBase *UnsafeRemoveBase(ULONG_PTR id);
+
+ CordbBase *UnsafeFindFirst(HASHFIND *find);
+ CordbBase *UnsafeFindNext(HASHFIND *find);
+
+ // Unlocked versions don't assert that the lock us held.
+ CordbBase *UnsafeUnlockedFindFirst(HASHFIND *find);
+ CordbBase *UnsafeUnlockedFindNext(HASHFIND *find);
+
+};
+
+
+// Typesafe wrapper around a normal hash table
+// T is expected to be a derived clas of CordbBase
+// Note that this still isn't fully typesafe. Ideally we'd take a strongly-typed key
+// instead of UINT_PTR (the type could have a fixed relationship to T, or could be
+// an additional template argument like standard template hash tables like std::hash_map<K,V>)
+template <class T>
+class CordbSafeHashTable : public CordbHashTable
+{
+public:
+#ifndef DACCESS_COMPILE
+ CordbSafeHashTable<T>(ULONG size) : CordbHashTable(size)
+ {
+ }
+#endif
+ // Typesafe wrappers
+ HRESULT AddBase(T * pBase) { return UnsafeAddBase(pBase); }
+
+ // Either add (eg, future cals to GetBase will succeed) or throw.
+ void AddBaseOrThrow(T * pBase)
+ {
+ HRESULT hr = AddBase(pBase);
+ IfFailThrow(hr);
+ }
+ HRESULT SwapBase(T* pBaseOld, T* pBaseNew) { return UnsafeSwapBase(pBaseOld, pBaseNew); }
+ // Move the function definition of GetBase to rspriv.inl to work around gcc 2.9.5 warnings
+ T* GetBase(ULONG_PTR id, BOOL fFab = TRUE);
+ T* GetBaseOrThrow(ULONG_PTR id, BOOL fFab = TRUE);
+
+ T* RemoveBase(ULONG_PTR id) { return static_cast<T*>(UnsafeRemoveBase(id)); }
+
+ T* FindFirst(HASHFIND *find) { return static_cast<T*>(UnsafeFindFirst(find)); }
+ T* FindNext(HASHFIND *find) { return static_cast<T*>(UnsafeFindNext(find)); }
+
+ // Neuter all items and clear
+ void NeuterAndClear(RSLock * pLock);
+
+ void CopyToArray(RSPtrArray<T> * pArray);
+ void TransferToArray(RSPtrArray<T> * pArray);
+};
+
+
+class CordbHashTableEnum : public CordbBase,
+public ICorDebugProcessEnum,
+public ICorDebugBreakpointEnum,
+public ICorDebugStepperEnum,
+public ICorDebugThreadEnum,
+public ICorDebugModuleEnum,
+public ICorDebugAppDomainEnum,
+public ICorDebugAssemblyEnum
+{
+ // Private ctors. Use build function to access.
+ CordbHashTableEnum(
+ CordbBase * pOwnerObj,
+ NeuterList * pOwnerList,
+ CordbHashTable *table,
+ const _GUID &id);
+
+public:
+ static void BuildOrThrow(
+ CordbBase * pOwnerObj,
+ NeuterList * pOwnerList,
+ CordbHashTable *table,
+ const _GUID &id,
+ RSInitHolder<CordbHashTableEnum> * pHolder);
+
+ CordbHashTableEnum(CordbHashTableEnum *cloneSrc);
+
+ ~CordbHashTableEnum();
+ virtual void Neuter();
+
+
+#ifdef _DEBUG
+ // For debugging (asserts, logging, etc) provide a pretty name (this is 1:1 w/ the VTable)
+ virtual const char * DbgGetName() { return "CordbHashTableEnum"; };
+#endif
+
+
+ HRESULT Next(ULONG celt, CordbBase *bases[], ULONG *pceltFetched);
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugEnum
+ //-----------------------------------------------------------
+
+ COM_METHOD Skip(ULONG celt);
+ COM_METHOD Reset();
+ COM_METHOD Clone(ICorDebugEnum **ppEnum);
+ COM_METHOD GetCount(ULONG *pcelt);
+
+ //-----------------------------------------------------------
+ // ICorDebugProcessEnum
+ //-----------------------------------------------------------
+
+ COM_METHOD Next(ULONG celt, ICorDebugProcess *processes[],
+ ULONG *pceltFetched)
+ {
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(processes, ICorDebugProcess *,
+ celt, true, true);
+ VALIDATE_POINTER_TO_OBJECT(pceltFetched, ULONG *);
+
+ return (Next(celt, (CordbBase **)processes, pceltFetched));
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugBreakpointEnum
+ //-----------------------------------------------------------
+
+ COM_METHOD Next(ULONG celt, ICorDebugBreakpoint *breakpoints[],
+ ULONG *pceltFetched)
+ {
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(breakpoints, ICorDebugBreakpoint *,
+ celt, true, true);
+ VALIDATE_POINTER_TO_OBJECT(pceltFetched, ULONG *);
+
+ return (Next(celt, (CordbBase **)breakpoints, pceltFetched));
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugStepperEnum
+ //-----------------------------------------------------------
+
+ COM_METHOD Next(ULONG celt, ICorDebugStepper *steppers[],
+ ULONG *pceltFetched)
+ {
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(steppers, ICorDebugStepper *,
+ celt, true, true);
+ VALIDATE_POINTER_TO_OBJECT(pceltFetched, ULONG *);
+
+ return (Next(celt, (CordbBase **)steppers, pceltFetched));
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugThreadEnum
+ //-----------------------------------------------------------
+
+ COM_METHOD Next(ULONG celt, ICorDebugThread *threads[],
+ ULONG *pceltFetched)
+ {
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(threads, ICorDebugThread *,
+ celt, true, true);
+ VALIDATE_POINTER_TO_OBJECT(pceltFetched, ULONG *);
+
+ return (Next(celt, (CordbBase **)threads, pceltFetched));
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugModuleEnum
+ //-----------------------------------------------------------
+
+ COM_METHOD Next(ULONG celt, ICorDebugModule *modules[],
+ ULONG *pceltFetched)
+ {
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(modules, ICorDebugModule *,
+ celt, true, true);
+ VALIDATE_POINTER_TO_OBJECT(pceltFetched, ULONG *);
+
+ return (Next(celt, (CordbBase **)modules, pceltFetched));
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugAppDomainEnum
+ //-----------------------------------------------------------
+
+ COM_METHOD Next(ULONG celt, ICorDebugAppDomain *appdomains[],
+ ULONG *pceltFetched)
+ {
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(appdomains, ICorDebugAppDomain *,
+ celt, true, true);
+ VALIDATE_POINTER_TO_OBJECT(pceltFetched, ULONG *);
+
+ return (Next(celt, (CordbBase **)appdomains, pceltFetched));
+ }
+ //-----------------------------------------------------------
+ // ICorDebugAssemblyEnum
+ //-----------------------------------------------------------
+
+ COM_METHOD Next(ULONG celt, ICorDebugAssembly *assemblies[],
+ ULONG *pceltFetched)
+ {
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(assemblies, ICorDebugAssembly *,
+ celt, true, true);
+ VALIDATE_POINTER_TO_OBJECT(pceltFetched, ULONG *);
+
+ return (Next(celt, (CordbBase **)assemblies, pceltFetched));
+ }
+private:
+ // Owning object is our link to the CordbProcess* tree. Never null until we're neutered.
+ // NeuterList is related to the owning object. Need to cache it so that we can pass it on
+ // to our clones.
+ CordbBase * m_pOwnerObj; // provides us w/ a CordbProcess*
+ NeuterList * m_pOwnerNeuterList;
+
+
+ CordbHashTable *m_table;
+ bool m_started;
+ bool m_done;
+ HASHFIND m_hashfind;
+ REFIID m_guid;
+ ULONG m_iCurElt;
+ ULONG m_count;
+ BOOL m_fCountInit;
+
+#ifdef _DEBUG
+ // timestampt of hashtable when we start enumerating it. Useful for detecting if the table
+ // changes underneath us.
+ int m_DbgChangeCount;
+ void AssertValid();
+#else
+ void AssertValid() { }
+#endif
+
+private:
+ //These factor code between Next & Skip
+ HRESULT PrepForEnum(CordbBase **pBase);
+
+ // Note that the set of types advanced by Pre & by Post are disjoint, and
+ // that the union of these two sets are all possible types enuerated by
+ // the CordbHashTableEnum.
+ HRESULT AdvancePreAssign(CordbBase **pBase);
+ HRESULT AdvancePostAssign(CordbBase **pBase,
+ CordbBase **b,
+ CordbBase **bEnd);
+
+ // This factors some code that initializes the module enumerator.
+ HRESULT SetupModuleEnum();
+
+};
+
+
+//-----------------------------------------------------------------------------
+// Neuter List
+// Dtors can be called at any time (whenever Cordbg calls Release, which is outside
+// of our control), so we never want to do significant work in a dtor
+// (this includes sending IPC events + neutering).
+// So objects can queue themselves up to be neutered at a safe time.
+//
+// Items in a NeuterList should only contain state in the Right-Side.
+// If the item holds resources in the left-side, it should be placed on a
+// code:LeftSideResourceCleanupList
+//-----------------------------------------------------------------------------
+class NeuterList
+{
+public:
+ NeuterList();
+ ~NeuterList();
+
+ // Add an object to be neutered.
+ // Anybody calls this to add themselves to the list.
+ // This will add it to the list and maintain an internal reference to it.
+ void Add(CordbProcess * pProcess, CordbBase * pObject);
+
+ // Add w/o checking for safety. Should only be used by Process-list enum.
+ void UnsafeAdd(CordbProcess * pProcess, CordbBase * pObject);
+
+ // Neuter everything on the list.
+ // This should only be called by the "owner", but we can't really enforce that.
+ // This will release all internal references and empty the list.
+ void NeuterAndClear(CordbProcess * pProcess);
+
+ // Sweep for all objects that are marked as 'm_fNeuterAtWill'.
+ // Neuter and remove these.
+ void SweepAllNeuterAtWillObjects(CordbProcess * pProcess);
+
+protected:
+ struct Node
+ {
+ RSSmartPtr<CordbBase> m_pObject;
+ Node * m_pNext;
+ };
+
+ // Manipulating the list is done under the Process lock.
+ Node * m_pHead;
+};
+
+//-----------------------------------------------------------------------------
+// This list is for objects that hold left-side resources.
+// If the object does not hold left-side resources, it can be placed on a
+// code:NeuterList
+//-----------------------------------------------------------------------------
+class LeftSideResourceCleanupList : public NeuterList
+{
+public:
+ // dispose everything contained in the list by calling SafeDispose() on each element
+ void SweepNeuterLeftSideResources(CordbProcess * pProcess);
+ void NeuterLeftSideResourcesAndClear(CordbProcess * pProcess);
+};
+
+//-------------------------------------------------------------------------
+//
+// Optional<T>
+// Stores a value along with a bit indicating whether the value is valid.
+//
+// This is particularly useful for LS data read via DAC. We need to gracefully
+// handle missing data, and we may want to track independent pieces of data
+// separately (often with lazy initialization). It's essential that we can't
+// easily lose track of whether the data has been cached yet or not. So
+// rather than have extra "isValid" bools everywhere, we use this class to
+// encapsulate the validity bit in with the data, and ASSERT that it is true
+// whenever reading out the data.
+// Note that the client must still remember to call GetValue only when HasValue
+// is true. Since C++ doesn't have type-safe sum types, we can't enforce this
+// explicitly at compile time (ML-style datatypes and pattern matching is perfect
+// for this).
+//
+// Note that we could consider adding some operator overloads to make using
+// instances of this class more transparent. Experience will tell if this
+// is a good idea or not.
+//
+template <typename T>
+class Optional
+{
+public:
+ // By default, initialize to invalid
+ Optional() : m_fHasValue(false), m_value(T()) {}
+
+ // Allow implicit initialization from a value (for copyable T)
+ Optional(const T& val) : m_fHasValue(true), m_value(val) {}
+
+ // Returns true if a value has been stored
+ bool HasValue() const { return m_fHasValue; }
+
+ // Extract the value. Can only be called when HasValue is true.
+ const T& GetValue() { _ASSERTE(m_fHasValue); return m_value; }
+
+ // Get a writable pointer to the value structure, for filling in uncopyable data structures
+ T * GetValueAddr() { return &m_value; }
+
+ // Explicitly mark this object as having a value (for use after writing to it directly using
+ // GetValueAddr. Not necessary for simple/primitive types).
+ void SetHasValue() { m_fHasValue = true; }
+
+ // Also gets compiler-default copy constructor and assignment operator if T has them
+
+private:
+ bool m_fHasValue;
+ T m_value;
+};
+
+
+/* ------------------------------------------------------------------------- *
+ * Cordb class
+ * ------------------------------------------------------------------------- */
+
+class Cordb : public CordbBase, public ICorDebug, public ICorDebugRemote
+{
+public:
+ Cordb(CorDebugInterfaceVersion iDebuggerVersion);
+ virtual ~Cordb();
+ virtual void Neuter();
+
+
+
+#ifdef _DEBUG_IMPL
+ virtual const char * DbgGetName() { return "Cordb"; }
+
+ // Under Debug, we keep some extra state for tracking leaks. The goal is that
+ // we can assert that we aren't leaking internal refs. We'd like to assert that
+ // we're not leaking external refs, but since we can't force Cordbg to release,
+ // we can't really assert that.
+ // So the idea is that when Cordbg has released its last Cordb object, that
+ // all internal references have been released.
+ // Unfortunately, certain CordbBase objects are unrooted and thus we have no
+ // good time to neuter them and clean up any internal references they may hold.
+ // So we keep count of those guys too.
+ static LONG s_DbgMemTotalOutstandingCordb;
+ static LONG s_DbgMemTotalOutstandingInternalRefs;
+#endif
+
+ //
+ // Turn this on to enable an array which will contain all objects that have
+ // not been completely released.
+ //
+ // #define TRACK_OUTSTANDING_OBJECTS 1
+
+#ifdef TRACK_OUTSTANDING_OBJECTS
+
+#define MAX_TRACKED_OUTSTANDING_OBJECTS 256
+ static void *Cordb::s_DbgMemOutstandingObjects[MAX_TRACKED_OUTSTANDING_OBJECTS];
+ static LONG Cordb::s_DbgMemOutstandingObjectMax;
+#endif
+
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebug
+ //-----------------------------------------------------------
+
+#ifdef FEATURE_CORECLR
+ HRESULT SetTargetCLR(HMODULE hmodTargetCLR);
+#endif // FEATURE_CORECLR
+
+ COM_METHOD Initialize();
+ COM_METHOD Terminate();
+ COM_METHOD SetManagedHandler(ICorDebugManagedCallback *pCallback);
+ COM_METHOD SetUnmanagedHandler(ICorDebugUnmanagedCallback *pCallback);
+ COM_METHOD CreateProcess(LPCWSTR lpApplicationName,
+ __in_z LPWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ PVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation,
+ CorDebugCreateProcessFlags debuggingFlags,
+ ICorDebugProcess **ppProcess);
+ COM_METHOD DebugActiveProcess(DWORD dwProcessId, BOOL fWin32Attach, ICorDebugProcess **ppProcess);
+ COM_METHOD EnumerateProcesses(ICorDebugProcessEnum **ppProcess);
+ COM_METHOD GetProcess(DWORD dwProcessId, ICorDebugProcess **ppProcess);
+ COM_METHOD CanLaunchOrAttach(DWORD dwProcessId, BOOL win32DebuggingEnabled);
+
+ //-----------------------------------------------------------
+ // CorDebug
+ //-----------------------------------------------------------
+
+ static COM_METHOD CreateObjectV1(REFIID id, void **object);
+#if defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ static COM_METHOD CreateObjectTelesto(REFIID id, void ** pObject);
+#endif // FEATURE_DBGIPC_TRANSPORT_DI
+ static COM_METHOD CreateObject(CorDebugInterfaceVersion iDebuggerVersion, REFIID id, void **object);
+
+ //-----------------------------------------------------------
+ // ICorDebugRemote
+ //-----------------------------------------------------------
+
+ COM_METHOD CreateProcessEx(ICorDebugRemoteTarget * pRemoteTarget,
+ LPCWSTR lpApplicationName,
+ __in_z LPWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ PVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation,
+ CorDebugCreateProcessFlags debuggingFlags,
+ ICorDebugProcess ** ppProcess);
+
+ COM_METHOD DebugActiveProcessEx(ICorDebugRemoteTarget * pRemoteTarget,
+ DWORD dwProcessId,
+ BOOL fWin32Attach,
+ ICorDebugProcess ** ppProcess);
+
+
+ //-----------------------------------------------------------
+ // Methods not exposed via a COM interface.
+ //-----------------------------------------------------------
+
+ HRESULT CreateProcessCommon(ICorDebugRemoteTarget * pRemoteTarget,
+ LPCWSTR lpApplicationName,
+ __in_z LPWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ PVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation,
+ CorDebugCreateProcessFlags debuggingFlags,
+ ICorDebugProcess **ppProcess);
+
+ HRESULT DebugActiveProcessCommon(ICorDebugRemoteTarget * pRemoteTarget, DWORD id, BOOL win32Attach, ICorDebugProcess **ppProcess);
+
+ void EnsureCanLaunchOrAttach(BOOL fWin32DebuggingEnabled);
+
+ void EnsureAllowAnotherProcess();
+ void AddProcess(CordbProcess* process);
+ void RemoveProcess(CordbProcess* process);
+ CordbSafeHashTable<CordbProcess> *GetProcessList();
+
+ void LockProcessList();
+ void UnlockProcessList();
+
+ #ifdef _DEBUG
+ bool ThreadHasProcessListLock();
+ #endif
+
+
+ HRESULT SendIPCEvent(CordbProcess * pProcess,
+ DebuggerIPCEvent * pEvent,
+ SIZE_T eventSize);
+
+ void ProcessStateChanged();
+
+ HRESULT WaitForIPCEventFromProcess(CordbProcess* process,
+ CordbAppDomain *appDomain,
+ DebuggerIPCEvent* event);
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+public:
+ RSExtSmartPtr<ICorDebugManagedCallback> m_managedCallback;
+ RSExtSmartPtr<ICorDebugManagedCallback2> m_managedCallback2;
+ RSExtSmartPtr<ICorDebugManagedCallback3> m_managedCallback3;
+ RSExtSmartPtr<ICorDebugUnmanagedCallback> m_unmanagedCallback;
+
+ CordbRCEventThread* m_rcEventThread;
+
+ CorDebugInterfaceVersion GetDebuggerVersion() const;
+
+#ifdef FEATURE_CORESYSTEM
+ HMODULE GetTargetCLR() { return m_targetCLR; }
+#endif
+
+private:
+ bool IsCreateProcessSupported();
+ bool IsInteropDebuggingSupported();
+ void CheckCompatibility();
+
+ CordbSafeHashTable<CordbProcess> m_processes;
+
+ // List to track outstanding CordbProcessEnum objects.
+ NeuterList m_pProcessEnumList;
+
+ RSLock m_processListMutex;
+ BOOL m_initialized;
+
+ // This is the version of the ICorDebug APIs that the debugger believes it's consuming.
+ CorDebugInterfaceVersion m_debuggerSpecifiedVersion;
+
+//Note - this code could be useful outside coresystem, but keeping the change localized
+// because we are late in the win8 release
+#ifdef FEATURE_CORESYSTEM
+ HMODULE m_targetCLR;
+#endif
+};
+
+
+
+
+/* ------------------------------------------------------------------------- *
+ * AppDomain class
+ * ------------------------------------------------------------------------- */
+
+// Provides the implementation for ICorDebugAppDomain, ICorDebugAppDomain2,
+// and ICorDebugAppDomain3
+class CordbAppDomain : public CordbBase,
+ public ICorDebugAppDomain,
+ public ICorDebugAppDomain2,
+ public ICorDebugAppDomain3,
+ public ICorDebugAppDomain4
+{
+public:
+ // Create a CordbAppDomain object based on a pointer to the AppDomain instance in the CLR
+ CordbAppDomain(CordbProcess * pProcess,
+ VMPTR_AppDomain vmAppDomain);
+
+ virtual ~CordbAppDomain();
+
+ virtual void Neuter();
+
+ using CordbBase::GetProcess;
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbAppDomain"; }
+#endif
+
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugController
+ //-----------------------------------------------------------
+
+ COM_METHOD Stop(DWORD dwTimeout);
+ COM_METHOD Continue(BOOL fIsOutOfBand);
+ COM_METHOD IsRunning(BOOL * pbRunning);
+ COM_METHOD HasQueuedCallbacks(ICorDebugThread * pThread,
+ BOOL * pbQueued);
+ COM_METHOD EnumerateThreads(ICorDebugThreadEnum ** ppThreads);
+ COM_METHOD SetAllThreadsDebugState(CorDebugThreadState state, ICorDebugThread * pExceptThisThread);
+
+ // Deprecated, returns E_NOTIMPL
+ COM_METHOD Detach();
+
+ COM_METHOD Terminate(unsigned int exitCode);
+
+ COM_METHOD CanCommitChanges(
+ ULONG cSnapshots,
+ ICorDebugEditAndContinueSnapshot * pSnapshots[],
+ ICorDebugErrorInfoEnum ** pError);
+
+ COM_METHOD CommitChanges(
+ ULONG cSnapshots,
+ ICorDebugEditAndContinueSnapshot * pSnapshots[],
+ ICorDebugErrorInfoEnum ** pError);
+
+ //-----------------------------------------------------------
+ // ICorDebugAppDomain
+ //-----------------------------------------------------------
+ /*
+ * GetProcess returns the process containing the app domain
+ */
+
+ COM_METHOD GetProcess(ICorDebugProcess ** ppProcess);
+
+ /*
+ * EnumerateAssemblies enumerates all assemblies in the app domain
+ */
+
+ COM_METHOD EnumerateAssemblies(ICorDebugAssemblyEnum ** ppAssemblies);
+
+ COM_METHOD GetModuleFromMetaDataInterface(IUnknown * pIMetaData,
+ ICorDebugModule ** ppModule);
+ /*
+ * EnumerateBreakpoints returns an enum of all active breakpoints
+ * in the app domain. This includes all types of breakpoints :
+ * function breakpoints, data breakpoints, etc.
+ */
+
+ COM_METHOD EnumerateBreakpoints(ICorDebugBreakpointEnum ** ppBreakpoints);
+
+ /*
+ * EnumerateSteppers returns an enum of all active steppers in the app domain.
+ */
+
+ COM_METHOD EnumerateSteppers(ICorDebugStepperEnum ** ppSteppers);
+
+ // Deprecated, always returns true.
+ COM_METHOD IsAttached(BOOL * pfAttached);
+
+ // Returns the friendly name of the AppDomain
+ COM_METHOD GetName(ULONG32 cchName,
+ ULONG32 * pcchName,
+ __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[]);
+
+ /*
+ * GetObject returns the runtime app domain object.
+ * Note: This method is not yet implemented.
+ */
+
+ COM_METHOD GetObject(ICorDebugValue ** ppObject);
+
+ // Deprecated, does nothing
+ COM_METHOD Attach();
+ COM_METHOD GetID(ULONG32 * pId);
+
+ //-----------------------------------------------------------
+ // ICorDebugAppDomain2 APIs
+ //-----------------------------------------------------------
+ COM_METHOD GetArrayOrPointerType(CorElementType elementType,
+ ULONG32 nRank,
+ ICorDebugType * pTypeArg,
+ ICorDebugType ** ppResultType);
+
+ COM_METHOD GetFunctionPointerType(ULONG32 cTypeArgs,
+ ICorDebugType * rgpTypeArgs[],
+ ICorDebugType ** ppResultType);
+
+ //-----------------------------------------------------------
+ // ICorDebugAppDomain3 APIs
+ //-----------------------------------------------------------
+ COM_METHOD GetCachedWinRTTypesForIIDs(
+ ULONG32 cGuids,
+ GUID * guids,
+ ICorDebugTypeEnum * * ppTypesEnum);
+
+ COM_METHOD GetCachedWinRTTypes(
+ ICorDebugGuidToTypeEnum * * ppType);
+
+ //-----------------------------------------------------------
+ // ICorDebugAppDomain4
+ //-----------------------------------------------------------
+ COM_METHOD GetObjectForCCW(CORDB_ADDRESS ccwPointer, ICorDebugValue **ppManagedObject);
+
+ // Get the VMPTR for this appdomain.
+ VMPTR_AppDomain GetADToken() { return m_vmAppDomain; }
+
+ // Given a metadata interface, find the module in this appdomain that matches it.
+ CordbModule * GetModuleFromMetaDataInterface(IUnknown *pIMetaData);
+
+ // Lookup a module from the cache. Create and to the cache if needed.
+ CordbModule * LookupOrCreateModule(VMPTR_Module vmModuleToken, VMPTR_DomainFile vmDomainFileToken);
+
+ // Lookup a module from the cache. Create and to the cache if needed.
+ CordbModule * LookupOrCreateModule(VMPTR_DomainFile vmDomainFileToken);
+
+ // Callback from DAC for module enumeration
+ static void ModuleEnumerationCallback(VMPTR_DomainFile vmModule, void * pUserData);
+
+ // Use DAC to add any modules for this assembly.
+ void PrepopulateModules();
+
+ void InvalidateName() { m_strAppDomainName.Clear(); }
+
+public:
+ ULONG m_AppDomainId;
+
+ CordbAssembly * LookupOrCreateAssembly(VMPTR_DomainAssembly vmDomainAssembly);
+ CordbAssembly * LookupOrCreateAssembly(VMPTR_Assembly vmAssembly);
+ void RemoveAssemblyFromCache(VMPTR_DomainAssembly vmDomainAssembly);
+
+
+ CordbSafeHashTable<CordbBreakpoint> m_breakpoints;
+
+ // Unique objects that represent the use of some
+ // basic ELEMENT_TYPE's as type parameters. These
+ // are shared acrosss the entire process. We could
+ // go and try to find the classes corresponding to these
+ // element types but it seems simpler just to keep
+ // them as special cases.
+ CordbSafeHashTable<CordbType> m_sharedtypes;
+
+ CordbAssembly * CacheAssembly(VMPTR_DomainAssembly vmDomainAssembly);
+ CordbAssembly * CacheAssembly(VMPTR_Assembly vmAssembly);
+
+
+ // Cache of modules in this appdomain. In the VM, modules live in an assembly.
+ // This cache lives on the appdomain because we generally want to do appdomain (or process)
+ // wide lookup.
+ // This is indexed by VMPTR_DomainFile, which has appdomain affinity.
+ // This is populated by code:CordbAppDomain::LookupOrCreateModule (which may be invoked
+ // anytime the RS gets hold of a VMPTR), and are removed at the unload event.
+ CordbSafeHashTable<CordbModule> m_modules;
+private:
+ // Cache of assemblies in this appdomain.
+ // This is indexed by VMPTR_DomainAssembly, which has appdomain affinity.
+ // This is populated by code:CordbAppDomain::LookupOrCreateAssembly (which may be invoked
+ // anytime the RS gets hold of a VMPTR), and are removed at the unload event.
+ CordbSafeHashTable<CordbAssembly> m_assemblies;
+
+ static void AssemblyEnumerationCallback(VMPTR_DomainAssembly vmDomainAssembly, void * pThis);
+ void PrepopulateAssembliesOrThrow();
+
+ // Use DAC to refresh our name
+ HRESULT RefreshName();
+
+ StringCopyHolder m_strAppDomainName;
+
+ NeuterList m_TypeNeuterList; // List of types owned by this AppDomain.
+
+ // List of Sweepable objects owned by this AppDomain.
+ // This includes some objects taht hold resources in the left-side (mainly
+ // as CordbHandleValue, see code:CordbHandleValue::Dispose), as well as:
+ // - Cordb*Value objects that survive across continues and have appdomain affinity.
+ LeftSideResourceCleanupList m_SweepableNeuterList;
+
+ VMPTR_AppDomain m_vmAppDomain;
+public:
+ // The "Long" exit list is for items that don't get neutered until the appdomain exits.
+ // The "Sweepable" exit list is for items that may be neuterable sooner than AD exit.
+ // By splitting out the list, we can just try to sweep the "Sweepable" list and we
+ // don't waste any time sweeping things on the "Long" list that aren't neuterable anyways.
+ NeuterList * GetLongExitNeuterList() { return &m_TypeNeuterList; }
+ LeftSideResourceCleanupList * GetSweepableExitNeuterList() { return &m_SweepableNeuterList; }
+
+ void AddToTypeList(CordbBase *pObject);
+
+};
+
+
+/* ------------------------------------------------------------------------- *
+ * Assembly class
+ * ------------------------------------------------------------------------- */
+
+class CordbAssembly : public CordbBase, public ICorDebugAssembly, ICorDebugAssembly2
+{
+public:
+ CordbAssembly(CordbAppDomain * pAppDomain,
+ VMPTR_Assembly vmAssembly,
+ VMPTR_DomainAssembly vmDomainAssembly);
+ virtual ~CordbAssembly();
+ virtual void Neuter();
+
+ using CordbBase::GetProcess;
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbAssembly"; }
+#endif
+
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugAssembly
+ //-----------------------------------------------------------
+
+ /*
+ * GetProcess returns the process containing the assembly
+ */
+ COM_METHOD GetProcess(ICorDebugProcess ** ppProcess);
+
+ // Gets the AppDomain containing this assembly
+ COM_METHOD GetAppDomain(ICorDebugAppDomain ** ppAppDomain);
+
+ /*
+ * EnumerateModules enumerates all modules in the assembly
+ */
+ COM_METHOD EnumerateModules(ICorDebugModuleEnum ** ppModules);
+
+ /*
+ * GetCodeBase returns the code base used to load the assembly
+ */
+ COM_METHOD GetCodeBase(ULONG32 cchName,
+ ULONG32 * pcchName,
+ __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[]);
+
+ // returns the filename of the assembly, or "<unknown>" for in-memory assemblies
+ COM_METHOD GetName(ULONG32 cchName,
+ ULONG32 * pcchName,
+ __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[]);
+
+
+ //-----------------------------------------------------------
+ // ICorDebugAssembly2
+ //-----------------------------------------------------------
+
+ /*
+ * IsFullyTrusted returns a flag indicating whether the security system
+ * has granted the assembly full trust.
+ */
+ COM_METHOD IsFullyTrusted(BOOL * pbFullyTrusted);
+
+ //-----------------------------------------------------------
+ // internal accessors
+ //-----------------------------------------------------------
+
+#ifdef _DEBUG
+ void DbgAssertAssemblyDeleted();
+
+ static void DbgAssertAssemblyDeletedCallback(VMPTR_DomainAssembly vmDomainAssembly, void * pUserData);
+#endif // _DEBUG
+
+ CordbAppDomain * GetAppDomain() { return m_pAppDomain; }
+
+ VMPTR_DomainAssembly GetDomainAssemblyPtr() { return m_vmDomainAssembly; }
+private:
+ VMPTR_Assembly m_vmAssembly;
+ VMPTR_DomainAssembly m_vmDomainAssembly;
+ CordbAppDomain * m_pAppDomain;
+
+ StringCopyHolder m_strAssemblyFileName;
+ Optional<BOOL> m_foptIsFullTrust;
+};
+
+
+//-----------------------------------------------------------------------------
+// Describe what to do w/ a win32 debug event
+//-----------------------------------------------------------------------------
+class Reaction
+{
+public:
+ enum Type
+ {
+ // Inband events: Dispatch to Cordbg
+ // safe for stopping the shell and communicating with the runtime
+ cInband,
+
+ // workaround. Inband event, but NewEvent =false
+ cInband_NotNewEvent,
+
+ // This is a debug event that corresponds with getting to the beginning
+ // of a first chance hijack.
+ cFirstChanceHijackStarted,
+
+ // This is the debug event that corresponds with getting to the end of
+ // a hijack. To continue we need to restore an unhijacked context
+ cInbandHijackComplete,
+
+ // This is a debug event which corresponds to re-hiting a previous
+ // IB event after returning from the hijack. Now we have already dispatched it
+ // so we know how the user wants it to be continued
+ // Continue immediately with the previously determined
+ cInbandExceptionRetrigger,
+
+ // This debug event is a breakpoint in unmanaged code that we placed. It will need
+ // the M2UHandoffHijack to run the in process breakpoint handling code.
+ cBreakpointRequiringHijack,
+
+ // Oob events: Dispatch to Cordbg
+ // Not safe stopping events. They must be continued immediately.
+ cOOB,
+
+ // CLR internal exception, Continue(not_handled), don't dispatch
+ // The CLR expects this exception and will deal with it properly.
+ cCLR,
+
+ // Don't dispatch. Continue(DBG_CONTINUE).
+ // Common for flare.
+ cIgnore
+ };
+
+ Type GetType() const { return m_type; };
+
+#ifdef _DEBUG
+ const char * GetReactionName()
+ {
+ switch(m_type)
+ {
+ case cInband: return "cInband";
+ case cInband_NotNewEvent: return "cInband_NotNewEvent";
+ case cInbandHijackComplete: return "cInbandHijackComplete";
+ case cInbandExceptionRetrigger: return "cInbandExceptionRetrigger";
+ case cBreakpointRequiringHijack: return "cBreakpointRequiringHijack";
+ case cOOB: return "cOOB";
+ case cCLR: return "cCLR";
+ case cIgnore: return "cIgnore";
+ default: return "<unknown>";
+ }
+ }
+ int GetLine()
+ {
+ return m_line;
+ }
+#endif
+
+ Reaction(Type t, int line) : m_type(t) {
+#ifdef _DEBUG
+ m_line = line;
+
+ LOG((LF_CORDB, LL_EVERYTHING, "Reaction:%s (determined on line: %d)\n", GetReactionName(), line));
+#endif
+ };
+
+ void operator=(const Reaction & other)
+ {
+ m_type = other.m_type;
+#ifdef _DEBUG
+ m_line = other.m_line;
+#endif
+ }
+
+protected:
+ Type m_type;
+
+#ifdef _DEBUG
+ // Under a debug build, track the line # for where this came from.
+ int m_line;
+#endif
+};
+
+// Macro for creating a Reaction.
+#define REACTION(type) Reaction(Reaction::type, __LINE__)
+
+// Different forms of Unmanaged Continue
+enum EUMContinueType
+{
+ cOobUMContinue,
+ cInternalUMContinue,
+ cRealUMContinue
+};
+
+/* ------------------------------------------------------------------------- *
+ * Process class
+ * ------------------------------------------------------------------------- */
+
+
+#ifdef _DEBUG
+// On debug, we can afford a larger native event queue..
+const int DEBUG_EVENTQUEUE_SIZE = 30;
+#else
+const int DEBUG_EVENTQUEUE_SIZE = 10;
+#endif
+
+void DeleteIPCEventHelper(DebuggerIPCEvent *pDel);
+
+
+// Private interface on CordbProcess that ShimProcess needs to emulate V2 functionality.
+// The fact that we need private hooks means that V3 is not sufficiently finished to allow building
+// a V2 debugger. This interface should shrink over time (and eventually go away) as the functionality gets exposed
+// publicly.
+// CordbProcess calls back into ShimProcess too, so the public surface of code:ShimProcess plus
+// the spots in CordbProcess that call them are additional surface area that may need to addressed
+// to make the shim public.
+class IProcessShimHooks
+{
+public:
+ // Get the OS Process ID of the target.
+ virtual DWORD GetPid() = 0;
+
+ // Request a synchronization for attach.
+ // This essentially just sends an AsyncBreak to the left-side. Once the target is
+ // synchronized, the Shim can use inspection to send all the various fake-attach events.
+ //
+ // Once the shim has a way of requesting a synchronization from out-of-process for an
+ // arbitrary running target that's not stopped at a managed debug event, we can
+ // remove this.
+ virtual void QueueManagedAttachIfNeeded() = 0;
+
+ // Hijack a thread at an unhandled exception to allow us to resume executing the target so
+ // that the helper thread can run and service IPC requests. This is also needed to allow
+ // func-eval at a 2nd-chance exception
+ //
+ // This will require an architectural change to remove. Either:
+ // - actions like func-eval / synchronization may call this directly themselves.
+ // - the CLR's managed Unhandled-exception event is moved out of the native
+ // unhandled-exception event, thus making native unhandled exceptions uninteresting to ICorDebug.
+ // - everything is out-of-process, and so the CLR doesn't need to continue after an unhandled
+ // native exception.
+ virtual BOOL HijackThreadForUnhandledExceptionIfNeeded(DWORD dwThreadId) = 0;
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ // Private hook to do the bulk of the interop-debugging goo. This includes hijacking inband
+ // events and queueing them so that the helper-thread can run.
+ //
+ // We can remove this once we kill the helper-thread, or after enough functionality is
+ // out-of-process that the debugger doesn't need the helper thread when stopped at an event.
+ virtual void HandleDebugEventForInteropDebugging(const DEBUG_EVENT * pEvent) = 0;
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ // Get the modules in the order that they were loaded. This is needed to send the fake-attach events
+ // for module load in the right order.
+ //
+ // This can be removed once ICorDebug's enumerations are ordered.
+ virtual void GetModulesInLoadOrder(
+ ICorDebugAssembly * pAssembly,
+ RSExtSmartPtr<ICorDebugModule>* pModules,
+ ULONG countModules) = 0;
+
+ // Get the assemblies in the order that they were loaded. This is needed to send the fake-attach events
+ // for assembly load in the right order.
+ //
+ // This can be removed once ICorDebug's enumerations are ordered.
+ virtual void GetAssembliesInLoadOrder(
+ ICorDebugAppDomain * pAppDomain,
+ RSExtSmartPtr<ICorDebugAssembly>* pAssemblies,
+ ULONG countAssemblies) = 0;
+
+ // Queue up fake connection events for attach.
+ // ICorDebug doesn't expose any enumeration for connections, so the shim needs to call into a
+ // private hook to enumerate them for attach.
+ virtual void QueueFakeConnectionEvents() = 0;
+
+ // This finishes initializing the IPC channel between the LS + RS, which includes duplicating
+ // some handles and events.
+ //
+ // This can be removed once the IPC channel is completely gone and all communication goes
+ // soley through the data-target.
+ virtual void FinishInitializeIPCChannel() = 0;
+
+ // Called when stopped at a managed debug event to request a synchronization.
+ // This can be replaced when we expose synchronization from ICorDebug.
+ // The fact that the debuggee is at a managed debug event greatly simplifies the request here
+ // (in contrast to QueueManagedAttachIfNeeded). It means that we can just flip a flag from
+ // out-of-process, and when the debuggee thread resumes, it can check that flag and do the
+ // synchronization from in-process.
+ virtual void RequestSyncAtEvent()= 0;
+
+ virtual bool IsThreadSuspendedOrHijacked(ICorDebugThread * pThread) = 0;
+};
+
+
+// entry for the array of connections in EnumerateConnectionsData
+struct EnumerateConnectionsEntry
+{
+public:
+ StringCopyHolder m_pName; // name of the connection
+ DWORD m_dwID; // ID of the connection
+};
+
+// data structure used in the callback for enumerating connections (code:CordbProcess::QueueFakeConnectionEvents)
+struct EnumerateConnectionsData
+{
+public:
+ ~EnumerateConnectionsData()
+ {
+ if (m_pEntryArray != NULL)
+ {
+ delete [] m_pEntryArray;
+ m_pEntryArray = NULL;
+ }
+ }
+
+ CordbProcess * m_pThis; // the "this" process
+ EnumerateConnectionsEntry * m_pEntryArray; // an array of connections to be filled in
+ UINT32 m_uIndex; // the next entry in the array to be filled
+};
+
+// data structure used in the callback for asserting that an appdomain has been deleted
+// (code:CordbProcess::DbgAssertAppDomainDeleted)
+struct DbgAssertAppDomainDeletedData
+{
+public:
+ CordbProcess * m_pThis;
+ VMPTR_AppDomain m_vmAppDomainDeleted;
+};
+
+class CordbProcess :
+ public CordbBase,
+ public ICorDebugProcess,
+ public ICorDebugProcess2,
+ public ICorDebugProcess3,
+ public ICorDebugProcess4,
+ public ICorDebugProcess5,
+ public ICorDebugProcess7,
+ public ICorDebugProcess8,
+ public IDacDbiInterface::IAllocator,
+ public IDacDbiInterface::IMetaDataLookup,
+ public IProcessShimHooks
+#ifdef FEATURE_LEGACYNETCF_DBG_HOST_CONTROL
+ , public ICorDebugLegacyNetCFHostCallbackInvoker_PrivateWindowsPhoneOnly
+#endif
+{
+ // Ctor is private. Use OpenVirtualProcess instead.
+ CordbProcess(ULONG64 clrInstanceId, IUnknown * pDataTarget, HMODULE hDacModule, Cordb * pCordb, DWORD dwProcessID, ShimProcess * pShim);
+
+public:
+
+ virtual ~CordbProcess();
+ virtual void Neuter();
+
+ // Neuter left-side resources for all children
+ void NeuterChildrenLeftSideResources();
+
+ // Neuter all of all children, but not the actual process object.
+ void NeuterChildren();
+
+
+ // The way to instantiate a new CordbProcess object.
+ // @dbgtodo managed pipeline - this is not fully active in all scenarios yet.
+ static HRESULT OpenVirtualProcess(ULONG64 clrInstanceId,
+ IUnknown * pDataTarget,
+ HMODULE hDacModule,
+ Cordb * pCordb,
+ DWORD dwProcessID,
+ ShimProcess * pShim,
+ CordbProcess ** ppProcess);
+
+ // Helper function to determine whether this ICorDebug is compatibile with a debugger
+ // designed for the specified major version
+ static bool IsCompatibleWith(DWORD clrMajorVersion);
+
+ //-----------------------------------------------------------
+ // IMetaDataLookup
+ // -----------------------------------------------------------
+ IMDInternalImport * LookupMetaData(VMPTR_PEFile vmPEFile, bool &isILMetaDataForNGENImage);
+
+ // Helper functions for LookupMetaData implementation
+ IMDInternalImport * LookupMetaDataFromDebugger(VMPTR_PEFile vmPEFile,
+ bool &isILMetaDataForNGENImage,
+ CordbModule * pModule);
+
+ IMDInternalImport * LookupMetaDataFromDebuggerForSingleFile(CordbModule * pModule,
+ LPCWSTR pwszImagePath,
+ DWORD dwTimeStamp,
+ DWORD dwImageSize);
+
+
+ //-----------------------------------------------------------
+ // IDacDbiInterface::IAllocator
+ //-----------------------------------------------------------
+
+ void * Alloc(SIZE_T lenBytes);
+ void Free(void * p);
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbProcess"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return BaseAddRefEnforceExternal();
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return BaseReleaseEnforceExternal();
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugController
+ //-----------------------------------------------------------
+
+ COM_METHOD Stop(DWORD dwTimeout);
+ COM_METHOD Deprecated_Continue();
+ COM_METHOD IsRunning(BOOL *pbRunning);
+ COM_METHOD HasQueuedCallbacks(ICorDebugThread *pThread, BOOL *pbQueued);
+ COM_METHOD EnumerateThreads(ICorDebugThreadEnum **ppThreads);
+ COM_METHOD SetAllThreadsDebugState(CorDebugThreadState state,
+ ICorDebugThread *pExceptThisThread);
+ COM_METHOD Detach();
+ COM_METHOD Terminate(unsigned int exitCode);
+
+ COM_METHOD CanCommitChanges(
+ ULONG cSnapshots,
+ ICorDebugEditAndContinueSnapshot *pSnapshots[],
+ ICorDebugErrorInfoEnum **pError);
+
+ COM_METHOD CommitChanges(
+ ULONG cSnapshots,
+ ICorDebugEditAndContinueSnapshot *pSnapshots[],
+ ICorDebugErrorInfoEnum **pError);
+
+ COM_METHOD Continue(BOOL fIsOutOfBand);
+ COM_METHOD ThreadForFiberCookie(DWORD fiberCookie,
+ ICorDebugThread **ppThread);
+ COM_METHOD GetHelperThreadID(DWORD *pThreadID);
+
+ //-----------------------------------------------------------
+ // ICorDebugProcess
+ //-----------------------------------------------------------
+
+ COM_METHOD GetID(DWORD *pdwProcessId);
+ COM_METHOD GetHandle(HANDLE *phProcessHandle);
+ COM_METHOD EnableSynchronization(BOOL bEnableSynchronization);
+ COM_METHOD GetThread(DWORD dwThreadId, ICorDebugThread **ppThread);
+ COM_METHOD EnumerateBreakpoints(ICorDebugBreakpointEnum **ppBreakpoints);
+ COM_METHOD EnumerateSteppers(ICorDebugStepperEnum **ppSteppers);
+ COM_METHOD EnumerateObjects(ICorDebugObjectEnum **ppObjects);
+ COM_METHOD IsTransitionStub(CORDB_ADDRESS address, BOOL *pbTransitionStub);
+ COM_METHOD EnumerateModules(ICorDebugModuleEnum **ppModules);
+ COM_METHOD GetModuleFromMetaDataInterface(IUnknown *pIMetaData,
+ ICorDebugModule **ppModule);
+ COM_METHOD SetStopState(DWORD threadID, CorDebugThreadState state);
+ COM_METHOD IsOSSuspended(DWORD threadID, BOOL *pbSuspended);
+ COM_METHOD GetThreadContext(DWORD threadID, ULONG32 contextSize,
+ BYTE context[]);
+ COM_METHOD SetThreadContext(DWORD threadID, ULONG32 contextSize,
+ BYTE context[]);
+ COM_METHOD ReadMemory(CORDB_ADDRESS address, DWORD size, BYTE buffer[],
+ SIZE_T *read);
+ COM_METHOD WriteMemory(CORDB_ADDRESS address, DWORD size, BYTE buffer[],
+ SIZE_T *written);
+
+ COM_METHOD ClearCurrentException(DWORD threadID);
+
+ /*
+ * EnableLogMessages enables/disables sending of log messages to the
+ * debugger for logging.
+ */
+ COM_METHOD EnableLogMessages(BOOL fOnOff);
+
+ /*
+ * ModifyLogSwitch modifies the specified switch's severity level.
+ */
+ COM_METHOD ModifyLogSwitch(__in_z WCHAR *pLogSwitchName, LONG lLevel);
+
+ COM_METHOD EnumerateAppDomains(ICorDebugAppDomainEnum **ppAppDomains);
+ COM_METHOD GetObject(ICorDebugValue **ppObject);
+
+ //-----------------------------------------------------------
+ // ICorDebugProcess2
+ //-----------------------------------------------------------
+
+ COM_METHOD GetThreadForTaskID(TASKID taskId, ICorDebugThread2 ** ppThread);
+ COM_METHOD GetVersion(COR_VERSION* pInfo);
+
+ COM_METHOD SetUnmanagedBreakpoint(CORDB_ADDRESS address, ULONG32 bufsize, BYTE buffer[], ULONG32 * bufLen);
+ COM_METHOD ClearUnmanagedBreakpoint(CORDB_ADDRESS address);
+ COM_METHOD GetCodeAtAddress(CORDB_ADDRESS address, ICorDebugCode ** pCode, ULONG32 * offset);
+
+ COM_METHOD SetDesiredNGENCompilerFlags(DWORD pdwFlags);
+ COM_METHOD GetDesiredNGENCompilerFlags(DWORD *pdwFlags );
+
+ COM_METHOD GetReferenceValueFromGCHandle(UINT_PTR handle, ICorDebugReferenceValue **pOutValue);
+
+ //-----------------------------------------------------------
+ // ICorDebugProcess3
+ //-----------------------------------------------------------
+
+ // enables or disables CustomNotifications of a given type
+ COM_METHOD SetEnableCustomNotification(ICorDebugClass * pClass, BOOL fEnable);
+
+ //-----------------------------------------------------------
+ // ICorDebugProcess4
+ //-----------------------------------------------------------
+ COM_METHOD Filter(
+ const BYTE pRecord[],
+ DWORD countBytes,
+ CorDebugRecordFormat format,
+ DWORD dwFlags,
+ DWORD dwThreadId,
+ ICorDebugManagedCallback *pCallback,
+ DWORD * pContinueStatus);
+
+ COM_METHOD ProcessStateChanged(CorDebugStateChange eChange);
+
+ //-----------------------------------------------------------
+ // ICorDebugProcess5
+ //-----------------------------------------------------------
+ COM_METHOD GetGCHeapInformation(COR_HEAPINFO *pHeapInfo);
+ COM_METHOD EnumerateHeap(ICorDebugHeapEnum **ppObjects);
+ COM_METHOD EnumerateHeapRegions(ICorDebugHeapSegmentEnum **ppRegions);
+ COM_METHOD GetObject(CORDB_ADDRESS addr, ICorDebugObjectValue **pObject);
+ COM_METHOD EnableNGENPolicy(CorDebugNGENPolicy ePolicy);
+ COM_METHOD EnumerateGCReferences(BOOL enumerateWeakReferences, ICorDebugGCReferenceEnum **ppEnum);
+ COM_METHOD EnumerateHandles(CorGCReferenceType types, ICorDebugGCReferenceEnum **ppEnum);
+ COM_METHOD GetTypeID(CORDB_ADDRESS obj, COR_TYPEID *pId);
+ COM_METHOD GetTypeForTypeID(COR_TYPEID id, ICorDebugType **ppType);
+ COM_METHOD GetArrayLayout(COR_TYPEID id, COR_ARRAY_LAYOUT *pLayout);
+ COM_METHOD GetTypeLayout(COR_TYPEID id, COR_TYPE_LAYOUT *pLayout);
+ COM_METHOD GetTypeFields(COR_TYPEID id, ULONG32 celt, COR_FIELD fields[], ULONG32 *pceltNeeded);
+
+ //-----------------------------------------------------------
+ // ICorDebugProcess7
+ //-----------------------------------------------------------
+ COM_METHOD SetWriteableMetadataUpdateMode(WriteableMetadataUpdateMode flags);
+
+ //-----------------------------------------------------------
+ // ICorDebugProcess8
+ //-----------------------------------------------------------
+ COM_METHOD EnableExceptionCallbacksOutsideOfMyCode(BOOL enableExceptionsOutsideOfJMC);
+
+#ifdef FEATURE_LEGACYNETCF_DBG_HOST_CONTROL
+ // ---------------------------------------------------------------
+ // ICorDebugLegacyNetCFHostCallbackInvoker_PrivateWindowsPhoneOnly
+ // ---------------------------------------------------------------
+
+ COM_METHOD InvokePauseCallback();
+ COM_METHOD InvokeResumeCallback();
+
+#endif
+
+ //-----------------------------------------------------------
+ // Methods not exposed via a COM interface.
+ //-----------------------------------------------------------
+
+ HRESULT ContinueInternal(BOOL fIsOutOfBand);
+ HRESULT StopInternal(DWORD dwTimeout, VMPTR_AppDomain pAppDomainToken);
+
+ // Sets an unmanaged breakpoint at the target address
+ HRESULT SetUnmanagedBreakpointInternal(CORDB_ADDRESS address, ULONG32 bufsize, BYTE buffer[], ULONG32 * bufLen);
+
+ // Allocate a buffer within the target and return the range. Throws on error.
+ TargetBuffer GetRemoteBuffer(ULONG cbBuffer); // throws
+
+ // Same as above except also copy-in the contents of a RS buffer using WriteProcessMemory
+ HRESULT GetAndWriteRemoteBuffer(CordbAppDomain *pDomain, unsigned int bufferSize, const void *bufferFrom, void **ppBuffer);
+
+ /*
+ * This will release a previously allocated left side buffer.
+ * Often they are deallocated by the LS itself.
+ */
+ HRESULT ReleaseRemoteBuffer(void **ppBuffer);
+
+
+ void TargetConsistencyCheck(bool fExpression);
+
+ // Activate interop-debugging, after the process has initially been Init()
+ void EnableInteropDebugging();
+
+ HRESULT Init();
+ void DeleteQueuedEvents();
+ void CleanupHalfBakedLeftSide();
+ void Terminating(BOOL fDetach);
+
+ CordbThread * TryLookupThread(VMPTR_Thread vmThread);
+ CordbThread * TryLookupOrCreateThreadByVolatileOSId(DWORD dwThreadId);
+ CordbThread * TryLookupThreadByVolatileOSId(DWORD dwThreadId);
+ CordbThread * LookupOrCreateThread(VMPTR_Thread vmThread);
+
+ void QueueManagedAttachIfNeeded();
+ void QueueManagedAttachIfNeededWorker();
+ HRESULT QueueManagedAttach();
+
+ void DetachShim();
+
+ // Flush for when the process is running.
+ void FlushProcessRunning();
+
+ // Flush all state.
+ void FlushAll();
+
+ BOOL HijackThreadForUnhandledExceptionIfNeeded(DWORD dwThreadId);
+
+ // Filter a CLR notification (subset of exceptions).
+ void FilterClrNotification(
+ DebuggerIPCEvent * pManagedEvent,
+ RSLockHolder * pLockHolder,
+ ICorDebugManagedCallback * pCallback);
+
+ // Wrapper to invoke IClrDataTarget4::ContinueStatusChanged
+ void ContinueStatusChanged(DWORD dwThreadId, CORDB_CONTINUE_STATUS dwContinueStatus);
+
+
+ // Request a synchronization to occur after a debug event is dispatched.
+ void RequestSyncAtEvent();
+
+ //
+ // Basic managed event plumbing
+ //
+
+ // This is called on the first IPC event from the debuggee. It initializes state.
+ void FinishInitializeIPCChannel();
+ void FinishInitializeIPCChannelWorker();
+
+ // This is called on each IPC event from the debuggee.
+ void HandleRCEvent(DebuggerIPCEvent * pManagedEvent, RSLockHolder * pLockHolder, ICorDebugManagedCallback * pCallback);
+
+ // Queue the RC event.
+ void QueueRCEvent(DebuggerIPCEvent * pManagedEvent);
+
+ // This marshals a managed debug event from the
+ void MarshalManagedEvent(DebuggerIPCEvent * pManagedEvent);
+
+ // This copies a managed debug event from the IPC block and to pManagedEvent.
+ // The event still needs to be marshalled.
+ void CopyRCEventFromIPCBlock(DebuggerIPCEvent * pManagedEvent);
+
+ // This copies a managed debug event out of the Native-Debug event envelope.
+ // The event still needs to be marshalled.
+ bool CopyManagedEventFromTarget(const EXCEPTION_RECORD * pRecord, DebuggerIPCEvent * pLocalManagedEvent);
+
+ // Helper for Filter() to verify parameters and return a type-safe exception record.
+ const EXCEPTION_RECORD * ValidateExceptionRecord(
+ const BYTE pRawRecord[],
+ DWORD countBytes,
+ CorDebugRecordFormat format);
+
+ // Helper to read a structure from the target.
+ template<typename T>
+ HRESULT SafeReadStruct(CORDB_ADDRESS pRemotePtr, T* pLocalBuffer);
+
+ // Helper to write a structure into the target.
+ template<typename T>
+ HRESULT SafeWriteStruct(CORDB_ADDRESS pRemotePtr, const T* pLocalBuffer);
+
+ // Reads a buffer from the target
+ HRESULT SafeReadBuffer(TargetBuffer tb, BYTE * pLocalBuffer, BOOL throwOnError = TRUE);
+
+ // Writes a buffer to the target
+ void SafeWriteBuffer(TargetBuffer tb, const BYTE * pLocalBuffer);
+
+#if defined(FEATURE_INTEROP_DEBUGGING)
+ void DuplicateHandleToLocalProcess(HANDLE * pLocalHandle, RemoteHANDLE * pRemoteHandle);
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ bool IsThreadSuspendedOrHijacked(ICorDebugThread * pICorDebugThread);
+
+ // Helper to get PID internally.
+ DWORD GetPid();
+
+ HRESULT GetRuntimeOffsets();
+
+ // Are we blocked waiting fo ran OOB event to be continue?
+ bool IsWaitingForOOBEvent()
+ {
+#ifdef FEATURE_INTEROP_DEBUGGING
+ return m_outOfBandEventQueue != NULL;
+#else
+ // If no interop, then we're never waiting for an OOB event.
+ return false;
+#endif
+ }
+
+ //
+ // Shim callbacks to simulate fake attach events.
+ //
+
+
+ // Callback for Shim to get the assemblies in load order
+ void GetAssembliesInLoadOrder(
+ ICorDebugAppDomain * pAppDomain,
+ RSExtSmartPtr<ICorDebugAssembly>* pAssemblies,
+ ULONG countAssemblies);
+
+ // Callback for Shim to get the modules in load order
+ void GetModulesInLoadOrder(
+ ICorDebugAssembly * pAssembly,
+ RSExtSmartPtr<ICorDebugModule>* pModules,
+ ULONG countModules);
+
+ // Functions to queue fake Connection events on attach.
+ static void CountConnectionsCallback(DWORD id, LPCWSTR pName, void * pUserData);
+ static void EnumerateConnectionsCallback(DWORD id, LPCWSTR pName, void * pUserData);
+ void QueueFakeConnectionEvents();
+
+
+
+ void DispatchRCEvent();
+
+ // Dispatch a single event via the callbacks.
+ void RawDispatchEvent(
+ DebuggerIPCEvent * pEvent,
+ RSLockHolder * pLockHolder,
+ ICorDebugManagedCallback * pCallback1,
+ ICorDebugManagedCallback2 * pCallback2,
+ ICorDebugManagedCallback3 * pCallback3);
+
+ void MarkAllThreadsDirty();
+
+ bool CheckIfLSExited();
+
+ void Lock()
+ {
+ // Lock Hierarchy - shouldn't have List lock when taking/release the process lock.
+
+ m_processMutex.Lock();
+ LOG((LF_CORDB, LL_EVERYTHING, "P::Lock enter, this=0x%p\n", this));
+ }
+
+ void Unlock()
+ {
+ // Lock Hierarchy - shouldn't have List lock when taking/releasing the process lock.
+
+ LOG((LF_CORDB, LL_EVERYTHING, "P::Lock leave, this=0x%p\n", this));
+ m_processMutex.Unlock();
+ }
+
+#ifdef _DEBUG
+ bool ThreadHoldsProcessLock()
+ {
+ return m_processMutex.HasLock();
+ }
+#endif
+
+ // Expose the process lock.
+ // This is the main lock in V3.
+ RSLock * GetProcessLock()
+ {
+ return &m_processMutex;
+ }
+
+
+ // @dbgtodo synchronization - the SG lock goes away in V3.
+ // Expose the stop-go lock b/c varios Cordb objects in our process tree may need to take it.
+ RSLock * GetStopGoLock()
+ {
+ return &m_StopGoLock;
+ }
+
+
+ void UnrecoverableError(HRESULT errorHR,
+ unsigned int errorCode,
+ const char *errorFile,
+ unsigned int errorLine);
+ HRESULT CheckForUnrecoverableError();
+ void VerifyControlBlock();
+
+ // The implementation of EnumerateThreads without the public API error checks
+ VOID InternalEnumerateThreads(RSInitHolder<CordbHashTableEnum> * ppThreads);
+
+ //-----------------------------------------------------------
+ // Convenience routines
+ //-----------------------------------------------------------
+
+ // Is it safe to send events to the LS?
+ bool IsSafeToSendEvents() { return !m_unrecoverableError && !m_terminated && !m_detached; }
+
+ bool IsWin32EventThread();
+
+ void HandleSyncCompleteRecieved();
+
+ // Send a truly asynchronous IPC event.
+ void SendAsyncIPCEvent(DebuggerIPCEventType t);
+
+ HRESULT SendIPCEvent(DebuggerIPCEvent *event, SIZE_T eventSize)
+ {
+ // @dbgtodo - eventually remove this when all IPC events are gone.
+ // In V3 paths, we can't send IPC events.
+ if (GetShim() == NULL)
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "!! Can't send IPC event in V3. %s", IPCENames::GetName(event->type));
+ return E_NOTIMPL;
+ }
+ _ASSERTE(m_cordb != NULL);
+ return (m_cordb->SendIPCEvent(this, event, eventSize));
+ }
+
+ void InitAsyncIPCEvent(DebuggerIPCEvent *ipce,
+ DebuggerIPCEventType type,
+ VMPTR_AppDomain vmAppDomain)
+ {
+ // Async events only allowed for the following:
+ _ASSERTE(type == DB_IPCE_ATTACHING);
+
+ InitIPCEvent(ipce, type, false, vmAppDomain);
+ ipce->asyncSend = true;
+ }
+
+ void InitIPCEvent(DebuggerIPCEvent *ipce,
+ DebuggerIPCEventType type,
+ bool twoWay,
+ VMPTR_AppDomain vmAppDomain
+ )
+ {
+ // zero out the event in case we try and use any uninitialized fields
+ memset( ipce, 0, sizeof(DebuggerIPCEvent) );
+
+ _ASSERTE((!vmAppDomain.IsNull()) ||
+ type == DB_IPCE_GET_GCHANDLE_INFO ||
+ type == DB_IPCE_ENABLE_LOG_MESSAGES ||
+ type == DB_IPCE_MODIFY_LOGSWITCH ||
+ type == DB_IPCE_ASYNC_BREAK ||
+ type == DB_IPCE_CONTINUE ||
+ type == DB_IPCE_GET_BUFFER ||
+ type == DB_IPCE_RELEASE_BUFFER ||
+ type == DB_IPCE_IS_TRANSITION_STUB ||
+ type == DB_IPCE_ATTACHING ||
+ type == DB_IPCE_APPLY_CHANGES ||
+ type == DB_IPCE_CONTROL_C_EVENT_RESULT ||
+ type == DB_IPCE_SET_REFERENCE ||
+ type == DB_IPCE_SET_ALL_DEBUG_STATE ||
+ type == DB_IPCE_GET_THREAD_FOR_TASKID ||
+ type == DB_IPCE_DETACH_FROM_PROCESS ||
+ type == DB_IPCE_INTERCEPT_EXCEPTION ||
+ type == DB_IPCE_GET_NGEN_COMPILER_FLAGS ||
+ type == DB_IPCE_SET_NGEN_COMPILER_FLAGS ||
+ type == DB_IPCE_SET_VALUE_CLASS);
+
+ ipce->type = type;
+ ipce->hr = S_OK;
+ ipce->processId = 0;
+ ipce->vmAppDomain = vmAppDomain;
+ ipce->vmThread = VMPTR_Thread::NullPtr();
+ ipce->replyRequired = twoWay;
+ ipce->asyncSend = false;
+ ipce->next = NULL;
+ }
+
+ // Looks up a previously constructed CordbClass instance without creating. May return NULL if the
+ // CordbClass instance doesn't exist.
+ CordbClass * LookupClass(ICorDebugAppDomain * pAppDomain, VMPTR_DomainFile vmDomainFile, mdTypeDef classToken);
+
+ CordbModule * LookupOrCreateModule(VMPTR_DomainFile vmDomainFile);
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ CordbUnmanagedThread *GetUnmanagedThread(DWORD dwThreadId)
+ {
+ _ASSERTE(ThreadHoldsProcessLock());
+ return m_unmanagedThreads.GetBase(dwThreadId);
+ }
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ /*
+ * This will cleanup the patch table, releasing memory,etc.
+ */
+ void ClearPatchTable();
+
+ /*
+ * This will grab the patch table from the left side & go through
+ * it to gather info needed for faster access. If address,size,buffer
+ * are passed in, while going through the table we'll undo patches
+ * in buffer at the same time
+ */
+ HRESULT RefreshPatchTable(CORDB_ADDRESS address = NULL, SIZE_T size = NULL, BYTE buffer[] = NULL);
+
+ // Find if a patch exists at a given address.
+ HRESULT FindPatchByAddress(CORDB_ADDRESS address, bool *patchFound, bool *patchIsUnmanaged);
+
+ enum AB_MODE
+ {
+ AB_READ,
+ AB_WRITE
+ };
+
+ /*
+ * Once we've called RefreshPatchTable to get the patch table,
+ * this routine will iterate through the patches & either apply
+ * or unapply the patches to buffer. AB_READ => Replaces patches
+ * in buffer with the original opcode, AB_WRTE => replace opcode
+ * with breakpoint instruction, caller is responsible for
+ * updating the patchtable back to the left side.
+ *
+ * <TODO>@todo Perf Instead of a copy, undo the changes
+ * Since the 'buffer' arg is an [in] param, we're not supposed to
+ * change it. If we do, we'll allocate & copy it to bufferCopy
+ * (we'll also set *pbUpdatePatchTable to true), otherwise we
+ * don't manipuldate bufferCopy (so passing a NULL in for
+ * reading is fine).</TODO>
+ */
+ HRESULT AdjustBuffer(CORDB_ADDRESS address,
+ SIZE_T size,
+ BYTE buffer[],
+ BYTE **bufferCopy,
+ AB_MODE mode,
+ BOOL *pbUpdatePatchTable = NULL);
+
+ /*
+ * AdjustBuffer, above, doesn't actually update the local patch table
+ * if asked to do a write. It stores the changes alongside the table,
+ * and this will cause the changes to be written to the table (for
+ * a range of left-side addresses
+ */
+ void CommitBufferAdjustments(CORDB_ADDRESS start,
+ CORDB_ADDRESS end);
+
+ /*
+ * Clear the stored changes, or they'll sit there until we
+ * accidentally commit them
+ */
+ void ClearBufferAdjustments();
+
+
+
+
+ //-----------------------------------------------------------
+ // Accessors for key synchronization fields.
+ //-----------------------------------------------------------
+
+ // If CAD is NULL, returns true if all appdomains (ie, the entire process)
+ // is synchronized. Otherwise, returns true if the specified appdomain is
+ // synch'd.
+ bool GetSynchronized();
+ void SetSynchronized(bool fSynch);
+
+ void IncStopCount();
+ void DecStopCount();
+
+ // Gets the exact stop count. You need the Proecss lock for this.
+ int GetStopCount();
+
+ // Just gets whether we're stopped or not (m_stopped > 0).
+ // You only need the StopGo lock for this.
+ // This is biases towards returning false.
+ bool IsStopped();
+
+ bool GetSyncCompleteRecv();
+ void SetSyncCompleteRecv(bool fSyncRecv);
+
+
+ // Cordbg may not always continue during a callback; but we really shouldn't do meaningful
+ // work after a callback has returned yet before they've called continue. Thus we may need
+ // to remember some state at the time of dispatch so that we do stuff at continue.
+ // Only example here is neutering... we'd like to Neuter an object X after the ExitX callback,
+ // but we can't neuter it until Continue. So remember X when we dispatch, and neuter this at continue.
+ // Use a smart ptr to keep it alive until we neuter it.
+
+ // Add objects to various neuter lists.
+ // NeuterOnContinue is for all objects that can be neutered once we continue.
+ // NeuterOnExit is for all objects that can survive continues (but are neutered on process shutdown).
+ // If an object's external ref count goes to 0, it gets promoted to the NeuterOnContinue list.
+ void AddToNeuterOnExitList(CordbBase *pObject);
+ void AddToNeuterOnContinueList(CordbBase *pObject);
+
+ NeuterList * GetContinueNeuterList() { return &m_ContinueNeuterList; }
+ NeuterList * GetExitNeuterList() { return &m_ExitNeuterList; }
+
+ void AddToLeftSideResourceCleanupList(CordbBase * pObject);
+
+ // Routines to read and write thread context records between the processes safely.
+ HRESULT SafeReadThreadContext(LSPTR_CONTEXT pRemoteContext, DT_CONTEXT * pCtx);
+ HRESULT SafeWriteThreadContext(LSPTR_CONTEXT pRemoteContext, const DT_CONTEXT * pCtx);
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ // Record a win32 event for debugging purposes.
+ void DebugRecordWin32Event(const DEBUG_EVENT * pEvent, CordbUnmanagedThread * pUThread);
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ //-----------------------------------------------------------
+ // Interop Helpers
+ //-----------------------------------------------------------
+
+ // Get the DAC interface.
+ IDacDbiInterface * GetDAC();
+
+ // Get the data-target, which provides access to the debuggee.
+ ICorDebugDataTarget * GetDataTarget();
+
+ BOOL IsDacInitialized();
+
+ void ForceDacFlush();
+
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ // Deal with native debug events for the interop-debugging scenario.
+ void HandleDebugEventForInteropDebugging(const DEBUG_EVENT * pEvent);
+
+ void ResumeHijackedThreads();
+
+ //@todo - We should try to make these all private
+ CordbUnmanagedThread *HandleUnmanagedCreateThread(DWORD dwThreadId, HANDLE hThread, void *lpThreadLocalBase);
+
+ HRESULT ContinueOOB();
+ void QueueUnmanagedEvent(CordbUnmanagedThread *pUThread, const DEBUG_EVENT *pEvent);
+ void DequeueUnmanagedEvent(CordbUnmanagedThread *pUThread);
+ void QueueOOBUnmanagedEvent(CordbUnmanagedThread *pUThread, const DEBUG_EVENT *pEvent);
+ void DequeueOOBUnmanagedEvent(CordbUnmanagedThread *pUThread);
+ void DispatchUnmanagedInBandEvent();
+ void DispatchUnmanagedOOBEvent();
+ bool ExceptionIsFlare(DWORD exceptionCode, const void *exceptionAddress);
+
+ bool IsSpecialStackOverflowCase(CordbUnmanagedThread *pUThread, const DEBUG_EVENT *pEvent);
+
+ HRESULT SuspendUnmanagedThreads();
+ HRESULT ResumeUnmanagedThreads();
+
+ HRESULT HijackIBEvent(CordbUnmanagedEvent * pUnmanagedEvent);
+
+ BOOL HasUndispatchedNativeEvents();
+ BOOL HasUserUncontinuedNativeEvents();
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ HRESULT StartSyncFromWin32Stop(BOOL * pfAsyncBreakSent);
+
+
+ // For interop attach, we first do native, and then once Cordbg continues from
+ // the loader-bp, we kick off the managed attach. This field remembers that
+ // whether we need the managed attach.
+ // @dbgtodo managed pipeline - hoist to shim.
+ bool m_fDoDelayedManagedAttached;
+
+
+
+ // Table of CordbEval objects that we've sent over to the LS.
+ // This is synced via the process lock.
+ RsPtrTable<CordbEval> m_EvalTable;
+
+ void PrepopulateThreadsOrThrow();
+
+ // Lookup or create an appdomain.
+ CordbAppDomain * LookupOrCreateAppDomain(VMPTR_AppDomain vmAppDomain);
+
+ // Get the shared app domain.
+ CordbAppDomain * GetSharedAppDomain();
+
+ // Get metadata dispenser.
+ IMetaDataDispenserEx * GetDispenser();
+
+ // Sets a bitfield reflecting the managed debugging state at the time of
+ // the jit attach.
+ HRESULT GetAttachStateFlags(CLR_DEBUGGING_PROCESS_FLAGS *pFlags);
+
+ HRESULT GetTypeForObject(CORDB_ADDRESS obj, CordbType **ppType, CordbAppDomain **pAppDomain = NULL);
+
+ WriteableMetadataUpdateMode GetWriteableMetadataUpdateMode() { return m_writableMetadataUpdateMode; }
+private:
+
+#ifdef _DEBUG
+ // Assert that vmAppDomainDeleted doesn't show up in dac enumerations
+ void DbgAssertAppDomainDeleted(VMPTR_AppDomain vmAppDomainDeleted);
+
+ // Callback helper for DbgAssertAppDomainDeleted.
+ static void DbgAssertAppDomainDeletedCallback(VMPTR_AppDomain vmAppDomain, void * pUserData);
+#endif // _DEBUG
+
+ static void ThreadEnumerationCallback(VMPTR_Thread vmThread, void * pUserData);
+
+
+ // Callback for AppDomain enumeration
+ static void AppDomainEnumerationCallback(VMPTR_AppDomain vmAppDomain, void * pUserData);
+
+ // Helper to create a new CordbAppDomain around the vmptr and cache it
+ CordbAppDomain * CacheAppDomain(VMPTR_AppDomain vmAppDomain);
+
+ // Helper to traverse Appdomains in target and build up our cache.
+ void PrepopulateAppDomainsOrThrow();
+
+
+ void ProcessFirstLogMessage (DebuggerIPCEvent *event);
+ void ProcessContinuedLogMessage (DebuggerIPCEvent *event);
+
+ void CloseIPCHandles();
+ void UpdateThreadsForAdUnload( CordbAppDomain* pAppDomain );
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ // Each win32 debug event needs to be triaged to get a Reaction.
+ Reaction TriageBreakpoint(CordbUnmanagedThread * pUnmanagedThread, const DEBUG_EVENT * pEvent);
+ Reaction TriageSyncComplete();
+ Reaction Triage1stChanceNonSpecial(CordbUnmanagedThread * pUnmanagedThread, const DEBUG_EVENT * pEvent);
+ Reaction TriageExcep1stChanceAndInit(CordbUnmanagedThread * pUnmanagedThread, const DEBUG_EVENT * pEvent);
+ Reaction TriageExcep2ndChanceAndInit(CordbUnmanagedThread * pUnmanagedThread, const DEBUG_EVENT * pEvent);
+ Reaction TriageWin32DebugEvent(CordbUnmanagedThread * pUnmanagedThread, const DEBUG_EVENT * pEvent);
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+public:
+ RSSmartPtr<Cordb> m_cordb;
+
+private:
+ // OS process handle to live process.
+ // @dbgtodo - , Move this into the Shim. This should only be needed in the live-process
+ // case. Get rid of this since it breaks the data-target abstraction.
+ // For Mac debugging, this handle is of course not the real process handle. This is just a handle to
+ // wait on for process termination.
+ HANDLE m_handle;
+
+public:
+ // Wrapper to get the OS process handle. This is unsafe because it breaks the data-target abstraction.
+ // The only things that need this should be calls to DuplicateHandle, and some shimming work.
+ HANDLE UnsafeGetProcessHandle()
+ {
+ return m_handle;
+ }
+
+ // Set when code:CordbProcess::Detach is called.
+ // Public APIs can check this and return CORDBG_E_PROCESS_DETACHED.
+ // @dbgtodo managed pipeline - really could merge this with neuter.
+ bool m_detached;
+
+ // True if we code:CordbProcess::Stop is called before the managed CreateProcess event.
+ // In this case, m_initialized is false, and we can't send an AsyncBreak event to the LS.
+ // (since the LS isn't going to send a SyncComplete event back since the CLR isn't loaded/ready).
+ // @dbgtodo managed pipeline - move into shim, along with Stop/Continue.
+ bool m_uninitializedStop;
+
+
+ // m_exiting is true if we know the LS is starting to exit (if the
+ // RS is telling the LS to exit) or if we know the LS has already exited.
+ bool m_exiting;
+
+
+ // m_terminated can only be set to true if we know 100% the LS has exited (ie, somebody
+ // waited on the LS process handle).
+ bool m_terminated;
+
+ bool m_unrecoverableError;
+
+ bool m_specialDeferment;
+ bool m_helperThreadDead; // flag used for interop
+
+ // This tracks if the loader breakpoint has been received during interop-debugging.
+ // The Loader Breakpoint is an breakpoint event raised by the OS once the debugger is attached.
+ // It comes in both Attach and Launch scenarios.
+ // This is also used in fake-native debugging scenarios.
+ bool m_loaderBPReceived;
+
+
+private:
+
+ // MetaData dispenser.
+ RSExtSmartPtr<IMetaDataDispenserEx> m_pMetaDispenser;
+
+ //
+ // Count of the number of outstanding CordbEvals in the process.
+ //
+ LONG m_cOutstandingEvals;
+
+ // Number of oustanding code:CordbHandleValue objects containing
+ // Left-side resources. This can be used to tell if ICorDebug needs to
+ // cleanup gc handles.
+ LONG m_cOutstandingHandles;
+
+ // Pointer to the CordbModule instance that can currently change the Jit flags.
+ // There can be at most one of these. It will represent a module that has just been loaded, before the
+ // Continue is sent. See code:CordbProcess::RawDispatchEvent and code:CordbProcess::ContinueInternal.
+ CordbModule * m_pModuleThatCanChangeJitFlags;
+
+public:
+ LONG OutstandingEvalCount()
+ {
+ return m_cOutstandingEvals;
+ }
+
+ void IncrementOutstandingEvalCount()
+ {
+ InterlockedIncrement(&m_cOutstandingEvals);
+ }
+
+ void DecrementOutstandingEvalCount()
+ {
+ InterlockedDecrement(&m_cOutstandingEvals);
+ }
+
+ LONG OutstandingHandles();
+ void IncrementOutstandingHandles();
+ void DecrementOutstandingHandles();
+
+ //
+ // Is it OK to detach at this time
+ //
+ HRESULT IsReadyForDetach();
+
+
+private:
+ // This is a target pointer that uniquely identifies the runtime in the target.
+ // This lets ICD discriminate between multiple CLRs within a single process.
+ // On windows, this is the base-address of mscorwks.dll in the target.
+ // If this is 0, then we have V2 semantics where there was only 1 CLR in the target.
+ // In that case, we can lazily initialize it in code:CordbProcess::CopyManagedEventFromTarget.
+ // This is just used for backwards compat.
+ CORDB_ADDRESS m_clrInstanceId;
+
+ // List of things that get neutered on process exit and Continue respectively.
+ NeuterList m_ExitNeuterList;
+ NeuterList m_ContinueNeuterList;
+
+ // List of objects that hold resources into the left-side.
+ // This is currently for funceval, which cleans up resources in code:CordbEval::SendCleanup.
+ // @dbgtodo - , (func-eval feature crew): we can get rid of this
+ // list if we make func-eval not hold resources after it's complete.
+ LeftSideResourceCleanupList m_LeftSideResourceCleanupList;
+
+ // m_stopCount, m_synchronized, & m_syncCompleteReceived are key fields describing
+ // the processes' sync status.
+ DWORD m_stopCount;
+
+ // m_synchronized is the Debugger's view of SyncStatus. It will go high & low for each
+ // callback. Continue() will set this to false.
+ // This flag is true roughly from the time that we've dispatched a managed callback
+ // until the time that it's continued.
+ bool m_synchronized;
+
+ // m_syncCompleteReceived tells us if the runtime is _actually_ sychronized. It goes
+ // high once we get a SyncComplete, and it goes low once we actually send the continue.
+ // This is always set by the thread that receives the sync-complete. In interop, that's the w32et.
+ // Thus this is the most accurate indication of wether the Debuggee is _actually_ synchronized or not.
+ bool m_syncCompleteReceived;
+
+
+ // Back pointer to Shim process. This is used for hooks back into the shim.
+ // If this is Non-null, then we're emulating the V2 case. If this is NULL, then it's the real V3 pipeline.
+ RSExtSmartPtr<ShimProcess> m_pShim;
+
+ CordbSafeHashTable<CordbThread> m_userThreads;
+
+public:
+ ShimProcess* GetShim();
+
+ bool m_oddSync;
+
+
+ void BuildThreadEnum(CordbBase * pOwnerObj, NeuterList * pOwnerList, RSInitHolder<CordbHashTableEnum> * pHolder);
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ // List of unmanaged threads. This is only populated for interop-debugging.
+ CordbSafeHashTable<CordbUnmanagedThread> m_unmanagedThreads;
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ CordbSafeHashTable<CordbAppDomain> m_appDomains;
+
+ CordbAppDomain * m_sharedAppDomain;
+
+ // Since a stepper can begin in one appdomain, and complete in another,
+ // we put the hashtable here, rather than on specific appdomains.
+ CordbSafeHashTable<CordbStepper> m_steppers;
+
+ // Used to figure out if we have to refresh any reference objects
+ // on the left side. Gets incremented each time a continue is called, or
+ // global debugee state is modified in some other way.
+ UINT m_continueCounter;
+
+ // Used to track whether the DAC cache has been flushed.
+ // We use this information to determine whether CordbStackWalk instances need to
+ // be refreshed.
+ UINT m_flushCounter;
+
+ // The DCB is essentially a buffer area used to temporarily hold information read from the debugger
+ // control block residing on the LS helper thread. We make no assumptions about the validity of this
+ // information over time, so before using a value from it on the RS, we will always update this buffer
+ // with a call to UpdateRightSideDCB. This uses a ReadProcessMemory to get the current information from
+ // the LS DCB.
+ DebuggerIPCControlBlock * GetDCB() {return ((m_pEventChannel == NULL) ? NULL : m_pEventChannel->GetDCB());}
+
+
+ DebuggerIPCRuntimeOffsets m_runtimeOffsets;
+ HANDLE m_leftSideEventAvailable;
+ HANDLE m_leftSideEventRead;
+#if defined(FEATURE_INTEROP_DEBUGGING)
+ HANDLE m_leftSideUnmanagedWaitEvent;
+#endif // FEATURE_INTEROP_DEBUGGING
+
+
+ // This becomes true when the RS receives its first managed event.
+ // This goes false in shutdown cases.
+ // If this is true, we can assume:
+ // - the CLR is loaded.
+ // - the IPC block is opened and initialized.
+ // - DAC is initialized (see code:CordbProcess::IsDacInitialized)
+ //
+ // If this is false, we can assume:
+ // - the CLR may not be loaded into the target process.
+ // - We can't send IPC events to the LS (because we can't expect a response)
+ //
+ // Many APIs can check this bit and return CORDBG_E_NOTREADY if it's false.
+ bool m_initialized;
+
+#ifdef _DEBUG
+ void * m_pDBGLastIPCEventType;
+#endif
+
+ bool m_stopRequested;
+ HANDLE m_stopWaitEvent;
+ RSLock m_processMutex;
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ // The number of threads which are IsFirstChanceHijacked
+ DWORD m_cFirstChanceHijackedThreads;
+
+ CordbUnmanagedEvent *m_unmanagedEventQueue;
+ CordbUnmanagedEvent *m_lastQueuedUnmanagedEvent;
+ CordbUnmanagedEvent *m_lastQueuedOOBEvent;
+ CordbUnmanagedEvent *m_outOfBandEventQueue;
+
+ CordbUnmanagedEvent *m_lastDispatchedIBEvent;
+ bool m_dispatchingUnmanagedEvent;
+ bool m_dispatchingOOBEvent;
+ bool m_doRealContinueAfterOOBBlock;
+
+ enum
+ {
+ PS_WIN32_STOPPED = 0x0001,
+ PS_HIJACKS_IN_PLACE = 0x0002,
+ PS_SOME_THREADS_SUSPENDED = 0x0004,
+ PS_WIN32_ATTACHED = 0x0008,
+ PS_WIN32_OUTOFBAND_STOPPED = 0x0010,
+ };
+
+ unsigned int m_state;
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ // True if we're interop-debugging, else false.
+ bool IsInteropDebugging();
+
+ DWORD m_helperThreadId; // helper thread ID calculated from sniffing from UM thread-create events.
+
+ // Is the given thread id a helper thread (real or worker?)
+ bool IsHelperThreadWorked(DWORD tid);
+
+ //
+ // We cache the LS patch table on the RS.
+ //
+
+ // The array of entries. (The patchtable is a hash implemented as a single-array)
+ // This array includes empty entries.
+ // There is an auxillary bucket structure used to map hash codes to array indices.
+ // We traverse the array, and we recognize an empty slot
+ // if DebuggerControllerPatch::opcode == 0.
+ // If we haven't gotten the table, then m_pPatchTable is NULL
+ BYTE* m_pPatchTable;
+
+ // The number of entries (both used & unused) in m_pPatchTable.
+ UINT m_cPatch;
+
+ // so we know where to write the changes patchtable back to
+ // This has m_cPatch elements.
+ BYTE *m_rgData;
+
+ // Cached value of iNext entries such that:
+ // m_rgNextPatch[i] = ((DebuggerControllerPatch*)m_pPatchTable)[i]->iNext;
+ // where 0 <= i < m_cPatch
+ // This provides a linked list (via indices) to traverse the used entries of m_pPatchTable.
+ // This has m_cPatch elements.
+ ULONG *m_rgNextPatch;
+
+ // This has m_cPatch elements.
+ PRD_TYPE *m_rgUncommitedOpcode;
+
+ // CORDB_ADDRESS's are UINT_PTR's (64 bit under _WIN64, 32 bit otherwise)
+#if defined(DBG_TARGET_WIN64)
+#define MAX_ADDRESS (_UI64_MAX)
+#else
+#define MAX_ADDRESS (ULONG_MAX)
+#endif
+#define MIN_ADDRESS (0x0)
+ CORDB_ADDRESS m_minPatchAddr; //smallest patch in table
+ CORDB_ADDRESS m_maxPatchAddr;
+
+ // <TODO>@todo port : if slots of CHashTable change, so should these</TODO>
+#define DPT_TERMINATING_INDEX (UINT32_MAX)
+ // Index into m_pPatchTable of the first patch (first used entry).
+ ULONG m_iFirstPatch;
+
+ // Initializes the DAC
+ void InitDac();
+
+ // copy new data from LS DCB to RS buffer
+ void UpdateRightSideDCB();
+
+ // copy new data from RS DCB buffer to LS DCB
+ void UpdateLeftSideDCBField(void * rsFieldAddr, SIZE_T size);
+
+ // allocate and initialize the RS DCB buffer
+ void GetEventBlock(BOOL * pfBlockExists);
+
+ IEventChannel * GetEventChannel();
+
+ bool SupportsVersion(CorDebugInterfaceVersion featureVersion);
+
+ void StartEventDispatch(DebuggerIPCEventType event);
+ void FinishEventDispatch();
+ bool AreDispatchingEvent();
+
+ HANDLE GetHelperThreadHandle() { return m_hHelperThread; }
+
+ CordbAppDomain* GetDefaultAppDomain() { return m_pDefaultAppDomain; }
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ // Lookup if there's a native BP at the given address. Return NULL not found.
+ NativePatch * GetNativePatch(const void * pAddress);
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ bool IsBreakOpcodeAtAddress(const void * address);
+
+private:
+ //
+ // handle to helper thread. Used for managed debugging.
+ // Initialized only after we get the tid from the DCB.
+ HANDLE m_hHelperThread;
+
+ DebuggerIPCEventType m_dispatchedEvent; // what event are we currently dispatching?
+
+ RSLock m_StopGoLock;
+
+ // Each process has exactly one Default AppDomain
+ // @dbgtodo appdomain : We should try and simplify things by removing this.
+ // At the moment it's necessary for CordbProcess::UpdateThreadsForAdUnload.
+ CordbAppDomain* m_pDefaultAppDomain; // owned by m_appDomains
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ // Helpers
+ CordbUnmanagedThread * GetUnmanagedThreadFromEvent(const DEBUG_EVENT * pEvent);
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ // Ensure we have a CLR Instance ID to debug
+ HRESULT EnsureClrInstanceIdSet();
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ // // The full debug event is too large, so we just remember the important stuff.
+ struct MiniDebugEvent
+ {
+ BYTE code; // event code from the debug event
+ CordbUnmanagedThread * pUThread; // unmanaged thread this was on.
+ // @todo - we should have some misc data.
+ union
+ {
+ struct {
+ void * pAddress; // address of an exception
+ DWORD dwCode;
+ } ExceptionData;
+ struct {
+ void * pBaseAddress; // for module load & unload
+ } ModuleData;
+ } u;
+ };
+
+ // Group fields that are just used for debug support here.
+ // Some are included even in retail builds to help debug retail failures.
+ struct DebugSupport
+ {
+ // For debugging, we keep a rolling queue of the last N Win32 debug events.
+ MiniDebugEvent m_DebugEventQueue[DEBUG_EVENTQUEUE_SIZE];
+ int m_DebugEventQueueIdx;
+ int m_TotalNativeEvents;
+
+ // Breakdown of different types of native events
+ int m_TotalIB;
+ int m_TotalOOB;
+ int m_TotalCLR;
+ } m_DbgSupport;
+
+ CUnorderedArray<NativePatch, 10> m_NativePatchList;
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ //
+ // DAC
+ //
+
+ // Try to initalize DAC, may fail
+ BOOL TryInitializeDac();
+
+ // Expect DAC initialize to succeed.
+ void InitializeDac();
+
+
+ void CreateDacDbiInterface();
+
+ // Free DAC.
+ void FreeDac();
+
+
+ HModuleHolder m_hDacModule;
+ RSExtSmartPtr<ICorDebugDataTarget> m_pDACDataTarget;
+
+ // The mutable version of the data target, or null if read-only
+ RSExtSmartPtr<ICorDebugMutableDataTarget> m_pMutableDataTarget;
+
+ RSExtSmartPtr<ICorDebugMetaDataLocator> m_pMetaDataLocator;
+
+ IDacDbiInterface * m_pDacPrimitives;
+
+ IEventChannel * m_pEventChannel;
+
+ // If true, then we'll ASSERT if we detect the target is corrupt or inconsistent
+ // This switch is for diagnostics purposes only and should always be false in retail builds.
+ bool m_fAssertOnTargetInconsistency;
+
+ // When a successful attempt to read runtime offsets from LS occurs, this flag is set.
+ bool m_runtimeOffsetsInitialized;
+
+ // controls how metadata updated in the target is handled
+ WriteableMetadataUpdateMode m_writableMetadataUpdateMode;
+};
+
+// Some IMDArocess APIs are supported as interop-only.
+#define FAIL_IF_MANAGED_ONLY(pProcess) \
+{ CordbProcess * __Proc = pProcess; if (!__Proc->IsInteropDebugging()) return CORDBG_E_MUST_BE_INTEROP_DEBUGGING; }
+
+
+/* ------------------------------------------------------------------------- *
+ * Module class
+ * ------------------------------------------------------------------------- */
+
+class CordbModule : public CordbBase,
+ public ICorDebugModule,
+ public ICorDebugModule2,
+ public ICorDebugModule3
+{
+public:
+ CordbModule(CordbProcess * process,
+ VMPTR_Module vmModule,
+ VMPTR_DomainFile vmDomainFile);
+
+ virtual ~CordbModule();
+ virtual void Neuter();
+
+ using CordbBase::GetProcess;
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbModule"; }
+#endif
+
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugModule
+ //-----------------------------------------------------------
+
+ COM_METHOD GetProcess(ICorDebugProcess **ppProcess);
+ COM_METHOD GetBaseAddress(CORDB_ADDRESS *pAddress);
+ COM_METHOD GetAssembly(ICorDebugAssembly **ppAssembly);
+ COM_METHOD GetName(ULONG32 cchName, ULONG32 *pcchName, __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[]);
+ COM_METHOD EnableJITDebugging(BOOL bTrackJITInfo, BOOL bAllowJitOpts);
+ COM_METHOD EnableClassLoadCallbacks(BOOL bClassLoadCallbacks);
+
+ // Gets the latest version of a function given the methodDef token
+ COM_METHOD GetFunctionFromToken(mdMethodDef methodDef,
+ ICorDebugFunction **ppFunction);
+ COM_METHOD GetFunctionFromRVA(CORDB_ADDRESS rva, ICorDebugFunction **ppFunction);
+ COM_METHOD GetClassFromToken(mdTypeDef typeDef,
+ ICorDebugClass **ppClass);
+ COM_METHOD CreateBreakpoint(ICorDebugModuleBreakpoint **ppBreakpoint);
+
+ // Not implemented - legacy
+ COM_METHOD GetEditAndContinueSnapshot(
+ ICorDebugEditAndContinueSnapshot **ppEditAndContinueSnapshot);
+
+ COM_METHOD GetMetaDataInterface(REFIID riid, IUnknown **ppObj);
+ COM_METHOD GetToken(mdModule *pToken);
+ COM_METHOD IsDynamic(BOOL *pDynamic);
+ COM_METHOD GetGlobalVariableValue(mdFieldDef fieldDef,
+ ICorDebugValue **ppValue);
+ COM_METHOD GetSize(ULONG32 *pcBytes);
+ COM_METHOD IsInMemory(BOOL *pInMemory);
+
+ //-----------------------------------------------------------
+ // ICorDebugModule2
+ //-----------------------------------------------------------
+ COM_METHOD SetJMCStatus(
+ BOOL fIsUserCode,
+ ULONG32 cOthers,
+ mdToken others[]);
+
+ // Applies an EnC edit to the module
+ COM_METHOD ApplyChanges(
+ ULONG cbMetaData,
+ BYTE pbMetaData[],
+ ULONG cbIL,
+ BYTE pbIL[]);
+
+ // Resolve an assembly given an AssemblyRef token. Note that
+ // this will not trigger the loading of assembly. If assembly is not yet loaded,
+ // this will return an CORDBG_E_CANNOT_RESOLVE_ASSEMBLY error
+ COM_METHOD ResolveAssembly(mdToken tkAssemblyRef,
+ ICorDebugAssembly **ppAssembly);
+
+ // Sets EnC and optimization flags
+ COM_METHOD SetJITCompilerFlags(DWORD dwFlags);
+
+ // Gets EnC and optimization flags
+ COM_METHOD GetJITCompilerFlags(DWORD *pdwFlags);
+
+ //-----------------------------------------------------------
+ // ICorDebugModule3
+ //-----------------------------------------------------------
+ COM_METHOD CreateReaderForInMemorySymbols(REFIID riid,
+ void** ppObj);
+
+ //-----------------------------------------------------------
+ // Internal members
+ //-----------------------------------------------------------
+
+#ifdef _DEBUG
+ // Debug helper to ensure that module is no longer discoverable
+ void DbgAssertModuleDeleted();
+#endif // _DEBUG
+
+ // Internal help to get the "name" (filename or pretty name) of the module.
+ HRESULT GetNameWorker(ULONG32 cchName, ULONG32 *pcchName, __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[]);
+
+ // Marks that the module's metadata has become invalid and needs to be refetched.
+ void RefreshMetaData();
+
+ // Cache the current continue counter as the one that the LoadEvent is
+ // dispatched in.
+ void SetLoadEventContinueMarker();
+
+ // Return CORDBG_E_MUST_BE_IN_LOAD_MODULE if this module is not in its load callback.
+ HRESULT EnsureModuleIsInLoadCallback();
+
+ BOOL IsDynamic();
+
+ // Gets the latest version of the function for the methodDef, if any
+ CordbFunction * LookupFunctionLatestVersion(mdMethodDef methodToken);
+
+ // Gets the latest version of the function. Creates a new instance if none exists yet.
+ CordbFunction* LookupOrCreateFunctionLatestVersion(mdMethodDef funcMetaDataToken);
+
+ // Finds or creates a function for the first time (not for use on EnC if function doesn't exist yet)
+ CordbFunction * LookupOrCreateFunction(mdMethodDef token, SIZE_T enCVersion);
+
+ // Creates an CordbFunction instances for the first time (not for use on EnC)
+ CordbFunction * CreateFunction(mdMethodDef token, SIZE_T enCVersion);
+
+ // Creates a CordbFunction object to represent the specified EnC version
+ HRESULT UpdateFunction(mdMethodDef token,
+ SIZE_T newEnCVersion,
+ CordbFunction** ppFunction);
+
+ CordbClass* LookupClass(mdTypeDef classToken);
+ HRESULT LookupOrCreateClass(mdTypeDef classToken, CordbClass** ppClass);
+ HRESULT CreateClass(mdTypeDef classToken, CordbClass** ppClass);
+ HRESULT LookupClassByToken(mdTypeDef token, CordbClass **ppClass);
+ HRESULT ResolveTypeRef(mdTypeRef token, CordbClass **ppClass);
+ HRESULT ResolveTypeRefOrDef(mdToken token, CordbClass **ppClass);
+
+ // Sends the event to the left side to apply the changes to the debugee
+ HRESULT ApplyChangesInternal(
+ ULONG cbMetaData,
+ BYTE pbMetaData[],
+ ULONG cbIL,
+ BYTE pbIL[]);
+
+ // Pulls new metadata if needed in order to ensure the availability of
+ // the given token
+ void UpdateMetaDataCacheIfNeeded(mdToken token);
+
+ HRESULT InitPublicMetaDataFromFile(const WCHAR * pszFullPathName, DWORD dwOpenFlags, bool validateFileInfo);
+
+ // Creates a CordbNativeCode (if it's not already created) and adds it to the
+ // hash table of CordbNativeCodes belonging to the module.
+ CordbNativeCode * LookupOrCreateNativeCode(mdMethodDef methodToken,
+ VMPTR_MethodDesc methodDesc,
+ CORDB_ADDRESS startAddress);
+
+private:
+ // Set the metadata (both public and internal) for the module.
+ void InitMetaData(TargetBuffer buffer, BOOL useFileMappingOptimization);
+
+ // Checks if the given token is in the cached metadata
+ BOOL CheckIfTokenInMetaData(mdToken token);
+
+ // Update the public metadata given a buffer in the target.
+ void UpdatePublicMetaDataFromRemote(TargetBuffer bufferRemoteMetaData);
+
+ // Initialize just the public metadata by reading from an on-disk module
+ HRESULT InitPublicMetaDataFromFile();
+ // Initialize just the public metadata by reading new metadata from the buffer
+ void InitPublicMetaData(TargetBuffer buffer);
+
+ // Rebuild the internal metadata given the public one.
+ void UpdateInternalMetaData();
+
+ // Determines whether the on-disk metadata for this module is usable as the
+ // current metadata
+ BOOL IsFileMetaDataValid();
+
+ // Helper to copy metadata buffer from the Target to the host.
+ void CopyRemoteMetaData(TargetBuffer buffer, CoTaskMemHolder<VOID> * pLocalBuffer);
+
+
+ CordbAssembly * ResolveAssemblyInternal(mdToken tkAssemblyRef);
+
+ BOOL IsWinMD();
+
+ //-----------------------------------------------------------
+ // Convenience routines
+ //-----------------------------------------------------------
+
+public:
+ CordbAppDomain *GetAppDomain()
+ {
+ return m_pAppDomain;
+ }
+
+ CordbAssembly * GetCordbAssembly ();
+
+ // Get the module filename, or NULL if none. Throws on error.
+ const WCHAR * GetModulePath();
+
+ const WCHAR * GetNGenImagePath();
+
+ const VMPTR_DomainFile GetRuntimeDomainFile ()
+ {
+ return m_vmDomainFile;
+ }
+
+ const VMPTR_Module GetRuntimeModule()
+ {
+ return m_vmModule;
+ }
+
+ // Get symbol stream for in-memory modules.
+ IDacDbiInterface::SymbolFormat GetInMemorySymbolStream(IStream ** ppStream);
+
+ // accessor for PE file
+ VMPTR_PEFile GetPEFile();
+
+
+ IMetaDataImport * GetMetaDataImporter();
+
+ // accessor for Internal MetaData importer.
+ IMDInternalImport * GetInternalMD();
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+public:
+ CordbAssembly* m_pAssembly;
+ CordbAppDomain* m_pAppDomain;
+ CordbSafeHashTable<CordbClass> m_classes;
+
+ // A collection, indexed by methodDef, of the latest version of functions in this module
+ // The collection is filled lazily by LookupOrCreateFunction
+ CordbSafeHashTable<CordbFunction> m_functions;
+
+ // The real handle into the VM for a module. This is appdomain aware.
+ // This is the primary VM counterpart for the CordbModule.
+ VMPTR_DomainFile m_vmDomainFile;
+
+ VMPTR_Module m_vmModule;
+
+ DWORD m_EnCCount;
+
+private:
+
+ enum ILWinMDState
+ {
+ Uninitialized,
+ False,
+ True
+ };
+
+ // Base Address and size of this module in debuggee's process. Maybe null if unknown.
+ TargetBuffer m_PEBuffer;
+
+ BOOL m_fDynamic; // Dynamic modules can grow (like Reflection Emit)
+ BOOL m_fInMemory; // In memory modules don't have file-backing.
+ ILWinMDState m_isIlWinMD; // WinMD modules don't support all metadata interfaces
+
+ // Indicates that the module must serialize its metadata in process as part of metadata
+ // refresh. This is required for modules updated on the fly by the profiler
+ BOOL m_fForceMetaDataSerialize;
+
+ // Full path to module's image, if any. Empty if none, NULL if not yet set.
+ StringCopyHolder m_strModulePath;
+
+ // Full path to the ngen file. Empty if not ngenned, NULL if not yet set.
+ // This isn't exposed publicly, but we may use it internally for loading metadata.
+ StringCopyHolder m_strNGenImagePath;
+
+ // "Global" class for this module. Global functions + vars exist in this class.
+ RSSmartPtr<CordbClass> m_pClass;
+
+ // Handle to PEFile, useful for metadata lookups.
+ // this should always be non-null.
+ VMPTR_PEFile m_vmPEFile;
+
+
+ // Public metadata importer. This is lazily initialized and accessed from code:GetMetaDataImporter
+ // This is handed out to debugger clients via code:CordbModule::GetMetaDataInterface
+ // This is also tightly coupled to the internal metadata importer, m_pInternalMetaDataImport.
+ RSExtSmartPtr<IMetaDataImport> m_pIMImport;
+
+ // Internal metadata object. This is closely tied to the public metadata object (m_pIMImport).
+ // They share the same backing storage, but expose different interfaces to that storage.
+ // Debugger authors and tools use the public interfaces.
+ // DAC-ized operations in the VM require an IMDInternalImport.
+ // The public and internal must be updated together.
+ // This ultimately gets handed back to DAC via code:CordbProcess::LookupMetaData
+ RSExtSmartPtr<IMDInternalImport> m_pInternalMetaDataImport;
+
+ // Continue counter of when the module was loaded.
+ // See code:CordbModule::SetLoadEventContinueMarker for details
+ UINT m_nLoadEventContinueCounter;
+
+ // This is a table of all NativeCode objects in the module indexed
+ // by start address
+ // The collection is filled lazily by LookupOrCreateNativeCode
+ CordbSafeHashTable<CordbNativeCode> m_nativeCodeTable;
+};
+
+
+//-----------------------------------------------------------------------------
+// Cordb MDA notification
+//-----------------------------------------------------------------------------
+class CordbMDA : public CordbBase, public ICorDebugMDA
+{
+public:
+ CordbMDA(CordbProcess * pProc, DebuggerMDANotification * pData);
+ ~CordbMDA();
+
+ virtual void Neuter();
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbMDA"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRefEnforceExternal());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseReleaseEnforceExternal());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugMDA
+ //-----------------------------------------------------------
+
+ // Get the string for the type of the MDA. Never empty.
+ // This is a convenient performant alternative to getting the XML stream and extracting
+ // the type from that based off the schema.
+ COM_METHOD GetName(ULONG32 cchName, ULONG32 * pcchName, __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[]);
+
+ // Get a string description of the MDA. This may be empty (0-length).
+ COM_METHOD GetDescription(ULONG32 cchName, ULONG32 * pcchName, __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[]);
+
+ // Get the full associated XML for the MDA. This may be empty.
+ // This could be a potentially expensive operation if the xml stream is large.
+ // See the MDA documentation for the schema for this XML stream.
+ COM_METHOD GetXML(ULONG32 cchName, ULONG32 * pcchName, __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[]);
+
+ COM_METHOD GetFlags(CorDebugMDAFlags * pFlags);
+
+ // Thread that the MDA is fired on. We use the os tid instead of an ICDThread in case an MDA is fired on a
+ // native thread (or a managed thread that hasn't yet entered managed code and so we don't have a ICDThread
+ // object for it yet)
+ COM_METHOD GetOSThreadId(DWORD * pOsTid);
+
+private:
+ NewArrayHolder<WCHAR> m_szName;
+ NewArrayHolder<WCHAR> m_szDescription;
+ NewArrayHolder<WCHAR> m_szXml;
+
+ DWORD m_dwOSTID;
+ CorDebugMDAFlags m_flags;
+};
+
+
+
+struct CordbHangingField
+{
+ FREEHASHENTRY entry;
+ FieldData data;
+};
+
+// A hashtable for storing EnC hanging field information
+// FieldData.m_fldMetadataToken is the key
+class CordbHangingFieldTable : public CHashTableAndData<CNewDataNoThrow>
+{
+ private:
+
+ BOOL Cmp(SIZE_T k1, const HASHENTRY *pc2)
+ {
+ LIMITED_METHOD_CONTRACT;
+ return (ULONG)(UINT_PTR)(k1) !=
+ (reinterpret_cast<const CordbHangingField *>(pc2))->data.m_fldMetadataToken;
+ }
+
+ ULONG HASH(mdFieldDef fldToken)
+ {
+ LIMITED_METHOD_CONTRACT;
+ return fldToken;
+ }
+
+ SIZE_T KEY(mdFieldDef fldToken)
+ {
+ return (SIZE_T)fldToken;
+ }
+
+ public:
+
+#ifndef DACCESS_COMPILE
+
+ CordbHangingFieldTable() : CHashTableAndData<CNewDataNoThrow>(11)
+ {
+ NewInit(11, sizeof(CordbHangingField), 11);
+ }
+
+ FieldData * AddFieldInfo(FieldData * pInfo)
+ {
+ _ASSERTE(pInfo != NULL);
+
+ CordbHangingField *pEntry = (CordbHangingField *)Add(HASH(pInfo->m_fldMetadataToken));
+ pEntry->data = *pInfo; // copy everything over
+
+ // Return a pointer to the data
+ return &(pEntry->data);
+ }
+
+ void RemoveFieldInfo(mdFieldDef fldToken)
+ {
+ CordbHangingField *entry = (CordbHangingField*)Find(HASH(fldToken), KEY(fldToken));
+ _ASSERTE(entry != NULL);
+ Delete(HASH(fldToken), (HASHENTRY*)entry);
+ }
+
+#endif // #ifndef DACCESS_COMPILE
+
+ FieldData * GetFieldInfo(mdFieldDef fldToken)
+ {
+ CordbHangingField * entry = (CordbHangingField *)Find(HASH(fldToken), KEY(fldToken));
+ return (entry!=NULL?&(entry->data):NULL);
+ }
+};
+
+
+/* ------------------------------------------------------------------------- *
+ * Instantiation.
+ *
+ * This struct stores a set of type parameters. It is used in
+ * the heap-allocated data structures CordbType and CordbNativeCode.
+ *
+ * CordbType::m_inst. Stores the class type parameters if any,
+ * or the solitary array type parameter, or the solitary parameter
+ * to a byref type.
+ *
+ * CordbJITILFrame::m_genericArgs. Stores exact generic parameters for the generic method frame if available
+ * Need not be identicial if code is shared between generic instantiations.
+ * May be inexact if real instantiation has been optimized away off
+ * the frame (nb this gets reported by the left side)
+ *
+ * This is conceptually an array of Type-parameters, with the split (m_cClassTyPars) between
+ * where the Type's type-parameters end and the Method's type-parameters begin.
+ * ------------------------------------------------------------------------- */
+class Instantiation
+{
+public:
+ // Empty ctor
+ Instantiation()
+ : m_cInst(0), m_ppInst(NULL), m_cClassTyPars (0)
+ { }
+
+ // Instantiation for Type. 0 Method type-parameters.
+ Instantiation(unsigned int _cClassInst, CordbType **_ppClassInst)
+ : m_cInst(_cClassInst), m_ppInst(_ppClassInst), m_cClassTyPars(_cClassInst)
+ {LIMITED_METHOD_CONTRACT; }
+
+ // Instantiation for Type + Function.
+ Instantiation(unsigned int _cInst, CordbType **_ppInst, unsigned int numClassTyPars)
+ : m_cInst(_cInst), m_ppInst(_ppInst),
+ m_cClassTyPars (numClassTyPars)
+ { }
+
+ // Copy constructor.
+ Instantiation(const Instantiation &inst)
+ : m_cInst(inst.m_cInst), m_ppInst(inst.m_ppInst), m_cClassTyPars (inst.m_cClassTyPars)
+ { }
+
+ // Number of elements in array pointed to by m_ppInst
+ unsigned int m_cInst;
+
+ // Pointer to array of CordbType objects. Length of array is m_cInst.
+ // Array is Class Type parameters followed by Function's Type parameters.
+ // Eg, Instantiation for Class<Foo, Goo>::Func<Bar> would be {Foo, Goo, Bar}.
+ // m_cInst = 3, m_cClassTyPars = 2.
+ // In contrast, Instantiation for Class::Func<Foo, Goo, Bar> would have same
+ // array, but m_cClassTyPars = 0.
+ CordbType **m_ppInst;
+
+ // Track the split between Type vs. Method type-params.
+ unsigned int m_cClassTyPars;
+};
+
+//------------------------------------------------------------------------
+// CordbType: replaces the use of signatures.
+//
+// Left Side & Right Side
+// ---------------------------
+// CordbTypes may come from either the Right Side (via being built up from
+// ICorDebug), or from the Left-Side (being handed back from LS operations
+// like getting the type from an Object the LS handed back).
+// The RightSide CordbType corresponds to a Left-Side TypeHandle.
+// CordbTypes are communicated across the LS/RS boundary by marshalling
+// to BasicTypeData + ExpandedTypeData IPC events.
+//
+//
+// Invariants on CordbType
+// ---------------------------
+//
+// The m_elementType is NEVER ELEMENT_TYPE_VAR or ELEMENT_TYPE_MVAR or ELEMENT_TYPE_GENERICINST
+// CordbTypes are always _ground_ types (fully instantiated generics or non-generic types). If
+// they represent an instantiated type like List<int> then m_inst will be non-empty.
+//
+//
+// !!!! The m_elementType is NEVER ELEMENT_TYPE_VALUETYPE !!!!
+// !!!! To find out if it is a value type call CordbType::IsValueType() !!!!
+//
+// Where CordbTypes are stored
+// ---------------------------
+//
+// Because we could have a significant number of different instantiations for a given templated type,
+// we need an efficient way to store and retrieve the CordbType instances for these instantiations.
+// For this reason, we use a tree-like scheme to hash-cons types. To implement this we use the following
+// scheme:
+// - CordbTypes are created for "partially instantiated" types,
+// e.g. CordbTypes exist for "Dict" and "Dict<int>" even if the real
+// type being manipulated by the user is "Dict<int,string>"
+// - Subordinate types (E.g. Dict<int,string> is subordinate to Dict<int>,
+// which is itself subordinate to the type for Dict) get stored
+// in the m_spinetypes hash table of the parent type.
+// - In m_spinetypes the pointers of the CordbTypes themselves
+// are used for the unique ids for entries in the table.
+// Note that CordbType instances that are created for "partially instantiated" types
+// are never used for any purpose other than efficient hashing. Specifically, the debugger will
+// never have reason to expose a partially instantiated type outside of the hashing algorithm.
+//
+// CordbTypes have object identity: if 2 CordbTypes represent the same type (in the same AppDomain),
+// then they will be the same CordbType instance.
+//
+// Thus the representation for "Dict<class String,class Foo, class Foo* >" goes as follows:
+// 1. Assume the type Foo is represented by CordbClass *5678x
+// 1b. Assume the hashtable m_sharedtypes in the AppDomain maps E_T_STRING to the CordbType *0ABCx
+// Assume m_type in class Foo (i.e. CordbClass *5678x) is the CordbType *0DEFx
+// Assume m_type in class Foo maps E_T_PTR to the CordbType *0647x
+// 2. The hash table m_spinetypes in "Dict" maps "0ABCx" to a new CordbType
+// representing Dict<String> (a single type application)
+// 3. The hash table m_spinetypes in this new CordbType maps "0DEFx" to a
+// new CordbType representing Dict<class String,class Foo>
+// 3. The hash table m_spinetypes in this new CordbType maps "0647" to a
+// new CordbType representing Dict<class String,class Foo, class Foo*>
+//
+// This lets us reuse the existing hash table scheme to build
+// up instantiated types of arbitrary size.
+//
+// Array types are similar, excpet that they start with a head type
+// for the "type constructor", e.g. "_ []" is a type constructor with rank 1
+// and m_elementType = ELEMENT_TYPE_SZARRAY. These head constructors are
+// stored in the m_sharedtypes table in the appdomain. The actual instantiations
+// of the array types are then subordinate types to the array constructor type.
+//
+// Other types are simpler, and have unique objects stored in the m_sharedtypes
+// table in the appdomain. This table is indexed by CORDBTYPE_ID in RsType.cpp
+//
+//
+// Memory Management of CordbTypes
+// ---------------------------
+// All CordbTypes are ultimately stored off the CordbAppDomain object.
+// The most common place is in the AppDomain's neuter-list.
+//
+// See definition of ICorDebugType for further invariants on types.
+//
+
+class CordbType : public CordbBase, public ICorDebugType
+{
+public:
+ CordbType(CordbAppDomain *appdomain, CorElementType ty, unsigned int rank);
+ CordbType(CordbAppDomain *appdomain, CorElementType ty, CordbClass *c);
+ CordbType(CordbType *tycon, CordbType *tyarg);
+ virtual ~CordbType();
+ virtual void Neuter();
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbType"; }
+#endif
+
+ // If you want to force the init to happen even if we think the class
+ // is up to date, set fForceInit to TRUE
+ HRESULT Init(BOOL fForceInit);
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef();
+ ULONG STDMETHODCALLTYPE Release();
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugType
+ //-----------------------------------------------------------
+
+ COM_METHOD GetType(CorElementType *ty);
+ COM_METHOD GetClass(ICorDebugClass **ppClass);
+ COM_METHOD EnumerateTypeParameters(ICorDebugTypeEnum **ppTyParEnum);
+ COM_METHOD GetFirstTypeParameter(ICorDebugType **ppType);
+ COM_METHOD GetBase(ICorDebugType **ppType);
+ COM_METHOD GetStaticFieldValue(mdFieldDef fieldDef,
+ ICorDebugFrame * pFrame,
+ ICorDebugValue ** ppValue);
+ COM_METHOD GetRank(ULONG32 *pnRank);
+
+ //-----------------------------------------------------------
+ // Non-COM members
+ //-----------------------------------------------------------
+
+ //-----------------------------------------------------------
+ // Basic constructor operations for the algebra of types.
+ // These all create unique objects within an AppDomain.
+ //-----------------------------------------------------------
+
+ // This one is used to create simple types, e.g. int32, int64, typedbyref etc.
+ static HRESULT MkType(CordbAppDomain * pAppDomain,
+ CorElementType elementType,
+ CordbType ** ppResultType);
+
+ // This one is used to create array, pointer and byref types
+ static HRESULT MkType(CordbAppDomain * pAppDomain,
+ CorElementType elementType,
+ ULONG rank,
+ CordbType * pType,
+ CordbType ** ppResultType);
+
+ // This one is used to create function pointer types. et must be ELEMENT_TYPE_FNPTR
+ static HRESULT MkType(CordbAppDomain * pAppDomain,
+ CorElementType elementType,
+ const Instantiation * pInst,
+ CordbType ** ppResultType);
+
+ // This one is used to class and value class types, e.g. "class MyClass" or "class ArrayList<int>"
+ static HRESULT MkType(CordbAppDomain * pAppDomain,
+ CorElementType elementType,
+ CordbClass * pClass,
+ const Instantiation * pInst,
+ CordbType ** ppResultType);
+
+ // Some derived constructors... Use this one if the type is definitely not
+ // a paramterized type, e.g. to implement functions on the API where types cannot
+ // be parameterized.
+ static HRESULT MkUnparameterizedType(CordbAppDomain *appdomain, CorElementType et, CordbClass *cl, CordbType **ppType);
+
+ //-----------------------------------------------------------
+ // Basic destructor operations over the algebra
+ //-----------------------------------------------------------
+ void DestUnaryType(CordbType **pRes) ;
+ void DestConstructedType(CordbClass **pClass, Instantiation *pInst);
+ void DestNaryType(Instantiation *pInst);
+
+ CorElementType GetElementType() { return m_elementType; }
+ VMPTR_DomainFile GetDomainFile();
+ VMPTR_Module GetModule();
+
+ // If this is a ptr type, get the CordbType that it points to.
+ // Eg, for CordbType("Int*"), returns CordbType("Int").
+ // If not a ptr type, returns null.
+ // Since it's all internal, no reference counting.
+ // This is effectively a specialized version of DestUnaryType.
+ CordbType * GetPointerElementType();
+
+
+ // Create a type from metadata
+ static HRESULT SigToType(CordbModule * pModule, SigParser * pSigParser, const Instantiation * pInst, CordbType ** ppResultType);
+
+ // Create a type from from the data received from the left-side
+ static HRESULT TypeDataToType(CordbAppDomain *appdomain, DebuggerIPCE_ExpandedTypeData *data, CordbType **pRes);
+ static HRESULT TypeDataToType(CordbAppDomain *appdomain, DebuggerIPCE_BasicTypeData *data, CordbType **pRes);
+ static HRESULT InstantiateFromTypeHandle(CordbAppDomain * appdomain,
+ VMPTR_TypeHandle vmTypeHandle,
+ CorElementType et,
+ CordbClass * tycon,
+ CordbType ** pRes);
+
+ // Prepare data to send back to left-side during Init() and FuncEval. Fail if the the exact
+ // type data is requested but was not fetched correctly during Init()
+ HRESULT TypeToBasicTypeData(DebuggerIPCE_BasicTypeData *data);
+ void TypeToExpandedTypeData(DebuggerIPCE_ExpandedTypeData *data);
+ void TypeToTypeArgData(DebuggerIPCE_TypeArgData *data);
+
+ void CountTypeDataNodes(unsigned int *count);
+ static void CountTypeDataNodesForInstantiation(unsigned int genericArgsCount, ICorDebugType *genericArgs[], unsigned int *count);
+ static void GatherTypeData(CordbType *type, DebuggerIPCE_TypeArgData **curr_tyargData);
+ static void GatherTypeDataForInstantiation(unsigned int genericArgsCount, ICorDebugType *genericArgs[], DebuggerIPCE_TypeArgData **curr_tyargData);
+
+ HRESULT GetParentType(CordbClass * baseClass, CordbType ** ppRes);
+
+ // These are available after Init() has been called....
+ HRESULT GetUnboxedObjectSize(ULONG32 *res);
+ HRESULT GetFieldInfo(mdFieldDef fldToken, FieldData ** ppFieldData);
+
+ CordbAppDomain *GetAppDomain() { return m_appdomain; }
+
+ bool IsValueType();
+
+ // Is this type a GC-root.
+ bool IsGCRoot();
+
+#ifdef FEATURE_64BIT_ALIGNMENT
+ // checks if the type requires 8-byte alignment.
+ // this is not exposed via ICorDebug at present.
+ HRESULT CordbType::RequiresAlign8(BOOL* isRequired);
+#endif
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+public:
+ // Internal representation of the element type. This may not map exactly to the public element type.
+ // Specifically, m_elementType is NEVER:
+ // ELEMENT_TYPE_VAR, ELEMENT_TYPE_MVAR, ELEMENT_TYPE_GENERICINST,
+ // or ELEMENT_TYPE_VALUETYPE.
+ // To find out if this CordbType corresponds to a value type (instead of Reference type) call CordbType::IsValueType()
+ CorElementType m_elementType;
+
+ // The appdomain that this type lives in. Types (and their type-parameters) are all contained in a single appdomain.
+ // (alhtough the types may be from different modules).
+ // This is valid for all CordbType objects, regardless of m_elementType;
+ CordbAppDomain * m_appdomain;
+
+ // The matching class for this type.
+ // Initially only set for E_T_CLASS, lazily computed for E_T_STRING and E_T_OBJECT if needed
+ CordbClass * m_pClass;
+
+ ULONG m_rank; // Only set for E_T_ARRAY etc.
+
+ // Array of Type Parameters for this Type.
+ Instantiation m_inst;
+
+ // A unique mapping from CordbType objects that are type parameters to CordbType objects. Each mapping
+ // represents the use of the containing type as type constructor. e.g. If the containing type
+ // is CordbType(CordbClass "List") then the table here will map parameters such as (CordbType(CordbClass "String")) to
+ // the constructed type CordbType(CordbClass "List", <CordbType(CordbClass "String")>)
+ // @dbgtodo synchronization - this is currently protected by the Stop-Go lock. Transition to process-lock.
+ CordbSafeHashTable<CordbType> m_spinetypes;
+
+ // Valid after Init(), only for E_T_ARRAY etc.and E_T_CLASS when m_pClass->m_classInfo.m_genericArgsCount > 0.
+ // m_typeHandleExact is the precise Runtime type handle for this type.
+ VMPTR_TypeHandle m_typeHandleExact;
+
+ // Valid after Init(), only for E_T_CLASS, and when m_pClass->m_classInfo.m_genericArgsCount > 0.
+ // May not be set correctly if m_fieldInfoNeedsInit.
+ SIZE_T m_objectSize;
+
+ // DON'T KEEP POINTERS TO ELEMENTS OF m_pFields AROUND!!
+ // This may be deleted if the class gets EnC'd.
+ //
+ // Valid after Init(), only for E_T_CLASS, and when m_pClass->m_classInfo.m_genericArgsCount > 0
+ // All fields will be valid if we have m_typeHandleExact.
+ //
+ // Only some fields will be valid if we have called Init() but still have m_fieldInfoNeedsInit.
+ DacDbiArrayList<FieldData> m_fieldList;
+
+ HRESULT ReturnedByValue();
+
+private:
+ static HRESULT MkTyAppType(CordbAppDomain * pAddDomain,
+ CordbType * pType,
+ const Instantiation * pInst,
+ CordbType ** pResultType);
+
+ BOOL m_fieldInfoNeedsInit;
+
+private:
+ HRESULT InitInstantiationTypeHandle(BOOL fForceInit);
+ HRESULT InitInstantiationFieldInfo(BOOL fForceInit);
+ HRESULT InitStringOrObjectClass(BOOL fForceInit);
+};
+
+/* ------------------------------------------------------------------------- *
+ * Class class
+ * ------------------------------------------------------------------------- */
+
+class CordbClass : public CordbBase, public ICorDebugClass, public ICorDebugClass2
+{
+public:
+ CordbClass(CordbModule* m, mdTypeDef token);
+ virtual ~CordbClass();
+ virtual void Neuter();
+
+ using CordbBase::GetProcess;
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbClass"; }
+#endif
+
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugClass
+ //-----------------------------------------------------------
+
+ COM_METHOD GetStaticFieldValue(mdFieldDef fieldDef,
+ ICorDebugFrame *pFrame,
+ ICorDebugValue **ppValue);
+ COM_METHOD GetModule(ICorDebugModule **pModule);
+ COM_METHOD GetToken(mdTypeDef *pTypeDef);
+ //-----------------------------------------------------------
+ // ICorDebugClass2
+ //-----------------------------------------------------------
+ COM_METHOD GetParameterizedType(CorElementType elementType,
+ ULONG32 cTypeArgs,
+ ICorDebugType * rgpTypeArgs[],
+ ICorDebugType ** ppType);
+
+ COM_METHOD SetJMCStatus(BOOL fIsUserCode);
+
+ //-----------------------------------------------------------
+ // Convenience routines and Accessors
+ //-----------------------------------------------------------
+
+ // Helper to get containing module
+ CordbModule * GetModule()
+ {
+ return m_pModule;
+ }
+
+ // get the metadata token for this class
+ mdTypeDef GetToken() { return m_token; }
+
+ // Helper to get the AppDomain the class lives in.
+ CordbAppDomain * GetAppDomain()
+ {
+ return m_pModule->GetAppDomain();
+ }
+
+ // This only very roughly resembles the CLASS_LOAD_LEVEL concept in the VM.
+ // because DBI's needs are far more coarse grained. Also DBI
+ // may contain more, equal, or less information than what is available in
+ // native runtime data structures. We can have less when we are being lazy
+ // and haven't yet fetched it. We can have more if use an independent data
+ // source such as the metadata blob and then compute some type data ourselves
+ typedef enum
+ {
+ // At this state the constructor has been run.
+ // m_module and m_token will be valid
+ Constructed,
+
+ // At this state we have additionally certain to have initialized
+ // m_fIsValueClass and m_fHasTypeParams
+ // Calls to IsValueClass() and HasTypeParams() are valid
+ // This stage should be achievable as long as a runtime type handle
+ // exists, even if it is unrestored
+ BasicInfo,
+
+ //Everything is loaded, or at least anything created lazily from this
+ //point on should be certain to succeed (ie m_type)
+ FullInfo
+ }
+ ClassLoadLevel;
+
+ ClassLoadLevel GetLoadLevel()
+ {
+ return m_loadLevel;
+ }
+
+ // determine if a load event has been sent for this class
+ BOOL LoadEventSent() { return m_fLoadEventSent; }
+
+ // set value of m_fLoadEventSent
+ void SetLoadEventSent(BOOL fEventSent) { m_fLoadEventSent = fEventSent; }
+
+ // determine if the class has been unloaded
+ BOOL HasBeenUnloaded() { return m_fHasBeenUnloaded; }
+
+ // set value of m_fHasBeenUnloaded
+ void SetHasBeenUnloaded(BOOL fUnloaded) { m_fHasBeenUnloaded = (fUnloaded == TRUE); }
+
+ // determine if this is a value class
+ BOOL IsValueClassNoInit() { return m_fIsValueClass; }
+
+ // set value of m_fIsValueClass
+ void SetIsValueClass(BOOL fIsValueClass) { m_fIsValueClass = (fIsValueClass == TRUE); }
+
+ // determine if the value class is known
+ BOOL IsValueClassKnown() { return m_fIsValueClassKnown; }
+
+ // set value of m_fIsValueClassKnown
+ void SetIsValueClassKnown(BOOL fIsValueClassKnown) { m_fIsValueClassKnown = (fIsValueClassKnown == TRUE); }
+
+ // get value of m_type
+ CordbType * GetType() { return m_type; }
+
+ void SetType(CordbType * pType) { m_type.Assign(pType); }
+
+ // get the type parameter count
+ bool HasTypeParams() { _ASSERTE(m_loadLevel >= BasicInfo); return m_fHasTypeParams; }
+
+ // get the object size
+ SIZE_T ObjectSize() { return m_classInfo.m_objectSize; }
+
+ // get the metadata token for this class
+ mdTypeDef MDToken() { return m_token; }
+
+ // get the number of fields
+ unsigned int FieldCount() { return m_classInfo.m_fieldList.Count(); }
+
+ //-----------------------------------------------------------
+ // Functionality shared for CordbType and CordbClass
+ //-----------------------------------------------------------
+
+ static HRESULT SearchFieldInfo(CordbModule * module,
+ DacDbiArrayList<FieldData> * pFieldList,
+ mdTypeDef classToken,
+ mdFieldDef fldToken,
+ FieldData ** ppFieldData);
+
+ static HRESULT GetStaticFieldValue2(CordbModule * pModule,
+ FieldData * pFieldData,
+ BOOL fEnCHangingField,
+ const Instantiation * pInst,
+ ICorDebugFrame * pFrame,
+ ICorDebugValue ** ppValue);
+
+ //-----------------------------------------------------------
+ // Non-COM methods
+ //-----------------------------------------------------------
+
+ // Get information about a field that was added by EnC
+ HRESULT GetEnCHangingField(mdFieldDef fldToken,
+ FieldData ** ppFieldData,
+ CordbObjectValue * pObject);
+
+private:
+ // Get information via the DAC about a field added with Edit and Continue.
+ FieldData * GetEnCFieldFromDac(BOOL fStatic,
+ CordbObjectValue * pObject,
+ mdFieldDef fieldToken);
+
+ // Initialize an instance of EnCHangingFieldInfo.
+ void InitEnCFieldInfo(EnCHangingFieldInfo * pEncField,
+ BOOL fStatic,
+ CordbObjectValue * pObject,
+ mdFieldDef fieldToken,
+ mdTypeDef classToken);
+
+
+public:
+
+ // set or clear the custom notifications flag to control whether we ignore custom debugger notifications
+ void SetCustomNotifications(BOOL fEnable) { m_fCustomNotificationsEnabled = fEnable; }
+ BOOL CustomNotificationsEnabled () { return m_fCustomNotificationsEnabled; }
+
+ HRESULT GetFieldInfo(mdFieldDef fldToken, FieldData ** ppFieldData);
+
+ // If you want to force the init to happen even if we think the class
+ // is up to date, set fForceInit to TRUE
+ void Init(ClassLoadLevel desiredLoadLevel = FullInfo);
+
+ // determine if any fields for a type are unallocated statics
+ BOOL GotUnallocatedStatic(DacDbiArrayList<FieldData> * pFieldList);
+
+ bool IsValueClass();
+ HRESULT GetThisType(const Instantiation * pInst, CordbType ** ppResultType);
+ static HRESULT PostProcessUnavailableHRESULT(HRESULT hr,
+ IMetaDataImport *pImport,
+ mdFieldDef fieldDef);
+ mdTypeDef GetTypeDef() { return (mdTypeDef)m_id; }
+
+#ifdef EnC_SUPPORTED
+ // when we get an added field or method, mark the class to force re-init when we access it
+ void MakeOld()
+ {
+ m_loadLevel = Constructed;
+ }
+#endif // EnC_SUPPORTED
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+private:
+ // contains information about the type: size and
+ // field information
+ ClassInfo m_classInfo;
+
+ ClassLoadLevel m_loadLevel;
+
+ // @dbgtodo managed pipeline - can we get rid of both of these fields?
+ BOOL m_fLoadEventSent;
+ bool m_fHasBeenUnloaded;
+
+ // [m_type] is the type object for when this class is used
+ // as a type. If the class is a value class then it can represent
+ // either the boxed or unboxed type - it depends on the context where the
+ // type is used. For example on a CordbBoxValue it represents the type of the
+ // boxed VC, on a CordbVCObjectValue it represents the type of the unboxed VC.
+ //
+ // The type field starts of NULL as there
+ // is no need to create the type object until it is needed.
+ RSSmartPtr<CordbType> m_type;
+
+ // Module that this Class lives in. Valid at the Constructed type level.
+ CordbModule * m_pModule;
+
+ // the token for the type constructor - m_id cannot be used for constructed types
+ // valid at the Constructed type level
+ mdTypeDef m_token;
+
+ // Whether the class is a VC or not is discovered either by
+ // seeing the class used in a signature after ELEMENT_TYPE_VALUETYPE
+ // or ELEMENT_TYPE_CLASS or by going and asking the EE.
+ bool m_fIsValueClassKnown;
+
+ // Whether the class is a VC or not
+ bool m_fIsValueClass;
+
+ // Whether the class has generic type parameters in its definition
+ bool m_fHasTypeParams;
+
+ // Timestamp from GetProcess()->m_continueCounter, which we can use to tell if
+ // the process has been continued since we last took a snapshot.
+ UINT m_continueCounterLastSync;
+
+ // if we add static fields with EnC after this class is loaded (in the debuggee),
+ // their value will be hung off the FieldDesc. Hold information about such fields here.
+ CordbHangingFieldTable m_hangingFieldsStatic;
+
+ // this indicates whether we should send custom debugger notifications
+ BOOL m_fCustomNotificationsEnabled;
+
+};
+
+
+/* ------------------------------------------------------------------------- *
+ * TypeParameter enumerator class
+ * ------------------------------------------------------------------------- */
+
+class CordbTypeEnum : public CordbBase, public ICorDebugTypeEnum
+{
+public:
+ // Factory method: Create a new instance of this class. Returns NULL on out-of-memory.
+ // On success, returns a new initialized instance of CordbTypeEnum with ref-count 0 (just like a ctor).
+ // the life expectancy of the enumerator varies by caller so we require them to specify the applicable neuter list here.
+ static CordbTypeEnum* Build(CordbAppDomain * pAppDomain, NeuterList * pNeuterList, unsigned int cTypars, CordbType **ppTypars);
+ static CordbTypeEnum* Build(CordbAppDomain * pAppDomain, NeuterList * pNeuterList, unsigned int cTypars, RSSmartPtr<CordbType>*ppTypars);
+
+ virtual ~CordbTypeEnum() ;
+
+ virtual void Neuter();
+
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbTypeEnum"; }
+#endif
+
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugEnum
+ //-----------------------------------------------------------
+
+ COM_METHOD Skip(ULONG celt);
+ COM_METHOD Reset();
+ COM_METHOD Clone(ICorDebugEnum **ppEnum);
+ COM_METHOD GetCount(ULONG *pcelt);
+
+ //-----------------------------------------------------------
+ // ICorDebugTypeEnum
+ //-----------------------------------------------------------
+
+ COM_METHOD Next(ULONG celt, ICorDebugType *Types[], ULONG *pceltFetched);
+
+private:
+ // Private constructor, only partially initializes the object.
+ // Clients should use the 'Build' factory method to create an instance of this class.
+ CordbTypeEnum( CordbAppDomain * pAppDomain, NeuterList * pNeuterList );
+ template<class T> static CordbTypeEnum* BuildImpl(CordbAppDomain * pAppDomain, NeuterList * pNeuterList, unsigned int cTypars, T* ppTypars );
+
+ // Owning object.
+ CordbAppDomain * m_pAppDomain;
+
+ // Array of Types. We own the array, and share refs to the types.
+ // @todo- since these are guaranteed to be kept alive as long as we're not neutered,
+ // we don't need to keep refs to them.
+ RSSmartPtr<CordbType> * m_ppTypars;
+ UINT m_iCurrent;
+ UINT m_iMax;
+};
+
+/* ------------------------------------------------------------------------- *
+ * Code enumerator class
+ * ------------------------------------------------------------------------- */
+
+class CordbCodeEnum : public CordbBase, public ICorDebugCodeEnum
+{
+public:
+ CordbCodeEnum(unsigned int cCode, RSSmartPtr<CordbCode> * ppCode);
+ virtual ~CordbCodeEnum() ;
+
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbCodeEnum"; }
+#endif
+
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugEnum
+ //-----------------------------------------------------------
+
+ COM_METHOD Skip(ULONG celt);
+ COM_METHOD Reset();
+ COM_METHOD Clone(ICorDebugEnum **ppEnum);
+ COM_METHOD GetCount(ULONG *pcelt);
+
+ //-----------------------------------------------------------
+ // ICorDebugCodeEnum
+ //-----------------------------------------------------------
+
+ COM_METHOD Next(ULONG celt, ICorDebugCode *Codes[], ULONG *pceltFetched);
+
+private:
+ // Ptr to an array of CordbCode*
+ // We own the array.
+ RSSmartPtr<CordbCode> * m_ppCodes;
+ UINT m_iCurrent;
+ UINT m_iMax;
+};
+
+
+
+
+
+typedef CUnorderedArray<CordbCode*,11> UnorderedCodeArray;
+//<TODO>@todo port: different SIZE_T size/</TODO>
+const int DMI_VERSION_INVALID = 0;
+const int DMI_VERSION_MOST_RECENTLY_JITTED = 1;
+const int DMI_VERSION_MOST_RECENTLY_EnCED = 2;
+
+
+/* ------------------------------------------------------------------------- *
+ * Function class
+ *
+ * @review . The CordbFunction class now keeps a multiple MethodDescInfo
+ * structures in a hash table indexed by tokens provided by the left-side.
+ * In 99.9% of cases this hash table will only contain one entry - we only
+ * use a hashtable to cover the case where we have multiple JITtings of
+ * a single version of a function, in particular multiple JITtings of generic
+ * code under different instantiations. This will increase space usage.
+ * The way around it is to store one CordbNativeCode in-line in the CordbFunction
+ * class, or at least store one such pointer so no hash table will normally
+ * be needed. This is similar to other cases, e.g. the hash table in
+ * CordbClass used to indicate different CordbTypes made from that class -
+ * again in the normal case these tables will only contain one element.
+ *
+ * However, for the moment I've focused on correctness and we can minimize
+ * this space usage in due course.
+ * ------------------------------------------------------------------------- */
+
+const BOOL bNativeCode = FALSE;
+const BOOL bILCode = TRUE;
+
+//
+// Each E&C version gets its own function object. So the IL that a function
+// is associated w/ does not change.
+// B/C of generics, a single IL function may get jitted multiple times and
+// be associated w/ multiple native code blobs (CordbNativeCode).
+//
+class CordbFunction : public CordbBase, public ICorDebugFunction, public ICorDebugFunction2, public ICorDebugFunction3
+{
+public:
+ //-----------------------------------------------------------
+ // Create from scope and member objects.
+ //-----------------------------------------------------------
+ CordbFunction(CordbModule * m,
+ mdMethodDef token,
+ SIZE_T enCVersion);
+ virtual ~CordbFunction();
+ virtual void Neuter();
+
+
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbFunction"; }
+#endif
+
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugFunction
+ //-----------------------------------------------------------
+ COM_METHOD GetModule(ICorDebugModule **pModule);
+ COM_METHOD GetClass(ICorDebugClass **ppClass);
+ COM_METHOD GetToken(mdMethodDef *pMemberDef);
+ COM_METHOD GetILCode(ICorDebugCode **ppCode);
+ COM_METHOD GetNativeCode(ICorDebugCode **ppCode);
+ COM_METHOD CreateBreakpoint(ICorDebugFunctionBreakpoint **ppBreakpoint);
+ COM_METHOD GetLocalVarSigToken(mdSignature *pmdSig);
+ COM_METHOD GetCurrentVersionNumber(ULONG32 *pnCurrentVersion);
+
+ //-----------------------------------------------------------
+ // ICorDebugFunction2
+ //-----------------------------------------------------------
+ COM_METHOD SetJMCStatus(BOOL fIsUserCode);
+ COM_METHOD GetJMCStatus(BOOL * pfIsUserCode);
+ COM_METHOD EnumerateNativeCode(ICorDebugCodeEnum **ppCodeEnum) { return E_NOTIMPL; }
+ COM_METHOD GetVersionNumber(ULONG32 *pnCurrentVersion);
+
+ //-----------------------------------------------------------
+ // ICorDebugFunction3
+ //-----------------------------------------------------------
+ COM_METHOD GetActiveReJitRequestILCode(ICorDebugILCode **ppReJitedILCode);
+
+ //-----------------------------------------------------------
+ // Internal members
+ //-----------------------------------------------------------
+protected:
+ // Returns the function's ILCode and SigToken
+ HRESULT GetILCodeAndSigToken();
+
+ // Get the metadata token for the class to which a function belongs.
+ mdTypeDef InitParentClassOfFunctionHelper(mdToken funcMetaDataToken);
+
+ // Get information about one of the native code blobs for this function
+ HRESULT InitNativeCodeInfo();
+
+public:
+
+ // Get the class to which a given function belongs
+ HRESULT InitParentClassOfFunction();
+
+ void NotifyCodeCreated(CordbNativeCode* nativeCode);
+
+ HRESULT GetSig(SigParser *pMethodSigParser,
+ ULONG *pFunctionArgCount,
+ BOOL *pFunctionIsStatic);
+
+ HRESULT GetArgumentType(DWORD dwIndex, const Instantiation * pInst, CordbType ** ppResultType);
+
+
+ //-----------------------------------------------------------
+ // Internal routines
+ //-----------------------------------------------------------
+
+ // Get the existing IL code object
+ HRESULT GetILCode(CordbILCode ** ppCode);
+
+ // Finds or creates an ILCode for a given rejit request
+ HRESULT LookupOrCreateReJitILCode(VMPTR_SharedReJitInfo vmSharedRejitInfo,
+ CordbReJitILCode** ppILCode);
+
+
+#ifdef EnC_SUPPORTED
+ void MakeOld();
+#endif
+
+ //-----------------------------------------------------------
+ // Accessors
+ //-----------------------------------------------------------
+
+ // Get the AppDomain that this function lives in.
+ CordbAppDomain * GetAppDomain()
+ {
+ return (m_pModule->GetAppDomain());
+ }
+
+ // Get the CordbModule that this Function lives in.
+ CordbModule * GetModule()
+ {
+ return m_pModule;
+ }
+
+ // Get the CordbClass this of which this function is a member
+ CordbClass * GetClass()
+ {
+ return m_pClass;
+ }
+
+ // Get the IL code blob corresponding to this function
+ CordbILCode * GetILCode()
+ {
+ return m_pILCode;
+ }
+
+ // Get metadata token for this function
+ mdMethodDef GetMetadataToken()
+ {
+ return m_MDToken;
+ }
+
+ SIZE_T GetEnCVersionNumber()
+ {
+ return m_dwEnCVersionNumber;
+ }
+
+ CordbFunction * GetPrevVersion()
+ {
+ return m_pPrevVersion;
+ }
+
+ void SetPrevVersion(CordbFunction * prevVersion)
+ {
+ m_pPrevVersion.Assign(prevVersion);
+ }
+
+ typedef enum {kNativeOnly, kHasIL, kUnknownImpl} ImplementationKind;
+ ImplementationKind IsNativeImpl()
+ {
+ return (m_fIsNativeImpl);
+ }
+
+ // determine whether we have a native-only implementation
+ void InitNativeImpl();
+
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+private:
+ // The module that this Function is contained in. It maintains a strong reference to this object
+ // and will neuter this object.
+ CordbModule * m_pModule;
+
+ // The Class that this function is contained in.
+ CordbClass * m_pClass;
+
+ // We only have 1 IL blob associated with a given Function object.
+ RSSmartPtr<CordbILCode> m_pILCode;
+
+
+ // Generics allow a single IL method to be instantiated to multiple native
+ // code blobs. So CordbFunction : CordbNativeCode is 1:n.
+ // This pointer is to arbitrary one of those n code bodies.
+ // Someday we may need to get access to all N of them but not today
+ RSSmartPtr<CordbNativeCode> m_nativeCode;
+
+ // Metadata Token for the IL function. Scoped to m_module.
+ mdMethodDef m_MDToken;
+
+ // EnC version number of this instance
+ SIZE_T m_dwEnCVersionNumber;
+
+ // link to previous version of this function
+ RSSmartPtr<CordbFunction> m_pPrevVersion;
+
+ // Is the function implemented natively in the runtime?? (eg, it has no IL, may be an Ecall/fcall)
+ ImplementationKind m_fIsNativeImpl;
+
+ // True if method signature (argument) values are cached.
+ BOOL m_fCachedMethodValuesValid;
+
+ // Cached SigParser for this Function's argument signature.
+ // Only valid if m_fCachedMethodValuesValid is set.
+ SigParser m_methodSigParserCached;
+
+ // Cached Count of arguments in the argument signature.
+ // Only valid if m_fCachedMethodValuesValid is set.
+ ULONG m_argCountCached;
+
+ // Cached boolean if method is static or instance (part of the argument signature).
+ // Only valid if m_fCachedMethodValuesValid is set.
+ BOOL m_fIsStaticCached;
+
+ // A collection, indexed by VMPTR_SharedReJitInfo, of IL code for rejit requests
+ // The collection is filled lazily by LookupOrCreateReJitILCode
+ CordbSafeHashTable<CordbReJitILCode> m_reJitILCodes;
+};
+
+//-----------------------------------------------------------------------------
+// class CordbCode
+// Represents either IL or Native code blobs associated with a function.
+//
+// See the comments at the ICorDebugCode definition for invariants about Code objects.
+//
+//-----------------------------------------------------------------------------
+class CordbCode : public CordbBase, public ICorDebugCode
+{
+protected:
+ CordbCode(CordbFunction * pFunction, UINT_PTR id, SIZE_T encVersion, BOOL fIsIL);
+
+public:
+ virtual ~CordbCode();
+ virtual void Neuter();
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() = 0;
+#endif
+
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugCode
+ //-----------------------------------------------------------
+
+ COM_METHOD IsIL(BOOL * pbIL);
+ COM_METHOD GetFunction(ICorDebugFunction ** ppFunction);
+ COM_METHOD GetAddress(CORDB_ADDRESS * pStart) = 0;
+ COM_METHOD GetSize(ULONG32 * pcBytes);
+ COM_METHOD CreateBreakpoint(ULONG32 offset,
+ ICorDebugFunctionBreakpoint ** ppBreakpoint);
+ COM_METHOD GetCode(ULONG32 startOffset, ULONG32 endOffset,
+ ULONG32 cBufferAlloc,
+ BYTE buffer[],
+ ULONG32 * pcBufferSize);
+ COM_METHOD GetVersionNumber( ULONG32 * nVersion);
+ COM_METHOD GetILToNativeMapping(ULONG32 cMap,
+ ULONG32 * pcMap,
+ COR_DEBUG_IL_TO_NATIVE_MAP map[]) = 0;
+ COM_METHOD GetEnCRemapSequencePoints(ULONG32 cMap,
+ ULONG32 * pcMap,
+ ULONG32 offsets[]);
+
+ //-----------------------------------------------------------
+ // Accessors and convenience routines
+ //-----------------------------------------------------------
+
+ // get the CordbFunction instance for this code object
+ CordbFunction * GetFunction();
+
+ // get the actual code bytes for this function
+ virtual HRESULT ReadCodeBytes() = 0;
+
+ // get the size in bytes of this function
+ virtual ULONG32 GetSize() = 0;
+
+
+ // get the metadata token for this code object
+ mdMethodDef GetMetadataToken()
+ {
+ _ASSERTE(m_pFunction != NULL);
+ return (m_pFunction->GetMetadataToken());
+ }
+
+ // get the module this code object belongs to
+ CordbModule * GetModule()
+ {
+ _ASSERTE(m_pFunction != NULL);
+ return (m_pFunction->GetModule());
+ }
+
+ // get the function signature for this code blob or throw on failure
+ void GetSig(SigParser *pMethodSigParser,
+ ULONG *pFunctionArgCount,
+ BOOL *pFunctionIsStatic)
+ {
+ _ASSERTE(m_pFunction != NULL);
+ IfFailThrow(m_pFunction->GetSig(pMethodSigParser, pFunctionArgCount, pFunctionIsStatic));
+ }
+
+ // get the class to which this code blob belongs
+ CordbClass * GetClass()
+ {
+ _ASSERTE(m_pFunction != NULL);
+ return (m_pFunction->GetClass());
+ }
+
+ // Quick helper to get the AppDomain that this code object lives in.
+ CordbAppDomain *GetAppDomain()
+ {
+ _ASSERTE(m_pFunction != NULL);
+ return (m_pFunction->GetAppDomain());
+ }
+
+ // Get the EnC version of this blob
+ SIZE_T GetVersion() { return m_nVersion; };
+
+ // Return true if this is an IL code blob. Else return false.
+ BOOL IsIL() { return m_fIsIL; }
+
+ // convert to CordbNativeCode as long as m_fIsIl is false.
+ CordbNativeCode * AsNativeCode()
+ {
+ _ASSERTE(m_fIsIL == FALSE);
+ return reinterpret_cast<CordbNativeCode *>(this);
+ }
+
+ // convert to CordbILCode as long as m_fIsIl is true.
+ CordbILCode * AsILCode()
+ {
+ _ASSERTE(m_fIsIL == TRUE);
+ return reinterpret_cast<CordbILCode *>(this);
+ }
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+private:
+ UINT m_fIsIL : 1;
+
+ // EnC version number.
+ SIZE_T m_nVersion;
+
+protected:
+ // Our local copy of the code. It will be GetSize() bytes long.
+ BYTE * m_rgbCode; // will be NULL if we can't fit it into memory
+
+ UINT m_continueCounterLastSync;
+
+ // Owning Function associated with this code.
+ CordbFunction * m_pFunction;
+}; //class CordbCode
+
+
+
+
+
+/* ------------------------------------------------------------------------- *
+* CordbILCode class
+* This class represents an IL code blob for a particular EnC version. Thus it is
+* 1:1 with a given instantiation of CordbFunction. Provided functionality includes
+* methods to get the starting address and size of an IL code blob and to read
+* the actual bytes of IL into a buffer.
+ * ------------------------------------------------------------------------- */
+
+class CordbILCode : public CordbCode
+{
+public:
+ // Initialize a new CordbILCode instance
+ CordbILCode(CordbFunction *pFunction, TargetBuffer codeRegionInfo, SIZE_T nVersion, mdSignature localVarSigToken, UINT_PTR id = 0);
+
+#ifdef _DEBUG
+ const char * DbgGetName() { return "CordbILCode"; };
+#endif // _DEBUG
+
+ COM_METHOD GetAddress(CORDB_ADDRESS * pStart);
+ COM_METHOD GetILToNativeMapping(ULONG32 cMap,
+ ULONG32 * pcMap,
+ COR_DEBUG_IL_TO_NATIVE_MAP map[]);
+ // Quick helper for internal access to: GetAddress(CORDB_ADDRESS *pStart);
+ CORDB_ADDRESS GetAddress() { return m_codeRegionInfo.pAddress; }
+
+ // get total size of the IL code
+ ULONG32 GetSize() { return m_codeRegionInfo.cbSize; }
+
+#ifdef EnC_SUPPORTED
+ void MakeOld();
+#endif // EnC_SUPPORTED
+
+ HRESULT GetLocalVarSig(SigParser *pLocalsSigParser, ULONG *pLocalVarCount);
+ HRESULT GetLocalVariableType(DWORD dwIndex, const Instantiation * pInst, CordbType ** ppResultType);
+ mdSignature GetLocalVarSigToken();
+
+private:
+ // Read the actual bytes of IL code into the data member m_rgbCode.
+ // Helper routine for GetCode
+ HRESULT ReadCodeBytes();
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+private:
+#ifdef EnC_SUPPORTED
+ UINT m_fIsOld : 1; // marks this instance as an old EnC version
+ bool m_encBreakpointsApplied;
+#endif
+
+ // derived types can init this
+protected:
+ TargetBuffer m_codeRegionInfo; // stores the starting address and size of the
+ // IL code blob
+
+ // Metadata token for local's signature.
+ mdSignature m_localVarSigToken;
+
+}; // class CordbILCode
+
+/* ------------------------------------------------------------------------- *
+* CordbReJitILCode class
+* This class represents an IL code blob for a particular EnC version and
+* rejitID. Thus it is 1:N with a given instantiation of CordbFunction.
+* ------------------------------------------------------------------------- */
+
+class CordbReJitILCode : public CordbILCode, public ICorDebugILCode, public ICorDebugILCode2
+{
+public:
+ // Initialize a new CordbILCode instance
+ CordbReJitILCode(CordbFunction *pFunction, SIZE_T encVersion, VMPTR_SharedReJitInfo vmSharedReJitInfo);
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+ ULONG STDMETHODCALLTYPE AddRef();
+ ULONG STDMETHODCALLTYPE Release();
+ COM_METHOD QueryInterface(REFIID riid, void** ppInterface);
+
+
+ //-----------------------------------------------------------
+ // ICorDebugILCode
+ //-----------------------------------------------------------
+ COM_METHOD GetEHClauses(ULONG32 cClauses, ULONG32 * pcClauses, CorDebugEHClause clauses[]);
+
+
+ //-----------------------------------------------------------
+ // ICorDebugILCode2
+ //-----------------------------------------------------------
+ COM_METHOD GetLocalVarSigToken(mdSignature *pmdSig);
+ COM_METHOD GetInstrumentedILMap(ULONG32 cMap, ULONG32 *pcMap, COR_IL_MAP map[]);
+
+private:
+ HRESULT Init(DacSharedReJitInfo* pSharedReJitInfo);
+
+private:
+ ULONG32 m_cClauses;
+ NewArrayHolder<CorDebugEHClause> m_pClauses;
+ ULONG32 m_cbLocalIL;
+ NewArrayHolder<BYTE> m_pLocalIL;
+ ULONG32 m_cILMap;
+ NewArrayHolder<COR_IL_MAP> m_pILMap;
+};
+
+/* ------------------------------------------------------------------------- *
+ * CordbNativeCode class. These correspond to MethodDesc's on the left-side.
+ * There may or may not be a DebuggerJitInfo associated with the MethodDesc.
+ * At most one CordbNativeCode is created for each native code compilation of each method
+ * that is seen by the right-side. Note that if each method were JITted only once
+ * then this information could go in CordbFunction, however generics allow
+ * methods to be compiled more than once.
+ *
+ * The purpose of this class is to encapsulate details about a blob of jitted/ngen'ed
+ * code, including an optional set of mappings from IL to offsets in the native Code.
+ * ------------------------------------------------------------------------- */
+
+class CordbNativeCode : public CordbCode, public ICorDebugCode2, public ICorDebugCode3
+{
+public:
+ CordbNativeCode(CordbFunction * pFunction,
+ const NativeCodeFunctionData * pJitData,
+ BOOL fIsInstantiatedGeneric);
+#ifdef _DEBUG
+ const char * DbgGetName() { return "CordbNativeCode"; };
+#endif // _DEBUG
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugCode
+ //-----------------------------------------------------------
+ COM_METHOD GetAddress(CORDB_ADDRESS * pStart);
+ COM_METHOD GetILToNativeMapping(ULONG32 cMap,
+ ULONG32 * pcMap,
+ COR_DEBUG_IL_TO_NATIVE_MAP map[]);
+ //-----------------------------------------------------------
+ // ICorDebugCode2
+ //-----------------------------------------------------------
+ COM_METHOD GetCodeChunks(ULONG32 cbufSize, ULONG32 * pcnumChunks, CodeChunkInfo chunks[]);
+
+ COM_METHOD GetCompilerFlags(DWORD * pdwFlags);
+
+ //-----------------------------------------------------------
+ // ICorDebugCode3
+ //-----------------------------------------------------------
+ COM_METHOD GetReturnValueLiveOffset(ULONG32 ILoffset, ULONG32 bufferSize, ULONG32 *pFetched, ULONG32 *pOffsets);
+
+
+ //-----------------------------------------------------------
+ // Internal members
+ //-----------------------------------------------------------
+
+ HRESULT ILVariableToNative(DWORD dwIndex,
+ SIZE_T ip,
+ const ICorDebugInfo::NativeVarInfo ** ppNativeInfo);
+ void LoadNativeInfo();
+
+ //-----------------------------------------------------------
+ // Accessors and convenience routines
+ //-----------------------------------------------------------
+
+ // get the argument type for a generic
+ void GetArgumentType(DWORD dwIndex,
+ const Instantiation * pInst,
+ CordbType ** ppResultType)
+ {
+ CordbFunction * pFunction = GetFunction();
+ _ASSERTE(pFunction != NULL);
+ IfFailThrow(pFunction->GetArgumentType(dwIndex, pInst, ppResultType));
+ }
+
+ // Quick helper for internall access to: GetAddress(CORDB_ADDRESS *pStart);
+ CORDB_ADDRESS GetAddress() { return m_rgCodeRegions[kHot].pAddress; };
+
+ VMPTR_MethodDesc GetVMNativeCodeMethodDescToken() { return m_vmNativeCodeMethodDescToken; };
+
+ // Worker function for GetReturnValueLiveOffset.
+ HRESULT GetReturnValueLiveOffsetImpl(Instantiation *currentInstantiation, ULONG32 ILoffset, ULONG32 bufferSize, ULONG32 *pFetched, ULONG32 *pOffsets);
+
+ // get total size of the code including both hot and cold regions
+ ULONG32 GetSize();
+
+ // get the size of the cold region(s) only
+ ULONG32 GetColdSize();
+
+ // Return true if the Code is split into hot + cold regions.
+ bool HasColdRegion() { return m_rgCodeRegions[kCold].pAddress != NULL; }
+
+ // Get the number of fixed arguments for this function (the "this"
+ // but not varargs)
+ unsigned int GetFixedArgCount()
+ {
+ return m_nativeVarData.GetFixedArgCount();
+ }
+
+ // Get the number of all arguments for this function
+ // ("this" pointer, fixed args and varargs)
+ ULONG32 GetAllArgsCount()
+ {
+ return m_nativeVarData.GetAllArgsCount();
+ }
+
+ void SetAllArgsCount(ULONG32 count)
+ {
+ m_nativeVarData.SetAllArgsCount(count);
+ }
+
+ // Determine whether this is an instantiation of a generic function
+ BOOL IsInstantiatedGeneric()
+ {
+ return m_fIsInstantiatedGeneric != 0;
+ }
+
+ // Determine whether we have initialized the native variable and
+ // sequence point offsets
+ BOOL IsNativeCodeValid ()
+ {
+ return ((m_nativeVarData.IsInitialized() != 0) &&
+ (m_sequencePoints.IsInitialized() != 0));
+ }
+
+ SequencePoints * GetSequencePoints()
+ {
+ return &m_sequencePoints;
+ }
+
+
+ // Given an ILOffset in the current function, return the class token and function token of the IL call target at that
+ // location. Also fill "methodSig" with the method's signature and "genericSig" with the method's generic signature.
+ HRESULT GetCallSignature(ULONG32 ILOffset, mdToken *pClass, mdToken *pMDFunction, SigParser &methodSig, SigParser &genericSig);
+
+ // Moves a method signature from the start of the signature to the location of the return value (passing out the
+ // number of generic parameters in the method).
+ static HRESULT SkipToReturn(SigParser &parser, ULONG *genArgCount = 0);
+
+private:
+ // Read the actual bytes of native code into the data member m_rgbCode.
+ // Helper routine for GetCode
+ HRESULT ReadCodeBytes();
+
+ // Returns a failure HRESULT if we cannot handle the return value of the given
+ // methodref, methoddef, or methodspec token, otherwise S_OK. Does NOT return S_FALSE;
+ HRESULT EnsureReturnValueAllowed(Instantiation *currentInstantiation, mdToken targetClass, SigParser &parser, SigParser &methodGenerics);
+ HRESULT EnsureReturnValueAllowedWorker(Instantiation *currentInstantiation, mdToken targetClass, SigParser &parser, SigParser &methodGenerics, ULONG genCount);
+
+ // Grabs the appropriate signature parser for a methodref, methoddef, methodspec.
+ HRESULT GetSigParserFromFunction(mdToken mdFunction, mdToken *pClass, SigParser &methodSig, SigParser &genericSig);
+
+ int GetCallInstructionLength(BYTE *buffer, ULONG32 len);
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+private:
+ // offset of the beginning of the last sequence point in the sequence point map
+ SIZE_T m_lastIL;
+
+ // start address(es) and size(s) of hot and cold regions
+ TargetBuffer m_rgCodeRegions[MAX_REGIONS];
+
+ // LS data structure--method desc for this instantiation.
+ VMPTR_MethodDesc m_vmNativeCodeMethodDescToken;
+
+ bool m_fCodeAvailable; // true iff the code has been jitted but not pitched
+
+ bool m_fIsInstantiatedGeneric; // true iff this is an instantiated generic
+
+ // information in the following two classes tracks native offsets and is initialized on demand.
+
+ // location and ID information for local variables. See code:NativeVarData for details.
+ NativeVarData m_nativeVarData;
+
+ // mapping between IL and native code sequence points.
+ SequencePoints m_sequencePoints;
+
+}; //class CordbNativeCode
+
+//---------------------------------------------------------------------------------------
+//
+// GetActiveInternalFramesData is used to enumerate internal frames on a specific thread.
+// It is used in conjunction with code:CordbThread::GetActiveInternalFramesCallback.
+// We store each internal frame in ppInternalFrames as we enumerate them.
+//
+
+struct GetActiveInternalFramesData
+{
+public:
+ // the thread we are walking
+ CordbThread * pThis;
+
+ // an array to store the internal frames
+ RSPtrArray<CordbInternalFrame> pInternalFrames;
+
+ // next element in the array to be filled
+ ULONG32 uIndex;
+};
+
+
+/* ------------------------------------------------------------------------- *
+ * Thread classes
+ * ------------------------------------------------------------------------- */
+
+class CordbThread : public CordbBase, public ICorDebugThread,
+ public ICorDebugThread2,
+ public ICorDebugThread3,
+ public ICorDebugThread4
+{
+public:
+ CordbThread(CordbProcess * pProcess, VMPTR_Thread);
+
+ virtual ~CordbThread();
+ virtual void Neuter();
+
+ using CordbBase::GetProcess;
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbThread"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ // there's an external add ref from within RS in CordbEnumFilter
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugThread
+ //-----------------------------------------------------------
+
+ COM_METHOD GetProcess(ICorDebugProcess **ppProcess);
+ COM_METHOD GetID(DWORD *pdwThreadId);
+ COM_METHOD GetHandle(HANDLE * phThreadHandle);
+ COM_METHOD GetAppDomain(ICorDebugAppDomain **ppAppDomain);
+ COM_METHOD SetDebugState(CorDebugThreadState state);
+ COM_METHOD GetDebugState(CorDebugThreadState *pState);
+ COM_METHOD GetUserState(CorDebugUserState *pState);
+ COM_METHOD GetCurrentException(ICorDebugValue ** ppExceptionObject);
+ COM_METHOD ClearCurrentException();
+ COM_METHOD CreateStepper(ICorDebugStepper **ppStepper);
+ COM_METHOD EnumerateChains(ICorDebugChainEnum **ppChains);
+ COM_METHOD GetActiveChain(ICorDebugChain **ppChain);
+ COM_METHOD GetActiveFrame(ICorDebugFrame **ppFrame);
+ COM_METHOD GetRegisterSet(ICorDebugRegisterSet **ppRegisters);
+ COM_METHOD CreateEval(ICorDebugEval **ppEval);
+ COM_METHOD GetObject(ICorDebugValue ** ppObject);
+
+ // ICorDebugThread2
+ COM_METHOD GetConnectionID(CONNID * pConnectionID);
+ COM_METHOD GetTaskID(TASKID * pTaskID);
+ COM_METHOD GetVolatileOSThreadID(DWORD * pdwTID);
+ COM_METHOD GetActiveFunctions(ULONG32 cFunctions, ULONG32 * pcFunctions, COR_ACTIVE_FUNCTION pFunctions[]);
+ // Intercept the current exception at the specified frame. pFrame must be a valid ICDFrame, possibly from
+ // a previous stackwalk.
+ COM_METHOD InterceptCurrentException(ICorDebugFrame * pFrame);
+
+
+
+ // ICorDebugThread3
+ COM_METHOD CreateStackWalk(ICorDebugStackWalk **ppStackWalk);
+
+ COM_METHOD GetActiveInternalFrames(ULONG32 cInternalFrames,
+ ULONG32 * pcInternalFrames,
+ ICorDebugInternalFrame2 * ppInternalFrames[]);
+
+ // ICorDebugThread4
+ COM_METHOD HasUnhandledException();
+
+ COM_METHOD GetBlockingObjects(ICorDebugBlockingObjectEnum **ppBlockingObjectEnum);
+
+ // Gets the current CustomNotification object from the thread or NULL if no such object exists
+ COM_METHOD GetCurrentCustomDebuggerNotification(ICorDebugValue ** ppNotificationObject);
+ //-----------------------------------------------------------
+ // Internal members
+ //-----------------------------------------------------------
+
+ // callback used to enumerate the internal frames on a thread
+ static void GetActiveInternalFramesCallback(const DebuggerIPCE_STRData * pFrameData,
+ void * pUserData);
+
+ CorDebugUserState GetUserState();
+
+ // Given a FramePointer, find the matching CordbFrame.
+ HRESULT FindFrame(ICorDebugFrame ** ppFrame, FramePointer fp);
+
+ // Get the task ID for this thread.
+ TASKID GetTaskID();
+
+ void RefreshStack();
+ void CleanupStack();
+ void MarkStackFramesDirty();
+
+#if !defined(DBG_TARGET_ARM) // @ARMTODO
+
+#if defined(DBG_TARGET_X86)
+ // Converts the values in the floating point register area of the context to real number values.
+ void Get32bitFPRegisters(CONTEXT * pContext);
+
+#elif defined(DBG_TARGET_AMD64) || defined(DBG_TARGET_ARM64)
+ // Converts the values in the floating point register area of the context to real number values.
+ void Get64bitFPRegisters(FPRegister64 * rgContextFPRegisters, int start, int nRegisters);
+#endif // DBG_TARGET_X86
+
+ // Initializes the float state members of this instance of CordbThread. This function gets the context and
+ // converts the floating point values from their context representation to real number values.
+ void LoadFloatState();
+
+#endif //!DBG_TARGET_ARM @ARMTODO
+
+ HRESULT SetIP( bool fCanSetIPOnly,
+ CordbNativeCode * pNativeCode,
+ SIZE_T offset,
+ bool fIsIL );
+
+ // Tells the LS to remap to the latest version of the function
+ HRESULT SetRemapIP(SIZE_T offset);
+
+ // Ask the left-side for the current (up-to-date) AppDomain of this thread's IP.
+ // This should be preferred over using the cached value from GetAppDomain.
+ HRESULT GetCurrentAppDomain(CordbAppDomain ** ppAppDomain);
+
+ //-----------------------------------------------------------
+ // Convenience routines
+ //-----------------------------------------------------------
+
+ // The last domain from which a debug event for this thread was sent.
+ // This usually (but not always) the domain the thread is currently executing in.
+ // Since this is a cache, it may sometimes be out-of-date. I believe all current
+ // usage of this is OK (we pass AppDomains around a lot without really using them),
+ // but no new code should rely on this value.
+ // TODO: eliminate this and the m_pAppDomain field entirely
+ CordbAppDomain *GetAppDomain()
+ {
+ return (m_pAppDomain);
+ }
+
+ DWORD GetVolatileOSThreadID();
+
+ //////////////////////////////////////////////////////////////////////////
+ //
+ // Get Context
+ //
+ // <TODO>TODO: Since Thread will share the memory with RegisterSets, how
+ // do we know that the RegisterSets have relinquished all pointers
+ // to the m_pContext structure?</TODO>
+ //
+ // Returns: NULL if the thread's CONTEXT structure couldn't be obtained
+ // A pointer to the CONTEXT otherwise.
+ //
+ //
+ //////////////////////////////////////////////////////////////////////////
+ HRESULT GetManagedContext( DT_CONTEXT ** ppContext );
+ HRESULT SetManagedContext( DT_CONTEXT * pContext );
+
+ // API to retrieve the thread handle from the LS.
+ void InternalGetHandle(HANDLE * phThread);
+ void RefreshHandle(HANDLE * phThread);
+
+ // NeuterList that's executed when this Thread's stack is refreshed.
+ // Chain + Frame + some Value enums can be held on this.
+ NeuterList * GetRefreshStackNeuterList()
+ {
+ return &m_RefreshStackNeuterList;
+ }
+
+ DWORD GetUniqueId();
+
+
+ // Hijack a thread at a 2nd-chance exception so that it can execute the CLR's UEF
+ void HijackForUnhandledException();
+
+ // check whether the specified frame lives on the stack of the current thread
+ bool OwnsFrame(CordbFrame *pFrame);
+
+ // Specify that there's an outstanding exception on this thread.
+ void SetExInfo(VMPTR_OBJECTHANDLE vmExcepObjHandle);
+
+ VMPTR_OBJECTHANDLE GetThreadExceptionRawObjectHandle() { return m_vmExcepObjHandle; }
+ bool HasException() { return m_fException; }
+
+ void SetUnhandledNativeException(const EXCEPTION_RECORD * pExceptionRecord);
+ bool HasUnhandledNativeException();
+
+#ifdef _DEBUG
+ // Helper to assert that this thread no longer appears in dac-dbi enumerations
+ void DbgAssertThreadDeleted();
+
+ // Callback for DbgAssertThreadDeleted
+ static void DbgAssertThreadDeletedCallback(VMPTR_Thread vmThread, void * pUserData);
+#endif // _DEBUG
+
+ // Determine if the thread's current exception is managed or unmanaged.
+ BOOL IsThreadExceptionManaged();
+
+ // This is a private hook for the shim to create a CordbRegisterSet for a ShimChain.
+ void CreateCordbRegisterSet(DT_CONTEXT * pContext,
+ BOOL fActive,
+ CorDebugChainReason reason,
+ ICorDebugRegisterSet ** ppRegSet);
+
+ // This is a private hook for the shim to convert an ICDFrame into an ICDInternalFrame for a dynamic
+ // method. Refer to the function header for more information.
+ BOOL ConvertFrameForILMethodWithoutMetadata(ICorDebugFrame * pFrame,
+ ICorDebugInternalFrame2 ** ppInternalFrame2);
+
+ // Gets/sets m_fCreationEventQueued
+ bool CreateEventWasQueued();
+ void SetCreateEventQueued();
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+public:
+ // RS Cache for LS context.
+ // NULL if we haven't allocated memory for a Right side context
+ DT_CONTEXT * m_pContext;
+
+ // Set to the CONTEXT pointer in the LS if this LS thread is
+ // stopped in managed code. This may be either stopped for execution control
+ // (breakpoint / single-step exception) or hijacked w/ a redirected frame because
+ // another thread synced the LS.
+ // This context is used by the RS to set enregistered vars.
+ VMPTR_CONTEXT m_vmLeftSideContext;
+
+ // indicates whether m_pContext is up-to-date
+ bool m_fContextFresh;
+
+ // last domain we've seen this thread.
+ // If the appdomain exits, it will clear out this value.
+ CordbAppDomain *m_pAppDomain;
+
+ // Handle to VM's Thread* object. This is the primary key for a CordbThread object
+ // @dbgtodo ICDThread - merge with m_id;
+ VMPTR_Thread m_vmThreadToken;
+
+ // Unique ID for this thread. See code:CordbThread::GetID for semantics of this field.
+ DWORD m_dwUniqueID;
+
+ CorDebugThreadState m_debugState; // Note that this is for resume
+ // purposes, NOT the current state of
+ // the thread.
+
+ // The frames are all protected under the Stop-Go lock.
+ // This field indicates whether the stack is valid (i.e. no update is necessary).
+ bool m_fFramesFresh;
+
+ // This is a cache of V3 ICDFrames. The cache is only used by two functions:
+ // - code:CordbThread::GetActiveFunctions
+ // - code:CordbThread::InterceptCurrentException.
+ //
+ // We don't clear the cache in CleanupStack() because we don't refresh the cache every time we stop.
+ // Instead, we mark m_fFramesFresh in CleanupStack() and clear the cache only when it is used next time.
+ CDynArray<CordbFrame *> m_stackFrames;
+
+#if !defined(DBG_TARGET_ARM) // @ARMTODO
+ bool m_fFloatStateValid;
+ unsigned int m_floatStackTop;
+ double m_floatValues[DebuggerIPCE_FloatCount];
+#endif // !DBG_TARGET_ARM @ARMTODO
+
+private:
+ // True for the window after an Exception callback, but before it's been continued.
+ // We dispatch two exception events in a row (ICDManagedCallback::Exception and ICDManagedCallback2::Exception),
+ // and a debugger may normally just skip the first one knowing it can stop on the 2nd once.
+ // Both events will set this bit high. Be careful not to reset this bit inbetween them.
+ bool m_fException;
+
+ // True if a creation event has been queued for this thread
+ // The event may or may not have been dispatched yet
+ // Bugfix DevDiv2\DevDiv 77523 - this is only being set from ShimProcess::QueueFakeThreadAttachEventsNativeOrder
+ bool m_fCreationEventQueued;
+
+ // Object handle for Exception object in debuggee.
+ VMPTR_OBJECTHANDLE m_vmExcepObjHandle;
+
+public:
+
+ //Returns true if current user state of a thread is USER_WAIT_SLEEP_JOIN
+ bool IsThreadWaitingOrSleeping();
+
+ // Returns true if the thread is dead. See function header for definition.
+ bool IsThreadDead();
+
+ // Return CORDBG_E_BAD_THREAD_STATE if the thread is dead.
+ HRESULT EnsureThreadIsAlive();
+
+ // On a RemapBreakpoint, the debugger will eventually call RemapFunction and
+ // we need to communicate the IP back to LS. So we stash the address of where
+ // to store the IP here and stuff it in on RemapFunction.
+ // If we're not at an outstanding RemapOpportunity, this will be NULL
+ REMOTE_PTR m_EnCRemapFunctionIP;
+
+private:
+ void ClearStackFrameCache();
+
+ // True iff this thread has an unhandled exception on it.
+ // Set high when Filter() gets noitifed of an unhandled exception.
+ // Set Low if the thread is hijacked.
+ bool m_fHasUnhandledException;
+
+ // Exception record for last unhandled exception on this thread.
+ // Lazily initialized.
+ EXCEPTION_RECORD * m_pExceptionRecord;
+
+ static const CorDebugUserState kInvalidUserState = CorDebugUserState(-1);
+ CorDebugUserState m_userState; // This is the current state of the
+ // thread, at the time that the
+ // left side synchronized
+
+ // NeuterList that's executed when this Thread's stack is refreshed.
+ // This list is for everything related to stackwalking, i.e. everything which is invalidated
+ // if the stack changes in any way. This list is cleared when any of the following is called:
+ // 1) Continue()
+ // 2) SetIP()
+ // 3) RemapFunction()
+ // 4) ICDProcess::SetThreadContext()
+ NeuterList m_RefreshStackNeuterList;
+
+ // The following two data members are used for caching thread handles.
+ // @dbgtodo - Remove in V3 (can't have local handles with data-target abstraction);
+ // offload to the shim to support V2 scenarios.
+ HANDLE m_hCachedThread;
+ HANDLE m_hCachedOutOfProcThread;
+};
+
+/* ------------------------------------------------------------------------- *
+ * StackWalk class
+ * ------------------------------------------------------------------------- */
+
+class CordbStackWalk : public CordbBase, public ICorDebugStackWalk
+{
+public:
+ CordbStackWalk(CordbThread * pCordbThread);
+ virtual ~CordbStackWalk();
+ virtual void Neuter();
+
+ // helper function for Neuter
+ virtual void DeleteAll();
+
+ using CordbBase::GetProcess;
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbStackWalk"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugStackWalk
+ //-----------------------------------------------------------
+
+ COM_METHOD GetContext(ULONG32 contextFlags,
+ ULONG32 contextBufSize,
+ ULONG32 * pContextSize,
+ BYTE pbContextBuf[]);
+ COM_METHOD SetContext(CorDebugSetContextFlag flag, ULONG32 contextSize, BYTE context[]);
+ COM_METHOD Next();
+ COM_METHOD GetFrame(ICorDebugFrame **ppFrame);
+
+ //-----------------------------------------------------------
+ // Internal members
+ //-----------------------------------------------------------
+
+ void SetContextWorker(CorDebugSetContextFlag flag, ULONG32 contextSize, BYTE context[]);
+ HRESULT GetFrameWorker(ICorDebugFrame **ppFrame);
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+public:
+ void Init();
+
+private:
+ // handle legacy V2 hijacking for unhandled hardware exceptions
+ void CheckForLegacyHijackCase();
+
+ // refresh the data for this instance of CordbStackWalk if we have had an IPC event followed by a
+ // continue since we got the information.
+ void RefreshIfNeeded();
+
+ // unwind the frame and update m_context with the new context
+ BOOL UnwindStackFrame();
+
+ // the thread on which this CordbStackWalk is created
+ CordbThread * m_pCordbThread;
+
+ // This is the same iterator used by the runtime itself.
+ IDacDbiInterface::StackWalkHandle m_pSFIHandle;
+
+ // buffers used for stackwalking
+ DT_CONTEXT m_context;
+
+ // Used to figure out if we have to refresh any reference objects
+ // on the left side. We set it to CordbProcess::m_flushCounter on
+ // creation and will check it against that value when we call GetFrame or Next.
+ // If it doesn't match, an IPC event has occurred and the values will need to be
+ // refreshed via the DAC.
+ UINT m_lastSyncFlushCounter;
+
+ // cached flag used for refreshing a CordbStackWalk
+ CorDebugSetContextFlag m_cachedSetContextFlag;
+
+ // We unwind one frame ahead of time to get the FramePointer on x86.
+ // These fields are used for the bookkeeping.
+ RSSmartPtr<CordbFrame> m_pCachedFrame;
+ HRESULT m_cachedHR;
+ bool m_fIsOneFrameAhead;
+};
+
+
+class CordbContext : public CordbBase, public ICorDebugContext
+{
+public:
+
+ CordbContext() : CordbBase(NULL, 0, enumCordbContext) {}
+
+
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbContext"; }
+#endif
+
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugContext
+ //-----------------------------------------------------------
+private:
+
+} ;
+
+
+/* ------------------------------------------------------------------------- *
+ * Frame class
+ * ------------------------------------------------------------------------- */
+
+class CordbFrame : public CordbBase, public ICorDebugFrame
+{
+protected:
+ // Ctor to provide dummy frame that just wraps a frame-pointer
+ CordbFrame(CordbProcess * pProcess, FramePointer fp);
+
+public:
+ CordbFrame(CordbThread * pThread,
+ FramePointer fp,
+ SIZE_T ip,
+ CordbAppDomain * pCurrentAppDomain);
+
+ virtual ~CordbFrame();
+ virtual void Neuter();
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbFrame"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugFrame
+ //-----------------------------------------------------------
+
+ COM_METHOD GetChain(ICorDebugChain **ppChain);
+
+ // Derived versions of Frame will implement GetCode.
+ COM_METHOD GetCode(ICorDebugCode **ppCode) = 0;
+
+ COM_METHOD GetFunction(ICorDebugFunction **ppFunction);
+ COM_METHOD GetFunctionToken(mdMethodDef *pToken);
+
+ COM_METHOD GetStackRange(CORDB_ADDRESS *pStart, CORDB_ADDRESS *pEnd);
+ COM_METHOD GetCaller(ICorDebugFrame **ppFrame);
+ COM_METHOD GetCallee(ICorDebugFrame **ppFrame);
+ COM_METHOD CreateStepper(ICorDebugStepper **ppStepper);
+
+ //-----------------------------------------------------------
+ // Convenience routines
+ //-----------------------------------------------------------
+
+ CordbAppDomain *GetCurrentAppDomain()
+ {
+ return m_currentAppDomain;
+ }
+
+ // Internal helper to get a CordbFunction for this frame.
+ virtual CordbFunction *GetFunction() = 0;
+
+ FramePointer GetFramePointer()
+ {
+ return m_fp;
+ }
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+ // Accessors to return NULL or typesafe cast to derived frame
+ virtual CordbInternalFrame * GetAsInternalFrame() { return NULL; }
+ virtual CordbNativeFrame * GetAsNativeFrame() { return NULL; }
+
+ // determine if the frame pointer is in the stack range owned by the frame
+ bool IsContainedInFrame(FramePointer fp);
+
+ // This is basically a complicated cast function. We are casting from an ICorDebugFrame to a CordbFrame.
+ static CordbFrame* GetCordbFrameFromInterface(ICorDebugFrame *pFrame);
+
+ virtual const DT_CONTEXT * GetContext() const { return NULL; }
+
+public:
+ // this represents the IL offset for a CordbJITILFrame, the native offset for a CordbNativeFrame,
+ // and 0 for a CordbInternalFrame
+ SIZE_T m_ip;
+
+ CordbThread * m_pThread;
+
+ CordbAppDomain *m_currentAppDomain;
+ FramePointer m_fp;
+
+protected:
+ // indicates whether this frame is the leaf frame; lazily initialized
+ mutable Optional<bool> m_optfIsLeafFrame;
+
+private:
+#ifdef _DEBUG
+ // For tracking down neutering bugs;
+ UINT m_DbgContinueCounter;
+#endif
+};
+
+// Dummy frame that just wraps a frame pointer.
+// This is used to pass a FramePointer back in the Exception2 callback.
+// Currently, the callback passes back an ICorDebugFrame as a way of exposing a cross-platform
+// frame pointer. However passing back an ICDFrame means we need to do a stackwalk, and
+// that may not be possible in V3:
+// - the stackwalk is very chatty, and may be too much work just to give an exception notification.
+// - in 64-bit, we may not even be able to do the stackwalk ourselves.
+//
+// The shim can take the framePointer and do the stackwalk and resolve it to a real frame,
+// so V2 emulation scenarios will continue to work.
+// @dbgtodo exception - resolve this when we iron out exceptions in V3.
+class CordbPlaceholderFrame : public CordbFrame
+{
+public:
+ // Ctor to provide dummy frame that just wraps a frame-pointer
+ CordbPlaceholderFrame(CordbProcess * pProcess, FramePointer fp)
+ : CordbFrame(pProcess, fp)
+ {
+ }
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbFrame"; }
+#endif
+
+ // Provide dummy implementation for some methods. These should never be called.
+ COM_METHOD GetCode(ICorDebugCode **ppCode)
+ {
+ _ASSERTE(!"Don't call this");
+ return E_NOTIMPL;
+ }
+ virtual CordbFunction *GetFunction()
+ {
+ _ASSERTE(!"Don't call this");
+ return NULL;
+ }
+};
+
+class CordbInternalFrame : public CordbFrame, public ICorDebugInternalFrame, public ICorDebugInternalFrame2
+{
+public:
+ CordbInternalFrame(CordbThread * pThread,
+ FramePointer fp,
+ CordbAppDomain * pCurrentAppDomain,
+ const DebuggerIPCE_STRData * pData);
+
+ CordbInternalFrame(CordbThread * pThread,
+ FramePointer fp,
+ CordbAppDomain * pCurrentAppDomain,
+ CorDebugInternalFrameType frameType,
+ mdMethodDef funcMetadataToken,
+ CordbFunction * pFunction,
+ VMPTR_MethodDesc vmMethodDesc);
+
+ virtual void Neuter();
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbInternalFrame"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugFrame
+ //-----------------------------------------------------------
+
+ COM_METHOD GetChain(ICorDebugChain **ppChain)
+ {
+ return (CordbFrame::GetChain(ppChain));
+ }
+
+ // We don't expose a code-object for stubs.
+ COM_METHOD GetCode(ICorDebugCode **ppCode)
+ {
+ return CORDBG_E_CODE_NOT_AVAILABLE;
+ }
+
+ COM_METHOD GetFunction(ICorDebugFunction **ppFunction)
+ {
+ return (CordbFrame::GetFunction(ppFunction));
+ }
+ COM_METHOD GetFunctionToken(mdMethodDef *pToken)
+ {
+ return (CordbFrame::GetFunctionToken(pToken));
+ }
+
+ COM_METHOD GetCaller(ICorDebugFrame **ppFrame)
+ {
+ return (CordbFrame::GetCaller(ppFrame));
+ }
+ COM_METHOD GetCallee(ICorDebugFrame **ppFrame)
+ {
+ return (CordbFrame::GetCallee(ppFrame));
+ }
+ COM_METHOD CreateStepper(ICorDebugStepper **ppStepper)
+ {
+ return E_NOTIMPL;
+ }
+
+ COM_METHOD GetStackRange(CORDB_ADDRESS *pStart, CORDB_ADDRESS *pEnd);
+
+ //-----------------------------------------------------------
+ // ICorDebugInternalFrame
+ //-----------------------------------------------------------
+
+ // Get the type of internal frame. This will never be STUBFRAME_NONE.
+ COM_METHOD GetFrameType(CorDebugInternalFrameType * pType)
+ {
+ VALIDATE_POINTER_TO_OBJECT(pType, CorDebugInternalFrameType)
+ *pType = m_eFrameType;
+ return S_OK;
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugInternalFrame2
+ //-----------------------------------------------------------
+
+ COM_METHOD GetAddress(CORDB_ADDRESS * pAddress);
+ COM_METHOD IsCloserToLeaf(ICorDebugFrame * pFrameToCompare,
+ BOOL * pIsCloser);
+
+ BOOL IsCloserToLeafWorker(ICorDebugFrame * pFrameToCompare);
+
+ //-----------------------------------------------------------
+ // Non COM methods
+ //-----------------------------------------------------------
+
+ virtual CordbFunction *GetFunction();
+
+
+ // Accessors to return NULL or typesafe cast to derived frame
+ virtual CordbInternalFrame * GetAsInternalFrame() { return this; }
+
+ // accessor for the shim private hook code:CordbThread::ConvertFrameForILMethodWithoutMetadata
+ BOOL ConvertInternalFrameForILMethodWithoutMetadata(ICorDebugInternalFrame2 ** ppInternalFrame2);
+
+protected:
+ // the frame type
+ CorDebugInternalFrameType m_eFrameType;
+
+ // the method token of the method (if any) associated with the internal frame
+ mdMethodDef m_funcMetadataToken;
+
+ // the method (if any) associated with the internal frame
+ RSSmartPtr<CordbFunction> m_function;
+
+ VMPTR_MethodDesc m_vmMethodDesc;
+};
+
+//---------------------------------------------------------------------------------------
+//
+// This class implements ICorDebugRuntimeUnwindableFrame. It is used to mark a native stack frame
+// which requires special unwinding and which doesn't correspond to any IL code. It is really
+// just a marker to tell the debugger to use the managed unwinder. The debugger is still responsible
+// to do all the inspection and symbol lookup. An example is the hijack stub.
+//
+
+class CordbRuntimeUnwindableFrame : public CordbFrame, public ICorDebugRuntimeUnwindableFrame
+{
+public:
+ CordbRuntimeUnwindableFrame(CordbThread * pThread,
+ FramePointer fp,
+ CordbAppDomain * pCurrentAppDomain,
+ DT_CONTEXT * pContext);
+
+ virtual void Neuter();
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbRuntimeUnwindableFrame"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+
+ COM_METHOD QueryInterface(REFIID riid, void ** ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugFrame
+ //-----------------------------------------------------------
+
+ //
+ // Just return E_NOTIMPL for everything.
+ // See the class comment.
+ //
+
+ COM_METHOD GetChain(ICorDebugChain ** ppChain)
+ {
+ return E_NOTIMPL;
+ }
+
+ COM_METHOD GetCode(ICorDebugCode ** ppCode)
+ {
+ return E_NOTIMPL;
+ }
+
+ COM_METHOD GetFunction(ICorDebugFunction ** ppFunction)
+ {
+ return E_NOTIMPL;
+ }
+
+ COM_METHOD GetFunctionToken(mdMethodDef * pToken)
+ {
+ return E_NOTIMPL;
+ }
+
+ COM_METHOD GetCaller(ICorDebugFrame ** ppFrame)
+ {
+ return E_NOTIMPL;
+ }
+
+ COM_METHOD GetCallee(ICorDebugFrame ** ppFrame)
+ {
+ return E_NOTIMPL;
+ }
+
+ COM_METHOD CreateStepper(ICorDebugStepper ** ppStepper)
+ {
+ return E_NOTIMPL;
+ }
+
+ COM_METHOD GetStackRange(CORDB_ADDRESS * pStart, CORDB_ADDRESS * pEnd)
+ {
+ return E_NOTIMPL;
+ }
+
+ //-----------------------------------------------------------
+ // Non COM methods
+ //-----------------------------------------------------------
+
+ virtual CordbFunction * GetFunction()
+ {
+ return NULL;
+ }
+
+ virtual const DT_CONTEXT * GetContext() const;
+
+private:
+ DT_CONTEXT m_context;
+};
+
+
+class CordbValueEnum : public CordbBase, public ICorDebugValueEnum
+{
+public:
+ enum ValueEnumMode {
+ LOCAL_VARS_ORIGINAL_IL,
+ LOCAL_VARS_REJIT_IL,
+ ARGS,
+ } ;
+
+ CordbValueEnum(CordbNativeFrame *frame, ValueEnumMode mode);
+ HRESULT Init();
+ ~CordbValueEnum();
+ virtual void Neuter();
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbValueEnum"; }
+#endif
+
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugEnum
+ //-----------------------------------------------------------
+
+ COM_METHOD Skip(ULONG celt);
+ COM_METHOD Reset();
+ COM_METHOD Clone(ICorDebugEnum **ppEnum);
+ COM_METHOD GetCount(ULONG *pcelt);
+
+ //-----------------------------------------------------------
+ // ICorDebugValueEnum
+ //-----------------------------------------------------------
+
+ COM_METHOD Next(ULONG celt, ICorDebugValue *values[], ULONG *pceltFetched);
+
+private:
+ CordbNativeFrame* m_frame;
+ ValueEnumMode m_mode;
+ UINT m_iCurrent;
+ UINT m_iMax;
+};
+
+
+/* ------------------------------------------------------------------------- *
+ * Misc Info for the Native Frame class
+ * ------------------------------------------------------------------------- */
+
+struct CordbMiscFrame
+{
+public:
+ CordbMiscFrame();
+
+ // new-style constructor
+ CordbMiscFrame(DebuggerIPCE_JITFuncData * pJITFuncData);
+
+#if defined(DBG_TARGET_WIN64) || defined(DBG_TARGET_ARM)
+ SIZE_T parentIP;
+ FramePointer fpParentOrSelf;
+ bool fIsFilterFunclet;
+#endif // DBG_TARGET_WIN64 || DBG_TARGET_ARM
+};
+
+
+/* ------------------------------------------------------------------------- *
+ * Native Frame class
+ * ------------------------------------------------------------------------- */
+
+class CordbNativeFrame : public CordbFrame, public ICorDebugNativeFrame, public ICorDebugNativeFrame2
+{
+public:
+ CordbNativeFrame(CordbThread * pThread,
+ FramePointer fp,
+ CordbNativeCode * pNativeCode,
+ SIZE_T ip,
+ DebuggerREGDISPLAY * pDRD,
+ TADDR addrAmbientESP,
+ bool fQuicklyUnwound,
+ CordbAppDomain * pCurrentAppDomain,
+ CordbMiscFrame * pMisc = NULL,
+ DT_CONTEXT * pContext = NULL);
+ virtual ~CordbNativeFrame();
+ virtual void Neuter();
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbNativeFrame"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugFrame
+ //-----------------------------------------------------------
+
+ COM_METHOD GetChain(ICorDebugChain **ppChain)
+ {
+ return (CordbFrame::GetChain(ppChain));
+ }
+ COM_METHOD GetCode(ICorDebugCode **ppCode);
+ COM_METHOD GetFunction(ICorDebugFunction **ppFunction)
+ {
+ return (CordbFrame::GetFunction(ppFunction));
+ }
+ COM_METHOD GetFunctionToken(mdMethodDef *pToken)
+ {
+ return (CordbFrame::GetFunctionToken(pToken));
+ }
+ COM_METHOD GetCaller(ICorDebugFrame **ppFrame)
+ {
+ return (CordbFrame::GetCaller(ppFrame));
+ }
+ COM_METHOD GetCallee(ICorDebugFrame **ppFrame)
+ {
+ return (CordbFrame::GetCallee(ppFrame));
+ }
+ COM_METHOD CreateStepper(ICorDebugStepper **ppStepper)
+ {
+ return (CordbFrame::CreateStepper(ppStepper));
+ }
+
+ COM_METHOD GetStackRange(CORDB_ADDRESS *pStart, CORDB_ADDRESS *pEnd);
+
+ //-----------------------------------------------------------
+ // ICorDebugNativeFrame
+ //-----------------------------------------------------------
+
+ COM_METHOD GetIP(ULONG32* pnOffset);
+ COM_METHOD SetIP(ULONG32 nOffset);
+ COM_METHOD GetRegisterSet(ICorDebugRegisterSet **ppRegisters);
+ COM_METHOD GetLocalRegisterValue(CorDebugRegister reg,
+ ULONG cbSigBlob,
+ PCCOR_SIGNATURE pvSigBlob,
+ ICorDebugValue ** ppValue);
+
+ COM_METHOD GetLocalDoubleRegisterValue(CorDebugRegister highWordReg,
+ CorDebugRegister lowWordReg,
+ ULONG cbSigBlob,
+ PCCOR_SIGNATURE pvSigBlob,
+ ICorDebugValue ** ppValue);
+
+ COM_METHOD GetLocalMemoryValue(CORDB_ADDRESS address,
+ ULONG cbSigBlob,
+ PCCOR_SIGNATURE pvSigBlob,
+ ICorDebugValue ** ppValue);
+
+ COM_METHOD GetLocalRegisterMemoryValue(CorDebugRegister highWordReg,
+ CORDB_ADDRESS lowWordAddress,
+ ULONG cbSigBlob,
+ PCCOR_SIGNATURE pvSigBlob,
+ ICorDebugValue ** ppValue);
+
+ COM_METHOD GetLocalMemoryRegisterValue(CORDB_ADDRESS highWordAddress,
+ CorDebugRegister lowWordRegister,
+ ULONG cbSigBlob,
+ PCCOR_SIGNATURE pvSigBlob,
+ ICorDebugValue ** ppValue);
+
+ COM_METHOD CanSetIP(ULONG32 nOffset);
+
+ //-----------------------------------------------------------
+ // ICorDebugNativeFrame2
+ //-----------------------------------------------------------
+
+ COM_METHOD IsChild(BOOL * pIsChild);
+
+ COM_METHOD IsMatchingParentFrame(ICorDebugNativeFrame2 *pPotentialParentFrame,
+ BOOL * pIsParent);
+
+ COM_METHOD GetStackParameterSize(ULONG32 * pSize);
+
+ //-----------------------------------------------------------
+ // Non-COM members
+ //-----------------------------------------------------------
+
+ // Accessors to return NULL or typesafe cast to derived frame
+ virtual CordbNativeFrame * GetAsNativeFrame() { return this; }
+
+ CordbFunction * GetFunction();
+ CordbNativeCode * GetNativeCode();
+ virtual const DT_CONTEXT * GetContext() const;
+
+ // Given the native variable information of a variable, return its value.
+ // This function assumes that the value is either in a register or on the stack
+ // (i.e. VLT_REG or VLT_STK).
+ SIZE_T GetRegisterOrStackValue(const ICorDebugInfo::NativeVarInfo * pNativeVarInfo);
+
+ HRESULT GetLocalRegisterValue(CorDebugRegister reg,
+ CordbType * pType,
+ ICorDebugValue **ppValue);
+ HRESULT GetLocalDoubleRegisterValue(CorDebugRegister highWordReg,
+ CorDebugRegister lowWordReg,
+ CordbType * pType,
+ ICorDebugValue **ppValue);
+ HRESULT GetLocalMemoryValue(CORDB_ADDRESS address,
+ CordbType * pType,
+ ICorDebugValue **ppValue);
+ HRESULT GetLocalByRefMemoryValue(CORDB_ADDRESS address,
+ CordbType * pType,
+ ICorDebugValue **ppValue);
+ HRESULT GetLocalRegisterMemoryValue(CorDebugRegister highWordReg,
+ CORDB_ADDRESS lowWordAddress,
+ CordbType * pType,
+ ICorDebugValue **ppValue);
+ HRESULT GetLocalMemoryRegisterValue(CORDB_ADDRESS highWordAddress,
+ CorDebugRegister lowWordRegister,
+ CordbType * pType,
+ ICorDebugValue **ppValue);
+ UINT_PTR * GetAddressOfRegister(CorDebugRegister regNum) const;
+ CORDB_ADDRESS GetLeftSideAddressOfRegister(CorDebugRegister regNum) const;
+#if !defined(DBG_TARGET_ARM) // @ARMTODO
+ HRESULT GetLocalFloatingPointValue(DWORD index,
+ CordbType * pType,
+ ICorDebugValue **ppValue);
+#endif // !DBG_TARGET_ARM @ARMTODO
+
+
+ CORDB_ADDRESS GetLSStackAddress(ICorDebugInfo::RegNum regNum, signed offset);
+
+ bool IsLeafFrame() const;
+
+ // Return the offset used for inspection purposes.
+ // Refer to the comment at the beginning of the function definition in RsThread.cpp for more information.
+ SIZE_T GetInspectionIP();
+
+ ULONG32 GetIPOffset();
+
+ // whether this is a funclet frame
+ bool IsFunclet();
+ bool IsFilterFunclet();
+
+#if defined(DBG_TARGET_WIN64) || defined(DBG_TARGET_ARM)
+ // return the offset of the parent method frame at which an exception occurs
+ SIZE_T GetParentIP();
+#endif // DBG_TARGET_WIN64 || DBG_TARGET_ARM
+
+ TADDR GetAmbientESP() { return m_taAmbientESP; }
+ TADDR GetReturnRegisterValue();
+
+ // accessor for the shim private hook code:CordbThread::ConvertFrameForILMethodWithoutMetadata
+ BOOL ConvertNativeFrameForILMethodWithoutMetadata(ICorDebugInternalFrame2 ** ppInternalFrame2);
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+public:
+ // the register set
+ DebuggerREGDISPLAY m_rd;
+
+ // This field is only true for Enter-Managed chain. It means that the register set is invalid.
+ bool m_quicklyUnwound;
+
+ // each CordbNativeFrame corresponds to exactly one CordbJITILFrame and one CordbNativeCode
+ RSSmartPtr<CordbJITILFrame> m_JITILFrame;
+ RSSmartPtr<CordbNativeCode> m_nativeCode;
+
+ // auxiliary information only used on 64-bit to find the parent stack pointer and offset for funclets
+ CordbMiscFrame m_misc;
+
+private:
+ // the ambient SP value only used on x86 to retrieve sp-relative local variables
+ // (most likely in a frameless method)
+ TADDR m_taAmbientESP;
+
+ // @dbgtodo inspection - When we DACize the various Cordb*Value classes, we should consider getting rid of the
+ // DebuggerREGDISPLAY and just use the CONTEXT. A lot of simplification can be done here.
+ DT_CONTEXT m_context;
+};
+
+
+/* ------------------------------------------------------------------------- *
+ * CordbRegisterSet class
+ *
+ * This can be obtained via GetRegisterSet from
+ * CordbNativeFrame
+ * CordbThread
+ *
+ * ------------------------------------------------------------------------- */
+
+#define SETBITULONG64( x ) ( (ULONG64)1 << (x) )
+#define SET_BIT_MASK(_mask, _reg) (_mask[(_reg) >> 3] |= (1 << ((_reg) & 7)))
+#define RESET_BIT_MASK(_mask, _reg) (_mask[(_reg) >> 3] &= ~(1 << ((_reg) & 7)))
+#define IS_SET_BIT_MASK(_mask, _reg) (_mask[(_reg) >> 3] & (1 << ((_reg) & 7)))
+
+
+class CordbRegisterSet : public CordbBase, public ICorDebugRegisterSet, public ICorDebugRegisterSet2
+{
+public:
+ CordbRegisterSet(DebuggerREGDISPLAY * pRegDisplay,
+ CordbThread * pThread,
+ bool fActive,
+ bool fQuickUnwind,
+ bool fTakeOwnershipOfDRD = false);
+
+
+ ~CordbRegisterSet();
+
+
+
+ virtual void Neuter();
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbRegisterSet"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRefEnforceExternal());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseReleaseEnforceExternal());
+ }
+
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+
+
+ //-----------------------------------------------------------
+ // ICorDebugRegisterSet
+ // More extensive explanation are in Src/inc/CorDebug.idl
+ //-----------------------------------------------------------
+ COM_METHOD GetRegistersAvailable(ULONG64 *pAvailable);
+
+ COM_METHOD GetRegisters(ULONG64 mask,
+ ULONG32 regCount,
+ CORDB_REGISTER regBuffer[]);
+ COM_METHOD SetRegisters( ULONG64 mask,
+ ULONG32 regCount,
+ CORDB_REGISTER regBuffer[])
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(regBuffer, CORDB_REGISTER,
+ regCount, true, true);
+
+ return E_NOTIMPL;
+ }
+
+ COM_METHOD GetThreadContext(ULONG32 contextSize, BYTE context[]);
+
+ // SetThreadContexthad a very problematic implementation in v1.1.
+ // We've ripped it out in V2.0 and E_NOTIMPL it. See V1.1 sources for what it used to look like
+ // in case we ever want to re-add it.
+ // If we ever re-implement it consider the following:
+ // - must fail on non-leaf frames (just check m_active).
+ // - must make sure that GetThreadContext() is fully accurate. If we don't have SetThCtx, then
+ // GetThreadCtx bugs are much more benign.
+ // - be sure to update any shared reg displays (what if a frame + chain have the same rd) and
+ // also update any cached contexts (such as CordbThread::m_context).
+ // - be sure to honor the context flags and only setting what we can set.
+ //
+ // Friday, July 16, 2004. (This date will be useful for Source control history)
+ COM_METHOD SetThreadContext(ULONG32 contextSize, BYTE context[])
+ {
+ return E_NOTIMPL;
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugRegisterSet2
+ // More extensive explanation are in Src/inc/CorDebug.idl
+ //-----------------------------------------------------------
+ COM_METHOD GetRegistersAvailable(ULONG32 regCount,
+ BYTE pAvailable[]);
+
+ COM_METHOD GetRegisters(ULONG32 maskCount,
+ BYTE mask[],
+ ULONG32 regCount,
+ CORDB_REGISTER regBuffer[]);
+
+ COM_METHOD SetRegisters(ULONG32 maskCount,
+ BYTE mask[],
+ ULONG32 regCount,
+ CORDB_REGISTER regBuffer[])
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(regBuffer, CORDB_REGISTER,
+ regCount, true, true);
+
+ return E_NOTIMPL;
+ }
+
+protected:
+ // Platform specific helper for GetThreadContext.
+ void InternalCopyRDToContext(DT_CONTEXT * pContext);
+
+ // Adapters to impl v2.0 interfaces on top of v1.0 interfaces.
+ HRESULT GetRegistersAvailableAdapter(ULONG32 regCount, BYTE pAvailable[]);
+ HRESULT GetRegistersAdapter(ULONG32 maskCount, BYTE mask[], ULONG32 regCount, CORDB_REGISTER regBuffer[]);
+
+
+ // This CordbRegisterSet is responsible to free this memory if m_fTakeOwnershipOfDRD is true. Otherwise,
+ // this memory is freed by the CordbNativeFrame or CordbThread which creates this CordbRegisterSet.
+ DebuggerREGDISPLAY *m_rd;
+ CordbThread *m_thread;
+ bool m_active; // true if we're the leafmost register set.
+ bool m_quickUnwind;
+
+ // true if the CordbRegisterSet owns the DebuggerREGDISPLAY pointer and needs to free the memory
+ bool m_fTakeOwnershipOfDRD;
+} ;
+
+
+
+
+/* ------------------------------------------------------------------------- *
+ * JIT-IL Frame class
+ * ------------------------------------------------------------------------- */
+
+class CordbJITILFrame : public CordbBase, public ICorDebugILFrame, public ICorDebugILFrame2, public ICorDebugILFrame3, public ICorDebugILFrame4
+{
+public:
+ CordbJITILFrame(CordbNativeFrame * pNativeFrame,
+ CordbILCode * pCode,
+ UINT_PTR ip,
+ CorDebugMappingResult mapping,
+ GENERICS_TYPE_TOKEN exactGenericArgsToken,
+ DWORD dwExactGenericArgsTokenIndex,
+ bool fVarArgFnx,
+ CordbReJitILCode * pReJitCode);
+ HRESULT Init();
+ virtual ~CordbJITILFrame();
+ virtual void Neuter();
+
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbJITILFrame"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugFrame
+ //-----------------------------------------------------------
+
+ COM_METHOD GetChain(ICorDebugChain **ppChain);
+ COM_METHOD GetCode(ICorDebugCode **ppCode);
+ COM_METHOD GetFunction(ICorDebugFunction **ppFunction);
+ COM_METHOD GetFunctionToken(mdMethodDef *pToken);
+ COM_METHOD GetStackRange(CORDB_ADDRESS *pStart, CORDB_ADDRESS *pEnd);
+ COM_METHOD CreateStepper(ICorDebugStepper **ppStepper);
+ COM_METHOD GetCaller(ICorDebugFrame **ppFrame);
+ COM_METHOD GetCallee(ICorDebugFrame **ppFrame);
+
+ //-----------------------------------------------------------
+ // ICorDebugILFrame
+ //-----------------------------------------------------------
+
+ COM_METHOD GetIP(ULONG32* pnOffset, CorDebugMappingResult *pMappingResult);
+ COM_METHOD SetIP(ULONG32 nOffset);
+ COM_METHOD EnumerateLocalVariables(ICorDebugValueEnum **ppValueEnum);
+ COM_METHOD GetLocalVariable(DWORD dwIndex, ICorDebugValue **ppValue);
+ COM_METHOD EnumerateArguments(ICorDebugValueEnum **ppValueEnum);
+ COM_METHOD GetArgument(DWORD dwIndex, ICorDebugValue ** ppValue);
+ COM_METHOD GetStackDepth(ULONG32 *pDepth);
+ COM_METHOD GetStackValue(DWORD dwIndex, ICorDebugValue **ppValue);
+ COM_METHOD CanSetIP(ULONG32 nOffset);
+
+ //-----------------------------------------------------------
+ // ICorDebugILFrame2
+ //-----------------------------------------------------------
+
+ // Called at an EnC remap opportunity to remap to the latest version of a function
+ COM_METHOD RemapFunction(ULONG32 nOffset);
+
+ COM_METHOD EnumerateTypeParameters(ICorDebugTypeEnum **ppTyParEnum);
+
+ //-----------------------------------------------------------
+ // ICorDebugILFrame3
+ //-----------------------------------------------------------
+
+ COM_METHOD GetReturnValueForILOffset(ULONG32 ILoffset, ICorDebugValue** ppReturnValue);
+
+ //-----------------------------------------------------------
+ // ICorDebugILFrame4
+ //-----------------------------------------------------------
+
+ COM_METHOD EnumerateLocalVariablesEx(ILCodeKind flags, ICorDebugValueEnum **ppValueEnum);
+ COM_METHOD GetLocalVariableEx(ILCodeKind flags, DWORD dwIndex, ICorDebugValue **ppValue);
+ COM_METHOD GetCodeEx(ILCodeKind flags, ICorDebugCode **ppCode);
+
+ //-----------------------------------------------------------
+ // Non-COM methods
+ //-----------------------------------------------------------
+
+ CordbModule *GetModule();
+
+ HRESULT GetNativeVariable(CordbType *type,
+ const ICorDebugInfo::NativeVarInfo *pNativeVarInfo,
+ ICorDebugValue **ppValue);
+
+ CordbAppDomain *GetCurrentAppDomain();
+
+ CordbFunction *GetFunction();
+
+ // ILVariableToNative serves to let the frame intercept accesses
+ // to var args variables.
+ HRESULT ILVariableToNative(DWORD dwIndex,
+ const ICorDebugInfo::NativeVarInfo ** ppNativeInfo);
+
+ // Fills in our array of var args variables
+ HRESULT FabricateNativeInfo(DWORD dwIndex,
+ const ICorDebugInfo::NativeVarInfo ** ppNativeInfo);
+
+ HRESULT GetArgumentType(DWORD dwIndex,
+ CordbType ** ppResultType);
+
+ // load the generics type and method arguments into a cache
+ void LoadGenericArgs();
+
+ HRESULT QueryInterfaceInternal(REFIID id, void** pInterface);
+
+ // Builds an generic Instaniation object from the mdClass and generic signature
+ // for what we are calling into.
+ static HRESULT BuildInstantiationForCallsite(CordbModule *pModule, NewArrayHolder<CordbType*> &types, Instantiation &inst, Instantiation *currentInstantiation, mdToken targetClass, SigParser funcGenerics);
+
+ CordbILCode* GetOriginalILCode();
+ CordbReJitILCode* GetReJitILCode();
+
+private:
+ void RefreshCachedVarArgSigParserIfNeeded();
+
+ // Worker function for GetReturnValueForILOffset.
+ HRESULT GetReturnValueForILOffsetImpl(ULONG32 ILoffset, ICorDebugValue** ppReturnValue);
+
+ // Given pType, fills ppReturnValue with the correct value.
+ HRESULT GetReturnValueForType(CordbType *pType, ICorDebugValue **ppReturnValue);
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+public:
+ // each CordbJITILFrame corresponds to exactly one CordbNativeFrame and one CordbILCode
+ CordbNativeFrame * m_nativeFrame;
+ CordbILCode * m_ilCode;
+
+ // the IL offset and the mapping result for the offset
+ UINT_PTR m_ip;
+ CorDebugMappingResult m_mapping;
+
+ // <vararg-specific fields>
+
+ // whether this is a vararg function
+ bool m_fVarArgFnx;
+
+ // the number of arguments, including the var args
+ ULONG m_allArgsCount;
+
+ // This byte array is used to store the signature for vararg methods.
+ // It points to the underlying memory used by m_sigParserCached, and it enables us to easily delete
+ // the underlying memory when the CordbJITILFrame is neutered.
+ BYTE * m_rgbSigParserBuf;
+
+ // Do not mutate this, instead make copies of it and use the copies, that way we are guaranteed to
+ // start at the correct position in the signature each time.
+ // The underlying memory used for the signature in the SigParser must not be in the DAC cache.
+ // Otherwise it may be flushed underneath us, and we would AV when we try to access it.
+ SigParser m_sigParserCached;
+
+ // the address of the first arg; only used for vararg functions
+ CORDB_ADDRESS m_FirstArgAddr;
+
+ // This is an array of variable information for the arguments.
+ // The variable information is fabricated by the RS.
+ ICorDebugInfo::NativeVarInfo * m_rgNVI;
+
+ // </vararg-specific fields>
+
+ Instantiation m_genericArgs; // the generics type arguments
+ BOOL m_genericArgsLoaded; // whether we have loaded and cached the generics type arguments
+
+ // An extra token to help fetch information about any generic
+ // parameters passed to the method, perhaps dynamically.
+ // This is the so-called generics type context/token.
+ //
+ // This token comes from the stackwalker and it may be NULL, in which case we need to retrieve the token
+ // ourselves using m_dwFrameParamsTokenIndex and the variable lifetime information.
+ GENERICS_TYPE_TOKEN m_frameParamsToken;
+
+ // IL Variable index of the Generics Arg Token.
+ DWORD m_dwFrameParamsTokenIndex;
+
+ // if this frame is instrumented with rejit, this will point to the instrumented IL code
+ RSSmartPtr<CordbReJitILCode> m_pReJitCode;
+};
+
+/* ------------------------------------------------------------------------- *
+ * Breakpoint class
+ * ------------------------------------------------------------------------- */
+
+enum CordbBreakpointType
+{
+ CBT_FUNCTION,
+ CBT_MODULE,
+ CBT_VALUE
+};
+
+class CordbBreakpoint : public CordbBase, public ICorDebugBreakpoint
+{
+public:
+ CordbBreakpoint(CordbProcess * pProcess, CordbBreakpointType bpType);
+ virtual void Neuter();
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbBreakpoint"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugBreakpoint
+ //-----------------------------------------------------------
+
+ COM_METHOD BaseIsActive(BOOL *pbActive);
+
+ //-----------------------------------------------------------
+ // Non-COM methods
+ //-----------------------------------------------------------
+ CordbBreakpointType GetBPType()
+ {
+ return m_type;
+ }
+
+ virtual void Disconnect() {}
+
+ CordbAppDomain *GetAppDomain()
+ {
+ return m_pAppDomain;
+ }
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+public:
+ bool m_active;
+ CordbAppDomain *m_pAppDomain;
+ CordbBreakpointType m_type;
+};
+
+/* ------------------------------------------------------------------------- *
+ * Function Breakpoint class
+ * ------------------------------------------------------------------------- */
+
+class CordbFunctionBreakpoint : public CordbBreakpoint,
+ public ICorDebugFunctionBreakpoint
+{
+public:
+ CordbFunctionBreakpoint(CordbCode *code, SIZE_T offset);
+ ~CordbFunctionBreakpoint();
+
+ virtual void Neuter();
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbFunctionBreakpoint"; }
+#endif
+
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugBreakpoint
+ //-----------------------------------------------------------
+
+ COM_METHOD GetFunction(ICorDebugFunction **ppFunction);
+ COM_METHOD GetOffset(ULONG32 *pnOffset);
+ COM_METHOD Activate(BOOL bActive);
+ COM_METHOD IsActive(BOOL *pbActive)
+ {
+ VALIDATE_POINTER_TO_OBJECT(pbActive, BOOL *);
+
+ return BaseIsActive(pbActive);
+ }
+
+ //-----------------------------------------------------------
+ // Non-COM methods
+ //-----------------------------------------------------------
+
+ void Disconnect();
+
+ //-----------------------------------------------------------
+ // Convenience routines
+ //-----------------------------------------------------------
+
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+ // Get a point to the LS BP object.
+ LSPTR_BREAKPOINT GetLsPtrBP();
+public:
+
+ // We need to have a strong pointer because we may access the m_code object after we're neutered.
+ // @todo - use external pointer b/c Breakpoints aren't yet rooted, and so this reference could be
+ // leaked.
+ RSExtSmartPtr<CordbCode> m_code;
+ SIZE_T m_offset;
+};
+
+/* ------------------------------------------------------------------------- *
+ * Module Breakpoint class
+ * ------------------------------------------------------------------------- */
+
+class CordbModuleBreakpoint : public CordbBreakpoint,
+ public ICorDebugModuleBreakpoint
+{
+public:
+ CordbModuleBreakpoint(CordbModule *pModule);
+
+
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbModuleBreakpoint"; }
+#endif
+
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugModuleBreakpoint
+ //-----------------------------------------------------------
+
+ COM_METHOD GetModule(ICorDebugModule **ppModule);
+ COM_METHOD Activate(BOOL bActive);
+ COM_METHOD IsActive(BOOL *pbActive)
+ {
+ VALIDATE_POINTER_TO_OBJECT(pbActive, BOOL *);
+
+ return BaseIsActive(pbActive);
+ }
+
+ //-----------------------------------------------------------
+ // Non-COM methods
+ //-----------------------------------------------------------
+
+ void Disconnect();
+
+public:
+ CordbModule *m_module;
+};
+
+
+/* ------------------------------------------------------------------------- *
+ * Stepper class
+ * ------------------------------------------------------------------------- */
+
+class CordbStepper : public CordbBase, public ICorDebugStepper, public ICorDebugStepper2
+{
+public:
+ CordbStepper(CordbThread *thread, CordbFrame *frame = NULL);
+
+
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbStepper"; }
+#endif
+
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugStepper
+ //-----------------------------------------------------------
+
+ COM_METHOD IsActive(BOOL *pbActive);
+ COM_METHOD Deactivate();
+ COM_METHOD SetInterceptMask(CorDebugIntercept mask);
+ COM_METHOD SetUnmappedStopMask(CorDebugUnmappedStop mask);
+ COM_METHOD Step(BOOL bStepIn);
+ COM_METHOD StepRange(BOOL bStepIn,
+ COR_DEBUG_STEP_RANGE ranges[],
+ ULONG32 cRangeCount);
+ COM_METHOD StepOut();
+ COM_METHOD SetRangeIL(BOOL bIL);
+
+ //-----------------------------------------------------------
+ // ICorDebugStepper2
+ //-----------------------------------------------------------
+ COM_METHOD SetJMC(BOOL fIsJMCStepper);
+
+ //-----------------------------------------------------------
+ // Convenience routines
+ //-----------------------------------------------------------
+
+ CordbAppDomain *GetAppDomain()
+ {
+ return (m_thread->GetAppDomain());
+ }
+
+ LSPTR_STEPPER GetLsPtrStepper();
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+ CordbThread *m_thread;
+ CordbFrame *m_frame;
+ REMOTE_PTR m_stepperToken;
+ bool m_active;
+ bool m_rangeIL;
+ bool m_fIsJMCStepper;
+ CorDebugUnmappedStop m_rgfMappingStop;
+ CorDebugIntercept m_rgfInterceptStop;
+};
+
+#define REG_SIZE sizeof(SIZE_T)
+
+// class RegisterInfo: encapsulates information necessary to identify and access a specific register in a
+// register display
+class RegisterInfo
+{
+public:
+ // constructor for an instance of RegisterInfo
+ // Arguments:
+ // input: kNumber - value from CorDebugRegister to identify the register
+ // addr - address in remote register display that holds the value
+ // output: no out parameters, but this instance of RegisterInfo has been initialized
+ RegisterInfo(const CorDebugRegister kNumber, CORDB_ADDRESS addr, SIZE_T value):
+ m_kRegNumber((CorDebugRegister)kNumber),
+ m_regAddr(addr),
+ m_regValue(value)
+ {};
+
+
+ // copy constructor
+ // Arguments:
+ // input: regInfo - register info from which the values for this instance will come
+ // output: no out parameters, but this instance of RegisterInfo has been initialized
+ RegisterInfo(const RegisterInfo * pRegInfo):
+ m_kRegNumber(pRegInfo->m_kRegNumber),
+ m_regAddr(pRegInfo->m_regAddr),
+ m_regValue(pRegInfo->m_regValue)
+ {};
+
+
+ //-------------------------------------
+ // data members
+ //-------------------------------------
+
+ // enumeration value to identify the register, e.g., REGISTER_X86_EAX, or REGISTER_AMD64_XMM0
+ CorDebugRegister m_kRegNumber;
+
+ // address in a context or frame register display of the register value
+ CORDB_ADDRESS m_regAddr;
+
+ // the actual value of the register
+ SIZE_T m_regValue;
+}; // class RegisterInfo
+
+// class EnregisteredValueHome: abstract class to encapsulate basic information for a register value, and
+// serve as a base class for values residing in register-based locations, such as a single register, a
+// register pair, or a register and memory location.
+class EnregisteredValueHome
+{
+public:
+
+ // constructor to initialize an instance of EnregisteredValueHome
+ EnregisteredValueHome(const CordbNativeFrame * pFrame);
+
+ virtual ~EnregisteredValueHome() {}
+
+ // virtual "copy constructor" to make a copy of "this" to be owned by a different instance of
+ // Cordb*Value. If an instance of CordbVCObjectValue represents an enregistered value class, it means
+ // there is a single field. This implies that the register for the CordbVCObject instance is the same as
+ // the register for its field. When we create a Cordb*Value to represent this field, we need to make a
+ // copy of the EnregisteredValueHome belonging to the CordbVCObject instance to become the
+ // EnregisteredValueHome of the Cord*Value representing the field.
+ // returns:
+ // a new cloned copy of this object, allocated on the heap.
+ // Caller is responsible for deleting the memory (using the standard delete operator).
+ // note:
+ // C++ allows derived implementations to differ on return type, thus allowing
+ // derived impls to return the cloned copy as its actual derived type, and not just as a base type.
+
+
+ virtual
+ EnregisteredValueHome * Clone() const = 0;
+
+ // set a remote enregistered location to a new value
+ // Arguments:
+ // input: pNewValue - buffer containing the new value along with its size
+ // pContext - context from which the value comes
+ // fIsSigned - indicates whether the value is signed or not. The value provided may be smaller than
+ // a register, in which case we'll need to extend it to a full register width. To do this
+ // correctly, we need to know whether to sign extend or zero extend. Currently, only
+ // the RegValueHome virtual function uses this, but we may need it if we introduce
+ // types that don't completely occupy the size of two registers.
+ // output: updates the remote enregistered value on success
+ // Note: Throws E_FAIL for invalid input or various HRESULTs from an
+ // unsuccessful call to WriteProcessMemory
+ virtual
+ void SetEnregisteredValue(MemoryRange newValue, DT_CONTEXT * pContext, bool fIsSigned) = 0;
+
+ // Gets an enregistered value and returns it to the caller
+ // Arguments:
+ // input: pValueOutBuffer - buffer in which to return the value, along with its size
+ // output: pValueOutBuffer - filled with the value
+ // Note: Throws E_NOTIMPL for attempts to get an enregistered value for a float register
+ // (implementation for derived class FloatRegValueHome)
+ virtual
+ void GetEnregisteredValue(MemoryRange valueOutBuffer) = 0;
+
+ // initialize an instance of RemoteAddress for use in an IPC event buffer with values from this
+ // instance of a derived class of EnregisteredValueHome
+ // Arguments: input: none--uses fields of "this"
+ // output: pRegAddr - address of an instance of RemoteAddress with field values set to corresponding
+ // field values of "this"
+ virtual
+ void CopyToIPCEType(RemoteAddress * pRegAddr) = 0;
+
+ // accessor
+ const CordbNativeFrame * GetFrame() const { return m_pFrame; };
+
+ //-------------------------------------
+ // data members
+ //-------------------------------------
+protected:
+ // The frame on which the value resides
+ const CordbNativeFrame * m_pFrame;
+
+}; // class EnregisteredValueHome
+
+typedef NewHolder<EnregisteredValueHome> EnregisteredValueHomeHolder;
+
+// class RegValueHome: encapsulates basic information for a value that resides in a single register
+// and serves as a base class for values residing in a register pair.
+class RegValueHome: public EnregisteredValueHome
+{
+public:
+
+ // initializing constructor
+ // Arguments:
+ // input: pFrame - frame to which the value belongs
+ // regNum - enumeration value corresponding to the particular hardware register in
+ // which the value resides
+ // regAddr - remote address within a register display (in a context or frame) of the
+ // register value
+ // output: no out parameters, but the instance has been initialized
+ RegValueHome(const CordbNativeFrame * pFrame,
+ CorDebugRegister regNum):
+ EnregisteredValueHome(pFrame),
+ m_reg1Info(regNum,
+ pFrame->GetLeftSideAddressOfRegister(regNum),
+ *(pFrame->GetAddressOfRegister(regNum)))
+ {};
+
+ // copy constructor
+ // Arguments:
+ // input: pRemoteRegAddr - instance of a remote register address from which the values for this
+ // instance will come
+ // output: no out parameters, but the instance has been initialized
+ RegValueHome(const RegValueHome * pRemoteRegAddr):
+ EnregisteredValueHome(pRemoteRegAddr->m_pFrame),
+ m_reg1Info(pRemoteRegAddr->m_reg1Info)
+ {};
+
+ // make a copy of this instance of RegValueHome
+ virtual
+ RegValueHome * Clone() const { return new RegValueHome(*this); };
+
+ // updates a register in a given context, and in the regdisplay of a given frame.
+ void SetContextRegister(DT_CONTEXT * pContext,
+ CorDebugRegister regNum,
+ SIZE_T newVal);
+
+ // set the value of a remote enregistered value
+ virtual
+ void SetEnregisteredValue(MemoryRange newValue, DT_CONTEXT * pContext, bool fIsSigned);
+
+ // Gets an enregistered value and returns it to the caller
+ virtual
+ void GetEnregisteredValue(MemoryRange valueOutBuffer);
+ // initialize an instance of RemoteAddress for use in an IPC event buffer with values from this
+ // instance of a derived class of RegValueHome
+ virtual
+ void CopyToIPCEType(RemoteAddress * pRegAddr);
+
+ //-------------------------------------
+ // data members
+ //-------------------------------------
+protected:
+ // The information for the register in which the value resides.
+ const RegisterInfo m_reg1Info;
+}; // class RegValueHome
+
+// class RegRegValueHome
+// derived class to add a second register for values that live in a pair of registers
+class RegRegValueHome: public RegValueHome
+{
+public:
+ // initializing constructor
+ // Arguments:
+ // input: pFrame - frame to which the value belongs
+ // reg1Num - enumeration value corresponding to the first particular hardware register in
+ // which the value resides
+ // reg1Addr - remote address within a register display (in a context or frame) of the
+ // first register
+ // reg2Num - enumeration value corresponding to the second particular hardware register in
+ // which the value resides
+ // reg2Addr - remote address within a register display (in a context or frame) of the
+ // second register
+ // output: no out parameters, but the instance has been initialized
+ RegRegValueHome(const CordbNativeFrame * pFrame,
+ CorDebugRegister reg1Num,
+ CorDebugRegister reg2Num):
+ RegValueHome(pFrame, reg1Num),
+ m_reg2Info(reg2Num,
+ pFrame->GetLeftSideAddressOfRegister(reg2Num),
+ *(pFrame->GetAddressOfRegister(reg2Num)))
+ {};
+
+ // copy constructor
+ // Arguments:
+ // input: pRemoteRegAddr - instance of a remote register address from which the values for this
+ // instance will come
+ // output: no out parameters, but the instance has been initialized
+ RegRegValueHome(const RegRegValueHome * pRemoteRegAddr):
+ RegValueHome(pRemoteRegAddr),
+ m_reg2Info(pRemoteRegAddr->m_reg2Info)
+ {};
+
+ // make a copy of this instance of RegRegValueHome
+ virtual
+ RegRegValueHome * Clone() const { return new RegRegValueHome(*this); };
+
+ // set the value of a remote enregistered value
+ virtual
+ void SetEnregisteredValue(MemoryRange newValue, DT_CONTEXT * pContext, bool fIsSigned);
+
+ // Gets an enregistered value and returns it to the caller
+ virtual
+ void GetEnregisteredValue(MemoryRange valueOutBuffer);
+
+ // initialize an instance of RemoteAddress for use in an IPC event buffer with values from this
+ // instance of a derived class of EnregisteredValueHome
+ void CopyToIPCEType(RemoteAddress * pRegAddr);
+
+ //-------------------------------------
+ // data members
+ //-------------------------------------
+
+protected:
+ // The information for the second of two registers in which the value resides.
+ const RegisterInfo m_reg2Info;
+}; // class RegRegValueHome
+
+// class RegAndMemBaseValueHome
+// derived from RegValueHome, this class is also a base class for RegMemValueHome
+// and MemRegValueHome, which add a memory location for reg-mem or mem-reg values
+class RegAndMemBaseValueHome: public RegValueHome
+{
+public:
+ // initializing constructor
+ // Arguments:
+ // input: pFrame - frame to which the value belongs
+ // reg1Num - enumeration value corresponding to the first particular hardware register in
+ // which the value resides
+ // reg1Addr - remote address within a register display (in a context or frame) of the
+ // register component of the value
+ // memAddr - remote address for the memory component of the value
+ // output: no out parameters, but the instance has been initialized
+ RegAndMemBaseValueHome(const CordbNativeFrame * pFrame,
+ CorDebugRegister reg1Num,
+ CORDB_ADDRESS memAddr):
+ RegValueHome(pFrame, reg1Num),
+ m_memAddr(memAddr)
+ {};
+
+ // copy constructor
+ // Arguments:
+ // input: pRemoteRegAddr - instance of a remote register address from which the values for this
+ // instance will come
+ // output: no out parameters, but the instance has been initialized
+ RegAndMemBaseValueHome(const RegAndMemBaseValueHome * pRemoteRegAddr):
+ RegValueHome(pRemoteRegAddr),
+ m_memAddr()
+ {};
+
+ // make a copy of this instance of RegRegValueHome
+ virtual
+ RegAndMemBaseValueHome * Clone() const = 0;
+
+ // set the value of a remote enregistered value
+ virtual
+ void SetEnregisteredValue(MemoryRange newValue, DT_CONTEXT * DT_pContext, bool fIsSigned) = 0;
+
+ // Gets an enregistered value and returns it to the caller
+ virtual
+ void GetEnregisteredValue(MemoryRange valueOutBuffer) = 0;
+
+ // initialize an instance of RemoteAddress for use in an IPC event buffer with values from this
+ // instance of a derived class of EnregisteredValueHome
+ virtual
+ void CopyToIPCEType(RemoteAddress * pRegAddr) = 0;
+
+ //-------------------------------------
+ // data members
+ //-------------------------------------
+
+protected:
+ // remote address for the memory component of the value
+ CORDB_ADDRESS m_memAddr;
+
+}; // class RegAndMemBaseValueHome;
+
+// class RegMemValueHome
+// type derived from abstract class RegAndMemBaseValueHome to represent a Register/Memory location where the
+// high order part of the value is kept in a register, and the low order part is kept in memory
+class RegMemValueHome: public RegAndMemBaseValueHome
+{
+public:
+
+ // initializing constructor
+ // Arguments:
+ // input: pFrame - frame to which the value belongs
+ // reg1Num - enumeration value corresponding to the first particular hardware register in
+ // which the value resides
+ // reg1Addr - remote address within a register display (in a context or frame) of the
+ // register component of the value
+ // memAddr - remote address for the memory component of the value
+ // output: no out parameters, but the instance has been initialized
+ RegMemValueHome(const CordbNativeFrame * pFrame,
+ CorDebugRegister reg1Num,
+ CORDB_ADDRESS memAddr):
+ RegAndMemBaseValueHome(pFrame, reg1Num, memAddr)
+ {};
+
+ // copy constructor
+ // Arguments:
+ // input: pRemoteRegAddr - instance of a remote register address from which the values for this
+ // instance will come
+ // output: no out parameters, but the instance has been initialized
+ RegMemValueHome(const RegMemValueHome * pRemoteRegAddr):
+ RegAndMemBaseValueHome(pRemoteRegAddr)
+ {};
+
+ // make a copy of this instance of RegMemValueHome
+ virtual
+ RegMemValueHome * Clone() const { return new RegMemValueHome(*this); };
+
+ // set the value of a remote enregistered value
+ virtual
+ void SetEnregisteredValue(MemoryRange newValue, DT_CONTEXT * pContext, bool fIsSigned);
+
+ // Gets an enregistered value and returns it to the caller
+ virtual
+ void GetEnregisteredValue(MemoryRange valueOutBuffer);
+
+ // initialize an instance of RemoteAddress for use in an IPC event buffer with values from this
+ // instance of a derived class of EnregisteredValueHome
+ virtual
+ void CopyToIPCEType(RemoteAddress * pRegAddr);
+
+}; // class RegMemValueHome;
+
+// class MemRegValueHome
+// type derived from abstract class RegAndMemBaseValueHome to represent a Register/Memory location where the
+// low order part of the value is kept in a register, and the high order part is kept in memory
+class MemRegValueHome: public RegAndMemBaseValueHome
+{
+public:
+
+ // initializing constructor
+ // Arguments:
+ // input: pFrame - frame to which the value belongs
+ // reg1Num - enumeration value corresponding to the first particular hardware register in
+ // which the value resides
+ // reg1Addr - remote address within a register display (in a context or frame) of the
+ // register component of the value
+ // memAddr - remote address for the memory component of the value
+ // output: no out parameters, but the instance has been initialized
+ MemRegValueHome(const CordbNativeFrame * pFrame,
+ CorDebugRegister reg1Num,
+ CORDB_ADDRESS memAddr):
+ RegAndMemBaseValueHome(pFrame, reg1Num, memAddr)
+ {};
+
+ // copy constructor
+ // Arguments:
+ // input: pRemoteRegAddr - instance of a remote register address from which the values for this
+ // instance will come
+ // output: no out parameters, but the instance has been initialized
+ MemRegValueHome(const MemRegValueHome * pRemoteRegAddr):
+ RegAndMemBaseValueHome(pRemoteRegAddr)
+ {};
+
+ // make a copy of this instance of MemRegValueHome
+ virtual
+ MemRegValueHome * Clone() const { return new MemRegValueHome(*this); };
+
+ // set the value of a remote enregistered value
+ virtual
+ void SetEnregisteredValue(MemoryRange newValue, DT_CONTEXT * pContext, bool fIsSigned);
+
+ // Gets an enregistered value and returns it to the caller
+ virtual
+ void GetEnregisteredValue(MemoryRange valueOutBuffer);
+
+ // initialize an instance of RemoteAddress for use in an IPC event buffer with values from this
+ // instance of a derived class of EnregisteredValueHome
+ virtual
+ void CopyToIPCEType(RemoteAddress * pRegAddr);
+
+}; // class MemRegValueHome;
+
+// class FloatRegValueHome
+// derived class to add an index into the FP register stack for a floating point value
+class FloatRegValueHome: public EnregisteredValueHome
+{
+public:
+ // initializing constructor
+ // Arguments:
+ // input: pFrame - frame to which the value belongs
+ // index - index into the floating point stack where the value resides
+ // output: no out parameters, but the instance has been initialized
+ FloatRegValueHome(const CordbNativeFrame * pFrame,
+ DWORD index):
+ EnregisteredValueHome(pFrame),
+ m_floatIndex(index)
+ {};
+
+ // copy constructor
+ // Arguments:
+ // input: pRemoteRegAddr - instance of a remote register address from which the values for this
+ // instance will come
+ // output: no out parameters, but the instance has been initialized
+ FloatRegValueHome(const FloatRegValueHome * pRemoteRegAddr):
+ EnregisteredValueHome(pRemoteRegAddr->m_pFrame),
+ m_floatIndex(pRemoteRegAddr->m_floatIndex)
+ {};
+
+ // make a copy of this instance of FloatRegValueHome
+ virtual
+ FloatRegValueHome * Clone() const { return new FloatRegValueHome(*this); };
+
+ // set the value of a remote enregistered value
+ virtual
+ void SetEnregisteredValue(MemoryRange newValue, DT_CONTEXT * pContext, bool fIsSigned);
+
+ // Gets an enregistered value and returns it to the caller
+ virtual
+ void GetEnregisteredValue(MemoryRange valueOutBuffer);
+
+ // initialize an instance of RemoteAddress for use in an IPC event buffer with values from this
+ // instance of a derived class of EnregisteredValueHome
+ virtual
+ void CopyToIPCEType(RemoteAddress * pRegAddr);
+
+ //-------------------------------------
+ // data members
+ //-------------------------------------
+
+protected:
+ // index into the FP registers for the register in which the floating point value resides
+ const DWORD m_floatIndex;
+ }; // class FloatRegValueHome
+
+// ----------------------------------------------------------------------------
+// Type hierarchy for value locations
+// ValueHome
+// | | |
+// ------------------ | -------------------
+// | | |
+// RemoteValueHome RegisterValueHome HandleValueHome
+// | |
+// -------- -------
+// | |
+// VCRemoteValueHome RefRemoteValueHome
+//
+// ValueHome: abstract base class, provides remote read and write utilities
+// RemoteValueHome: used for CordbObjectValue, CordbArrayValue, and CordbBoxValue instances,
+// which have only remote locations, and for other ICDValues with a remote address
+// RegisterValueHome: used for CordbGenericValue and CordbReferenceValue instances with
+// only a register location
+// HandleValueHome: used for CordbReferenceValue instances with only an object handle
+// VCRemoteValueHome: used for CordbVCObjectValue instances to supply special operation CreateInternalValue for
+// value class objects with only a remote location
+// RefRemoteValueHome: used for CordbReferenceValue instances with only a remote location
+//
+// In addition, we have a special type for the ValueHome field for CordbReferenceValue instances:
+// RefValueHome. This will have a field of type ValueHome and will implement extra operations only relevant
+// for object references.
+//
+// ----------------------------------------------------------------------------
+//
+class ValueHome
+{
+public:
+ ValueHome(CordbProcess * pProcess):
+ m_pProcess(pProcess) { _ASSERTE(pProcess != NULL); };
+
+ virtual
+ ~ValueHome() {}
+
+ // releases resources as necessary
+ virtual
+ void Clear() = 0;
+
+ // gets the remote address for the value or returns NULL if none exists
+ virtual
+ CORDB_ADDRESS GetAddress() = 0;
+
+ // Gets a value and returns it in dest
+ // Argument:
+ // input: none (uses fields of the instance)
+ // output: dest - buffer containing the value retrieved as long as the returned HRESULT doesn't
+ // indicate an error.
+ // Note: Throws errors from read process memory operation or GetThreadContext operation
+ virtual
+ void GetValue(MemoryRange dest) = 0;
+
+ // Sets a location to the value provided in src
+ // Arguments:
+ // input: src - buffer containing the new value to be set--memory for this buffer is owned by the caller
+ // pType - type information about the value
+ // output: none, but on success, changes m_remoteValue to hold the new value
+ // Note: Throws errors from SafeWriteBuffer
+ virtual
+ void SetValue(MemoryRange src, CordbType * pType) = 0;
+
+ // creates an ICDValue for a field or array element or for the value type of a boxed object
+ // Arguments:
+ // input: pType - type of the internal value
+ // offset - offset to the internal value
+ // localAddress - address of thelogical buffer within the parent class' local cached
+ // copy that holds the internal element
+ // size - size of the internal value
+ // output: ppValue - the newly created ICDValue instance
+ // Note: Throws for a variety of possible failures: OOM, E_FAIL, errors from
+ // ReadProcessMemory.
+ virtual
+ void CreateInternalValue(CordbType * pType,
+ SIZE_T offset,
+ void * localAddress,
+ ULONG32 size,
+ ICorDebugValue ** ppValue) = 0;
+
+ // Gets the value of a field or element of an existing ICDValue instance and returns it in dest
+ // Arguments
+ // input: offset - offset within the value to the internal field or element
+ // output: dest - buffer to hold the value--memory for this buffer is owned by the caller
+ // Note: Throws process memory write errors
+ virtual
+ void GetInternalValue(MemoryRange dest, SIZE_T offset) = 0;
+
+ // copies register information from this to a RemoteAddress instance for FuncEval
+ // Arguments:
+ // output: pRegAddr - copy of information in m_pRemoteRegAddr, converted to
+ // an instance of RemoteAddress
+ virtual
+ void CopyToIPCEType(RemoteAddress * pRegAddr) = 0;
+
+private:
+ // unimplemented copy constructor to prevent passing by value
+ ValueHome(ValueHome * pValHome);
+
+protected:
+ // --------------
+ // data member
+ // --------------
+ CordbProcess * m_pProcess;
+}; // class ValueHome
+
+// ============================================================================
+// RemoteValueHome class
+// ============================================================================
+// to be used for CordbObjectValue, CordbArrayValue, and CordbBoxValue, none of which ever have anything but
+// a remote address
+class RemoteValueHome: public ValueHome
+{
+public:
+ // constructor
+ // Note: It's possible that remoteValue.pAddress may be NULL--FuncEval makes
+ // empty GenericValues for literals in which case we would have neither a remote address nor a
+ // register address
+ RemoteValueHome(CordbProcess * pProcess, TargetBuffer remoteValue);
+
+ // gets the remote address for the value
+ virtual
+ CORDB_ADDRESS GetAddress() { return m_remoteValue.pAddress; };
+
+ // releases resources as necessary
+ virtual
+ void Clear() {};
+
+ // Gets a value and returns it in dest
+ virtual
+ void GetValue(MemoryRange dest);
+
+ // Sets a location to the value provided in src
+ virtual
+ void SetValue(MemoryRange src, CordbType * pType);
+
+ // creates an ICDValue for a field or array element or for the value type of a boxed object
+ virtual
+ void CreateInternalValue(CordbType * pType,
+ SIZE_T offset,
+ void * localAddress,
+ ULONG32 size,
+ ICorDebugValue ** ppValue);
+
+ // Gets the value of a field or element of an existing ICDValue instance and returns it in dest
+ virtual
+ void GetInternalValue(MemoryRange dest, SIZE_T offset);
+
+ // copies register information from this to a RemoteAddress instance for FuncEval
+ virtual
+ void CopyToIPCEType(RemoteAddress * pRegAddr);
+
+
+ // ----------------
+ // data member
+ // ----------------
+
+protected:
+ TargetBuffer m_remoteValue;
+}; // class RemoteValueHome
+
+// ============================================================================
+// RegisterValueHome class
+// ============================================================================
+// for values that may either have a remote location or be enregistered--
+// to be used for CordbGenericValue, and as base for CordbVCObjectValue and CordbReferenceValue
+class RegisterValueHome: public ValueHome
+{
+public:
+ // constructor
+ RegisterValueHome(CordbProcess * pProcess,
+ EnregisteredValueHomeHolder * ppRemoteRegAddr);
+
+ // clean up resources
+ virtual
+ void Clear();
+
+ // gets the remote address for the value or returns NULL if none exists
+ virtual
+ CORDB_ADDRESS GetAddress() { return NULL; };
+
+ // Gets a value and returns it in dest
+ virtual
+ void GetValue(MemoryRange dest);
+
+ // Sets a location to the value provided in src
+ virtual
+ void SetValue(MemoryRange src, CordbType * pType);
+
+ // creates an ICDValue for a field or array element or for the value type of a boxed object
+ virtual
+ void CreateInternalValue(CordbType * pType,
+ SIZE_T offset,
+ void * localAddress,
+ ULONG32 size,
+ ICorDebugValue ** ppValue);
+
+ // Gets the value of a field or element of an existing ICDValue instance and returns it in dest
+ virtual
+ void GetInternalValue(MemoryRange dest, SIZE_T offset);
+
+ // copies the register information from this to a RemoteAddress instance
+ virtual
+ void CopyToIPCEType(RemoteAddress * pRegAddr);
+
+protected:
+
+ // sets a remote enregistered location to a new value
+ void SetEnregisteredValue(MemoryRange src, bool fIsSigned);
+
+ // gets a value from an enregistered location
+ void GetEnregisteredValue(MemoryRange dest);
+
+ bool IsSigned(CorElementType elementType);
+
+ // ----------------
+ // data member
+ // ----------------
+
+protected:
+ // Left Side register location info for various kinds of (partly) enregistered values.
+ EnregisteredValueHome * m_pRemoteRegAddr;
+
+}; // class RegisterValueHome
+
+// ============================================================================
+// HandleValueHome class
+// ============================================================================
+
+class HandleValueHome: public ValueHome
+{
+public:
+ // constructor
+ // Arguments:
+ // input: pProcess - process to which the value belongs
+ // vmObjHandle - objectHandle holding the object address
+ HandleValueHome(CordbProcess * pProcess, VMPTR_OBJECTHANDLE vmObjHandle):
+ ValueHome(pProcess),
+ m_vmObjectHandle(vmObjHandle) {};
+
+ // releases resources as necessary
+ virtual
+ void Clear() {};
+
+ // gets the remote address for the value or returns NULL if none exists
+ virtual
+ CORDB_ADDRESS GetAddress();
+
+ // Gets a value and returns it in dest
+ virtual
+ void GetValue(MemoryRange dest);
+
+ // Sets a location to the value provided in src
+ virtual
+ void SetValue(MemoryRange src, CordbType * pType);
+
+ // creates an ICDValue for a field or array element or for the value type of a boxed object
+ virtual
+ void CreateInternalValue(CordbType * pType,
+ SIZE_T offset,
+ void * localAddress,
+ ULONG32 size,
+ ICorDebugValue ** ppValue);
+
+ // Gets the value of a field or element of an existing ICDValue instance and returns it in dest
+ virtual
+ void GetInternalValue(MemoryRange dest, SIZE_T offset);
+
+ // copies the register information from this to a RemoteAddress instance
+ virtual
+ void CopyToIPCEType(RemoteAddress * pRegAddr);
+
+ // ----------------
+ // data member
+ // ----------------
+private:
+ VMPTR_OBJECTHANDLE m_vmObjectHandle;
+}; // class HandleValueHome;
+
+// ============================================================================
+// VCRemoteValueHome class
+// ============================================================================
+// used only for CordbVCObjectValue
+class VCRemoteValueHome: public RemoteValueHome
+{
+public:
+ // constructor
+ VCRemoteValueHome(CordbProcess * pProcess,
+ TargetBuffer remoteValue):
+ RemoteValueHome(pProcess, remoteValue) {};
+
+ // Sets a location to the value provided in src
+ virtual
+ void SetValue(MemoryRange src, CordbType * pType);
+
+}; // class VCRemoteValueHome
+
+// ============================================================================
+// RefRemoteValueHome class
+// ============================================================================
+
+// used only for CordbReferenceValue
+class RefRemoteValueHome: public RemoteValueHome
+{
+public:
+ // constructor
+ // Arguments
+ RefRemoteValueHome(CordbProcess * pProcess,
+ TargetBuffer remoteValue);
+
+ // Sets a location to the value provided in src
+ virtual
+ void SetValue(MemoryRange src, CordbType * pType);
+
+}; // class RefRemoteValueHome
+
+// ============================================================================
+// RefValueHome class
+// ============================================================================
+
+// abstract superclass for derivations RefRemoteValueHome and RefRegValueHome
+class RefValueHome
+{
+public:
+ // constructor
+ RefValueHome() { m_pHome = NULL; m_fNullObjHandle = true; };
+
+ // constructor
+ RefValueHome(CordbProcess * pProcess,
+ TargetBuffer remoteValue,
+ EnregisteredValueHomeHolder * ppRemoteRegAddr,
+ VMPTR_OBJECTHANDLE vmObjHandle);
+
+ // indicates whether the object handle is null
+ bool ObjHandleIsNull() { return m_fNullObjHandle; };
+ void SetObjHandleFlag(bool isNull) { m_fNullObjHandle = isNull; };
+
+ // ----------------
+ // data members
+ // ----------------
+ // appropriate instantiation of ValueHome
+ ValueHome * m_pHome;
+
+private:
+ // true iff m_pHome is an instantiation of RemoteValueHome or RegisterValueHome
+ bool m_fNullObjHandle;
+}; // class RefValueHome
+
+typedef enum {kUnboxed, kBoxed} BoxedValue;
+#define EMPTY_BUFFER TargetBuffer(PTR_TO_CORDB_ADDRESS((void *)NULL), 0)
+
+// for an inheritance graph of the ICDValue types, // See file:./ICorDebugValueTypes.vsd for a diagram of the types.
+/* ------------------------------------------------------------------------- *
+ * Value class
+ * ------------------------------------------------------------------------- */
+
+class CordbValue : public CordbBase
+{
+public:
+ //-----------------------------------------------------------
+ // Constructor/destructor
+ //-----------------------------------------------------------
+ CordbValue(CordbAppDomain * appdomain,
+ CordbType * type,
+ CORDB_ADDRESS id,
+ bool isLiteral,
+ NeuterList * pList = NULL);
+
+ virtual ~CordbValue();
+ virtual void Neuter();
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugValue
+ //-----------------------------------------------------------
+
+ COM_METHOD GetType(CorElementType *pType)
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pType, CorElementType *);
+
+ *pType = m_type->m_elementType;
+ return (S_OK);
+ }
+
+ COM_METHOD GetSize(ULONG32 *pSize)
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pSize, ULONG32 *);
+
+ if (m_size > ULONG_MAX)
+ {
+ *pSize = ULONG_MAX;
+ return (COR_E_OVERFLOW);
+ }
+
+ *pSize = (ULONG)m_size;
+ return (S_OK);
+ }
+
+ COM_METHOD CreateBreakpoint(ICorDebugValueBreakpoint **ppBreakpoint);
+
+ //-----------------------------------------------------------
+ // ICorDebugValue2
+ //-----------------------------------------------------------
+
+ COM_METHOD GetExactType(ICorDebugType **ppType);
+
+ //-----------------------------------------------------------
+ // ICorDebugValue3
+ //-----------------------------------------------------------
+
+ COM_METHOD GetSize64(ULONG64 *pSize)
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pSize, ULONG64 *);
+
+ *pSize = m_size;
+ return (S_OK);
+ }
+
+ //-----------------------------------------------------------
+ // Methods not exported through COM
+ //-----------------------------------------------------------
+
+ // Helper for code:CordbValue::CreateValueByType. Create a new instance of CordbGenericValue
+ static
+ void CreateGenericValue(CordbAppDomain * pAppdomain,
+ CordbType * pType,
+ TargetBuffer remoteValue,
+ MemoryRange localValue,
+ EnregisteredValueHomeHolder * ppRemoteRegAddr,
+ ICorDebugValue** ppValue);
+
+ // Helper for code:CordbValue::CreateValueByType. Create a new instance of CordbVCObjectValue or
+ // CordbReferenceValue
+ static
+ void CreateVCObjOrRefValue(CordbAppDomain * pAppdomain,
+ CordbType * pType,
+ bool boxed,
+ TargetBuffer remoteValue,
+ MemoryRange localValue,
+ EnregisteredValueHomeHolder * ppRemoteRegAddr,
+ ICorDebugValue** ppValue);
+
+ // Create the proper ICDValue instance based on the given element type.
+ static void CreateValueByType(CordbAppDomain * appdomain,
+ CordbType * type,
+ bool boxed,
+ TargetBuffer remoteValue,
+ MemoryRange localValue,
+ EnregisteredValueHomeHolder * ppRemoteRegAddr,
+ ICorDebugValue** ppValue);
+
+ // Create the proper ICDValue instance based on the given remote heap object
+ static ICorDebugValue* CreateHeapValue(CordbAppDomain* pAppDomain,
+ VMPTR_Object vmObj);
+
+
+ // Returns a pointer to the ValueHome field of this instance of CordbValue if one exists or NULL
+ // otherwise. Therefore, this also tells us indirectly whether this instance of CordbValue is also an
+ // instance of one of its derived types and thus has a ValueHome field.
+ virtual
+ ValueHome * GetValueHome() { return NULL; };
+
+ static ULONG32 GetSizeForType(CordbType * pType, BoxedValue boxing);
+
+ virtual CordbAppDomain *GetAppDomain()
+ {
+ return m_appdomain;
+ }
+
+ HRESULT InternalCreateHandle(
+ CorDebugHandleType handleType,
+ ICorDebugHandleValue ** ppHandle);
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+public:
+ CordbAppDomain * m_appdomain;
+ RSSmartPtr<CordbType> m_type;
+
+ // size of the value
+ SIZE_T m_size;
+
+ // true if the value is a RS fabrication.
+ bool m_isLiteral;
+
+};
+
+/* ------------------------------------------------------------------------- *
+ * Value Breakpoint class
+ * ------------------------------------------------------------------------- */
+
+class CordbValueBreakpoint : public CordbBreakpoint,
+ public ICorDebugValueBreakpoint
+{
+public:
+ CordbValueBreakpoint(CordbValue *pValue);
+
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbValueBreakpoint"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugValueBreakpoint
+ //-----------------------------------------------------------
+
+ COM_METHOD GetValue(ICorDebugValue **ppValue);
+ COM_METHOD Activate(BOOL bActive);
+ COM_METHOD IsActive(BOOL *pbActive)
+ {
+ VALIDATE_POINTER_TO_OBJECT(pbActive, BOOL *);
+
+ return BaseIsActive(pbActive);
+ }
+
+ //-----------------------------------------------------------
+ // Non-COM methods
+ //-----------------------------------------------------------
+
+ void Disconnect();
+
+public:
+ CordbValue *m_value;
+};
+
+/* ------------------------------------------------------------------------- *
+ * Generic Value class
+ * ------------------------------------------------------------------------- */
+
+class CordbGenericValue : public CordbValue, public ICorDebugGenericValue, public ICorDebugValue2, public ICorDebugValue3
+{
+public:
+ CordbGenericValue(CordbAppDomain * appdomain,
+ CordbType * type,
+ TargetBuffer remoteValue,
+ EnregisteredValueHomeHolder * ppRemoteRegAddr);
+
+ CordbGenericValue(CordbType * pType);
+ // destructor
+ ~CordbGenericValue();
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbGenericValue"; }
+#endif
+
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugValue
+ //-----------------------------------------------------------
+
+ // gets the type of the value
+ // Arguments:
+ // output: pType - the type of the value. The caller must guarantee that pType is non-null.
+ // Return Value: S_OK on success, E_INVALIDARG on failure
+ COM_METHOD GetType(CorElementType *pType)
+ {
+ return (CordbValue::GetType(pType));
+ }
+
+ // gets the size of the value
+ // Arguments:
+ // output: pSize - the size of the value. The caller must guarantee that pSize is non-null.
+ // Return Value: S_OK on success, E_INVALIDARG on failure
+ COM_METHOD GetSize(ULONG32 *pSize)
+ {
+ return (CordbValue::GetSize(pSize));
+ }
+ COM_METHOD CreateBreakpoint(ICorDebugValueBreakpoint **ppBreakpoint)
+ {
+ return (CordbValue::CreateBreakpoint(ppBreakpoint));
+ }
+
+ // gets the remote (LS) address of the value. This may return NULL if the
+ // value is a literal or resides in a register.
+ // Arguments:
+ // output: pAddress - the address of the value. The caller must guarantee is
+ // non-Null
+ // Return Value: S_OK on success or E_INVALIDARG if pAddress is null
+ COM_METHOD GetAddress(CORDB_ADDRESS *pAddress)
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pAddress, CORDB_ADDRESS *);
+
+ *pAddress = m_pValueHome ? m_pValueHome->GetAddress() : NULL;
+ return (S_OK);
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugValue2
+ //-----------------------------------------------------------
+
+ COM_METHOD GetExactType(ICorDebugType **ppType)
+ {
+ return (CordbValue::GetExactType(ppType));
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugValue3
+ //-----------------------------------------------------------
+
+ COM_METHOD GetSize64(ULONG64 *pSize)
+ {
+ return (CordbValue::GetSize64(pSize));
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugGenericValue
+ //-----------------------------------------------------------
+
+ COM_METHOD GetValue(void *pTo);
+ COM_METHOD SetValue(void *pFrom);
+
+ //-----------------------------------------------------------
+ // Non-COM methods
+ //-----------------------------------------------------------
+
+ // initialize a generic value by copying the necessary data, either
+ // from the remote process or from another value in this process.
+ void Init(MemoryRange localValue);
+ bool CopyLiteralData(BYTE *pBuffer);
+
+ // Returns a pointer to the ValueHome field
+ virtual
+ ValueHome * GetValueHome() { return m_pValueHome; };
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+private:
+ // hold copies of up to 64-bit values.
+ BYTE m_pCopyOfData[8];
+
+ // location information--remote or register address
+ ValueHome * m_pValueHome;
+};
+
+
+/* ------------------------------------------------------------------------- *
+ * Reference Value class
+ * ------------------------------------------------------------------------- */
+
+class CordbReferenceValue : public CordbValue, public ICorDebugReferenceValue, public ICorDebugValue2, public ICorDebugValue3
+{
+public:
+ CordbReferenceValue(CordbAppDomain * pAppdomain,
+ CordbType * pType,
+ MemoryRange localValue,
+ TargetBuffer remoteValue,
+ EnregisteredValueHomeHolder * ppRegAddr,
+ VMPTR_OBJECTHANDLE vmObjectHandle);
+ CordbReferenceValue(CordbType * pType);
+ virtual ~CordbReferenceValue();
+ virtual void Neuter();
+
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbReferenceValue"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugValue
+ //-----------------------------------------------------------
+
+ COM_METHOD GetType(CorElementType *pType);
+
+ // get the size of the reference
+ // Arguments:
+ // output: pSize - the size of the value--this must be non-NULL
+ // Return Value: S_OK on success or E_INVALIDARG
+ COM_METHOD GetSize(ULONG32 *pSize)
+ {
+ return (CordbValue::GetSize(pSize));
+ }
+
+ COM_METHOD GetAddress(CORDB_ADDRESS *pAddress);
+ COM_METHOD CreateBreakpoint(ICorDebugValueBreakpoint **ppBreakpoint)
+ {
+ return (CordbValue::CreateBreakpoint(ppBreakpoint));
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugValue2
+ //-----------------------------------------------------------
+
+ COM_METHOD GetExactType(ICorDebugType **ppType)
+ {
+ return (CordbValue::GetExactType(ppType));
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugValue3
+ //-----------------------------------------------------------
+
+ COM_METHOD GetSize64(ULONG64 *pSize)
+ {
+ return (CordbValue::GetSize64(pSize));
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugReferenceValue
+ //-----------------------------------------------------------
+
+ COM_METHOD IsNull(BOOL * pfIsNull);
+ COM_METHOD GetValue(CORDB_ADDRESS *pAddress);
+ COM_METHOD SetValue(CORDB_ADDRESS address);
+ COM_METHOD Dereference(ICorDebugValue **ppValue);
+ COM_METHOD DereferenceStrong(ICorDebugValue **ppValue);
+
+ //-----------------------------------------------------------
+ // Non-COM methods
+ //-----------------------------------------------------------
+
+ // Helper function for SanityCheckPointer. Make an attempt to read memory at the address which is the
+ // value of the reference.
+ void TryDereferencingTarget();
+
+ // Do a sanity check on the pointer which is the value of the object reference. We can't efficiently
+ // ensure that the pointer is really good, so we settle for a quick check just to make sure the memory at
+ // the address is readable. We're actually just checking that we can dereference the pointer.
+ // If the address is invalid, this will throw.
+ void SanityCheckPointer (CorElementType type);
+
+ // get information about the reference when it's not an object address but another kind of pointer type:
+ // ELEMENT_TYPE_BYREF, ELEMENT_TYPE_PTR or ELEMENT_TYPE_FNPTR
+ void GetPointerData(CorElementType type, MemoryRange localValue);
+
+ // get basic object specific data when a reference points to an object, plus extra data if the object is
+ // an array or string
+ static
+ void GetObjectData(CordbProcess * pProcess,
+ void * objectAddress,
+ CorElementType type,
+ VMPTR_AppDomain vmAppdomain,
+ DebuggerIPCE_ObjectData * pInfo);
+
+ // get information about a TypedByRef object when the reference is the address of a TypedByRef structure.
+ static
+ void GetTypedByRefData(CordbProcess * pProcess,
+ CORDB_ADDRESS pTypedByRef,
+ CorElementType type,
+ VMPTR_AppDomain vmAppDomain,
+ DebuggerIPCE_ObjectData * pInfo);
+
+ // get the address of the object referenced
+ void * GetObjectAddress(MemoryRange localValue);
+
+ // update type information after initializing -- when we initialize, we may get more exact type
+ // information than we previously had
+ void UpdateTypeInfo();
+
+ // Initialize this CordbReferenceValue. This may involve inspecting the LS to get information about the
+ // referent.
+ HRESULT InitRef(MemoryRange localValue);
+
+ bool CopyLiteralData(BYTE *pBuffer);
+
+ static HRESULT Build(CordbAppDomain * appdomain,
+ CordbType * type,
+ TargetBuffer remoteValue,
+ MemoryRange localValue,
+ VMPTR_OBJECTHANDLE vmObjectHandle,
+ EnregisteredValueHomeHolder * ppRemoteRegAddr,
+ CordbReferenceValue** ppValue);
+
+ static HRESULT BuildFromGCHandle(CordbAppDomain *pAppDomain, VMPTR_OBJECTHANDLE gcHandle, ICorDebugReferenceValue ** pOutRef);
+
+ // Common dereference routine shared by both CordbReferenceValue + CordbHandleValue
+ static HRESULT DereferenceCommon(CordbAppDomain * pAppDomain,
+ CordbType * pType,
+ CordbType * pRealTypeOfTypedByref,
+ DebuggerIPCE_ObjectData * m_pInfo,
+ ICorDebugValue ** ppValue);
+
+ // Returns a pointer to the ValueHome field
+ virtual
+ ValueHome * GetValueHome() { return m_valueHome.m_pHome; };
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+public:
+ DebuggerIPCE_ObjectData m_info;
+ CordbType * m_realTypeOfTypedByref; // weak ref
+
+ RefValueHome m_valueHome;
+
+ // Indicates when we last syncronized our stored data (m_info) from the left side
+ UINT m_continueCounterLastSync;
+};
+
+/* ------------------------------------------------------------------------- *
+ * Object Value class
+ *
+ * Because of the oddness of string objects in the Runtime we have one
+ * object that implements both ObjectValue and StringValue. There is a
+ * definite string type, but its really just an object of the string
+ * class. Furthermore, you can have a variable whose type is listed as
+ * "class", but its an instance of the string class and therefore needs
+ * to be treated like a string.
+ * ------------------------------------------------------------------------- */
+
+class CordbObjectValue : public CordbValue,
+ public ICorDebugObjectValue,
+ public ICorDebugObjectValue2,
+ public ICorDebugGenericValue,
+ public ICorDebugStringValue,
+ public ICorDebugValue2,
+ public ICorDebugValue3,
+ public ICorDebugHeapValue2,
+ public ICorDebugHeapValue3,
+ public ICorDebugExceptionObjectValue,
+ public ICorDebugComObjectValue
+{
+public:
+
+ CordbObjectValue(CordbAppDomain * appdomain,
+ CordbType * type,
+ TargetBuffer remoteValue,
+ DebuggerIPCE_ObjectData * pObjectData );
+
+ virtual ~CordbObjectValue();
+
+
+ virtual void Neuter();
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbObjectValue"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void ** ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugValue
+ //-----------------------------------------------------------
+
+ COM_METHOD GetType(CorElementType * pType);
+ COM_METHOD GetSize(ULONG32 * pSize);
+ COM_METHOD GetAddress(CORDB_ADDRESS * pAddress);
+ COM_METHOD CreateBreakpoint(ICorDebugValueBreakpoint ** ppBreakpoint);
+
+ //-----------------------------------------------------------
+ // ICorDebugValue2
+ //-----------------------------------------------------------
+
+ COM_METHOD GetExactType(ICorDebugType ** ppType)
+ {
+ return (CordbValue::GetExactType(ppType));
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugValue3
+ //-----------------------------------------------------------
+
+ COM_METHOD GetSize64(ULONG64 *pSize);
+
+ //-----------------------------------------------------------
+ // ICorDebugHeapValue
+ //-----------------------------------------------------------
+
+ COM_METHOD IsValid(BOOL * pfIsValid);
+ COM_METHOD CreateRelocBreakpoint(ICorDebugValueBreakpoint ** ppBreakpoint);
+
+ //-----------------------------------------------------------
+ // ICorDebugHeapValue2
+ //-----------------------------------------------------------
+ COM_METHOD CreateHandle(CorDebugHandleType type, ICorDebugHandleValue ** ppHandle);
+
+ //-----------------------------------------------------------
+ // ICorDebugHeapValue3
+ //-----------------------------------------------------------
+ COM_METHOD GetThreadOwningMonitorLock(ICorDebugThread **ppThread, DWORD *pAcquisitionCount);
+ COM_METHOD GetMonitorEventWaitList(ICorDebugThreadEnum **ppThreadEnum);
+
+ //-----------------------------------------------------------
+ // ICorDebugObjectValue
+ //-----------------------------------------------------------
+
+ COM_METHOD GetClass(ICorDebugClass ** ppClass);
+ COM_METHOD GetFieldValue(ICorDebugClass * pClass,
+ mdFieldDef fieldDef,
+ ICorDebugValue ** ppValue);
+ COM_METHOD GetVirtualMethod(mdMemberRef memberRef,
+ ICorDebugFunction **ppFunction);
+ COM_METHOD GetContext(ICorDebugContext ** ppContext);
+ COM_METHOD IsValueClass(BOOL * pfIsValueClass);
+ COM_METHOD GetManagedCopy(IUnknown ** ppObject);
+ COM_METHOD SetFromManagedCopy(IUnknown * pObject);
+
+ COM_METHOD GetFieldValueForType(ICorDebugType * pType,
+ mdFieldDef fieldDef,
+ ICorDebugValue ** ppValue);
+
+ COM_METHOD GetVirtualMethodAndType(mdMemberRef memberRef,
+ ICorDebugFunction ** ppFunction,
+ ICorDebugType ** ppType);
+
+ //-----------------------------------------------------------
+ // ICorDebugGenericValue
+ //-----------------------------------------------------------
+
+ COM_METHOD GetValue(void * pTo);
+ COM_METHOD SetValue(void * pFrom);
+
+ //-----------------------------------------------------------
+ // ICorDebugStringValue
+ //-----------------------------------------------------------
+ COM_METHOD GetLength(ULONG32 * pcchString);
+ COM_METHOD GetString(ULONG32 cchString,
+ ULONG32 * ppcchStrin,
+ __out_ecount_opt(cchString) WCHAR szString[]);
+
+ //-----------------------------------------------------------
+ // ICorDebugExceptionObjectValue
+ //-----------------------------------------------------------
+ COM_METHOD EnumerateExceptionCallStack(ICorDebugExceptionObjectCallStackEnum** ppCallStackEnum);
+
+ //-----------------------------------------------------------
+ // ICorDebugComObjectValue
+ //-----------------------------------------------------------
+ COM_METHOD GetCachedInterfaceTypes(BOOL bIInspectableOnly,
+ ICorDebugTypeEnum** ppInterfacesEnum);
+
+ COM_METHOD GetCachedInterfacePointers(BOOL bIInspectableOnly,
+ ULONG32 celt,
+ ULONG32 *pcEltFetched,
+ CORDB_ADDRESS * ptrs);
+
+ //-----------------------------------------------------------
+ // Non-COM methods
+ //-----------------------------------------------------------
+
+ HRESULT Init();
+
+ DebuggerIPCE_ObjectData GetInfo() { return m_info; }
+ CordbHangingFieldTable * GetHangingFieldTable() { return &m_hangingFieldsInstance; }
+
+ // Returns a pointer to the ValueHome field
+ virtual
+ RemoteValueHome * GetValueHome() { return &m_valueHome; };
+
+protected:
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+ DebuggerIPCE_ObjectData m_info;
+ BYTE * m_pObjectCopy; // local cached copy of the object
+ BYTE * m_objectLocalVars; // var base in _this_ process
+ // points _into_ m_pObjectCopy
+ BYTE * m_stringBuffer; // points _into_ m_pObjectCopy
+
+ // remote location information
+ RemoteValueHome m_valueHome;
+
+ // If instances fields are added by EnC, their storage will be off the objects
+ // syncblock. Cache per-object information about such fields here.
+ CordbHangingFieldTable m_hangingFieldsInstance;
+
+private:
+ HRESULT IsExceptionObject();
+
+ BOOL m_fIsExceptionObject;
+
+ HRESULT IsRcw();
+
+ BOOL m_fIsRcw;
+};
+
+/* ------------------------------------------------------------------------- *
+ * Value Class Object Value class
+ * ------------------------------------------------------------------------- */
+
+class CordbVCObjectValue : public CordbValue,
+ public ICorDebugObjectValue, public ICorDebugObjectValue2,
+ public ICorDebugGenericValue, public ICorDebugValue2,
+ public ICorDebugValue3
+{
+public:
+ CordbVCObjectValue(CordbAppDomain * pAppdomain,
+ CordbType * pType,
+ TargetBuffer remoteValue,
+ EnregisteredValueHomeHolder * ppRemoteRegAddr);
+ virtual ~CordbVCObjectValue();
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbVCObjectValue"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugValue
+ //-----------------------------------------------------------
+
+ COM_METHOD GetType(CorElementType *pType);
+
+ COM_METHOD GetSize(ULONG32 *pSize)
+ {
+ return (CordbValue::GetSize(pSize));
+ }
+ COM_METHOD CreateBreakpoint(ICorDebugValueBreakpoint **ppBreakpoint)
+ {
+ return (CordbValue::CreateBreakpoint(ppBreakpoint));
+ }
+
+ COM_METHOD GetAddress(CORDB_ADDRESS *pAddress)
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pAddress, CORDB_ADDRESS *);
+
+ *pAddress = m_pValueHome->GetAddress();
+ return (S_OK);
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugValue2
+ //-----------------------------------------------------------
+
+ COM_METHOD GetExactType(ICorDebugType **ppType)
+ {
+ return (CordbValue::GetExactType(ppType));
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugValue3
+ //-----------------------------------------------------------
+
+ COM_METHOD GetSize64(ULONG64 *pSize)
+ {
+ return (CordbValue::GetSize64(pSize));
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugObjectValue
+ //-----------------------------------------------------------
+
+ COM_METHOD GetClass(ICorDebugClass **ppClass);
+ COM_METHOD GetFieldValue(ICorDebugClass *pClass,
+ mdFieldDef fieldDef,
+ ICorDebugValue **ppValue);
+ COM_METHOD GetVirtualMethod(mdMemberRef memberRef,
+ ICorDebugFunction **ppFunction);
+ COM_METHOD GetContext(ICorDebugContext **ppContext);
+ COM_METHOD IsValueClass(BOOL *pbIsValueClass);
+ COM_METHOD GetManagedCopy(IUnknown **ppObject);
+ COM_METHOD SetFromManagedCopy(IUnknown *pObject);
+ COM_METHOD GetFieldValueForType(ICorDebugType * pType,
+ mdFieldDef fieldDef,
+ ICorDebugValue ** ppValue);
+ COM_METHOD GetVirtualMethodAndType(mdMemberRef memberRef,
+ ICorDebugFunction **ppFunction,
+ ICorDebugType **ppType);
+
+ //-----------------------------------------------------------
+ // ICorDebugGenericValue
+ //-----------------------------------------------------------
+
+ COM_METHOD GetValue(void *pTo);
+ COM_METHOD SetValue(void *pFrom);
+
+ //-----------------------------------------------------------
+ // Non-COM methods
+ //-----------------------------------------------------------
+
+ // Initializes the Right-Side's representation of a Value Class object.
+ HRESULT Init(MemoryRange localValue);
+ //HRESULT ResolveValueClass();
+ CordbClass *GetClass();
+
+ // Returns a pointer to the ValueHome field
+ virtual
+ ValueHome * GetValueHome() { return m_pValueHome; };
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+private:
+
+ // local cached copy of the value class
+ BYTE * m_pObjectCopy;
+
+ // location information
+ ValueHome * m_pValueHome;
+};
+
+
+/* ------------------------------------------------------------------------- *
+ * Box Value class
+ * ------------------------------------------------------------------------- */
+
+class CordbBoxValue : public CordbValue,
+ public ICorDebugBoxValue,
+ public ICorDebugGenericValue,
+ public ICorDebugValue2,
+ public ICorDebugValue3,
+ public ICorDebugHeapValue2,
+ public ICorDebugHeapValue3
+{
+public:
+ CordbBoxValue(CordbAppDomain * appdomain,
+ CordbType * type,
+ TargetBuffer remoteValue,
+ ULONG32 size,
+ SIZE_T offsetToVars);
+ virtual ~CordbBoxValue();
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbBoxValue"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugValue
+ //-----------------------------------------------------------
+
+ COM_METHOD GetType(CorElementType *pType);
+
+ COM_METHOD GetSize(ULONG32 *pSize)
+ {
+ return (CordbValue::GetSize(pSize));
+ }
+ COM_METHOD CreateBreakpoint(ICorDebugValueBreakpoint **ppBreakpoint)
+ {
+ return (CordbValue::CreateBreakpoint(ppBreakpoint));
+ }
+
+ COM_METHOD GetAddress(CORDB_ADDRESS *pAddress)
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pAddress, CORDB_ADDRESS *);
+
+ *pAddress = m_valueHome.GetAddress();
+ return (S_OK);
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugValue2
+ //-----------------------------------------------------------
+
+ COM_METHOD GetExactType(ICorDebugType **ppType)
+ {
+ return (CordbValue::GetExactType(ppType));
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugValue3
+ //-----------------------------------------------------------
+
+ COM_METHOD GetSize64(ULONG64 *pSize)
+ {
+ return (CordbValue::GetSize64(pSize));
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugHeapValue
+ //-----------------------------------------------------------
+
+ COM_METHOD IsValid(BOOL *pbValid);
+ COM_METHOD CreateRelocBreakpoint(ICorDebugValueBreakpoint **ppBreakpoint);
+
+ //-----------------------------------------------------------
+ // ICorDebugHeapValue2
+ //-----------------------------------------------------------
+ COM_METHOD CreateHandle(CorDebugHandleType type, ICorDebugHandleValue ** ppHandle);
+
+ //-----------------------------------------------------------
+ // ICorDebugHeapValue3
+ //-----------------------------------------------------------
+ COM_METHOD GetThreadOwningMonitorLock(ICorDebugThread **ppThread, DWORD *pAcquisitionCount);
+ COM_METHOD GetMonitorEventWaitList(ICorDebugThreadEnum **ppThreadEnum);
+
+ //-----------------------------------------------------------
+ // ICorDebugGenericValue
+ //-----------------------------------------------------------
+
+ COM_METHOD GetValue(void *pTo);
+ COM_METHOD SetValue(void *pFrom);
+
+ //-----------------------------------------------------------
+ // ICorDebugBoxValue
+ //-----------------------------------------------------------
+ COM_METHOD GetObject(ICorDebugObjectValue **ppObject);
+
+ // Returns a pointer to the ValueHome field
+ virtual
+ RemoteValueHome * GetValueHome() { return &m_valueHome; };
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+private:
+ SIZE_T m_offsetToVars;
+
+ // remote location information
+ RemoteValueHome m_valueHome;
+
+};
+
+/* ------------------------------------------------------------------------- *
+ * Array Value class
+ * ------------------------------------------------------------------------- */
+
+class CordbArrayValue : public CordbValue,
+ public ICorDebugArrayValue,
+ public ICorDebugGenericValue,
+ public ICorDebugValue2,
+ public ICorDebugValue3,
+ public ICorDebugHeapValue2,
+ public ICorDebugHeapValue3
+{
+public:
+ CordbArrayValue(CordbAppDomain * appdomain,
+ CordbType * type,
+ DebuggerIPCE_ObjectData * pObjectInfo,
+ TargetBuffer remoteValue);
+ virtual ~CordbArrayValue();
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbArrayValue"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugValue
+ //-----------------------------------------------------------
+
+ COM_METHOD GetType(CorElementType *pType)
+ {
+ return (CordbValue::GetType(pType));
+ }
+ COM_METHOD GetSize(ULONG32 *pSize)
+ {
+ return (CordbValue::GetSize(pSize));
+ }
+ COM_METHOD GetAddress(CORDB_ADDRESS *pAddress)
+ {
+ VALIDATE_POINTER_TO_OBJECT(pAddress, CORDB_ADDRESS *);
+ *pAddress = m_valueHome.GetAddress();
+ return (S_OK);
+ }
+ COM_METHOD CreateBreakpoint(ICorDebugValueBreakpoint **ppBreakpoint)
+ {
+ return (CordbValue::CreateBreakpoint(ppBreakpoint));
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugValue2
+ //-----------------------------------------------------------
+
+ COM_METHOD GetExactType(ICorDebugType **ppType)
+ {
+ return (CordbValue::GetExactType(ppType));
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugValue3
+ //-----------------------------------------------------------
+
+ COM_METHOD GetSize64(ULONG64 *pSize)
+ {
+ return (CordbValue::GetSize64(pSize));
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugHeapValue
+ //-----------------------------------------------------------
+
+ COM_METHOD IsValid(BOOL *pbValid);
+ COM_METHOD CreateRelocBreakpoint(ICorDebugValueBreakpoint **ppBreakpoint);
+
+ //-----------------------------------------------------------
+ // ICorDebugHeapValue2
+ //-----------------------------------------------------------
+ COM_METHOD CreateHandle(CorDebugHandleType type, ICorDebugHandleValue ** ppHandle);
+
+ //-----------------------------------------------------------
+ // ICorDebugHeapValue3
+ //-----------------------------------------------------------
+ COM_METHOD GetThreadOwningMonitorLock(ICorDebugThread **ppThread, DWORD *pAcquisitionCount);
+ COM_METHOD GetMonitorEventWaitList(ICorDebugThreadEnum **ppThreadEnum);
+
+ //-----------------------------------------------------------
+ // ICorDebugArrayValue
+ //-----------------------------------------------------------
+
+ COM_METHOD GetElementType(CorElementType * pType);
+ COM_METHOD GetRank(ULONG32 * pnRank);
+ COM_METHOD GetCount(ULONG32 * pnCount);
+ COM_METHOD GetDimensions(ULONG32 cdim, ULONG32 dims[]);
+ COM_METHOD HasBaseIndicies(BOOL * pbHasBaseIndices);
+ COM_METHOD GetBaseIndicies(ULONG32 cdim, ULONG32 indices[]);
+ COM_METHOD GetElement(ULONG32 cdim, ULONG32 indices[], ICorDebugValue ** ppValue);
+ COM_METHOD GetElementAtPosition(ULONG32 nIndex, ICorDebugValue ** ppValue);
+
+ //-----------------------------------------------------------
+ // ICorDebugGenericValue
+ //-----------------------------------------------------------
+
+ COM_METHOD GetValue(void *pTo);
+ COM_METHOD SetValue(void *pFrom);
+
+ //-----------------------------------------------------------
+ // Non-COM methods
+ //-----------------------------------------------------------
+
+ HRESULT Init();
+
+ // Returns a pointer to the ValueHome field
+ virtual
+ RemoteValueHome * GetValueHome() { return &m_valueHome; };
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+private:
+ // contains information about the array, such as rank, number of elements, element size, etc.
+ DebuggerIPCE_ObjectData m_info;
+
+ // type of the elements
+ CordbType *m_elemtype;
+
+ // consists of three parts: a vector containing the lower bounds for each dimension,
+ // a vector containing the upper bounds for each dimension,
+ // a local cached copy of (part of) the array--initialized lazily when we
+ // request a particular element. If the array is large, we will store only
+ // part of it, swapping out the cached segment as necessary to retrieve
+ // requested elements.
+ BYTE * m_pObjectCopy;
+
+ // points to the beginning of the vector containing the lower bounds for each dimension in m_pObjectCopy
+ DWORD * m_arrayLowerBase;
+
+ // points to the beginning of the vector containing the lower bounds for each dimension in m_pObjectCopy
+ DWORD * m_arrayUpperBase;
+ // index of lower bound of data currently stored in m_pObjectCopy
+ SIZE_T m_idxLower;
+
+ // index of upper bound of data currently stored in m_pObjectCopy
+ SIZE_T m_idxUpper;
+
+ // remote location information
+ RemoteValueHome m_valueHome;
+
+};
+
+class CordbHandleValue : public CordbValue, public ICorDebugHandleValue, public ICorDebugValue2, public ICorDebugValue3
+{
+public:
+ CordbHandleValue(CordbAppDomain *appdomain,
+ CordbType *type,
+ CorDebugHandleType handleType);
+ HRESULT Init(VMPTR_OBJECTHANDLE pHandle);
+
+ virtual ~CordbHandleValue();
+
+ virtual void Neuter();
+ virtual void NeuterLeftSideResources();
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbHandleValue"; }
+#endif
+
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugHandleValue interface
+ //-----------------------------------------------------------
+ COM_METHOD GetHandleType(CorDebugHandleType *pType);
+
+
+ /*
+ * The final release of the interface will also dispose of the handle. This
+ * API provides the ability for client to early dispose the handle.
+ *
+ */
+ COM_METHOD Dispose();
+
+ //-----------------------------------------------------------
+ // ICorDebugValue interface
+ //-----------------------------------------------------------
+ COM_METHOD GetType(CorElementType *pType);
+ COM_METHOD GetSize(ULONG32 *pSize);
+ COM_METHOD GetAddress(CORDB_ADDRESS *pAddress);
+ COM_METHOD CreateBreakpoint(ICorDebugValueBreakpoint **ppBreakpoint);
+
+ //-----------------------------------------------------------
+ // ICorDebugValue2
+ //-----------------------------------------------------------
+
+ COM_METHOD GetExactType(ICorDebugType **ppType)
+ {
+ FAIL_IF_NEUTERED(this);
+
+ // If AppDomain is already unloaded, return error
+ if (m_appdomain->IsNeutered() == TRUE)
+ {
+ return COR_E_APPDOMAINUNLOADED;
+ }
+ if (m_vmHandle.IsNull())
+ {
+ return CORDBG_E_HANDLE_HAS_BEEN_DISPOSED;
+ }
+
+ return (CordbValue::GetExactType(ppType));
+ }
+
+ //-----------------------------------------------------------
+ // ICorDebugValue3
+ //-----------------------------------------------------------
+
+ COM_METHOD GetSize64(ULONG64 *pSize);
+
+ //-----------------------------------------------------------
+ // ICorDebugReferenceValue interface
+ //-----------------------------------------------------------
+
+ COM_METHOD IsNull(BOOL *pbNull);
+ COM_METHOD GetValue(CORDB_ADDRESS *pValue);
+ COM_METHOD SetValue(CORDB_ADDRESS value);
+ COM_METHOD Dereference(ICorDebugValue **ppValue);
+ COM_METHOD DereferenceStrong(ICorDebugValue **ppValue);
+
+ //-----------------------------------------------------------
+ // Non-COM methods
+ //-----------------------------------------------------------
+
+ // Returns a pointer to the ValueHome field
+ virtual
+ RemoteValueHome * GetValueHome() { return NULL; };
+
+private:
+ //BOOL RefreshHandleValue(void **pObjectToken);
+ HRESULT RefreshHandleValue();
+
+ // EE object handle pointer. Can be casted to OBJECTHANDLE when go to LS
+ // This instance owns the handle object and must call into the VM to release
+ // it.
+ // If this is non-null, then we increment code:CordbProces::IncrementOutstandingHandles.
+ // Once it goes null, we should decrement the count.
+ // Use AssignHandle, ClearHandle to keep this in sync.
+ VMPTR_OBJECTHANDLE m_vmHandle;
+
+
+ void AssignHandle(VMPTR_OBJECTHANDLE handle);
+ void ClearHandle();
+
+ BOOL m_fCanBeValid; // true if object "can" be valid. False when object is no longer valid.
+ CorDebugHandleType m_handleType; // handle type can be strong or weak
+ DebuggerIPCE_ObjectData m_info;
+; // ICORDebugClass of this object when we create the handle
+};
+
+// This class actually has the implementation for ICorDebugHeap3 interfaces. Any value which implements
+// the interface just delegates to these static calls.
+class CordbHeapValue3Impl
+{
+public:
+ static HRESULT GetThreadOwningMonitorLock(CordbProcess* pProcess,
+ CORDB_ADDRESS remoteObjAddress,
+ ICorDebugThread **ppThread,
+ DWORD *pAcquistionCount);
+ static HRESULT GetMonitorEventWaitList(CordbProcess* pProcess,
+ CORDB_ADDRESS remoteObjAddress,
+ ICorDebugThreadEnum **ppThreadEnum);
+};
+
+/* ------------------------------------------------------------------------- *
+ * Eval class
+ * ------------------------------------------------------------------------- */
+
+class CordbEval : public CordbBase, public ICorDebugEval, public ICorDebugEval2
+{
+public:
+ CordbEval(CordbThread* pThread);
+ virtual ~CordbEval();
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbEval"; }
+#endif
+
+ virtual void Neuter();
+ virtual void NeuterLeftSideResources();
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorDebugEval
+ //-----------------------------------------------------------
+
+ COM_METHOD CallFunction(ICorDebugFunction *pFunction,
+ ULONG32 nArgs,
+ ICorDebugValue *ppArgs[]);
+ COM_METHOD NewObject(ICorDebugFunction *pConstructor,
+ ULONG32 nArgs,
+ ICorDebugValue *ppArgs[]);
+ COM_METHOD NewObjectNoConstructor(ICorDebugClass *pClass);
+ COM_METHOD NewString(LPCWSTR string);
+ COM_METHOD NewArray(CorElementType elementType,
+ ICorDebugClass *pElementClass,
+ ULONG32 rank,
+ ULONG32 dims[],
+ ULONG32 lowBounds[]);
+ COM_METHOD IsActive(BOOL *pbActive);
+ COM_METHOD Abort();
+ COM_METHOD GetResult(ICorDebugValue **ppResult);
+ COM_METHOD GetThread(ICorDebugThread **ppThread);
+ COM_METHOD CreateValue(CorElementType elementType,
+ ICorDebugClass *pElementClass,
+ ICorDebugValue **ppValue);
+ COM_METHOD NewStringWithLength(LPCWSTR wszString, UINT iLength);
+
+ COM_METHOD CallParameterizedFunction(ICorDebugFunction * pFunction,
+ ULONG32 nTypeArgs,
+ ICorDebugType * rgpTypeArgs[],
+ ULONG32 nArgs,
+ ICorDebugValue * rgpArgs[]);
+
+ COM_METHOD CreateValueForType(ICorDebugType *pType,
+ ICorDebugValue **ppValue);
+
+ COM_METHOD NewParameterizedObject(ICorDebugFunction * pConstructor,
+ ULONG32 nTypeArgs,
+ ICorDebugType * rgpTypeArgs[],
+ ULONG32 nArgs,
+ ICorDebugValue * rgpArgs[]);
+
+ COM_METHOD NewParameterizedObjectNoConstructor(ICorDebugClass * pClass,
+ ULONG32 nTypeArgs,
+ ICorDebugType * rgpTypeArgs[]);
+
+ COM_METHOD NewParameterizedArray(ICorDebugType * pElementType,
+ ULONG32 rank,
+ ULONG32 dims[],
+ ULONG32 lowBounds[]);
+
+ //-----------------------------------------------------------
+ // ICorDebugEval2
+ //-----------------------------------------------------------
+
+ COM_METHOD RudeAbort();
+
+ //-----------------------------------------------------------
+ // Non-COM methods
+ //-----------------------------------------------------------
+ HRESULT GatherArgInfo(ICorDebugValue *pValue,
+ DebuggerIPCE_FuncEvalArgData *argData);
+ HRESULT SendCleanup();
+
+ // Create a RS literal for primitive type funceval result. In case the result is used as an argument for
+ // another funceval, we need to make sure that we're not relying on the LS value, which will be freed and
+ // thus unavailable.
+ HRESULT CreatePrimitiveLiteral(CordbType * pType,
+ ICorDebugValue ** ppValue);
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+
+ bool IsEvalDuringException() { return m_evalDuringException; }
+private:
+ // We must keep a strong reference to the thread so we can properly fail out of SendCleanup if someone releases an
+ // ICorDebugEval after the process has completely gone away.
+ RSSmartPtr<CordbThread> m_thread;
+
+ CordbFunction *m_function;
+ CordbClass *m_class;
+ DebuggerIPCE_FuncEvalType m_evalType;
+
+ HRESULT SendFuncEval(unsigned int genericArgsCount, ICorDebugType *genericArgs[], void *argData1, unsigned int argData1Size, void *argData2, unsigned int argData2Size, DebuggerIPCEvent * event);
+ HRESULT FilterHR(HRESULT hr);
+ BOOL DoAppDomainsMatch( CordbAppDomain* pAppDomain, ULONG32 nTypes, ICorDebugType *pTypes[], ULONG32 nValues, ICorDebugValue *pValues[] );
+
+public:
+ bool m_complete;
+ bool m_successful;
+ bool m_aborted;
+ void *m_resultAddr;
+
+ // This is an OBJECTHANDLE on the LS if func-eval creates a strong handle.
+ // This is a resource in the left-side and must be cleaned up in the left-side.
+ // This gets handled off to a CordbHandleValue (m_pHandleValue) once code:CordbEval::GetResult
+ // and then the CordbHandle is responsible for releasing it in the left-side.
+ // Issue!! This will be leaked if nobody calls GetResult().
+ VMPTR_OBJECTHANDLE m_vmObjectHandle;
+
+ // This is the corresponding cached CordbHandleValue for GetResult.
+ // This takes ownership of the strong handle, m_objectHandle.
+ // This is an External reference, which keeps the Value from being neutered
+ // on a NeuterAtWill sweep.
+ RSExtSmartPtr<CordbHandleValue> m_pHandleValue;
+
+ DebuggerIPCE_ExpandedTypeData m_resultType;
+ VMPTR_AppDomain m_resultAppDomainToken;
+
+ // Left-side memory that needs to be freed.
+ LSPTR_DEBUGGEREVAL m_debuggerEvalKey;
+
+
+ // If we're evalling during a thread's exception, remember the info so that we can restore it when we're done.
+ bool m_evalDuringException; // flag whether we're during the thread's exception.
+ VMPTR_OBJECTHANDLE m_vmThreadOldExceptionHandle; // object handle for thread's managed exception object.
+
+#ifdef _DEBUG
+ // Func-eval should perturb the the thread's current appdomain. So we remember it at start
+ // and then ensure that the func-eval complete restores it.
+ CordbAppDomain * m_DbgAppDomainStarted;
+#endif
+};
+
+
+/* ------------------------------------------------------------------------- *
+ * Win32 Event Thread class
+ * ------------------------------------------------------------------------- */
+const unsigned int CW32ET_UNKNOWN_PROCESS_SLOT = 0xFFffFFff; // it's a managed process,
+ //but we don't know which slot it's in - for Detach.
+
+//---------------------------------------------------------------------------------------
+//
+// Dedicated thread for win32 debugging operations.
+//
+// Notes:
+// This is owned by the ShimProcess object. That will both create this and destroy it.
+// OS restriction is that all win32 debugging APIs (CreateProcess, DebugActiveProcess,
+// DebugActiveProcessStop, WaitForDebugEvent, ContinueDebugEvent, etc) are on the same thread.
+//
+class CordbWin32EventThread
+{
+ friend class CordbProcess; //so that Detach can call ExitProcess
+public:
+ CordbWin32EventThread(Cordb * pCordb, ShimProcess * pShim);
+ virtual ~CordbWin32EventThread();
+
+ //
+ // You create a new instance of this class, call Init() to set it up,
+ // then call Start() start processing events. Stop() terminates the
+ // thread and deleting the instance cleans all the handles and such
+ // up.
+ //
+ HRESULT Init();
+ HRESULT Start();
+ HRESULT Stop();
+
+ HRESULT SendCreateProcessEvent(MachineInfo machineInfo,
+ LPCWSTR programName,
+ __in_z LPWSTR programArgs,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ PVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation,
+ CorDebugCreateProcessFlags corDebugFlags);
+
+ HRESULT SendDebugActiveProcessEvent(MachineInfo machineInfo,
+ DWORD pid,
+ bool fWin32Attach,
+ CordbProcess *pProcess);
+
+ HRESULT SendDetachProcessEvent(CordbProcess *pProcess);
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ HRESULT SendUnmanagedContinue(CordbProcess *pProcess,
+ EUMContinueType eContType);
+ HRESULT UnmanagedContinue(CordbProcess *pProcess,
+ EUMContinueType eContType);
+ void DoDbgContinue(CordbProcess * pProcess,
+ CordbUnmanagedEvent * pUnmanagedEvent);
+ void ForceDbgContinue(CordbProcess *pProcess,
+ CordbUnmanagedThread *ut,
+ DWORD contType,
+ bool contProcess);
+
+#endif //FEATURE_INTEROP_DEBUGGING
+
+ void LockSendToWin32EventThreadMutex()
+ {
+ LOG((LF_CORDB, LL_INFO10000, "W32ET::LockSendToWin32EventThreadMutex\n"));
+ m_sendToWin32EventThreadMutex.Lock();
+ }
+
+ void UnlockSendToWin32EventThreadMutex()
+ {
+ m_sendToWin32EventThreadMutex.Unlock();
+ LOG((LF_CORDB, LL_INFO10000, "W32ET::UnlockSendToWin32EventThreadMutex\n"));
+ }
+
+ bool IsWin32EventThread()
+ {
+ return (m_threadId == GetCurrentThreadId());
+ }
+
+ void Win32EventLoop();
+
+
+ INativeEventPipeline * GetNativePipeline();
+private:
+ void ThreadProc();
+ static DWORD WINAPI ThreadProc(LPVOID parameter);
+
+ void CreateProcess();
+
+
+ INativeEventPipeline * m_pNativePipeline;
+
+
+ void AttachProcess();
+
+ void HandleUnmanagedContinue();
+
+ void ExitProcess(bool fDetach);
+
+private:
+ RSSmartPtr<Cordb> m_cordb;
+
+ HANDLE m_thread;
+ DWORD m_threadId;
+ HANDLE m_threadControlEvent;
+ HANDLE m_actionTakenEvent;
+ BOOL m_run;
+
+ // The process that we're 1:1 with.
+ // This is set when we get a Create / Attach event.
+ // This is only used on the W32ET, which guarantees it will free of races.
+ RSSmartPtr<CordbProcess> m_pProcess;
+
+
+ ShimProcess * m_pShim;
+
+ // @todo - convert this into Stop-Go lock?
+ RSLock m_sendToWin32EventThreadMutex;
+
+ unsigned int m_action;
+ HRESULT m_actionResult;
+ union
+ {
+ struct
+ {
+ MachineInfo machineInfo;
+ LPCWSTR programName;
+ LPWSTR programArgs;
+ LPSECURITY_ATTRIBUTES lpProcessAttributes;
+ LPSECURITY_ATTRIBUTES lpThreadAttributes;
+ BOOL bInheritHandles;
+ DWORD dwCreationFlags;
+ PVOID lpEnvironment;
+ LPCWSTR lpCurrentDirectory;
+ LPSTARTUPINFOW lpStartupInfo;
+ LPPROCESS_INFORMATION lpProcessInformation;
+ CorDebugCreateProcessFlags corDebugFlags;
+ } createData;
+
+ struct
+ {
+ MachineInfo machineInfo;
+ DWORD processId;
+#if !defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ bool fWin32Attach;
+#endif
+ CordbProcess *pProcess;
+
+ // Wrapper to determine if we're interop-debugging.
+ bool IsInteropDebugging()
+ {
+#if !defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ return fWin32Attach;
+#else
+ return false;
+#endif
+ }
+ } attachData;
+
+ struct
+ {
+ CordbProcess *pProcess;
+ } detachData;
+
+ struct
+ {
+ CordbProcess *process;
+ EUMContinueType eContType;
+ } continueData;
+ } m_actionData;
+};
+
+
+// Thread-safe stack which.
+template <typename T>
+class InterlockedStack
+{
+public:
+ InterlockedStack();
+ ~InterlockedStack();
+
+ // Thread safe pushes + pops.
+ // Many threads can push simultaneously.
+ // Only 1 thread can pop.
+ void Push(T * pItem);
+ T * Pop();
+
+protected:
+ T * m_pHead;
+};
+
+//-----------------------------------------------------------------------------
+// Workitem to be placed on RCET worker queue.
+// There's 1 RCET for to be shared by all processes.
+//-----------------------------------------------------------------------------
+class RCETWorkItem
+{
+public:
+
+ virtual ~RCETWorkItem() {}
+
+ // Item is executed and then removed from the list and deleted.
+ virtual void Do() = 0;
+
+ CordbProcess * GetProcess() { return m_pProcess; }
+
+protected:
+ RCETWorkItem(CordbProcess * pProcess)
+ {
+ m_pProcess.Assign(pProcess);
+ m_next = NULL;
+ }
+
+ RSSmartPtr<CordbProcess> m_pProcess;
+
+ // This field is accessed by the InterlockedStack.
+ friend class InterlockedStack<RCETWorkItem>;
+ RCETWorkItem * m_next;
+};
+
+
+// Item to do Neutering work on ExitProcess.
+class ExitProcessWorkItem : public RCETWorkItem
+{
+public:
+ ExitProcessWorkItem(CordbProcess * pProc) : RCETWorkItem(pProc)
+ {
+ }
+
+ virtual void Do();
+};
+
+// Item to do send Attach event.
+class SendAttachProcessWorkItem : public RCETWorkItem
+{
+public:
+ SendAttachProcessWorkItem(CordbProcess * pProc) : RCETWorkItem(pProc)
+ {
+ }
+
+ virtual void Do();
+};
+
+
+/* ------------------------------------------------------------------------- *
+ * Runtime Controller Event Thread class
+ * ------------------------------------------------------------------------- */
+
+class CordbRCEventThread
+{
+public:
+ CordbRCEventThread(Cordb* cordb);
+ virtual ~CordbRCEventThread();
+
+ //
+ // You create a new instance of this class, call Init() to set it up,
+ // then call Start() start processing events. Stop() terminates the
+ // thread and deleting the instance cleans all the handles and such
+ // up.
+ //
+ HRESULT Init();
+ HRESULT Start();
+ HRESULT Stop();
+
+ // RCET will take ownership of this item and delete it.
+ void QueueAsyncWorkItem(RCETWorkItem * pItem);
+
+ HRESULT SendIPCEvent(CordbProcess* process,
+ DebuggerIPCEvent* event,
+ SIZE_T eventSize);
+
+ void ProcessStateChanged();
+ void FlushQueuedEvents(CordbProcess* process);
+
+ HRESULT WaitForIPCEventFromProcess(CordbProcess* process,
+ CordbAppDomain *pAppDomain,
+ DebuggerIPCEvent* event);
+
+ bool IsRCEventThread();
+
+private:
+ void DrainWorkerQueue();
+
+ void ThreadProc();
+ static DWORD WINAPI ThreadProc(LPVOID parameter);
+
+
+private:
+ InterlockedStack<class RCETWorkItem> m_WorkerStack;
+
+ RSSmartPtr<Cordb> m_cordb;
+ HANDLE m_thread;
+ DWORD m_threadId;
+ BOOL m_run;
+ HANDLE m_threadControlEvent;
+ BOOL m_processStateChanged;
+};
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+/* ------------------------------------------------------------------------- *
+ * Unmanaged Event struct
+ * ------------------------------------------------------------------------- */
+
+enum CordbUnmanagedEventState
+{
+
+ // The continued flags get set in one of a few patterns.
+ // 1) The event is continued having never been hijacked =>
+ // EventContinuedUnhijacked is set
+ // 2) The event is continued having been hijacked and then the process terminates or
+ // an error occurs before the hijack finishes =>
+ // EventContinuedHijacked is set
+ // 3) The event is continued having been hijacked, then the hijack completes and
+ // execution resumes in the debuggee
+ // EventContinuedHijacked is set
+ // EventContinuedUnhijacked is set
+
+ CUES_None = 0x00,
+ CUES_ExceptionCleared = 0x01,
+ CUES_EventContinuedHijacked = 0x02,
+ CUES_EventContinuedUnhijacked = 0x04,
+ CUES_Dispatched = 0x08,
+ CUES_ExceptionUnclearable = 0x10,
+
+ // This is set when a user continues the event by calling
+ // Continue()
+ CUES_UserContinued = 0x20,
+ // This is true if the event is an IB event
+ CUES_IsIBEvent = 0x40,
+};
+
+struct CordbUnmanagedEvent
+{
+public:
+ BOOL IsExceptionCleared() { return m_state & CUES_ExceptionCleared; }
+ BOOL IsEventContinuedHijacked() { return m_state & CUES_EventContinuedHijacked; }
+ BOOL IsEventContinuedUnhijacked() { return m_state & CUES_EventContinuedUnhijacked; }
+ BOOL IsEventUserContinued() { return m_state & CUES_UserContinued; }
+ BOOL IsEventWaitingForContinue()
+ {
+ return (!IsEventContinuedHijacked() && !IsEventContinuedUnhijacked());
+ }
+ BOOL IsDispatched() { return m_state & CUES_Dispatched; }
+ BOOL IsExceptionUnclearable() { return m_state & CUES_ExceptionUnclearable; }
+ BOOL IsIBEvent() { return m_state & CUES_IsIBEvent; }
+
+ void SetState(CordbUnmanagedEventState state) { m_state = (CordbUnmanagedEventState)(m_state | state); }
+ void ClearState(CordbUnmanagedEventState state) { m_state = (CordbUnmanagedEventState)(m_state & ~state); }
+
+ CordbUnmanagedThread *m_owner;
+ CordbUnmanagedEventState m_state;
+ DEBUG_EVENT m_currentDebugEvent;
+ CordbUnmanagedEvent *m_next;
+};
+
+
+/* ------------------------------------------------------------------------- *
+ * Unmanaged Thread class
+ * ------------------------------------------------------------------------- */
+
+enum CordbUnmanagedThreadState
+{
+ CUTS_None = 0x0000,
+ CUTS_Deleted = 0x0001,
+ CUTS_FirstChanceHijacked = 0x0002,
+ // Set when interop debugging needs the SS flag to be enabled
+ // regardless of what the user wants it to be
+ CUTS_IsSSFlagNeeded = 0x0004,
+ CUTS_GenericHijacked = 0x0008,
+ // when the m_raiseExceptionEntryContext is valid
+ CUTS_HasRaiseExceptionEntryCtx = 0x0010,
+ CUTS_BlockingForSync = 0x0020,
+ CUTS_Suspended = 0x0040,
+ CUTS_IsSpecialDebuggerThread = 0x0080,
+ // when the thread is re-executing RaiseException to retrigger an exception
+ CUTS_IsRaiseExceptionHijacked = 0x0100,
+ CUTS_HasIBEvent = 0x0200,
+ CUTS_HasOOBEvent = 0x0400,
+ CUTS_HasSpecialStackOverflowCase = 0x0800,
+#ifdef _DEBUG
+ CUTS_DEBUG_SingleStep = 0x1000,
+#endif
+ CUTS_SkippingNativePatch = 0x2000,
+ CUTS_HasContextSet = 0x4000,
+ // Set when interop debugging is making use of the single step flag
+ // but the user has not set it
+ CUTS_IsSSFlagHidden = 0x8000
+
+};
+
+class CordbUnmanagedThread : public CordbBase
+{
+public:
+ CordbUnmanagedThread(CordbProcess *pProcess, DWORD dwThreadId, HANDLE hThread, void *lpThreadLocalBase);
+ ~CordbUnmanagedThread();
+
+ using CordbBase::GetProcess;
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbUnmanagedThread"; }
+#endif
+
+ // CordbUnmanagedThread is a purely internal object. It's not exposed via ICorDebug APIs and so
+ // we should never use External AddRef.
+ ULONG STDMETHODCALLTYPE AddRef() { _ASSERTE(!"Don't use external addref on a CordbUnmanagedThread"); return (BaseAddRef());}
+ ULONG STDMETHODCALLTYPE Release() { _ASSERTE(!"Don't use external release on a CordbUnmanagedThread"); return (BaseRelease());}
+
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface)
+ {
+ _ASSERTE(!"Don't use QI on a CordbUnmanagedThread");
+ // Not really used since we never expose this class. If we ever do expose this class via the ICorDebug API then
+ // we should, of course, implement this.
+ return E_NOINTERFACE;
+ }
+
+ HRESULT LoadTLSArrayPtr();
+
+ // Hijacks this thread to a hijack worker function which recieves the current
+ // context and the provided exception record. The reason determines what code
+ // the hijack worker executes
+ HRESULT SetupFirstChanceHijack(EHijackReason::EHijackReason reason, const EXCEPTION_RECORD * pExceptionRecord);
+ HRESULT SetupFirstChanceHijackForSync();
+
+ HRESULT SetupGenericHijack(DWORD eventCode, const EXCEPTION_RECORD * pRecord);
+ HRESULT FixupFromGenericHijack();
+
+ HRESULT FixupAfterOOBException(CordbUnmanagedEvent * ue);
+
+ void SetupForSkipBreakpoint(NativePatch * pNativePatch);
+ void FixupForSkipBreakpoint();
+ bool IsCantStop();
+
+ // These are wrappers for the OS calls which hide
+ // the effects of hijacking and internal SS flag usage
+ HRESULT GetThreadContext(DT_CONTEXT * pContext);
+ HRESULT SetThreadContext(DT_CONTEXT * pContext);
+
+ // Turns on and off the internal usage of the SS flag
+ VOID BeginStepping();
+ VOID EndStepping();
+
+ // An accessor for &m_context, this value generally stores
+ // a context we may need to restore after a hijack completes
+ DT_CONTEXT * GetHijackCtx();
+
+private:
+ CORDB_ADDRESS m_stackBase;
+ CORDB_ADDRESS m_stackLimit;
+
+public:
+ BOOL GetStackRange(CORDB_ADDRESS *pBase, CORDB_ADDRESS *pLimit);
+
+ BOOL IsDeleted() {LIMITED_METHOD_CONTRACT; return m_state & CUTS_Deleted; }
+ BOOL IsFirstChanceHijacked() {LIMITED_METHOD_CONTRACT; return m_state & CUTS_FirstChanceHijacked; }
+ BOOL IsGenericHijacked() {LIMITED_METHOD_CONTRACT; return m_state & CUTS_GenericHijacked; }
+ BOOL IsBlockingForSync() {LIMITED_METHOD_CONTRACT; return m_state & CUTS_BlockingForSync; }
+ BOOL IsSuspended() {LIMITED_METHOD_CONTRACT; return m_state & CUTS_Suspended; }
+ BOOL IsSpecialDebuggerThread() {LIMITED_METHOD_CONTRACT; return m_state & CUTS_IsSpecialDebuggerThread; }
+ BOOL HasIBEvent() {LIMITED_METHOD_CONTRACT; return m_state & CUTS_HasIBEvent; }
+ BOOL HasOOBEvent() { return m_state & CUTS_HasOOBEvent; }
+ BOOL HasSpecialStackOverflowCase() {LIMITED_METHOD_CONTRACT; return m_state & CUTS_HasSpecialStackOverflowCase; }
+#ifdef _DEBUG
+ BOOL IsDEBUGTrace() { return m_state & CUTS_DEBUG_SingleStep; }
+#endif
+ BOOL IsSkippingNativePatch() { LIMITED_METHOD_CONTRACT; return m_state & CUTS_SkippingNativePatch; }
+ BOOL IsContextSet() { LIMITED_METHOD_CONTRACT; return m_state & CUTS_HasContextSet; }
+ BOOL IsSSFlagNeeded() { LIMITED_METHOD_CONTRACT; return m_state & CUTS_IsSSFlagNeeded; }
+ BOOL IsSSFlagHidden() { LIMITED_METHOD_CONTRACT; return m_state & CUTS_IsSSFlagHidden; }
+ BOOL HasRaiseExceptionEntryCtx() { LIMITED_METHOD_CONTRACT; return m_state & CUTS_HasRaiseExceptionEntryCtx; }
+ BOOL IsRaiseExceptionHijacked() { LIMITED_METHOD_CONTRACT; return m_state & CUTS_IsRaiseExceptionHijacked; }
+
+ void SetState(CordbUnmanagedThreadState state)
+ {
+ LIMITED_METHOD_CONTRACT;
+ m_state = (CordbUnmanagedThreadState)(m_state | state);
+ _ASSERTE(!IsSuspended() || !IsBlockingForSync());
+ _ASSERTE(!IsSuspended() || !IsFirstChanceHijacked());
+ }
+ void ClearState(CordbUnmanagedThreadState state) {LIMITED_METHOD_CONTRACT; m_state = (CordbUnmanagedThreadState)(m_state & ~state); }
+
+ void HijackToRaiseException();
+ void RestoreFromRaiseExceptionHijack();
+ void SaveRaiseExceptionEntryContext();
+ void ClearRaiseExceptionEntryContext();
+ BOOL IsExceptionFromLastRaiseException(const EXCEPTION_RECORD* pExceptionRecord);
+
+ CordbUnmanagedEvent *IBEvent() {LIMITED_METHOD_CONTRACT; return &m_IBEvent; }
+ CordbUnmanagedEvent *IBEvent2() {LIMITED_METHOD_CONTRACT; return &m_IBEvent2; }
+ CordbUnmanagedEvent *OOBEvent() { return &m_OOBEvent; }
+
+ DWORD GetOSTid()
+ {
+ return (DWORD) this->m_id;
+ }
+
+#ifdef DBG_TARGET_X86
+ // Stores the thread's current leaf SEH handler
+ HRESULT SaveCurrentLeafSeh();
+ // Restores the thread's leaf SEH handler from the previously saved value
+ HRESULT RestoreLeafSeh();
+#endif
+
+ // Logs basic data about a context to the debugging log
+ static VOID LogContext(DT_CONTEXT* pContext);
+
+public:
+ HANDLE m_handle;
+
+ // @dbgtodo - the TLS reading is only used for interop hijacks; which goes away in Arrowhead.
+ // Target address of the Thread Information Block (TIB).
+ void *m_threadLocalBase;
+
+ // Target address of the Thread Local Storage (TLS) array. This is for slots 0 -63.
+ void *m_pTLSArray;
+
+ // Target Address of extended Thread local Storage array. These are for slots about 63.
+ // This may be NULL if extended storage is not yet allocated.
+ void *m_pTLSExtendedArray;
+
+
+ CordbUnmanagedThreadState m_state;
+
+ CordbUnmanagedEvent m_IBEvent;
+ CordbUnmanagedEvent m_IBEvent2;
+ CordbUnmanagedEvent m_OOBEvent;
+
+ LSPTR_CONTEXT m_pLeftSideContext;
+ void *m_originalHandler;
+
+private:
+ // Spare context used for various purposes.
+ // See CordbUnmanagedThread::GetThreadContext for details
+ DT_CONTEXT m_context;
+
+ // The context of the thread the last time it called into kernel32!RaiseException
+ DT_CONTEXT m_raiseExceptionEntryContext;
+
+ DWORD m_raiseExceptionExceptionCode;
+ DWORD m_raiseExceptionExceptionFlags;
+ DWORD m_raiseExceptionNumberParameters;
+ ULONG_PTR m_raiseExceptionExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
+
+
+#ifdef DBG_TARGET_X86
+ // the SEH handler which was the leaf when SaveCurrentSeh was called (prior to hijack)
+ REMOTE_PTR m_pSavedLeafSeh;
+#endif
+
+ HRESULT EnableSSAfterBP();
+ bool GetEEThreadCantStopHelper();
+
+ DWORD_PTR GetTlsSlot(SIZE_T slot);
+ REMOTE_PTR GetPreDefTlsSlot(SIZE_T slot, bool * pRead);
+
+ void * m_pPatchSkipAddress;
+
+
+
+ /*
+ * This abstracts away an overload of the OS thread's TLS slot. In
+ * particular the runtime may or may not have created a thread object for
+ * a particular OS thread at any point.
+ *
+ * If the runtime has created a thread object, then it stores a pointer to
+ * that thread object in the thread's TLS slot.
+ *
+ * If not, then interop-debugging uses that TLS slot to store temporary
+ * information.
+ *
+ * To determine this, interop-debugging will set the low bit. Thus when
+ * we read the TLS slot, if it is non-NULL, anything w/o the low bit set
+ * is an EE thread object ptr. Anything with the low bit set is an
+ * interop-debugging value. Any NULL is null, and an indicator that
+ * there does not exist a runtime thread object for this thread yet.
+ *
+ */
+ REMOTE_PTR m_pEEThread;
+ REMOTE_PTR m_pdwTlsValue;
+ BOOL m_fValidTlsData;
+
+ UINT m_continueCountCached;
+
+ void CacheEEDebuggerWord();
+ HRESULT SetEEThreadValue(REMOTE_PTR EETlsValue);
+#ifdef FEATURE_IMPLICIT_TLS
+ DWORD_PTR GetEEThreadValue();
+ REMOTE_PTR GetClrModuleTlsDataAddress();
+ REMOTE_PTR GetEETlsDataBlock();
+#endif
+
+public:
+ HRESULT GetEEDebuggerWord(REMOTE_PTR *pValue);
+ HRESULT SetEEDebuggerWord(REMOTE_PTR value);
+ HRESULT GetEEThreadPtr(REMOTE_PTR *ppEEThread);
+
+ bool GetEEPGCDisabled();
+ void GetEEState(bool *threadStepping, bool *specialManagedException);
+ bool GetEEFrame();
+};
+#endif // FEATURE_INTEROP_DEBUGGING
+
+
+//********************************************************************************
+//**************** App Domain Publishing Service API *****************************
+//********************************************************************************
+
+
+class EnumElement
+{
+public:
+ EnumElement()
+ {
+ m_pData = NULL;
+ m_pNext = NULL;
+ }
+
+ void SetData (void *pData) { m_pData = pData;}
+ void *GetData () { return m_pData;}
+ void SetNext (EnumElement *pNext) { m_pNext = pNext;}
+ EnumElement *GetNext () { return m_pNext;}
+
+private:
+ void *m_pData;
+ EnumElement *m_pNext;
+};
+
+#if defined(FEATURE_DBG_PUBLISH)
+
+// Prototype of psapi!GetModuleFileNameEx.
+typedef DWORD FPGetModuleFileNameEx(HANDLE, HMODULE, LPTSTR, DWORD);
+
+
+class CorpubPublish : public CordbCommonBase, public ICorPublish
+{
+public:
+ CorpubPublish();
+ virtual ~CorpubPublish();
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbPublish"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorPublish
+ //-----------------------------------------------------------
+
+ COM_METHOD EnumProcesses(
+ COR_PUB_ENUMPROCESS Type,
+ ICorPublishProcessEnum **ppIEnum);
+
+ COM_METHOD GetProcess(
+ unsigned pid,
+ ICorPublishProcess **ppProcess);
+
+ //-----------------------------------------------------------
+ // CreateObject
+ //-----------------------------------------------------------
+ static COM_METHOD CreateObject(REFIID id, void **object)
+ {
+ *object = NULL;
+
+ if (id != IID_IUnknown && id != IID_ICorPublish)
+ return (E_NOINTERFACE);
+
+ CorpubPublish *pCorPub = new (nothrow) CorpubPublish();
+
+ if (pCorPub == NULL)
+ return (E_OUTOFMEMORY);
+
+ *object = (ICorPublish*)pCorPub;
+ pCorPub->AddRef();
+
+ return (S_OK);
+ }
+
+private:
+ HRESULT GetProcessInternal( unsigned pid, CorpubProcess **ppProcess );
+
+ // Cached information to get the process name. Not available on all platforms, so may be null.
+ HModuleHolder m_hPSAPIdll;
+ FPGetModuleFileNameEx * m_fpGetModuleFileNameEx;
+};
+
+class CorpubProcess : public CordbCommonBase, public ICorPublishProcess
+{
+public:
+ CorpubProcess(DWORD dwProcessId,
+ bool fManaged,
+ HANDLE hProcess,
+ HANDLE hMutex,
+ AppDomainEnumerationIPCBlock *pAD,
+#if !defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ IPCReaderInterface *pIPCReader,
+#endif // !FEATURE_DBGIPC_TRANSPORT_DI
+ FPGetModuleFileNameEx * fpGetModuleFileNameEx);
+ virtual ~CorpubProcess();
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CorpubProcess"; }
+#endif
+
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorPublishProcess
+ //-----------------------------------------------------------
+ COM_METHOD IsManaged(BOOL *pbManaged);
+
+ /*
+ * Enumerate the list of known application domains in the target process.
+ */
+ COM_METHOD EnumAppDomains(ICorPublishAppDomainEnum **ppEnum);
+
+ /*
+ * Returns the OS ID for the process in question.
+ */
+ COM_METHOD GetProcessID(unsigned *pid);
+
+ /*
+ * Get the display name for a process.
+ */
+ COM_METHOD GetDisplayName(ULONG32 cchName,
+ ULONG32 *pcchName,
+ __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[]);
+
+ CorpubProcess *GetNextProcess () { return m_pNext;}
+ void SetNext (CorpubProcess *pNext) { m_pNext = pNext;}
+
+ // Helper to tell if this process has exited
+ bool IsExited();
+
+public:
+ DWORD m_dwProcessId;
+
+private:
+ bool m_fIsManaged;
+ HANDLE m_hProcess;
+ HANDLE m_hMutex;
+ AppDomainEnumerationIPCBlock *m_AppDomainCB;
+#if !defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ IPCReaderInterface *m_pIPCReader; // controls the lifetime of the AppDomainEnumerationIPCBlock
+#endif // !FEATURE_DBGIPC_TRANSPORT_DI
+ CorpubProcess *m_pNext; // pointer to the next process in the process list
+ WCHAR *m_szProcessName;
+
+};
+
+class CorpubAppDomain : public CordbCommonBase, public ICorPublishAppDomain
+{
+public:
+ CorpubAppDomain (__in LPWSTR szAppDomainName, ULONG Id);
+ virtual ~CorpubAppDomain();
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CorpubAppDomain"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface (REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorPublishAppDomain
+ //-----------------------------------------------------------
+
+ /*
+ * Get the name and ID for an application domain.
+ */
+ COM_METHOD GetID (ULONG32 *pId);
+
+ /*
+ * Get the name for an application domain.
+ */
+ COM_METHOD GetName (ULONG32 cchName,
+ ULONG32 *pcchName,
+ __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[]);
+
+ CorpubAppDomain *GetNextAppDomain () { return m_pNext;}
+ void SetNext (CorpubAppDomain *pNext) { m_pNext = pNext;}
+
+private:
+ CorpubAppDomain *m_pNext;
+ WCHAR *m_szAppDomainName;
+ ULONG m_id;
+
+};
+
+class CorpubProcessEnum : public CordbCommonBase, public ICorPublishProcessEnum
+{
+public:
+ CorpubProcessEnum(CorpubProcess *pFirst);
+ virtual ~CorpubProcessEnum();
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CorpubProcessEnum"; }
+#endif
+
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorPublishProcessEnum
+ //-----------------------------------------------------------
+
+ COM_METHOD Skip(ULONG celt);
+ COM_METHOD Reset();
+ COM_METHOD Clone(ICorPublishEnum **ppEnum);
+ COM_METHOD GetCount(ULONG *pcelt);
+ COM_METHOD Next(ULONG celt,
+ ICorPublishProcess *objects[],
+ ULONG *pceltFetched);
+
+private:
+ CorpubProcess *m_pFirst;
+ CorpubProcess *m_pCurrent;
+
+};
+
+class CorpubAppDomainEnum : public CordbCommonBase, public ICorPublishAppDomainEnum
+{
+public:
+ CorpubAppDomainEnum(CorpubAppDomain *pFirst);
+ virtual ~CorpubAppDomainEnum();
+
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbAppDomainEnum"; }
+#endif
+
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ICorPublishAppDomainEnum
+ //-----------------------------------------------------------
+ COM_METHOD Skip(ULONG celt);
+ COM_METHOD Reset();
+ COM_METHOD Clone(ICorPublishEnum **ppEnum);
+ COM_METHOD GetCount(ULONG *pcelt);
+
+ COM_METHOD Next(ULONG celt,
+ ICorPublishAppDomain *objects[],
+ ULONG *pceltFetched);
+
+private:
+ CorpubAppDomain *m_pFirst;
+ CorpubAppDomain *m_pCurrent;
+
+};
+
+#endif // defined(FEATURE_DBG_PUBLISH)
+
+class CordbHeapEnum : public CordbBase, public ICorDebugHeapEnum
+{
+public:
+ CordbHeapEnum(CordbProcess *proc);
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbHeapEnum"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ COM_METHOD Skip(ULONG celt);
+ COM_METHOD Reset();
+ COM_METHOD Clone(ICorDebugEnum **ppEnum);
+ COM_METHOD GetCount(ULONG *pcelt);
+
+ COM_METHOD Next(ULONG celt,
+ COR_HEAPOBJECT objects[],
+ ULONG *pceltFetched);
+
+ virtual void Neuter()
+ {
+ Clear();
+ CordbBase::Neuter();
+ }
+private:
+ void Clear();
+
+private:
+ IDacDbiInterface::HeapWalkHandle mHeapHandle;
+};
+
+
+class CordbRefEnum : public CordbBase, public ICorDebugGCReferenceEnum
+{
+public:
+ CordbRefEnum(CordbProcess *proc, BOOL walkWeakRefs);
+ CordbRefEnum(CordbProcess *proc, CorGCReferenceType types);
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbHeapEnum"; }
+#endif
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ COM_METHOD Skip(ULONG celt);
+ COM_METHOD Reset();
+ COM_METHOD Clone(ICorDebugEnum **ppEnum);
+ COM_METHOD GetCount(ULONG *pcelt);
+
+ COM_METHOD Next(ULONG celt,
+ COR_GC_REFERENCE refs[],
+ ULONG *pceltFetched);
+
+ virtual void Neuter();
+
+private:
+ RefWalkHandle mRefHandle;
+ BOOL mEnumStacksFQ;
+ UINT32 mHandleMask;
+};
+
+// Since the hash table of modules is per app domain (and
+// threads is per process) (for fast lookup from the appdomain/process),
+// we need this wrapper
+// here which allows us to iterate through an assembly's
+// modules. Is basically filters out modules/threads that aren't
+// in the assembly/appdomain. This slow & awkward for assemblies, but fast
+// for the common case - appdomain lookup.
+class CordbEnumFilter : public CordbBase,
+ public ICorDebugThreadEnum,
+ public ICorDebugModuleEnum
+{
+public:
+ CordbEnumFilter(CordbBase * pOwnerObj, NeuterList * pOwnerList);
+ CordbEnumFilter(CordbEnumFilter*src);
+ virtual ~CordbEnumFilter();
+
+ virtual void Neuter();
+
+
+#ifdef _DEBUG
+ virtual const char * DbgGetName() { return "CordbEnumFilter"; }
+#endif
+
+
+ //-----------------------------------------------------------
+ // IUnknown
+ //-----------------------------------------------------------
+
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (BaseAddRef());
+ }
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ return (BaseRelease());
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // Common methods
+ //-----------------------------------------------------------
+ COM_METHOD Skip(ULONG celt);
+ COM_METHOD Reset();
+ COM_METHOD Clone(ICorDebugEnum **ppEnum);
+ COM_METHOD GetCount(ULONG *pcelt);
+ //-----------------------------------------------------------
+ // ICorDebugModuleEnum
+ //-----------------------------------------------------------
+ COM_METHOD Next(ULONG celt,
+ ICorDebugModule *objects[],
+ ULONG *pceltFetched);
+
+ //-----------------------------------------------------------
+ // ICorDebugThreadEnum
+ //-----------------------------------------------------------
+ COM_METHOD Next(ULONG celt,
+ ICorDebugThread *objects[],
+ ULONG *pceltFetched);
+
+ HRESULT Init (ICorDebugModuleEnum *pModEnum, CordbAssembly *pAssembly);
+ HRESULT Init (ICorDebugThreadEnum *pThreadEnum, CordbAppDomain *pAppDomain);
+
+
+private:
+ HRESULT NextWorker(ULONG celt, ICorDebugModule *objects[], ULONG *pceltFetched);
+ HRESULT NextWorker(ULONG celt,ICorDebugThread *objects[], ULONG *pceltFetched);
+
+ // Owning object is our link to the CordbProcess* tree. Never null until we're neutered.
+ // NeuterList is related to the owning object. Need to cache it so that we can pass it on
+ // to our clones.
+ CordbBase * m_pOwnerObj; // provides us w/ a CordbProcess*
+ NeuterList * m_pOwnerNeuterList;
+
+
+ EnumElement *m_pFirst;
+ EnumElement *m_pCurrent;
+ int m_iCount;
+};
+
+// Helpers to double-check the RS results against DAC.
+#if defined(_DEBUG)
+void CheckAgainstDAC(CordbFunction * pFunc, void * pIP, mdMethodDef mdExpected);
+#endif
+
+HRESULT CopyOutString(const WCHAR * pInputString, ULONG32 cchName, ULONG32 * pcchName, __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[]);
+
+
+
+inline UINT AllocCookieCordbEval(CordbProcess *pProc, CordbEval* p)
+{
+ _ASSERTE(pProc->GetProcessLock()->HasLock());
+ return pProc->m_EvalTable.Add(p);
+}
+inline CordbEval * UnwrapCookieCordbEval(CordbProcess *pProc, UINT cookie)
+{
+ _ASSERTE(pProc->GetProcessLock()->HasLock());
+ return pProc->m_EvalTable.LookupAndRemove(cookie);
+}
+
+
+// We defined this at the top of the file - undef it now so that we don't pollute other files.
+#undef CRITICAL_SECTION
+
+
+#ifdef RSCONTRACTS
+
+//-----------------------------------------------------------------------------
+// For debug builds, we maintain some thread-state to track debug bits
+// to help us do some more aggressive asserts.
+//-----------------------------------------------------------------------------
+
+class PublicAPIHolder;
+class PublicReentrantAPIHolder;
+class PublicCallbackHolder;
+class PublicDebuggerErrorCallbackHolder;
+
+class DbgRSThread
+{
+public:
+ friend class PublicAPIHolder;
+ friend class PublicReentrantAPIHolder;
+ friend class PublicCallbackHolder;
+ friend class PublicDebuggerErrorCallbackHolder;
+ friend class PrivateShimCallbackHolder;
+
+ DbgRSThread();
+
+ // The TLS slot that we'll put this thread object in.
+ static DWORD s_TlsSlot;
+
+ static LONG s_Total; // Total count of thread objects
+
+ // Get a thread object for the current thread via a TLS lookup.
+ static DbgRSThread * GetThread();
+
+ // Call during DllMain to release this.
+ static DbgRSThread * Create()
+ {
+ InterlockedIncrement(&s_Total);
+
+ DbgRSThread * p = new (nothrow) DbgRSThread();
+ BOOL f = TlsSetValue(s_TlsSlot, p);
+ _ASSERT(f);
+ return p;
+ }
+
+ void Destroy()
+ {
+ InterlockedDecrement(&s_Total);
+
+ BOOL f = TlsSetValue(s_TlsSlot, NULL);
+ _ASSERT(f);
+
+ delete this;
+ }
+
+ // Return true if this thread is inside the RS.
+ bool IsInRS() { return m_cInsideRS > 0; }
+
+ // Locking API..
+ // These will assert if the operation is unsafe.
+ void NotifyTakeLock(RSLock * pLock);
+ void NotifyReleaseLock(RSLock * pLock);
+
+ // Used to map other resources (like thread access) into the lock hierachy.
+ // Note this only effects lock leveling checks and doesn't effect HoldsAnyLock().
+ void TakeVirtualLock(RSLock::ERSLockLevel level);
+ void ReleaseVirtualLock(RSLock::ERSLockLevel level);
+
+ // return true if this thread is holding any RS locks. Useful to check on Public API transition boundaries.
+ bool HoldsAnyDbgApiLocks() { return m_cTotalDbgApiLocks > 0; }
+
+ enum EThreadType
+ {
+ cOther,
+ cW32ET
+ };
+ void SetThreadType(EThreadType e) { m_eThreadType = e; }
+
+ bool IsWin32EventThread() { return m_eThreadType == cW32ET; }
+
+ void SetUnrecoverableCallback(bool fIsUnrecoverableErrorCallback)
+ {
+ // Not reentrant.
+ _ASSERTE(m_fIsUnrecoverableErrorCallback != fIsUnrecoverableErrorCallback);
+
+ m_fIsUnrecoverableErrorCallback = fIsUnrecoverableErrorCallback;
+ }
+
+ inline void AssertThreadIsLockFree()
+ {
+ // If we're in an unrecoverable callback, we may hold locks.
+ _ASSERTE(m_fIsUnrecoverableErrorCallback
+ || !HoldsAnyDbgApiLocks() ||
+ !"Thread should not have locks on public/internal transition");
+ }
+
+protected:
+ EThreadType m_eThreadType;
+
+ // More debugging tidbits - tid that we're on, and a sanity checking cookie.
+ DWORD m_tid;
+ DWORD m_Cookie;
+
+ enum ECookie
+ {
+ COOKIE_VALUE = 0x12345678
+ };
+
+
+ // This tells us if the thread is currently in the scope of a PublicAPIHolder.
+ int m_cInsideRS;
+
+ // This tells us if a thread is currently being dispatched via a callback.
+ bool m_fIsInCallback;
+
+ // We explicitly track if this thread is in an unrecoverable error callback
+ // b/c that will weaken some other asserts.
+ // It would be nice to clean up the unrecoverable error callback and have it
+ // behave like all the other callbacks. Then we can remove this.
+ bool m_fIsUnrecoverableErrorCallback;
+
+ // Locking context. Used to tell what levels of locks we hold so we can determine if a lock is safe to take.
+ int m_cLocks[RSLock::LL_MAX];
+ int m_cTotalDbgApiLocks;
+};
+
+//-----------------------------------------------------------------------------
+// Mark when we enter / exit public APIs
+//-----------------------------------------------------------------------------
+
+// Holder for Non-reentrant Public API (this is the vast majority)
+class PublicAPIHolder
+{
+public:
+ PublicAPIHolder()
+ {
+ // on entry
+ DbgRSThread * pThread = DbgRSThread::GetThread();
+ pThread->m_cInsideRS++;
+ _ASSERTE(pThread->m_cInsideRS == 1 || !"Non-reentrant API being called re-entrantly");
+
+ // Should never be in public w/ these locks
+ pThread->AssertThreadIsLockFree();
+ }
+ ~PublicAPIHolder() {
+ // On exit.
+ DbgRSThread * pThread = DbgRSThread::GetThread();
+ pThread->m_cInsideRS--;
+ _ASSERTE(!pThread->IsInRS());
+
+ // Should never be in public w/ these locks. If we assert here,
+ // then we're leaking locks.
+ pThread->AssertThreadIsLockFree();
+ }
+};
+
+// Holder for reentrant public API
+class PublicReentrantAPIHolder
+{
+public:
+ PublicReentrantAPIHolder()
+ {
+ // on entry
+ DbgRSThread * pThread = DbgRSThread::GetThread();
+ pThread->m_cInsideRS++;
+
+ // Cache count now so that we can calidate it in the dtor.
+ m_oldCount = pThread->m_cInsideRS;
+ // Since a we may have been called from within the RS, we may hold locks
+ }
+ ~PublicReentrantAPIHolder()
+ {
+
+ // On exit.
+ DbgRSThread * pThread = DbgRSThread::GetThread();
+
+ // Ensure that our children were balanced
+ _ASSERTE(pThread->m_cInsideRS == m_oldCount);
+
+ pThread->m_cInsideRS--;
+ _ASSERTE(pThread->m_cInsideRS >= 0);
+
+ // Since a we may have been called from within the RS, we may hold locks
+ }
+private:
+ int m_oldCount;
+};
+
+// Special holder for DebuggerError callback. This adjusts InsideRS count w/o
+// verifying locks. This is very dangerous. We allow this b/c the Debugger Error callback can come at any time.
+class PublicDebuggerErrorCallbackHolder
+{
+public:
+ PublicDebuggerErrorCallbackHolder()
+ {
+ // Exiting from RS; entering Cordbg via a callback
+ DbgRSThread * pThread = DbgRSThread::GetThread();
+
+ // This callback is called from within the RS
+ _ASSERTE(pThread->IsInRS());
+
+ // Debugger error callback may be called from deep within the RS (after many nestings).
+ // So immediately jump to outside. We'll restore this in dtor.
+ m_oldCount = pThread->m_cInsideRS;
+ pThread->m_cInsideRS = 0;
+
+ _ASSERTE(!pThread->IsInRS());
+
+ // We may be leaking locks for the unrecoverable callback. We mark that so that
+ // the asserts about locking can be relaxed.
+ pThread->SetUnrecoverableCallback(true);
+ }
+
+ ~PublicDebuggerErrorCallbackHolder()
+ {
+ // Re-entering RS from after a callback.
+ DbgRSThread * pThread = DbgRSThread::GetThread();
+
+ pThread->SetUnrecoverableCallback(false);
+ pThread->m_cInsideRS = m_oldCount;
+
+ // Our status of being "Inside the RS" is now restored.
+ _ASSERTE(pThread->IsInRS());
+ }
+private:
+ int m_oldCount;
+};
+
+//---------------------------------------------------------------------------------------
+//
+// This is the same as the PublicCallbackHolder, except that this class doesn't assert that we are not holding
+// any locks when we call out to the shim.
+//
+// Notes:
+// @dbgtodo shim, synchronization - We need to settle on one consistent relationshipo between the RS
+// and the shim. Then we can clean up the sychronization story. Right now some code considers the shim
+// to be outside of the RS, and so we cannot hold any locks when we call out to the shim. However, there
+// are cases where we must hold a lock when we call out to the shim. For example, when we call out to the
+// shim to do a V2-style stackwalk, we need to be holding the stop-go lock so that another thread can't
+// come in and call Continue(). Finally, when we fix this, we should fix
+// PUBLIC_REENTRANT_API_ENTRY_FOR_SHIM() as well.
+//
+
+class PrivateShimCallbackHolder
+{
+public:
+ PrivateShimCallbackHolder()
+ {
+ // Exiting from RS; entering Cordbg via a callback
+ DbgRSThread * pThread = DbgRSThread::GetThread();
+
+ // This callback is called from within the RS
+ _ASSERTE(pThread->IsInRS());
+
+ // Debugger error callback may be called from deep within the RS (after many nestings).
+ // So immediately jump to outside. We'll restore this in dtor.
+ m_oldCount = pThread->m_cInsideRS;
+ pThread->m_cInsideRS = 0;
+
+ _ASSERTE(!pThread->IsInRS());
+ }
+
+ ~PrivateShimCallbackHolder()
+ {
+ // Re-entering RS from after a callback.
+ DbgRSThread * pThread = DbgRSThread::GetThread();
+
+ pThread->m_cInsideRS = m_oldCount;
+
+ // Our status of being "Inside the RS" is now restored.
+ _ASSERTE(pThread->IsInRS());
+ }
+private:
+ int m_oldCount;
+};
+
+class InternalAPIHolder
+{
+public:
+ InternalAPIHolder()
+ {
+ DbgRSThread * pThread = DbgRSThread::GetThread();
+
+ // Internal APIs should already be inside the RS.
+ _ASSERTE(pThread->IsInRS() ||!"Internal API being called directly from outside (there should be a public API on the stack)");
+ }
+ void dummy() {}
+};
+
+//---------------------------------------------------------------------------------------
+//
+// This is a simple holder to assert that the current thread is holding the process lock. The purpose of
+// having this holder is to enforce a lock ordering between the process lock in the RS and the DD lock in DAC.
+// If a thread needs to take the process lock, it must do so BEFORE taking the DD lock. Otherwise we could have
+// a deadlock between the process lock and the DD lock.
+//
+// Normally we take the process lock before calling out to DAC, and every DAC API takes the DD lock on entry.
+// Moreover, normally DAC doesn't call back into the RS. The exceptions we currently have are:
+// 1) enumeration callbacks (e.g. code:CordbProcess::AppDomainEnumerationCallback)
+// 2) code:IDacDbiInterface::IMetaDataLookup
+// 3) code:IDacDbiInterface::IAllocator
+// 4) code:IStringHolder
+//
+// Note that the last two are fine because they don't need to take the process lock. The first two categories
+// need to take the process lock before calling into DAC to avoid potential deadlocks.
+//
+
+class InternalDacCallbackHolder
+{
+public:
+ InternalDacCallbackHolder(CordbProcess * pProcess)
+ {
+ _ASSERTE(pProcess->ThreadHoldsProcessLock());
+ }
+};
+
+// cotract that occurs at public builds.
+#define PUBLIC_CONTRACT \
+ CONTRACTL { NOTHROW; } CONTRACTL_END;
+
+
+// Private hook for Shim to call into DBI.
+// Since Shim is considered outside DBI, we need to mark that we've re-entered.
+// Big difference is that we can throw across this boundary.
+// @dbgtodo private shim hook - Eventually, these will all go away since the shim will be fully public.
+#define PUBLIC_API_ENTRY_FOR_SHIM(_pThis) \
+ PublicAPIHolder __pah;
+
+
+#define PUBLIC_API_UNSAFE_ENTRY_FOR_SHIM(_pThis) \
+ PublicDebuggerErrorCallbackHolder __pahCallback;
+
+// @dbgtodo shim, synchronization - Because of the problem mentioned in the comments for
+// PrivateShimCallbackHolder, we need this macro so that we don't hit an assertion when we come back into
+// the RS from the shim.
+#define PUBLIC_REENTRANT_API_ENTRY_FOR_SHIM(_pThis) \
+ PublicReentrantAPIHolder __pah;
+
+//-----------------------------------------------------------------------------
+// Declare whether an API is public or internal
+// Public APIs have the following:
+// - We may be called concurrently from multiple threads (ie, not thread safe)
+// - This thread does not hold any RS Locks while entering or leaving this function.
+// - May or May-not be reentrant.
+// Internal APIs:
+// - let us specifically mark that we're not a public API, and
+// - we're only being called through a public API.
+//-----------------------------------------------------------------------------
+#define PUBLIC_API_ENTRY(_pThis) \
+ STRESS_LOG2(LF_CORDB, LL_INFO1000, "[Public API '%s', this=0x%p]\n", __FUNCTION__, _pThis); \
+ PUBLIC_CONTRACT; \
+ PublicAPIHolder __pah;
+
+// Mark public APIs that are re-entrant.
+// Very few of our APIs should be re-entrant. Even for field access APIs (like GetXXX), the
+// public version is heavier (eg, checking the HRESULT) so we benefit from having a fast
+// internal version and calling that directly.
+#define PUBLIC_REENTRANT_API_ENTRY(_pThis) \
+ STRESS_LOG2(LF_CORDB, LL_INFO1000, "[Public API (re) '%s', this=0x%p]\n", __FUNCTION__, _pThis); \
+ PUBLIC_CONTRACT; \
+ PublicReentrantAPIHolder __pah;
+
+
+
+// Mark internal APIs.
+// All internal APIs are reentrant (duh)
+#define INTERNAL_API_ENTRY(_pThis) InternalAPIHolder __pah; __pah.dummy();
+
+// Mark an internal API from ATT_REQUIRE_STOP / ATT_ALLOW_LIVE_DO_STOP_GO.
+// This can assert that we're safe to send IPC events (that we're stopped and hold the SG lock)
+// @dbgtodo synchronization - in V2, this would assert that we were synced.
+// In V3, our definition of Sync is in flux. Need to resolve this with the synchronization feature crew.
+#define INTERNAL_SYNC_API_ENTRY(pProc) \
+ CordbProcess * __pProc = (pProc); \
+ _ASSERTE(__pProc->GetStopGoLock()->HasLock() || !"Must have stop go lock for internal-sync-api"); \
+ InternalAPIHolder __pah; __pah.dummy();
+
+
+
+// Mark that a thread is owned by us. Thus the thread's "Inside RS" count > 0.
+#define INTERNAL_THREAD_ENTRY(_pThis) \
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "[Internal thread started, this=0x%p]\n", _pThis); \
+ PUBLIC_CONTRACT; \
+ PublicAPIHolder __pah;
+
+// @dbgtodo unrecoverable error - This sould be deprecated once we deprecate UnrecoverableError.
+#define PUBLIC_CALLBACK_IN_THIS_SCOPE_DEBUGGERERROR(_pThis) \
+ PublicDebuggerErrorCallbackHolder __pahCallback;
+
+#define PRIVATE_SHIM_CALLBACK_IN_THIS_SCOPE0(_pThis) \
+ PrivateShimCallbackHolder __pahCallback;
+
+// Mark places where DAC may call back into DBI. We need to assert that we are holding the process lock in
+// these places, since otherwise we could deadlock between the DD lock and the process lock.
+#define INTERNAL_DAC_CALLBACK(__pProcess) \
+ InternalDacCallbackHolder __idch(__pProcess);
+
+
+// Helper to log debug events.
+inline void StressLogNativeDebugEvent(const DEBUG_EVENT * pDebugEvent, bool fOOB)
+{
+ if ((pDebugEvent)->dwDebugEventCode == EXCEPTION_DEBUG_EVENT)
+ {
+ STRESS_LOG4(LF_CORDB, LL_EVERYTHING, "[Dispatching Win32 code=1 (EXCEPTION_DEBUG_EVENT, tid=%x, oob=%d, code=0x%x, 1st=%d]\n",
+ pDebugEvent->dwThreadId,
+ fOOB,
+ pDebugEvent->u.Exception.ExceptionRecord.ExceptionCode,
+ pDebugEvent->u.Exception.dwFirstChance);
+ }
+ else
+ {
+ STRESS_LOG3(LF_CORDB, LL_EVERYTHING, "[Dispatching Win32 code=%d, tid=%x, oob=%d.]\n",
+ pDebugEvent->dwDebugEventCode, pDebugEvent->dwThreadId, fOOB);
+ }
+
+}
+
+#define PUBLIC_WIN32_CALLBACK_IN_THIS_SCOPE(_pThis, _pDebugEvent, _fOOB) \
+ StressLogNativeDebugEvent(_pDebugEvent, _fOOB); \
+ PublicCallbackHolder __pahCallback(DB_IPCE_INVALID_EVENT);
+
+// Visisbility spec for dtors.
+// Currently, dtors are like public methods b/c they can be called from Release.
+// But they're also reentrant since they may be called from an internal-release.
+// @todo - we'd like to get all "useful" work out of the dtor; in which case we may
+// be able to change this to something more aggressive.
+#define DTOR_ENTRY(_pThis) PUBLIC_REENTRANT_API_ENTRY(_pThis)
+
+
+//-----------------------------------------------------------------------------
+// Typesafe bool for thread safety. This typesafety forces us to use
+// an specific reason for thread-safety, taken from a well-known list.
+// This is mostly concerned w/ being serialized.
+// Note that this assertion must be done on a per function basis and we
+// can't have any sort of 'ThreadSafetyReason CallerIsSafe()' b/c we can't
+// enforce that all of our callers are thread safe (only that our current caller is safe).
+//-----------------------------------------------------------------------------
+struct ThreadSafetyReason
+{
+public:
+ ThreadSafetyReason(bool f) { fIsSafe = f; }
+
+ bool fIsSafe;
+};
+
+// Different valid reasons that we may be threads safe.
+inline ThreadSafetyReason HoldsLock(RSLock * pLock)
+{
+ _ASSERTE(pLock != NULL);
+ return ThreadSafetyReason(pLock->HasLock());
+}
+inline ThreadSafetyReason OnW32ET(CordbProcess * pProc)
+{
+ return ThreadSafetyReason(IsWin32EventThread(pProc));
+}
+
+inline ThreadSafetyReason OnRCET(Cordb *pCordb)
+{
+ return ThreadSafetyReason (IsRCEventThread(pCordb));
+}
+
+// We use this when we assume that a function is thread-safe (b/c it's serialized).
+// The reason also lets us assert that our assumption is true.
+// By using a function, we enforce typesafety and thus require a valid reason
+// (as opposed to an arbitrary bool)
+inline void AssertThreadSafeHelper(ThreadSafetyReason r) {
+ _ASSERTE(r.fIsSafe);
+}
+
+//-----------------------------------------------------------------------------
+// Assert that the given scope is always called on a single thread b/c of
+// xReason. Common reasons may be b/c we hold a lock or we're always
+// called on a specific thread (Eg w32et).
+// The only valid reasons are of type ThreadSafetyReason (thus forcing us to
+// choose from a well-known list of valid reasons).
+//-----------------------------------------------------------------------------
+#define ASSERT_SINGLE_THREAD_ONLY(xReason) \
+ AssertThreadSafeHelper(xReason);
+
+#else
+
+//-----------------------------------------------------------------------------
+// Retail versions just nop. See the debug implementation for these
+// for their semantics.
+//-----------------------------------------------------------------------------
+
+#define PUBLIC_CONTRACT
+#define PUBLIC_API_ENTRY_FOR_SHIM(_pThis)
+#define PUBLIC_API_UNSAFE_ENTRY_FOR_SHIM(_pThis)
+#define PUBLIC_REENTRANT_API_ENTRY_FOR_SHIM(_pThis)
+#define PUBLIC_API_ENTRY(_pThis)
+#define PUBLIC_REENTRANT_API_ENTRY(_pThis)
+#define INTERNAL_API_ENTRY(_pThis)
+#define INTERNAL_SYNC_API_ENTRY(pProc)
+#define INTERNAL_THREAD_ENTRY(_pThis)
+#define PUBLIC_CALLBACK_IN_THIS_SCOPE_DEBUGGERERROR(_pThis)
+#define PRIVATE_SHIM_CALLBACK_IN_THIS_SCOPE0(_pThis)
+#define INTERNAL_DAC_CALLBACK(__pProcess)
+#define PUBLIC_WIN32_CALLBACK_IN_THIS_SCOPE(_pThis, _pDebugEvent, _fOOB)
+#define DTOR_ENTRY(_pThis)
+
+
+#define ASSERT_SINGLE_THREAD_ONLY(x)
+
+#endif // #if RSCONTRACTS
+
+
+class PublicCallbackHolder
+{
+public:
+ PublicCallbackHolder(RSLockHolder * pHolder, DebuggerIPCEventType type)
+ {
+ m_pHolder = pHolder;
+ _ASSERTE(!pHolder->IsNull()); // acquired
+
+ // Release the lock. We'll reacquire it at the dtor.
+ m_pHolder->Release();
+
+ Init(type);
+ }
+
+ PublicCallbackHolder(DebuggerIPCEventType type)
+ {
+ m_pHolder = NULL;
+ Init(type);
+ }
+
+ void Init(DebuggerIPCEventType type)
+ {
+ m_type = type;
+
+#if defined(RSCONTRACTS)
+ // Exiting from RS; entering Cordbg via a callback
+ DbgRSThread * pThread = DbgRSThread::GetThread();
+
+ // m_cInsideRS may be arbitrarily large if we're called from a PUBLIC_REENTRANT_API,
+ // so just remember the current count and blast it back to 0.
+ m_oldCount = pThread->m_cInsideRS;
+ pThread->m_cInsideRS = 0;
+
+ _ASSERTE(!pThread->IsInRS());
+
+ // Should never be in public w/ these locks. (Even if we're re-entrant.)
+ pThread->AssertThreadIsLockFree();
+#endif // RSCONTRACTS
+ }
+
+ ~PublicCallbackHolder()
+ {
+#if defined(RSCONTRACTS)
+ // Re-entering RS from after a callback.
+ DbgRSThread * pThread = DbgRSThread::GetThread();
+ _ASSERTE(!pThread->IsInRS());
+
+ pThread->m_cInsideRS = m_oldCount;
+
+ // Should never be in public w/ these locks. (Even if we're re-entrant.)
+ pThread->AssertThreadIsLockFree();
+#endif // RSCONTRACTS
+
+ // Reacquire the lock
+ if (m_pHolder != NULL)
+ {
+ m_pHolder->Acquire();
+ }
+ }
+protected:
+ int m_oldCount;
+ DebuggerIPCEventType m_type;
+ RSLockHolder * m_pHolder;
+};
+
+
+// Mark that a thread is calling out via a callback. This will adjust the "Inside RS" counter.
+#define PUBLIC_CALLBACK_IN_THIS_SCOPE(_pThis, pLockHolder, event) \
+ STRESS_LOG1(LF_CORDB, LL_EVERYTHING, "[Dispatching '%s']\n", IPCENames::GetName((event)->type)); \
+ PublicCallbackHolder __pahCallback(pLockHolder, (event)->type);
+
+#define PUBLIC_CALLBACK_IN_THIS_SCOPE1(_pThis, pLockHolder, event, formatLiteralString, arg0) \
+ STRESS_LOG2(LF_CORDB, LL_EVERYTHING, "[Dispatching '%s' " formatLiteralString "]\n", IPCENames::GetName((event)->type), arg0); \
+ PublicCallbackHolder __pahCallback(pLockHolder, (event)->type);
+
+#define PUBLIC_CALLBACK_IN_THIS_SCOPE2(_pThis, pLockHolder, event, formatLiteralString, arg0, arg1) \
+ STRESS_LOG3(LF_CORDB, LL_EVERYTHING, "[Dispatching '%s' " formatLiteralString "]\n", IPCENames::GetName((event)->type), arg0, arg1); \
+ PublicCallbackHolder __pahCallback(pLockHolder, (event)->type);
+
+#define PUBLIC_CALLBACK_IN_THIS_SCOPE3(_pThis, pLockHolder, event, formatLiteralString, arg0, arg1, arg2) \
+ STRESS_LOG4(LF_CORDB, LL_EVERYTHING, "[Dispatching '%s' " formatLiteralString "]\n", IPCENames::GetName((event)->type), arg0, arg1, arg2); \
+ PublicCallbackHolder __pahCallback(pLockHolder, (event)->type);
+
+
+#define PUBLIC_CALLBACK_IN_THIS_SCOPE0_NO_LOCK(_pThis) \
+ PublicCallbackHolder __pahCallback(DB_IPCE_INVALID_EVENT);
+
+#define PUBLIC_CALLBACK_IN_THIS_SCOPE0(_pThis, pLockHolder) \
+ PublicCallbackHolder __pahCallback(pLockHolder, DB_IPCE_INVALID_EVENT);
+
+
+//-----------------------------------------------------------------------------
+// Helpers
+inline void ValidateOrThrow(const void * p)
+{
+ if (p == NULL)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+}
+
+// aligns argBase on platforms that require it else it's a no-op
+inline void AlignAddressForType(CordbType* pArgType, CORDB_ADDRESS& argBase)
+{
+#ifdef DBG_TARGET_ARM
+// TODO: review the following
+#ifdef FEATURE_64BIT_ALIGNMENT
+ BOOL align = FALSE;
+ HRESULT hr = pArgType->RequiresAlign8(&align);
+ _ASSERTE(SUCCEEDED(hr));
+
+ if (align)
+ argBase = ALIGN_ADDRESS(argBase, 8);
+#endif // FEATURE_64BIT_ALIGNMENT
+#endif // DBG_TARGET_ARM
+}
+
+//-----------------------------------------------------------------------------
+// Macros to mark public ICorDebug functions
+// Usage:
+//
+// HRESULT CordbXYZ:Function(...)
+// {
+// HRESULT hr = S_OK;
+// PUBLIC_API_BEGIN(this);
+// // body, may throw
+// PUBLIC_API_END(hr);
+// return hr;
+// }
+#define PUBLIC_API_BEGIN(__this) \
+ CordbBase * __pThis = (__this); \
+ PUBLIC_API_ENTRY(__pThis); \
+ EX_TRY { \
+ RSLockHolder __lockHolder(__pThis->GetProcess()->GetProcessLock()); \
+ THROW_IF_NEUTERED(__pThis); \
+
+// You should not use this in general. We're adding it as a temporary workaround for a
+// particular scenario until we do the synchronization feature crew
+#define PUBLIC_API_NO_LOCK_BEGIN(__this) \
+ CordbBase * __pThis = (__this); \
+ PUBLIC_API_ENTRY(__pThis); \
+ EX_TRY { \
+ THROW_IF_NEUTERED(__pThis); \
+
+// Some APIs (that invoke callbacks), need to toggle the lock.
+#define GET_PUBLIC_LOCK_HOLDER() (&__lockHolder)
+
+#define PUBLIC_API_END(__hr) \
+ } EX_CATCH_HRESULT(__hr); \
+
+// @todo: clean up API constracts. Should we really be taking the Process lock for
+// reentrant APIS??
+#define PUBLIC_REENTRANT_API_BEGIN(__this) \
+ CordbBase * __pThis = (__this); \
+ PUBLIC_REENTRANT_API_ENTRY(__pThis); \
+ EX_TRY { \
+ RSLockHolder __lockHolder(__pThis->GetProcess()->GetProcessLock()); \
+ THROW_IF_NEUTERED(__pThis); \
+
+#define PUBLIC_REENTRANT_API_END(__hr) \
+ } EX_CATCH_HRESULT(__hr); \
+
+// If an API needs to take the stop/go lock as well as the process lock, the
+// stop/go lock has to be taken first. This is an alternative to PUBLIC_REENTRANT_API_BEGIN
+// that allows this, since it doesn't take the process lock. It should be closed with
+// PUBLIC_REENTRANT_API_END
+#define PUBLIC_REENTRANT_API_NO_LOCK_BEGIN(__this) \
+ CordbBase * __pThis = (__this); \
+ PUBLIC_REENTRANT_API_ENTRY(__pThis); \
+ EX_TRY { \
+ THROW_IF_NEUTERED(__pThis); \
+
+
+//-----------------------------------------------------------------------------
+// For debugging ease, cache some global values.
+// Include these in retail & free because that's where we need them the most!!
+// Optimized builds may not let us view locals & parameters. So Having these
+// cached as global values should let us inspect almost all of
+// the interesting parts of the RS even in a Retail build!
+//-----------------------------------------------------------------------------
+struct RSDebuggingInfo
+{
+ // There should only be 1 global Cordb object. Store it here.
+ Cordb * m_Cordb;
+
+ // We have lots of processes. Keep a pointer to the most recently touched
+ // (subjective) process, as a hint about what our "current" process is.
+ // If we're only debugging 1 process, this will be sufficient.
+ CordbProcess * m_MRUprocess;
+
+ CordbRCEventThread * m_RCET;
+};
+
+#include "rspriv.inl"
+
+#endif // #if RSPRIV_H
+
+
diff --git a/src/debug/di/rspriv.inl b/src/debug/di/rspriv.inl
new file mode 100644
index 0000000000..00e4c233b6
--- /dev/null
+++ b/src/debug/di/rspriv.inl
@@ -0,0 +1,723 @@
+// 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.
+//*****************************************************************************
+// File: rspriv.inl
+//
+
+//
+// Inline functions for rspriv.h
+//
+//*****************************************************************************
+
+#ifndef RSPRIV_INL_
+#define RSPRIV_INL_
+
+#include "rspriv.h"
+
+// Get the native pipeline object, which resides on the Win32EventThread.
+inline
+INativeEventPipeline * CordbWin32EventThread::GetNativePipeline()
+{
+ return m_pNativePipeline;
+}
+
+
+// True if we're interop-debugging, else false.
+// Note, we include this even in Non-interop builds because there are runtime checks throughout the APIs
+// that certain operations only succeed/fail in interop-debugging.
+inline
+bool CordbProcess::IsInteropDebugging()
+{
+#ifdef FEATURE_INTEROP_DEBUGGING
+ return (m_state & PS_WIN32_ATTACHED) != 0;
+#else
+ return false;
+#endif // FEATURE_INTEROP_DEBUGGING
+}
+
+
+//-----------------------------------------------------------------------------
+// Get the ShimProcess object.
+//
+// Returns:
+// ShimProcess object if available; else NULL.
+//
+// Notes:
+// This shim has V2 emulation logic.
+// If we have no ShimProcess object, then we're in a V3 codepath.
+// @dbgtodo - eventually, remove all emulation and this function.
+//-----------------------------------------------------------------------------
+inline
+ShimProcess * CordbProcess::GetShim()
+{
+ return m_pShim;
+};
+
+
+
+//---------------------------------------------------------------------------------------
+// Helper to read a structure from the target
+//
+// Arguments:
+// T - type of structure to read.
+// pRemotePtr - remote pointer into target (src).
+// pLocalBuffer - local buffer to copy into (Dest).
+//
+// Return Value:
+// Returns S_OK on success, in the event of a short read returns ERROR_PARTIAL_COPY
+//
+// Notes:
+// This just does a raw Byte copy, but does not do any Marshalling.
+// This fails if any part of the buffer can't be read.
+//
+//---------------------------------------------------------------------------------------
+template<typename T>
+HRESULT CordbProcess::SafeReadStruct(CORDB_ADDRESS pRemotePtr, T * pLocalBuffer)
+{
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ TargetBuffer tb(pRemotePtr, sizeof(T));
+ SafeReadBuffer(tb, (PBYTE) pLocalBuffer);
+ }
+ EX_CATCH_HRESULT(hr) ;
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+// Destructor for RSInitHolder. Will safely neuter and release the object.
+template<class T> inline
+RSInitHolder<T>::~RSInitHolder()
+{
+ if (m_pObject != NULL)
+ {
+ CordbProcess * pProcess = m_pObject->GetProcess();
+ RSLockHolder lockHolder(pProcess->GetProcessLock());
+
+ m_pObject->Neuter();
+
+ // Can't explicitly call 'delete' because somebody may have taken a reference.
+ m_pObject.Clear();
+ }
+}
+
+//---------------------------------------------------------------------------------------
+// Helper to write a structure to the target
+//
+// Arguments:
+// T - type of structure to read.
+// pRemotePtr - remote pointer into target (dest).
+// pLocalBuffer - local buffer to write (Src).
+//
+// Return Value:
+// Returns S_OK on success, in the event of a short write returns ERROR_PARTIAL_COPY
+//
+// Notes:
+// This just does a raw Byte copy into the Target, but does not do any Marshalling.
+// This fails if any part of the buffer can't be written.
+//
+//---------------------------------------------------------------------------------------
+template<typename T> inline
+HRESULT CordbProcess::SafeWriteStruct(CORDB_ADDRESS pRemotePtr, const T* pLocalBuffer)
+{
+ HRESULT hr= S_OK;
+ EX_TRY
+ {
+ TargetBuffer tb(pRemotePtr, sizeof(T));
+ SafeWriteBuffer(tb, (BYTE *) (pLocalBuffer));
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+inline
+CordbModule *CordbJITILFrame::GetModule()
+{
+ return (m_ilCode->GetModule());
+}
+
+inline
+CordbAppDomain *CordbJITILFrame::GetCurrentAppDomain()
+{
+ return (m_nativeFrame->GetCurrentAppDomain());
+}
+
+//-----------------------------------------------------------------------------
+// Called to notify that we must flush DAC
+//-----------------------------------------------------------------------------
+inline
+void CordbProcess::ForceDacFlush()
+{
+ // We need to take the process lock here because otherwise we could race with the Arrowhead stackwalking
+ // APIs. The Arrowhead stackwalking APIs check the flush counter and refresh all the state if necessary.
+ // However, while one thread is refreshing the state of the stackwalker, another thread may come in
+ // and force a flush. That's why we need to take a process lock before we flush. We need to synchronize
+ // with other threads which are using DAC memory.
+ RSLockHolder lockHolder(GetProcessLock());
+
+ // For Mac debugging, it is not safe to call into the DAC once code:INativeEventPipeline::TerminateProcess
+ // is called. Also, we must check m_exiting under the process lock.
+ if (!m_exiting)
+ {
+ if (m_pDacPrimitives != NULL)
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "Flush() - old counter: %d", m_flushCounter);
+ m_flushCounter++;
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ m_pDacPrimitives->FlushCache();
+ }
+ EX_CATCH_HRESULT(hr);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+ }
+ }
+}
+
+
+inline
+CordbFunction *CordbJITILFrame::GetFunction()
+{
+ return m_nativeFrame->m_nativeCode->GetFunction();
+}
+
+//-----------------------------------------------------------------------------
+// Helpers to assert threading semantics.
+//-----------------------------------------------------------------------------
+inline bool IsWin32EventThread(CordbProcess * p)
+{
+ _ASSERTE(p!= NULL);
+ return p->IsWin32EventThread();
+}
+
+inline bool IsRCEventThread(Cordb* p)
+{
+ _ASSERTE(p!= NULL);
+ return (p->m_rcEventThread != NULL) && p->m_rcEventThread->IsRCEventThread();
+}
+
+
+
+//-----------------------------------------------------------------------------
+// StopContinueHolder. Ensure that we're synced during a certain region.
+//-----------------------------------------------------------------------------
+inline HRESULT StopContinueHolder::Init(CordbProcess * p)
+{
+ _ASSERTE(p != NULL);
+ LOG((LF_CORDB, LL_INFO100000, "Doing RS internal Stop\n"));
+ HRESULT hr = p->StopInternal(INFINITE, VMPTR_AppDomain::NullPtr());
+ if ((hr == CORDBG_E_PROCESS_TERMINATED) || SUCCEEDED(hr))
+ {
+ // Better be synced after calling Stop!
+ _ASSERTE(p->GetSynchronized());
+ m_p = p;
+ }
+
+ return hr;
+};
+
+inline StopContinueHolder::~StopContinueHolder()
+{
+ // If Init() failed to call Stop, then don't call continue
+ if (m_p == NULL)
+ return;
+
+ HRESULT hr;
+ LOG((LF_CORDB, LL_INFO100000, "Doing RS internal Continue\n"));
+ hr = m_p->ContinueInternal(false);
+ SIMPLIFYING_ASSUMPTION(
+ (hr == CORDBG_E_PROCESS_TERMINATED) ||
+ (hr == CORDBG_E_PROCESS_DETACHED) ||
+ (hr == CORDBG_E_OBJECT_NEUTERED) ||
+ (hr == E_ACCESSDENIED) || //Sadly in rare cases we leak this error code instead of PROCESS_TERMINATED
+ //See Dev10 bug 872621
+ SUCCEEDED(hr));
+}
+
+//-----------------------------------------------------------------------------
+// Neutering on the base object
+//-----------------------------------------------------------------------------
+inline
+void CordbCommonBase::Neuter()
+{
+ LOG((LF_CORDB, LL_EVERYTHING, "Memory: CordbBase object neutered: this=%p, id=%p\n", this, m_id));
+ m_fIsNeutered = 1;
+}
+
+// Unsafe neuter for an object that's already dead. Only use this if you know exactly what you're doing.
+// The point here is that we can mark the object neutered even though we may not hold the stop-go lock.
+inline
+void CordbCommonBase::UnsafeNeuterDeadObject()
+{
+ LOG((LF_CORDB, LL_EVERYTHING, "Memory: CordbBase object neutered: this=%p, id=%p\n", this, m_id));
+ m_fIsNeutered = 1;
+}
+
+
+//-----------------------------------------------------------------------------
+// Reference Counting
+//-----------------------------------------------------------------------------
+inline
+void CordbCommonBase::InternalAddRef()
+{
+ CONSISTENCY_CHECK_MSGF((m_RefCount & CordbBase_InternalRefCountMask) != (CordbBase_InternalRefCountMax),
+ ("Internal AddRef overlow, External Count = %d,\n'%s' @ 0x%p",
+ (m_RefCount >> CordbBase_ExternalRefCountShift), this->DbgGetName(), this));
+
+ // Since the internal ref-count is the lower bits, and we know we'll never overflow ;)
+ // we can just do an interlocked increment on the whole 32 bits.
+#ifdef TRACK_OUTSTANDING_OBJECTS
+ MixedRefCountUnsigned Count =
+#endif
+
+ InterlockedIncrement64((MixedRefCountSigned*) &m_RefCount);
+
+
+#ifdef _DEBUG_IMPL
+
+ // For leak detection in debug builds, track all internal references.
+ InterlockedIncrement(&Cordb::s_DbgMemTotalOutstandingInternalRefs);
+#endif
+
+#ifdef TRACK_OUTSTANDING_OBJECTS
+ if ((Count & CordbBase_InternalRefCountMask) != 1)
+ {
+ return;
+ }
+
+ LONG i;
+
+ for (i = 0; i < Cordb::s_DbgMemOutstandingObjectMax; i++)
+ {
+ if (Cordb::s_DbgMemOutstandingObjects[i] == NULL)
+ {
+ if (InterlockedCompareExchangeT(&(Cordb::s_DbgMemOutstandingObjects[i]), (LPVOID) this, NULL) == NULL)
+ {
+ return;
+ }
+ }
+ }
+
+ do
+ {
+ i = Cordb::s_DbgMemOutstandingObjectMax + 1;
+ }
+ while ((i < MAX_TRACKED_OUTSTANDING_OBJECTS) &&
+ (InterlockedCompareExchange(&Cordb::s_DbgMemOutstandingObjectMax, i, i - 1) != (i - 1)));
+
+ if (i < MAX_TRACKED_OUTSTANDING_OBJECTS)
+ {
+ Cordb::s_DbgMemOutstandingObjects[i] = this;
+ }
+#endif
+
+}
+
+// Derived versions of AddRef / Release will call these.
+// External AddRef.
+inline
+ULONG CordbCommonBase::BaseAddRef()
+{
+ Volatile<MixedRefCountUnsigned> ref;
+ MixedRefCountUnsigned refNew;
+ ExternalRefCount cExternalCount;
+
+ // Compute what refNew ought to look like; and then If m_RefCount hasn't changed on us
+ // (via another thread), then stash the new one in.
+ do
+ {
+ ref = m_RefCount;
+
+ cExternalCount = (ExternalRefCount) (ref >> CordbBase_ExternalRefCountShift);
+
+ if (cExternalCount == CordbBase_InternalRefCountMax)
+ {
+ CONSISTENCY_CHECK_MSGF(false, ("Overflow in External AddRef. Internal Count =%d,\n'%s' @ 0x%p",
+ (ref & CordbBase_InternalRefCountMask), this->DbgGetName(), this));
+
+ // Ignore any AddRefs beyond this... This will screw up Release(), but we're
+ // probably already so screwed it wouldn't matter.
+ return cExternalCount;
+ }
+
+ cExternalCount++;
+
+ refNew = (((MixedRefCountUnsigned)cExternalCount) << CordbBase_ExternalRefCountShift) | (ref & CordbBase_InternalRefCountMask);
+ }
+ while ((MixedRefCountUnsigned)InterlockedCompareExchange64((MixedRefCountSigned*)&m_RefCount, refNew, ref) != ref);
+
+ return cExternalCount;
+}
+
+// Do an AddRef against the External count. This is a semantics issue.
+// We use this when an internal component Addrefs out-parameters (which Cordbg will call Release on).
+inline
+void CordbCommonBase::ExternalAddRef()
+{
+ // Call on BaseAddRef() to avoid any asserts that prevent stuff from inside the RS from bumping
+ // up the external ref count.
+ BaseAddRef();
+}
+
+inline
+void CordbCommonBase::InternalRelease()
+{
+ CONSISTENCY_CHECK_MSGF((m_RefCount & CordbBase_InternalRefCountMask) != 0,
+ ("Internal Release underflow, External Count = %d,\n'%s' @ 0x%p",
+ (m_RefCount >> CordbBase_ExternalRefCountShift), this->DbgGetName(), this));
+
+#ifdef _DEBUG_IMPL
+ // For leak detection in debug builds, track all internal references.
+ InterlockedDecrement(&Cordb::s_DbgMemTotalOutstandingInternalRefs);
+#endif
+
+
+
+ // The internal count is in the low 16 bits, and we know that we'll never underflow the internal
+ // release. ;)
+ // Furthermore we know that ExternalRelease will prevent us from underflowing the external release count.
+ // Thus we can just do an simple decrement here, and compare against 0x00000000 (which is the value
+ // when both the Internal + External counts are at 0)
+ MixedRefCountSigned cRefCount = InterlockedDecrement64((MixedRefCountSigned*) &m_RefCount);
+
+#ifdef TRACK_OUTSTANDING_OBJECTS
+ if ((cRefCount & CordbBase_InternalRefCountMask) == 0)
+ {
+ for (LONG i = 0; i < Cordb::s_DbgMemOutstandingObjectMax; i++)
+ {
+ if (Cordb::s_DbgMemOutstandingObjects[i] == this)
+ {
+ Cordb::s_DbgMemOutstandingObjects[i] = NULL;
+ break;
+ }
+ }
+ }
+#endif
+
+
+ if (cRefCount == 0x00000000)
+ {
+ delete this;
+ }
+}
+
+// Do an external release.
+inline
+ULONG CordbCommonBase::BaseRelease()
+{
+ Volatile<MixedRefCountUnsigned> ref;
+ MixedRefCountUnsigned refNew;
+ ExternalRefCount cExternalCount;
+
+ // Compute what refNew ought to look like; and then If m_RefCount hasn't changed on us
+ // (via another thread), then stash the new one in.
+ do
+ {
+ ref = m_RefCount;
+
+ cExternalCount = (ExternalRefCount) (ref >> CordbBase_ExternalRefCountShift);
+
+ if (cExternalCount == 0)
+ {
+ CONSISTENCY_CHECK_MSGF(false, ("Underflow in External Release. Internal Count = %d\n'%s' @ 0x%p",
+ (ref & CordbBase_InternalRefCountMask), this->DbgGetName(), this));
+
+ // Ignore any Releases beyond this... This will screw up Release(), but we're
+ // probably already so screwed it wouldn't matter.
+ // It's very important that we don't let the release count go negative (both
+ // Releases assumes this when deciding whether to delete)
+ return 0;
+ }
+
+ cExternalCount--;
+
+ refNew = (((MixedRefCountUnsigned) cExternalCount) << CordbBase_ExternalRefCountShift) | (ref & CordbBase_InternalRefCountMask);
+ }
+ while ((MixedRefCountUnsigned)InterlockedCompareExchange64((MixedRefCountSigned*)&m_RefCount, refNew, ref) != ref);
+
+ // If the external count just dropped to 0, then this object can be neutered.
+ if (cExternalCount == 0)
+ {
+ m_fNeuterAtWill = 1;
+ }
+
+ if (refNew == 0)
+ {
+ delete this;
+ return 0;
+ }
+ return cExternalCount;
+
+}
+
+
+inline ULONG CordbCommonBase::BaseAddRefEnforceExternal()
+{
+ // External refs shouldn't be called while in the RS
+#ifdef RSCONTRACTS
+ DbgRSThread * pThread = DbgRSThread::GetThread();
+ CONSISTENCY_CHECK_MSGF(!pThread->IsInRS(),
+ ("External addref for pThis=0x%p, name='%s' called from within RS",
+ this, this->DbgGetName()
+ ));
+#endif
+ return (BaseAddRef());
+
+}
+
+inline ULONG CordbCommonBase::BaseReleaseEnforceExternal()
+{
+#ifdef RSCONTRACTS
+ DbgRSThread * pThread = DbgRSThread::GetThread();
+
+ CONSISTENCY_CHECK_MSGF(!pThread->IsInRS(),
+ ("External release for pThis=0x%p, name='%s' called from within RS",
+ this, this->DbgGetName()
+ ));
+#endif
+
+ return (BaseRelease());
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Locks
+//-----------------------------------------------------------------------------
+
+// Base class
+#ifdef _DEBUG
+inline bool RSLock::HasLock()
+{
+ CONSISTENCY_CHECK_MSGF(IsInit(), ("RSLock '%s' not inited", m_szTag));
+ return m_tidOwner == ::GetCurrentThreadId();
+}
+#endif
+
+#ifdef _DEBUG
+// Ctor+ Dtor are only used for asserts.
+inline RSLock::RSLock()
+{
+ m_eAttr = cLockUninit;
+ m_tidOwner = (DWORD)-1;
+};
+
+inline RSLock::~RSLock()
+{
+ // If this lock is still ininitialized, then no body ever deleted the critical section
+ // for it and we're leaking.
+ CONSISTENCY_CHECK_MSGF(!IsInit(), ("Leaking Critical section for RS Lock '%s'", m_szTag));
+}
+#endif
+
+
+// Initialize a lock.
+inline void RSLock::Init(const char * szTag, int eAttr, ERSLockLevel level)
+{
+ CONSISTENCY_CHECK_MSGF(!IsInit(), ("RSLock '%s' already inited", szTag));
+#ifdef _DEBUG
+ m_szTag = szTag;
+ m_eAttr = eAttr;
+ m_count = 0;
+ m_level = level;
+
+ // Must be either re-entrant xor flat. (not neither; not both)
+ _ASSERTE(IsReentrant() ^ ((m_eAttr & cLockFlat) == cLockFlat));
+#endif
+ _ASSERTE((level >= 0) && (level <= RSLock::LL_MAX));
+
+ _ASSERTE(IsInit());
+
+ InitializeCriticalSection(&m_lock);
+}
+
+// Cleanup a lock.
+inline void RSLock::Destroy()
+{
+ CONSISTENCY_CHECK_MSGF(IsInit(), ("RSLock '%s' not inited", m_szTag));
+ DeleteCriticalSection(&m_lock);
+
+#ifdef _DEBUG
+ m_eAttr = cLockUninit; // No longer initialized.
+ _ASSERTE(!IsInit());
+#endif
+}
+
+inline void RSLock::Lock()
+{
+ CONSISTENCY_CHECK_MSGF(IsInit(), ("RSLock '%s' not inited", m_szTag));
+
+#ifdef RSCONTRACTS
+ DbgRSThread * pThread = DbgRSThread::GetThread();
+ pThread->NotifyTakeLock(this);
+#endif
+
+ EnterCriticalSection(&m_lock);
+#ifdef _DEBUG
+ m_tidOwner = ::GetCurrentThreadId();
+ m_count++;
+
+ // Either count == 1 or we're re-entrant.
+ _ASSERTE((m_count == 1) || (m_eAttr == cLockReentrant));
+#endif
+}
+
+inline void RSLock::Unlock()
+{
+ CONSISTENCY_CHECK_MSGF(IsInit(), ("RSLock '%s' not inited", m_szTag));
+
+#ifdef _DEBUG
+ _ASSERTE(HasLock());
+ m_count--;
+ _ASSERTE(m_count >= 0);
+ if (m_count == 0)
+ {
+ m_tidOwner = (DWORD)-1;
+ }
+#endif
+
+#ifdef RSCONTRACTS
+ // NotifyReleaseLock needs to be called before we release the lock.
+ // Note that HasLock()==false at this point. NotifyReleaseLock relies on that.
+ DbgRSThread * pThread = DbgRSThread::GetThread();
+ pThread->NotifyReleaseLock(this);
+#endif
+
+ LeaveCriticalSection(&m_lock);
+}
+
+template <class T>
+inline T* CordbSafeHashTable<T>::GetBase(ULONG_PTR id, BOOL fFab)
+{
+ return static_cast<T*>(UnsafeGetBase(id, fFab));
+}
+
+template <class T>
+inline T* CordbSafeHashTable<T>::GetBaseOrThrow(ULONG_PTR id, BOOL fFab)
+{
+ T* pResult = GetBase(id, fFab);
+ if (pResult == NULL)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+ else
+ {
+ return pResult;
+ }
+}
+
+// Copy the contents of the hash to an strong-ref array
+//
+// Arguments:
+// pArray - array to allocate storage and copy to
+//
+// Assumptions:
+// Caller locks.
+//
+// Notes:
+// Array takes strong internal references.
+// This can be useful for dancing around locks; eg: If we want to iterate on a hash
+// and do an operation that requires a lock that can't be held when iterating.
+// (Example: Neuter needs Big stop-go lock; Hash is protected by little Process-lock).
+//
+template <class T>
+inline void CordbSafeHashTable<T>::CopyToArray(RSPtrArray<T> * pArray)
+{
+ // Assumes caller has necessary locks to iterate
+ UINT32 count = GetCount();
+ pArray->AllocOrThrow(count);
+
+
+ HASHFIND find;
+ UINT32 idx = 0;
+
+ T * pCordbBase = FindFirst(&find);
+ while(idx < count)
+ {
+ pArray->Assign(idx, pCordbBase);
+ idx++;
+ pCordbBase = FindNext(&find);
+ }
+
+ // Assert is at end.
+ _ASSERTE(pCordbBase == NULL);
+}
+
+// Empty the contents of the hash to an array. Array gets ownersship.
+//
+// Arguments:
+// pArray - array to allocate and get ownership
+//
+// Assumptions:
+// Caller locks.
+//
+// Notes:
+// Hashtable will be empty after this.
+template <class T>
+inline void CordbSafeHashTable<T>::TransferToArray(RSPtrArray<T> * pArray)
+{
+ // Assumes caller has necessary locks
+
+ HASHFIND find;
+ UINT32 count = GetCount();
+ UINT32 idx = 0;
+
+ pArray->AllocOrThrow(count);
+
+ while(idx < count)
+ {
+ T * pCordbBase = FindFirst(&find);
+ _ASSERTE(pCordbBase != NULL);
+ pArray->Assign(idx, pCordbBase);
+
+ idx++;
+ // We're removing while iterating the collection.
+ // But we reset the iteration each time by calling FindFirst.
+ RemoveBase((ULONG_PTR)pCordbBase->m_id); // this will call release, adjust GetCount()
+ }
+
+ // Assert is at end.
+ _ASSERTE(GetCount() == 0);
+}
+
+//
+// Neuter all elements in the hash table and empty the hash.
+//
+// Arguments:
+// pLock - lock required to iterate through hash.
+//
+// Assumptions:
+// Caller ensured it's safe to Neuter.
+// Caller has locked the hash.
+//
+template <class T>
+inline void CordbSafeHashTable<T>::NeuterAndClear(RSLock * pLock)
+{
+ _ASSERTE(pLock->HasLock());
+
+ HASHFIND find;
+ UINT32 count = GetCount();
+ UINT32 idx = 0;
+
+ while(idx < count)
+ {
+ T * pCordbBase = FindFirst(&find);
+ _ASSERTE(pCordbBase != NULL);
+
+ // Using this Validate to help track down bug DevDiv bugs 739406
+ pCordbBase->ValidateObject();
+ pCordbBase->Neuter();
+ idx++;
+
+ // We're removing while iterating the collection.
+ // But we reset the iteration each time by calling FindFirst.
+ RemoveBase((ULONG_PTR)pCordbBase->m_id); // this will call release, adjust GetCount()
+ }
+
+ // Assert is at end.
+ _ASSERTE(GetCount() == 0);
+}
+
+
+#endif // RSPRIV_INL_
diff --git a/src/debug/di/rsregsetcommon.cpp b/src/debug/di/rsregsetcommon.cpp
new file mode 100644
index 0000000000..1a2c57f63f
--- /dev/null
+++ b/src/debug/di/rsregsetcommon.cpp
@@ -0,0 +1,248 @@
+// 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.
+//*****************************************************************************
+// File: RSRegSetCommon.cpp
+//
+
+// Common cross-platform behavior of reg sets.
+// Platform specific stuff is in CordbRegisterSet.cpp located in
+// the platform sub-dir.
+//
+//*****************************************************************************
+#include "stdafx.h"
+#include "primitives.h"
+
+/* ------------------------------------------------------------------------- *
+ * Common (cross-platform) Register-Set stuff
+ * ------------------------------------------------------------------------- */
+
+
+CordbRegisterSet::CordbRegisterSet(
+ DebuggerREGDISPLAY * pRegDisplay,
+ CordbThread * pThread,
+ bool fActive,
+ bool fQuickUnwind,
+ bool fTakeOwnershipOfDRD /*= false*/)
+ : CordbBase(pThread->GetProcess(), 0, enumCordbRegisterSet)
+{
+ _ASSERTE( pRegDisplay != NULL );
+ _ASSERTE( pThread != NULL );
+ m_rd = pRegDisplay;
+ m_thread = pThread;
+ m_active = fActive;
+ m_quickUnwind = fQuickUnwind;
+
+ m_fTakeOwnershipOfDRD = fTakeOwnershipOfDRD;
+
+ // Add to our parent thread's neuter list.
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ pThread->GetRefreshStackNeuterList()->Add(GetProcess(), this);
+ }
+ EX_CATCH_HRESULT(hr);
+ SetUnrecoverableIfFailed(GetProcess(), hr);
+}
+
+void CordbRegisterSet::Neuter()
+{
+ m_thread = NULL;
+ if (m_fTakeOwnershipOfDRD)
+ {
+ delete m_rd;
+ }
+ m_rd = NULL;
+
+ CordbBase::Neuter();
+}
+
+CordbRegisterSet::~CordbRegisterSet()
+{
+ _ASSERTE(this->IsNeutered());
+}
+
+
+HRESULT CordbRegisterSet::QueryInterface(REFIID riid, void **ppInterface)
+{
+ // <NOTE>
+ // This is an exception to the rule that a QI for a higher version API should fail if
+ // the debugger does not support that version of the API. The reasoning is that
+ // while higher versions of other APIs support enhanced functionality and are not
+ // required, this particular API is required on IA64. An example scenario is when an
+ // Everett debuggger is ported to Whidbey and the user wants to use the debugger on IA64.
+ // The user should not be required to implement the ICorDebugManagedCallback2 API, as would
+ // be the case if we make the versioning check like other higher version APIs.
+ // </NOTE>
+ if (riid == IID_ICorDebugRegisterSet)
+ {
+ *ppInterface = static_cast<ICorDebugRegisterSet*>(this);
+ }
+ else if (riid == IID_ICorDebugRegisterSet2)
+ {
+ *ppInterface = static_cast<ICorDebugRegisterSet2*>(this);
+ }
+ else if (riid == IID_IUnknown)
+ {
+ *ppInterface = static_cast<IUnknown*>(static_cast<ICorDebugRegisterSet*>(this));
+ }
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// This is just a convenience function to convert a regdisplay into a Context.
+// Since a context has more info than a regdisplay, the conversion isn't perfect
+// and the context can't be fully accurate.
+//
+// Inputs:
+// contextSize - sizeof incoming context buffer in bytes
+// context - buffer to copy this regdisplay's OS CONTEXT structure into.
+//
+// Returns S_OK on success.
+//-----------------------------------------------------------------------------
+HRESULT CordbRegisterSet::GetThreadContext(ULONG32 contextSize, BYTE context[])
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ _ASSERTE( m_thread != NULL );
+ if( contextSize < sizeof( DT_CONTEXT ))
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ ValidateOrThrow(context);
+
+ DT_CONTEXT *pInputContext = reinterpret_cast<DT_CONTEXT *> (context);
+
+ // Just to be safe, zero out the buffer we got in while preserving the ContextFlags.
+ // On X64 the ContextFlags field is not the first 4 bytes of the DT_CONTEXT.
+ DWORD dwContextFlags = pInputContext->ContextFlags;
+ ZeroMemory(context, contextSize);
+ pInputContext->ContextFlags = dwContextFlags;
+
+ // Augment the leafmost (active) register w/ information from the current context.
+ DT_CONTEXT * pLeafContext = NULL;
+ if (m_active)
+ {
+ EX_TRY
+ {
+ // This may fail, but it is not a disastrous failure in this case. All we care is whether
+ // pLeafContext is updated to a non-NULL value.
+ m_thread->GetManagedContext( &pLeafContext);
+ }
+ EX_CATCH
+ {
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ if (pLeafContext != NULL)
+ {
+ // @todo - shouldn't this be a context-flags sensitive copy?
+ memmove( pInputContext, pLeafContext, sizeof( DT_CONTEXT) );
+ }
+ }
+
+
+ // Now update the registers based on the current frame.
+ // This is a very platform specific action.
+ InternalCopyRDToContext(pInputContext);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// Helpers to impl IRegSet2 on top of original IRegSet.
+// These are useful on platforms that don't need IRegSet2 (like x86 + amd64).
+// See CorDebug.idl for details.
+//
+// Inputs:
+// regCount - size of pAvailable buffer in bytes
+// pAvailable - buffer to hold bitvector of available registers.
+// On success, bit at position CorDebugRegister is 1 iff that
+// register is available.
+// Returns S_OK on success.
+//-----------------------------------------------------------------------------
+HRESULT CordbRegisterSet::GetRegistersAvailableAdapter(
+ ULONG32 regCount,
+ BYTE pAvailable[])
+{
+ // Defer to call on v1.0 interface
+ HRESULT hr = S_OK;
+
+ if (regCount < sizeof(ULONG64))
+ {
+ return E_INVALIDARG;
+ }
+
+ _ASSERTE(pAvailable != NULL);
+
+ ULONG64 availRegs;
+ hr = this->GetRegistersAvailable(&availRegs);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ // Nor marshal our 64-bit value into the outgoing byte array.
+ for(int iBit = 0; iBit < (int) sizeof(availRegs) * 8; iBit++)
+ {
+ ULONG64 test = SETBITULONG64(iBit);
+ if (availRegs & test)
+ {
+ SET_BIT_MASK(pAvailable, iBit);
+ }
+ else
+ {
+ RESET_BIT_MASK(pAvailable, iBit);
+ }
+ }
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Helpers to impl IRegSet2 on top of original IRegSet.
+// These are useful on platforms that don't need IRegSet2 (like x86 + amd64).
+// See CorDebug.idl for details.
+//
+// Inputs:
+// maskCount - size of mask buffer in bytes.
+// mask - input buffer specifying registers to request
+// regCount - size of regBuffer in bytes
+// regBuffer - output buffer, regBuffer[n] = value of register at n-th active
+// bit in mask.
+// Returns S_OK on success.
+//-----------------------------------------------------------------------------
+
+// mask input requrest registers, which get written to regCount buffer.
+HRESULT CordbRegisterSet::GetRegistersAdapter(
+ ULONG32 maskCount, BYTE mask[],
+ ULONG32 regCount, CORDB_REGISTER regBuffer[])
+{
+ // Convert input mask to orig mask.
+ ULONG64 maskOrig = 0;
+
+ for(UINT iBit = 0; iBit < maskCount * 8; iBit++)
+ {
+ if (IS_SET_BIT_MASK(mask, iBit))
+ {
+ maskOrig |= SETBITULONG64(iBit);
+ }
+ }
+
+ return this->GetRegisters(maskOrig,
+ regCount, regBuffer);
+}
diff --git a/src/debug/di/rsstackwalk.cpp b/src/debug/di/rsstackwalk.cpp
new file mode 100644
index 0000000000..8ade4c9a74
--- /dev/null
+++ b/src/debug/di/rsstackwalk.cpp
@@ -0,0 +1,822 @@
+// 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.
+//
+// RsStackWalk.cpp
+//
+
+//
+// This file contains the implementation of the V3 managed stackwalking API.
+//
+// ======================================================================================
+
+#include "stdafx.h"
+#include "primitives.h"
+
+
+//---------------------------------------------------------------------------------------
+//
+// Constructor for CordbStackWalk.
+//
+// Arguments:
+// pCordbThread - the thread on which this stackwalker is created
+//
+
+CordbStackWalk::CordbStackWalk(CordbThread * pCordbThread)
+ : CordbBase(pCordbThread->GetProcess(), 0, enumCordbStackWalk),
+ m_pCordbThread(pCordbThread),
+ m_pSFIHandle(NULL),
+ m_cachedSetContextFlag(SET_CONTEXT_FLAG_ACTIVE_FRAME),
+ m_cachedHR(S_OK),
+ m_fIsOneFrameAhead(false)
+{
+ m_pCachedFrame.Clear();
+}
+
+void CordbStackWalk::Init()
+{
+ CordbProcess * pProcess = GetProcess();
+ m_lastSyncFlushCounter = pProcess->m_flushCounter;
+
+ IDacDbiInterface * pDAC = pProcess->GetDAC();
+ pDAC->CreateStackWalk(m_pCordbThread->m_vmThreadToken,
+ &m_context,
+ &m_pSFIHandle);
+
+ // see the function header of code:CordbStackWalk::CheckForLegacyHijackCase
+ CheckForLegacyHijackCase();
+
+ // Add itself to the neuter list.
+ m_pCordbThread->GetRefreshStackNeuterList()->Add(GetProcess(), this);
+}
+
+// ----------------------------------------------------------------------------
+// CordbStackWalk::CheckForLegacyHijackCase
+//
+// Description:
+// @dbgtodo legacy interop debugging - In the case of an unhandled hardware exception, the
+// thread will be hijacked to code:Debugger::GenericHijackFunc, which the stackwalker doesn't know how to
+// unwind. We can teach the stackwalker to recognize that hijack stub, but since it's going to be deprecated
+// anyway, it's not worth the effort. So we check for the hijack CONTEXT here and use it as the CONTEXT. This
+// check should be removed when we are completely
+// out-of-process.
+//
+
+void CordbStackWalk::CheckForLegacyHijackCase()
+{
+#if defined(FEATURE_INTEROP_DEBUGGING)
+ CordbProcess * pProcess = GetProcess();
+
+ // Only do this if we have a shim and we are interop-debugging.
+ if ((pProcess->GetShim() != NULL) &&
+ pProcess->IsInteropDebugging())
+ {
+ // And only if we have a CordbUnmanagedThread and we are hijacked to code:Debugger::GenericHijackFunc
+ CordbUnmanagedThread * pUT = pProcess->GetUnmanagedThread(m_pCordbThread->GetVolatileOSThreadID());
+ if (pUT != NULL)
+ {
+ if (pUT->IsFirstChanceHijacked() || pUT->IsGenericHijacked())
+ {
+ // The GetThreadContext function hides the effects of hijacking and returns the unhijacked context
+ m_context.ContextFlags = DT_CONTEXT_FULL;
+ pUT->GetThreadContext(&m_context);
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ pDAC->SetStackWalkCurrentContext(m_pCordbThread->m_vmThreadToken,
+ m_pSFIHandle,
+ SET_CONTEXT_FLAG_ACTIVE_FRAME,
+ &m_context);
+ }
+ }
+ }
+#endif // FEATURE_INTEROP_DEBUGGING
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Destructor for CordbStackWalk.
+//
+// Notes:
+// We don't really need to do anything here since the CordbStackWalk should have been neutered already.
+//
+
+CordbStackWalk::~CordbStackWalk()
+{
+ _ASSERTE(IsNeutered());
+}
+
+//---------------------------------------------------------------------------------------
+//
+// This function resets all the state on a CordbStackWalk and releases all the memory.
+// It is used for neutering and refreshing.
+//
+
+void CordbStackWalk::DeleteAll()
+{
+ _ASSERTE(GetProcess()->GetProcessLock()->HasLock());
+
+ // delete allocated memory
+ if (m_pSFIHandle)
+ {
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+#if defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ // For Mac debugging, it's not safe to call into the DAC once
+ // code:INativeEventPipeline::TerminateProcess is called. This is because the transport will not
+ // work anymore. The sole purpose of calling DeleteStackWalk() is to release the resources and
+ // memory allocated for the stackwalk. In the remote debugging case, the memory is allocated in
+ // the debuggee process. If the process is already terminated, then it's ok to skip the call.
+ if (!GetProcess()->m_exiting)
+#endif // FEATURE_DBGIPC_TRANSPORT_DI
+ {
+ // This Delete call shouldn't actually throw. Worst case, the DDImpl leaked memory.
+ GetProcess()->GetDAC()->DeleteStackWalk(m_pSFIHandle);
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+ m_pSFIHandle = NULL;
+ }
+
+ // clear out the cached frame
+ m_pCachedFrame.Clear();
+ m_cachedHR = S_OK;
+ m_fIsOneFrameAhead = false;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Release all memory used by the stackwalker.
+//
+//
+// Notes:
+// CordbStackWalk is neutered by CordbThread or CleanupStack().
+//
+
+void CordbStackWalk::Neuter()
+{
+ if (IsNeutered())
+ {
+ return;
+ }
+
+ DeleteAll();
+ CordbBase::Neuter();
+}
+
+// standard QI function
+HRESULT CordbStackWalk::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugStackWalk)
+ {
+ *pInterface = static_cast<ICorDebugStackWalk*>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugStackWalk*>(this));
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Refreshes all the state stored on the CordbStackWalk. This is necessary because sending IPC events to
+// the LS flushes the DAC cache, and m_pSFIHandle is allocated entirely in DAC memory. So, we keep track
+// of whether we have sent an IPC event and refresh the CordbStackWalk if necessary.
+//
+// Notes:
+// Throws on error.
+//
+
+void CordbStackWalk::RefreshIfNeeded()
+{
+ CordbProcess * pProcess = GetProcess();
+ _ASSERTE(pProcess->GetProcessLock()->HasLock());
+
+ // check if we need to refresh
+ if (m_lastSyncFlushCounter != pProcess->m_flushCounter)
+ {
+ // Make a local copy of the CONTEXT here. DeleteAll() will delete the CONTEXT on the cached frame,
+ // and CreateStackWalk() actually uses the CONTEXT buffer we pass to it.
+ DT_CONTEXT ctx;
+ if (m_fIsOneFrameAhead)
+ {
+ ctx = *(m_pCachedFrame->GetContext());
+ }
+ else
+ {
+ ctx = m_context;
+ }
+
+ // clear all the state
+ DeleteAll();
+
+ // create a new stackwalk handle
+ pProcess->GetDAC()->CreateStackWalk(m_pCordbThread->m_vmThreadToken,
+ &m_context,
+ &m_pSFIHandle);
+
+ // advance the stackwalker to where we originally were
+ SetContextWorker(m_cachedSetContextFlag, sizeof(DT_CONTEXT), reinterpret_cast<BYTE *>(&ctx));
+
+ // update the sync counter
+ m_lastSyncFlushCounter = pProcess->m_flushCounter;
+ }
+} // CordbStackWalk::RefreshIfNeeded()
+
+//---------------------------------------------------------------------------------------
+//
+// Retrieves the CONTEXT of the current frame.
+//
+// Arguments:
+// contextFlags - context flags used to determine the required size for the buffer
+// contextBufSize - size of the CONTEXT buffer
+// pContextSize - out parameter; returns the size required for the CONTEXT buffer
+// pbContextBuf - the CONTEXT buffer
+//
+// Return Value:
+// Return S_OK on success.
+// Return CORDBG_E_PAST_END_OF_STACK if we are already at the end of the stack.
+// Return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) if the buffer is too small.
+// Return E_FAIL on other failures.
+//
+
+HRESULT CordbStackWalk::GetContext(ULONG32 contextFlags,
+ ULONG32 contextBufSize,
+ ULONG32 * pContextSize,
+ BYTE pbContextBuf[])
+{
+ HRESULT hr = S_OK;
+ PUBLIC_REENTRANT_API_BEGIN(this)
+ {
+ RefreshIfNeeded();
+
+ // set the required size for the CONTEXT buffer
+ if (pContextSize != NULL)
+ {
+ *pContextSize = ContextSizeForFlags(contextFlags);
+ }
+
+ // If all the user wants to know is the CONTEXT size, then we are done.
+ if ((contextBufSize != 0) && (pbContextBuf != NULL))
+ {
+ if (contextBufSize < 4)
+ {
+ ThrowWin32(ERROR_INSUFFICIENT_BUFFER);
+ }
+
+ DT_CONTEXT * pContext = reinterpret_cast<DT_CONTEXT *>(pbContextBuf);
+
+ // Some helper functions that examine the context expect the flags to be initialized.
+ pContext->ContextFlags = contextFlags;
+
+ // check the size of the incoming buffer
+ if (!CheckContextSizeForBuffer(contextBufSize, pbContextBuf))
+ {
+ ThrowWin32(ERROR_INSUFFICIENT_BUFFER);
+ }
+
+ // Check if we are one frame ahead. If so, returned the CONTEXT on the cached frame.
+ if (m_fIsOneFrameAhead)
+ {
+ if (m_pCachedFrame != NULL)
+ {
+ const DT_CONTEXT * pSrcContext = m_pCachedFrame->GetContext();
+ _ASSERTE(pSrcContext);
+ CORDbgCopyThreadContext(pContext, pSrcContext);
+ }
+ else
+ {
+ // We encountered a problem when we were trying to initialize the CordbNativeFrame.
+ // However, the problem occurred after we have unwound the current frame.
+ // What do we do here? We don't have the CONTEXT anymore.
+ _ASSERTE(FAILED(m_cachedHR));
+ ThrowHR(m_cachedHR);
+ }
+ }
+ else
+ {
+ // No easy way out in this case. We have to call the DDI.
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+
+ IDacDbiInterface::FrameType ft = pDAC->GetStackWalkCurrentFrameInfo(m_pSFIHandle, NULL);
+ if (ft == IDacDbiInterface::kInvalid)
+ {
+ ThrowHR(E_FAIL);
+ }
+ else if (ft == IDacDbiInterface::kAtEndOfStack)
+ {
+ ThrowHR(CORDBG_E_PAST_END_OF_STACK);
+ }
+ else if (ft == IDacDbiInterface::kExplicitFrame)
+ {
+ ThrowHR(CORDBG_E_NO_CONTEXT_FOR_INTERNAL_FRAME);
+ }
+ else
+ {
+ // We always store the current CONTEXT, so just copy it into the buffer.
+ CORDbgCopyThreadContext(pContext, &m_context);
+ }
+ }
+ }
+ }
+ PUBLIC_REENTRANT_API_END(hr);
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Set the stackwalker to the specified CONTEXT.
+//
+// Arguments:
+// flag - context flags used to determine the size of the CONTEXT
+// contextSize - the size of the CONTEXT
+// context - the CONTEXT as a byte array
+//
+// Return Value:
+// Return S_OK on success.
+// Return E_INVALIDARG if context is NULL
+// Return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) if the CONTEXT is too small.
+// Return E_FAIL on other failures.
+//
+
+HRESULT CordbStackWalk::SetContext(CorDebugSetContextFlag flag, ULONG32 contextSize, BYTE context[])
+{
+ HRESULT hr = S_OK;
+ PUBLIC_REENTRANT_API_BEGIN(this)
+ {
+ RefreshIfNeeded();
+ SetContextWorker(flag, contextSize, context);
+ }
+ PUBLIC_REENTRANT_API_END(hr);
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Refer to the comment for code:CordbStackWalk::SetContext
+//
+
+void CordbStackWalk::SetContextWorker(CorDebugSetContextFlag flag, ULONG32 contextSize, BYTE context[])
+{
+ if (context == NULL)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ if (!CheckContextSizeForBuffer(contextSize, context))
+ {
+ ThrowWin32(ERROR_INSUFFICIENT_BUFFER);
+ }
+
+ // invalidate the cache
+ m_pCachedFrame.Clear();
+ m_cachedHR = S_OK;
+ m_fIsOneFrameAhead = false;
+
+ DT_CONTEXT * pSrcContext = reinterpret_cast<DT_CONTEXT *>(context);
+
+ // Check the incoming CONTEXT using a temporary CONTEXT buffer before updating our real CONTEXT buffer.
+ // The incoming CONTEXT is not required to have all the bits set in its CONTEXT flags, so only update
+ // the registers specified by the CONTEXT flags. Note that CORDbgCopyThreadContext() honours the CONTEXT
+ // flags on both the source and the destination CONTEXTs when it copies them.
+ DT_CONTEXT tmpCtx = m_context;
+ tmpCtx.ContextFlags |= pSrcContext->ContextFlags;
+ CORDbgCopyThreadContext(&tmpCtx, pSrcContext);
+
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ IfFailThrow(pDAC->CheckContext(m_pCordbThread->m_vmThreadToken, &tmpCtx));
+
+ // At this point we have done all of our checks to verify that the incoming CONTEXT is sane, so we can
+ // update our internal CONTEXT buffer.
+ m_context = tmpCtx;
+ m_cachedSetContextFlag = flag;
+
+ pDAC->SetStackWalkCurrentContext(m_pCordbThread->m_vmThreadToken,
+ m_pSFIHandle,
+ flag,
+ &m_context);
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Helper to perform all the necessary operations when we unwind, including:
+// 1) Unwind
+// 2) Save the new unwound CONTEXT
+//
+// Return Value:
+// Return TRUE if we successfully unwind to the next frame.
+// Return FALSE if there is no more frame to walk.
+// Throw on error.
+//
+
+BOOL CordbStackWalk::UnwindStackFrame()
+{
+ CordbProcess * pProcess = GetProcess();
+ _ASSERTE(pProcess->GetProcessLock()->HasLock());
+
+ IDacDbiInterface * pDAC = pProcess->GetDAC();
+ BOOL retVal = pDAC->UnwindStackWalkFrame(m_pSFIHandle);
+
+ // Now that we have unwound, make sure we update the CONTEXT buffer to reflect the current stack frame.
+ // This call is safe regardless of whether the unwind is successful or not.
+ pDAC->GetStackWalkCurrentContext(m_pSFIHandle, &m_context);
+
+ return retVal;
+} // CordbStackWalk::UnwindStackWalkFrame
+
+//---------------------------------------------------------------------------------------
+//
+// Unwind the stackwalker to the next frame.
+//
+// Return Value:
+// Return S_OK on success.
+// Return CORDBG_E_FAIL_TO_UNWIND_FRAME if the unwind fails.
+// Return CORDBG_S_AT_END_OF_STACK if we have reached the end of the stack as a result of this unwind.
+// Return CORDBG_E_PAST_END_OF_STACK if we are already at the end of the stack to begin with.
+//
+
+HRESULT CordbStackWalk::Next()
+{
+ HRESULT hr = S_OK;
+ PUBLIC_REENTRANT_API_BEGIN(this)
+ {
+ RefreshIfNeeded();
+ if (m_fIsOneFrameAhead)
+ {
+ // We have already unwound to the next frame when we materialize the CordbNativeFrame
+ // for the current frame. So we just need to clear the cache because we are already at
+ // the next frame.
+ if (m_pCachedFrame != NULL)
+ {
+ m_pCachedFrame.Clear();
+ }
+ m_cachedHR = S_OK;
+ m_fIsOneFrameAhead = false;
+ }
+ else
+ {
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ IDacDbiInterface::FrameType ft = IDacDbiInterface::kInvalid;
+
+ ft = pDAC->GetStackWalkCurrentFrameInfo(this->m_pSFIHandle, NULL);
+ if (ft == IDacDbiInterface::kAtEndOfStack)
+ {
+ ThrowHR(CORDBG_E_PAST_END_OF_STACK);
+ }
+
+ // update the cahced flag to indicate that we have reached an unwind CONTEXT
+ m_cachedSetContextFlag = SET_CONTEXT_FLAG_UNWIND_FRAME;
+
+ if (UnwindStackFrame())
+ {
+ hr = S_OK;
+ }
+ else
+ {
+ hr = CORDBG_S_AT_END_OF_STACK;
+ }
+ }
+ }
+ PUBLIC_REENTRANT_API_END(hr);
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Retrieves an ICDFrame corresponding to the current frame:
+// Stopped At Out Parameter Return Value
+// ---------- ------------- ------------
+// explicit frame CordbInternalFrame S_OK
+// managed stack frame CordbNativeFrame S_OK
+// native stack frame NULL S_FALSE
+//
+// Arguments:
+// ppFrame - out parameter; return the ICDFrame
+//
+// Return Value:
+// On success return the HRs above.
+// Return CORDBG_E_PAST_END_OF_STACK if we are already at the end of the stack.
+// Return E_INVALIDARG if ppFrame is NULL
+// Return E_FAIL on other errors.
+//
+// Notes:
+// This is just a wrapper with an EX_TRY/EX_CATCH_HRESULT for GetFrameWorker().
+//
+
+HRESULT CordbStackWalk::GetFrame(ICorDebugFrame ** ppFrame)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_REENTRANT_API_NO_LOCK_BEGIN(this)
+ {
+ ATT_REQUIRE_STOPPED_MAY_FAIL_OR_THROW(GetProcess(), ThrowHR);
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+
+ RefreshIfNeeded();
+ hr = GetFrameWorker(ppFrame);
+ }
+ PUBLIC_REENTRANT_API_END(hr);
+
+ if (FAILED(hr))
+ {
+ if (m_fIsOneFrameAhead && (m_pCachedFrame == NULL))
+ {
+ // We encountered a problem when we try to materialize a CordbNativeFrame.
+ // Cache the failure HR so that we can return it later if the caller
+ // calls GetFrame() again or GetContext().
+ m_cachedHR = hr;
+ }
+ }
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Refer to the comment for code:CordbStackWalk::GetFrame
+//
+
+HRESULT CordbStackWalk::GetFrameWorker(ICorDebugFrame ** ppFrame)
+{
+ _ASSERTE(GetProcess()->GetProcessLock()->HasLock());
+
+ if (ppFrame == NULL)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+ *ppFrame = NULL;
+
+ RSInitHolder<CordbFrame> pResultFrame(NULL);
+
+ if (m_fIsOneFrameAhead)
+ {
+ if (m_pCachedFrame != NULL)
+ {
+ pResultFrame.Assign(m_pCachedFrame);
+ pResultFrame.TransferOwnershipExternal(ppFrame);
+ return S_OK;
+ }
+ else
+ {
+ // We encountered a problem when we were trying to initialize the CordbNativeFrame.
+ // However, the problem occurred after we have unwound the current frame.
+ // Whatever error code we return, it should be the same one GetContext() returns.
+ _ASSERTE(FAILED(m_cachedHR));
+ ThrowHR(m_cachedHR);
+ }
+ }
+
+ IDacDbiInterface * pDAC = NULL;
+ DebuggerIPCE_STRData frameData;
+ ZeroMemory(&frameData, sizeof(frameData));
+ IDacDbiInterface::FrameType ft = IDacDbiInterface::kInvalid;
+
+ pDAC = GetProcess()->GetDAC();
+ ft = pDAC->GetStackWalkCurrentFrameInfo(m_pSFIHandle, &frameData);
+
+ if (ft == IDacDbiInterface::kInvalid)
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CSW::GFW - invalid stackwalker (%p)", this);
+ ThrowHR(E_FAIL);
+ }
+ else if (ft == IDacDbiInterface::kAtEndOfStack)
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CSW::GFW - past end of stack (%p)", this);
+ ThrowHR(CORDBG_E_PAST_END_OF_STACK);
+ }
+ else if (ft == IDacDbiInterface::kNativeStackFrame)
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CSW::GFW - native stack frame (%p)", this);
+ return S_FALSE;
+ }
+ else if (ft == IDacDbiInterface::kExplicitFrame)
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CSW::GFW - explicit frame (%p)", this);
+
+ // We no longer expect to get internal frames by unwinding.
+ GetProcess()->TargetConsistencyCheck(false);
+ }
+ else if (ft == IDacDbiInterface::kManagedStackFrame)
+ {
+ _ASSERTE(frameData.eType == DebuggerIPCE_STRData::cMethodFrame);
+
+ HRESULT hr = S_OK;
+
+ // In order to find the FramePointer on x86, we need to unwind to the next frame.
+ // Technically, only x86 needs to do this, because the x86 runtime stackwalker doesn't uwnind
+ // one frame ahead of time. However, we are doing this on all platforms to keep things simple.
+ BOOL fSuccess = UnwindStackFrame();
+ (void)fSuccess; //prevent "unused variable" error from GCC
+ _ASSERTE(fSuccess);
+
+ m_fIsOneFrameAhead = true;
+#if defined(DBG_TARGET_X86)
+ frameData.fp = pDAC->GetFramePointer(m_pSFIHandle);
+#endif // DBG_TARGET_X86
+
+ // currentFuncData contains general information about the method.
+ // It has no information about any particular jitted instance of the method.
+ DebuggerIPCE_FuncData * pFuncData = &(frameData.v.funcData);
+
+ // currentJITFuncData contains information about the current jitted instance of the method
+ // on the stack.
+ DebuggerIPCE_JITFuncData * pJITFuncData = &(frameData.v.jitFuncData);
+
+ // Lookup the appdomain that the thread was in when it was executing code for this frame. We pass this
+ // to the frame when we create it so we can properly resolve locals in that frame later.
+ CordbAppDomain * pCurrentAppDomain = GetProcess()->LookupOrCreateAppDomain(frameData.vmCurrentAppDomainToken);
+ _ASSERTE(pCurrentAppDomain != NULL);
+
+ // Lookup the module
+ CordbModule* pModule = pCurrentAppDomain->LookupOrCreateModule(pFuncData->vmDomainFile);
+ PREFIX_ASSUME(pModule != NULL);
+
+ // Create or look up a CordbNativeCode. There is one for each jitted instance of a method,
+ // and we may have multiple instances because of generics.
+ CordbNativeCode * pNativeCode = pModule->LookupOrCreateNativeCode(pFuncData->funcMetadataToken,
+ pJITFuncData->vmNativeCodeMethodDescToken,
+ pJITFuncData->nativeStartAddressPtr);
+ IfFailThrow(hr);
+
+ // The native code object will create the function object if needed
+ CordbFunction * pFunction = pNativeCode->GetFunction();
+
+ // A CordbFunction is theoretically the uninstantiated method, yet for back-compat we allow
+ // debuggers to assume that it corresponds to exactly 1 native code blob. In order for
+ // an open generic function to know what native code to give back, we attach an arbitrary
+ // native code that we located through code inspection.
+ // Note that not all CordbFunction objects get created via stack traces because you can also
+ // create them by name. In that case you still won't get code for Open generic functions
+ // because we will never have attached one and the lookup by token is insufficient. This
+ // behavior mimics our 2.0 debugging behavior though so its not a regression.
+ pFunction->NotifyCodeCreated(pNativeCode);
+
+ IfFailThrow(hr);
+
+ _ASSERTE((pFunction != NULL) && (pNativeCode != NULL));
+
+ // initialize the auxiliary info required for funclets
+ CordbMiscFrame miscFrame(pJITFuncData);
+
+ // Create the native frame.
+ CordbNativeFrame* pNativeFrame = new CordbNativeFrame(m_pCordbThread,
+ frameData.fp,
+ pNativeCode,
+ pJITFuncData->nativeOffset,
+ &(frameData.rd),
+ frameData.v.taAmbientESP,
+ !!frameData.quicklyUnwound,
+ pCurrentAppDomain,
+ &miscFrame,
+ &(frameData.ctx));
+
+ pResultFrame.Assign(static_cast<CordbFrame *>(pNativeFrame));
+ m_pCachedFrame.Assign(static_cast<CordbFrame *>(pNativeFrame));
+
+ // @dbgtodo dynamic language debugging
+ // If we are dealing with a dynamic method (e.g. an IL stub, a LCG method, etc.),
+ // then we don't have the metadata or the debug info (sequence points, etc.).
+ // This means that we can't do anything meaningful with a CordbJITILFrame anyway,
+ // so let's not create the CordbJITILFrame at all. Note that methods created with
+ // RefEmit are okay, i.e. they have metadata.
+
+ // The check for IsNativeImpl() != CordbFunction::kNativeOnly catches an odd profiler
+ // case. A profiler can rewrite assemblies at load time so that a P/invoke becomes a
+ // regular managed method. mscordbi isn't yet designed to handle runtime metadata
+ // changes, so it still thinks the method is a p/invoke. If we only relied on
+ // frameData.v.fNoMetadata which is populated by the DAC, that will report
+ // FALSE (the method does have metadata/IL now). However pNativeCode->LoadNativeInfo
+ // is going to check DBI's metadata and calculate this is a p/invoke, which will
+ // throw an exception that the method isn't IL.
+ // Ideally we probably want to expose the profiler's change to the method,
+ // however that will take significant work. Part of that is correctly detecting and
+ // updating metadata in DBI, part is determinging if/how the debugger is notified,
+ // and part is auditing mscordbi to ensure that anything we cached based on the
+ // old metadata is correctly invalidated.
+ // Since this is a late fix going into a controlled servicing release I have
+ // opted for a much narrower fix. Doing the check for IsNativeImpl() != CordbFunction::kNativeOnly
+ // will continue to treat our new method as though it was a p/invoke, and the
+ // debugger will not provide IL for it. The debugger can't inspect within the profiler
+ // modified method, but at least the error won't leak out to interfere with inspection
+ // of the callstack as a whole.
+ if (!frameData.v.fNoMetadata &&
+ pNativeCode->GetFunction()->IsNativeImpl() != CordbFunction::kNativeOnly)
+ {
+ pNativeCode->LoadNativeInfo();
+
+ // By design, when a managed exception occurs we return the sequence point containing the faulting
+ // instruction in the leaf frame. In the past we didn't always achieve this,
+ // but we are being more deliberate about this behavior now.
+
+ // If jsutAfterILThrow is true, it means nativeOffset points to the return address of IL_Throw
+ // (or another JIT exception helper) after an exception has been thrown.
+ // In such cases we want to adjust nativeOffset, so it will point an actual exception callsite.
+ // By subtracting STACKWALK_CONTROLPC_ADJUST_OFFSET from nativeOffset you can get
+ // an address somewhere inside CALL instruction.
+ // This ensures more consistent placement of exception line highlighting in Visual Studio
+ DWORD nativeOffsetToMap = pJITFuncData->jsutAfterILThrow ?
+ (DWORD)pJITFuncData->nativeOffset - STACKWALK_CONTROLPC_ADJUST_OFFSET :
+ (DWORD)pJITFuncData->nativeOffset;
+ CorDebugMappingResult mappingType;
+ ULONG uILOffset = pNativeCode->GetSequencePoints()->MapNativeOffsetToIL(
+ nativeOffsetToMap,
+ &mappingType);
+
+ // Find or create the IL Code, and the pJITILFrame.
+ RSExtSmartPtr<CordbILCode> pCode;
+
+ // The code for populating CordbFunction ILCode looks really bizzare... it appears to only grab the
+ // correct version of the IL if that is still the current EnC version yet it is populated deliberately
+ // late bound at which point the latest version may be different. In fact even here the latest version
+ // could already be different, but this is no worse than what the code used to do
+ hr = pFunction->GetILCode(&pCode);
+ IfFailThrow(hr);
+ _ASSERTE(pCode != NULL);
+
+ // We populate the code for ReJit eagerly to make sure we still have it if the profiler removes the
+ // instrumentation later. Of course the only way it will still be accesible to our caller is if he
+ // saves a pointer to the ILCode.
+ // I'm not sure if ignoring rejit for mini-dumps is the right call long term, but we aren't doing
+ // anything special to collect the memory at dump time so we better be prepared to not fetch it here.
+ // We'll attempt to treat it as not being instrumented, though I suspect the abstraction is leaky.
+ RSSmartPtr<CordbReJitILCode> pReJitCode;
+ EX_TRY_ALLOW_DATATARGET_MISSING_MEMORY
+ {
+ VMPTR_ReJitInfo reJitInfo = VMPTR_ReJitInfo::NullPtr();
+ IfFailThrow(GetProcess()->GetDAC()->GetReJitInfo(pJITFuncData->vmNativeCodeMethodDescToken, pJITFuncData->nativeStartAddressPtr, &reJitInfo));
+ if (!reJitInfo.IsNull())
+ {
+ VMPTR_SharedReJitInfo sharedReJitInfo = VMPTR_SharedReJitInfo::NullPtr();
+ IfFailThrow(GetProcess()->GetDAC()->GetSharedReJitInfo(reJitInfo, &sharedReJitInfo));
+ IfFailThrow(pFunction->LookupOrCreateReJitILCode(sharedReJitInfo, &pReJitCode));
+ }
+ }
+ EX_END_CATCH_ALLOW_DATATARGET_MISSING_MEMORY
+
+
+
+ RSInitHolder<CordbJITILFrame> pJITILFrame(new CordbJITILFrame(pNativeFrame,
+ pCode,
+ uILOffset,
+ mappingType,
+ frameData.v.exactGenericArgsToken,
+ frameData.v.dwExactGenericArgsTokenIndex,
+ !!frameData.v.fVarArgs,
+ pReJitCode));
+
+ // Initialize the frame. This is a nop if the method is not a vararg method.
+ hr = pJITILFrame->Init();
+ IfFailThrow(hr);
+
+ pNativeFrame->m_JITILFrame.Assign(pJITILFrame);
+ pJITILFrame.ClearAndMarkDontNeuter();
+ }
+
+ STRESS_LOG3(LF_CORDB, LL_INFO1000, "CSW::GFW - managed stack frame (%p): CNF - 0x%p, CJILF - 0x%p",
+ this, pNativeFrame, pNativeFrame->m_JITILFrame.GetValue());
+ } // kManagedStackFrame
+ else if (ft == IDacDbiInterface::kNativeRuntimeUnwindableStackFrame)
+ {
+ _ASSERTE(frameData.eType == DebuggerIPCE_STRData::cRuntimeNativeFrame);
+
+ // In order to find the FramePointer on x86, we need to unwind to the next frame.
+ // Technically, only x86 needs to do this, because the x86 runtime stackwalker doesn't uwnind
+ // one frame ahead of time. However, we are doing this on all platforms to keep things simple.
+ BOOL fSuccess = UnwindStackFrame();
+ (void)fSuccess; //prevent "unused variable" error from GCC
+ _ASSERTE(fSuccess);
+
+ m_fIsOneFrameAhead = true;
+#if defined(DBG_TARGET_X86)
+ frameData.fp = pDAC->GetFramePointer(m_pSFIHandle);
+#endif // DBG_TARGET_X86
+
+ // Lookup the appdomain that the thread was in when it was executing code for this frame. We pass this
+ // to the frame when we create it so we can properly resolve locals in that frame later.
+ CordbAppDomain * pCurrentAppDomain =
+ GetProcess()->LookupOrCreateAppDomain(frameData.vmCurrentAppDomainToken);
+ _ASSERTE(pCurrentAppDomain != NULL);
+
+ CordbRuntimeUnwindableFrame * pRuntimeFrame = new CordbRuntimeUnwindableFrame(m_pCordbThread,
+ frameData.fp,
+ pCurrentAppDomain,
+ &(frameData.ctx));
+
+ pResultFrame.Assign(static_cast<CordbFrame *>(pRuntimeFrame));
+ m_pCachedFrame.Assign(static_cast<CordbFrame *>(pRuntimeFrame));
+
+ STRESS_LOG2(LF_CORDB, LL_INFO1000, "CSW::GFW - runtime unwindable stack frame (%p): 0x%p",
+ this, pRuntimeFrame);
+ }
+
+ pResultFrame.TransferOwnershipExternal(ppFrame);
+
+ return S_OK;
+}
diff --git a/src/debug/di/rsthread.cpp b/src/debug/di/rsthread.cpp
new file mode 100644
index 0000000000..ae9b43cd01
--- /dev/null
+++ b/src/debug/di/rsthread.cpp
@@ -0,0 +1,11006 @@
+// 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.
+//*****************************************************************************
+
+//
+// File: rsthread.cpp
+//
+//*****************************************************************************
+#include "stdafx.h"
+#include "primitives.h"
+#include <float.h>
+#include <tls.h>
+
+// Stack-based holder for RSPTRs that we allocated to give to the LS.
+// If LS successfully takes ownership of them, then call SuppressRelease().
+// Else, dtor will free them up.
+// This is using a table protected by the ProcessLock().
+template <class T>
+class RsPtrHolder
+{
+ T * m_pObject;
+ RsPointer<T> m_ptr;
+public:
+ RsPtrHolder(T* pObject)
+ {
+ _ASSERTE(pObject != NULL);
+ m_ptr.AllocHandle(pObject->GetProcess(), pObject);
+ m_pObject = pObject;
+ }
+
+ // If owner didn't call SuppressRelease() to take ownership, then have dtor free it.
+ ~RsPtrHolder()
+ {
+ if (!m_ptr.IsNull())
+ {
+ // @dbgtodo synchronization - push this up. Note that since this is in a dtor;
+ // need to order it well against RSLockHolder.
+ RSLockHolder lockHolder(m_pObject->GetProcess()->GetProcessLock());
+ T* pObjTest = m_ptr.UnWrapAndRemove(m_pObject->GetProcess());
+ (void)pObjTest; //prevent "unused variable" error from GCC
+ _ASSERTE(pObjTest == m_pObject);
+ }
+ }
+
+ RsPointer<T> Ptr()
+ {
+ return m_ptr;
+ }
+ void SuppressRelease()
+ {
+ m_ptr = RsPointer<T>::NullPtr();
+ }
+
+};
+
+/* ------------------------------------------------------------------------- *
+ * Managed Thread classes
+ * ------------------------------------------------------------------------- */
+
+
+//---------------------------------------------------------------------------------------
+//
+// Instantiate a CordbThread object, which represents a managed thread.
+//
+// Arguments:
+// process - non-null process object that this thread lives in.
+// id - OS thread id of this thread.
+// handle - OS Handle to the native thread in the debuggee.
+//
+//---------------------------------------------------------------------------------------
+
+CordbThread::CordbThread(CordbProcess * pProcess, VMPTR_Thread vmThread) :
+ CordbBase(pProcess,
+ VmPtrToCookie(vmThread),
+ enumCordbThread),
+ m_pContext(NULL),
+ m_fContextFresh(false),
+ m_pAppDomain(NULL),
+ m_debugState(THREAD_RUN),
+ m_fFramesFresh(false),
+#if !defined(DBG_TARGET_ARM) // @ARMTODO
+ m_fFloatStateValid(false),
+ m_floatStackTop(0),
+#endif // !DBG_TARGET_ARM @ARMTODO
+ m_fException(false),
+ m_fCreationEventQueued(false),
+ m_EnCRemapFunctionIP(NULL),
+ m_userState(kInvalidUserState),
+ m_hCachedThread(INVALID_HANDLE_VALUE),
+ m_hCachedOutOfProcThread(INVALID_HANDLE_VALUE)
+{
+ m_fHasUnhandledException = FALSE;
+ m_pExceptionRecord = NULL;
+
+ // Thread id may be a "fake" OS id for a CLRHosted thread.
+ m_vmThreadToken = vmThread;
+
+ // This id must be unique for the thread. V2 uses the current OS thread id.
+ // If we ever support fibers, then we need to use something more unique than that.
+ m_dwUniqueID = pProcess->GetDAC()->GetUniqueThreadID(vmThread); // may throw
+
+ LOG((LF_CORDB, LL_INFO1000, "CT::CT new thread 0x%p vmptr=0x%p id=0x%x\n",
+ this, m_vmThreadToken, m_dwUniqueID));
+
+ // Unique ID should never be 0.
+ _ASSERTE(m_dwUniqueID != 0);
+
+ m_vmLeftSideContext = VMPTR_CONTEXT::NullPtr();
+ m_vmExcepObjHandle = VMPTR_OBJECTHANDLE::NullPtr();
+
+#if defined(_DEBUG) && !defined(DBG_TARGET_ARM) // @ARMTODO
+ for (unsigned int i = 0;
+ i < (sizeof(m_floatValues) / sizeof(m_floatValues[0]));
+ i++)
+ {
+ m_floatValues[i] = 0;
+ }
+#endif
+
+ // Set AppDomain
+ VMPTR_AppDomain vmAppDomain = pProcess->GetDAC()->GetCurrentAppDomain(vmThread);
+ m_pAppDomain = pProcess->LookupOrCreateAppDomain(vmAppDomain);
+ _ASSERTE(m_pAppDomain != NULL);
+}
+
+
+CordbThread::~CordbThread()
+{
+ // We've already been neutered, thus we don't need to call CleanupStack().
+ // That will have neutered + cleared frames + chains.
+ _ASSERTE(IsNeutered());
+
+ // Cleared in neuter
+ _ASSERTE(m_pContext == NULL);
+ _ASSERTE(m_hCachedThread == INVALID_HANDLE_VALUE);
+ _ASSERTE(m_pExceptionRecord == NULL);
+}
+
+// Neutered by the CordbProcess
+void CordbThread::Neuter()
+{
+ if (IsNeutered())
+ {
+ return;
+ }
+
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+
+ delete m_pExceptionRecord;
+ m_pExceptionRecord = NULL;
+
+ // Neuter frames & Chains.
+ CleanupStack();
+
+
+ if (m_hCachedThread != INVALID_HANDLE_VALUE)
+ {
+ CloseHandle(m_hCachedThread);
+ m_hCachedThread = INVALID_HANDLE_VALUE;
+ }
+
+ if( m_pContext != NULL )
+ {
+ delete [] m_pContext;
+ m_pContext = NULL;
+ }
+
+ ClearStackFrameCache();
+
+ CordbBase::Neuter();
+}
+
+HRESULT CordbThread::QueryInterface(REFIID id, void ** ppInterface)
+{
+ if (id == IID_ICorDebugThread)
+ {
+ *ppInterface = static_cast<ICorDebugThread *>(this);
+ }
+ else if (id == IID_ICorDebugThread2)
+ {
+ *ppInterface = static_cast<ICorDebugThread2 *>(this);
+ }
+ else if (id == IID_ICorDebugThread3)
+ {
+ *ppInterface = static_cast<ICorDebugThread3*>(this);
+ }
+ else if (id == IID_ICorDebugThread4)
+ {
+ *ppInterface = static_cast<ICorDebugThread4*>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *ppInterface = static_cast<IUnknown *>(static_cast<ICorDebugThread *>(this));
+ }
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+
+#ifdef _DEBUG
+// Callback helper for code:CordbThread::DbgAssertThreadDeleted
+//
+// Arguments:
+// vmThread - thread from enumeration of threads in the target.
+// pUserData - the CordbThread for the thread that's deleted
+//
+// static
+void CordbThread::DbgAssertThreadDeletedCallback(VMPTR_Thread vmThread, void * pUserData)
+{
+ CordbThread * pThis = reinterpret_cast<CordbThread *>(pUserData);
+ INTERNAL_DAC_CALLBACK(pThis->GetProcess());
+
+ VMPTR_Thread vmThreadDelete = pThis->m_vmThreadToken;
+
+ CONSISTENCY_CHECK_MSGF((vmThread != vmThreadDelete),
+ ("A Thread Exit event was sent, but it still shows up in the enumeration.\n vmThreadDelete=%p\n",
+ VmPtrToCookie(vmThreadDelete)));
+}
+
+// Debug-only helper to Assert that this thread is no longer discoverable in DacDbi enumerations
+// This is designed to enforce the code:IDacDbiInterface#Enumeration rules for enumerations.
+void CordbThread::DbgAssertThreadDeleted()
+{
+ // Enumerate through all threads and ensure the deleted threads don't show up.
+ GetProcess()->GetDAC()->EnumerateThreads(
+ DbgAssertThreadDeletedCallback,
+ this);
+}
+#endif // _DEBUG
+
+
+//---------------------------------------------------------------------------------------
+// Mark that this thread has an unhandled native exception on it.
+//
+// Arguments
+// pRecord - exception record of 2nd-chance exception that we're hijacking at. This will
+// get deep copied into the CordbThread object in case it's needed for hijacking later.
+//
+// Notes:
+// This bit is cleared in code:CordbThread::HijackForUnhandledException
+void CordbThread::SetUnhandledNativeException(const EXCEPTION_RECORD * pExceptionRecord)
+{
+ m_fHasUnhandledException = true;
+
+ if (m_pExceptionRecord == NULL)
+ {
+ m_pExceptionRecord = new EXCEPTION_RECORD(); // throws
+ }
+ memcpy(m_pExceptionRecord, pExceptionRecord, sizeof(EXCEPTION_RECORD));
+}
+
+//-----------------------------------------------------------------------------
+// Returns true if the thread has an unhandled exception
+// This is during the window after code:CordbThread::SetUnhandledNativeException is called,
+// but before code:CordbThread::HijackForUnhandledException
+bool CordbThread::HasUnhandledNativeException()
+{
+ return m_fHasUnhandledException;
+}
+
+
+//---------------------------------------------------------------------------------------
+// Determine if the thread's latest exception is a managed exception
+//
+// Notes:
+// The CLR's UnhandledExceptionFilter has to make this same determination.
+//
+BOOL CordbThread::IsThreadExceptionManaged()
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ // A Thread's latest exception is managed if the VM Thread object has a managed object
+ // for the thread's Current Exception property. The CLR's Exception system is very diligent
+ // about tracking and clearing the thread's managed exception property. The runtime will clear
+ // the object if the exception is caught by unmanaged code (it can do this in the 2nd-pass).
+
+ // It's the presence of a throwable that makes the difference between a managed
+ // exception event and an unmanaged exception event.
+
+ VMPTR_OBJECTHANDLE vmObject = GetProcess()->GetDAC()->GetCurrentException(m_vmThreadToken);
+
+ bool fHasThrowable = !vmObject.IsNull();
+
+ return fHasThrowable;
+
+}
+
+// ----------------------------------------------------------------------------
+// CordbThread::CreateCordbRegisterSet
+//
+// Description:
+// This is a private hook for the shim to create a CordbRegisterSet for a ShimChain.
+//
+// Arguments:
+// * pContext - the CONTEXT to be converted; this must be the leaf CONTEXT of a chain
+// * fLeaf - whether the chain is the leaf chain or not
+// * reason - the chain reason; this is needed for legacy reasons (see below)
+// * ppRegSet - out parameter; return the newly created ICDRegisterSet
+//
+// Notes:
+// * Note that the fQuickUnwind argument of the ctor of CordbRegisterSet is only true
+// for an enter-managed chain. We need to keep the same behaviour here. That's why we need the
+// chain reason.
+//
+
+void CordbThread::CreateCordbRegisterSet(DT_CONTEXT * pContext,
+ BOOL fLeaf,
+ CorDebugChainReason reason,
+ ICorDebugRegisterSet ** ppRegSet)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ PUBLIC_REENTRANT_API_ENTRY_FOR_SHIM(GetProcess());
+
+ IfFailThrow(EnsureThreadIsAlive());
+
+ // The CordbRegisterSet is responsible for freeing this memory.
+ NewHolder<DebuggerREGDISPLAY> pDRD(new DebuggerREGDISPLAY());
+
+ // convert the CONTEXT to a DebuggerREGDISPLAY
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ pDAC->ConvertContextToDebuggerRegDisplay(pContext, pDRD, fLeaf);
+
+ // create the CordbRegisterSet
+ RSInitHolder<CordbRegisterSet> pRS(new CordbRegisterSet(pDRD,
+ this,
+ (fLeaf == TRUE),
+ (reason == CHAIN_ENTER_MANAGED),
+ true));
+ pDRD.SuppressRelease();
+
+ pRS.TransferOwnershipExternal(ppRegSet);
+}
+
+// ----------------------------------------------------------------------------
+// CordbThread::ConvertFrameForILMethodWithoutMetadata
+//
+// Description:
+// This is a private hook for the shim to convert an ICDFrame into an ICDInternalFrame for a dynamic
+// method. There are two cases where we need this:
+// 1) In Arrowhead, dynamic methods are exposed as first-class stack frames, not internal frames. Thus,
+// the shim needs a way to convert an ICDNativeFrame for a dynamic method in Arrowhead to an
+// ICDInternalFrame of type STUBFRAME_LIGHTWEIGHT_FUNCTION in V2. Furthermore, IL stubs,
+// which are also considered as a type of dynamic methods, are not exposed in V2 at all.
+//
+// 2) In V2, PrestubMethodFrames (PMFs) can be exposed as one of two things: a chain of type
+// CHAIN_CLASS_INIT in most cases, or an internal frame of type STUBFRAME_LIGHTWEIGHT_FUNCTION if
+// the method being jitted is a dynamic method. There is no way to make this distinction at the
+// public ICD level.
+//
+// Arguments:
+// * pNativeFrame - the native frame to be converted
+// * ppInternalFrame - out parameter; the converted internal frame; could be NULL (see Notes below)
+//
+// Returns:
+// Return TRUE if conversion has occurred. Note that even if the return value is TRUE, ppInternalFrame
+// could be NULL. See Notes below.
+//
+// Notes:
+// * There are two main types of dynamic methods: ones which are generated by the runtime itself for
+// internal purposes (i.e. IL stubs), and ones which are generated by the user. ppInternalFrame
+// is NULL for IL stubs. We need this functionality because IL stubs are not exposed at all in V2.
+//
+
+BOOL CordbThread::ConvertFrameForILMethodWithoutMetadata(ICorDebugFrame * pFrame,
+ ICorDebugInternalFrame2 ** ppInternalFrame2)
+{
+ PUBLIC_REENTRANT_API_ENTRY_FOR_SHIM(GetProcess());
+
+ _ASSERTE(ppInternalFrame2 != NULL);
+ *ppInternalFrame2 = NULL;
+
+ HRESULT hr = E_FAIL;
+
+ CordbFrame * pRealFrame = CordbFrame::GetCordbFrameFromInterface(pFrame);
+
+ CordbInternalFrame * pInternalFrame = pRealFrame->GetAsInternalFrame();
+ if (pInternalFrame != NULL)
+ {
+ // The input is an internal frame.
+
+ // Check its frame type.
+ CorDebugInternalFrameType type;
+ hr = pInternalFrame->GetFrameType(&type);
+ IfFailThrow(hr);
+
+ if (type != STUBFRAME_JIT_COMPILATION)
+ {
+ // No conversion is necessary.
+ return FALSE;
+ }
+ else
+ {
+ // We are indeed dealing with a PrestubMethodFrame.
+ return pInternalFrame->ConvertInternalFrameForILMethodWithoutMetadata(ppInternalFrame2);
+ }
+ }
+ else
+ {
+ // The input is a native frame.
+ CordbNativeFrame * pNativeFrame = pRealFrame->GetAsNativeFrame();
+ _ASSERTE(pNativeFrame != NULL);
+
+ return pNativeFrame->ConvertNativeFrameForILMethodWithoutMetadata(ppInternalFrame2);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Hijack a thread at an unhandled exception. This lets it execute the
+// CLR's Unhandled Exception Filter (which will send the managed 2nd-chance exception event)
+//
+// Notes:
+// OS will not execute Unhandled Exception Filter (UEF) when debugger is attached.
+// The CLR's UEF does useful work, like dispatching 2nd-chance managed exception event
+// and allowing Func-eval and Continuable Exceptions for unhandled exceptions.
+// So hijack the thread, and the hijack will then execute the CLR's UEF just
+// like the OS would.
+void CordbThread::HijackForUnhandledException()
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(m_pExceptionRecord != NULL);
+
+ _ASSERTE(m_fHasUnhandledException);
+ m_fHasUnhandledException = false;
+
+
+ ULONG32 dwThreadId = GetVolatileOSThreadID();
+
+ // Note that the data-target is not atomic, and we have no rollback mechanism.
+ // We have to do several writes. If the data-target fails the writes half-way through the
+ // target will be inconsistent.
+
+ // We don't bother remembering the original context. LS hijack will have the
+ // context on its stack and will pass it to RS just like it does for filter-context.
+ GetProcess()->GetDAC()->Hijack(
+ m_vmThreadToken,
+ dwThreadId,
+ m_pExceptionRecord,
+ NULL, // LS will have the context.
+ 0, // size of context
+ EHijackReason::kUnhandledException,
+ NULL,
+ NULL);
+
+ // Notify debugger to clear the exception.
+ // This will invoke the data-target.
+ GetProcess()->ContinueStatusChanged(dwThreadId, DBG_CONTINUE);
+}
+
+
+HRESULT CordbThread::GetProcess(ICorDebugProcess ** ppProcess)
+{
+ PUBLIC_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(ppProcess, ICorDebugProcess **);
+ FAIL_IF_NEUTERED(this);
+
+ *ppProcess = GetProcess();
+ GetProcess()->ExternalAddRef();
+
+ return S_OK;
+}
+
+// Public implementation of ICorDebugThread::GetID
+// Back in V1.0, GetID originally meant the OS thread ID that this managed thread was running on.
+// In theory, that can change (fibers, logical thread scheduling, etc). However, in practice, in V1.0, it would
+// not. Thus debuggers took a depedency on GetID being constant.
+// In V2, this returns an opaque handle that is unique to this thread and stable for this thread's lifetime.
+//
+// Compare to code:CordbThread::GetVolatileOSThreadID, which returns the actual OS thread Id (which may change).
+HRESULT CordbThread::GetID(DWORD * pdwThreadId)
+{
+ PUBLIC_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(pdwThreadId, DWORD *);
+ FAIL_IF_NEUTERED(this);
+
+ *pdwThreadId = GetUniqueId();
+
+ return S_OK;
+}
+
+// Returns a unique ID that's stable for the life of this thread.
+// In a non-hosted scenarios, this can be the OS thread id.
+DWORD CordbThread::GetUniqueId()
+{
+ return m_dwUniqueID;
+}
+
+// Implementation of public API, ICorDebugThread::GetHandle
+// @dbgtodo ICDThread - deprecate in V3, offload to Shim
+HRESULT CordbThread::GetHandle(HANDLE * phThreadHandle)
+{
+ PUBLIC_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(phThreadHandle, HANDLE *);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ if (GetProcess()->GetShim() == NULL)
+ {
+ _ASSERTE(!"CordbThread::GetHandle() should be not be called on the new architecture");
+ *phThreadHandle = NULL;
+ return E_NOTIMPL;
+ }
+
+#if !defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ HANDLE hThread;
+ InternalGetHandle(&hThread); // throws on error
+ *phThreadHandle = hThread;
+ }
+ EX_CATCH_HRESULT(hr);
+#else // FEATURE_DBGIPC_TRANSPORT_DI
+ // In the old SL implementation of Mac debugging, we return a thread handle faked up by the PAL on the Mac.
+ // The returned handle is meaningless. Here we explicitly return E_NOTIMPL. We plan to deprecate this
+ // function in Dev10 anyway.
+ //
+ // @dbgtodo Mac - Check with VS to see if they need the thread handle, e.g. for waiting on thread
+ // termination.
+ HRESULT hr = E_NOTIMPL;
+#endif // !FEATURE_DBGIPC_TRANSPORT_DI
+
+ return hr;
+}
+
+// Note that we can return invalid handle
+void CordbThread::InternalGetHandle(HANDLE * phThread)
+{
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+
+ RefreshHandle(phThread);
+}
+
+//---------------------------------------------------------------------------------------
+//
+// This is a simple helper to check if a frame lives on the stack of the current thread.
+//
+// Arguments:
+// pFrame - the stack frame to check
+//
+// Return Value:
+// whether the frame lives on the stack of the current thread
+//
+// Assumption:
+// This function assumes that the stack frames are valid, i.e. the stack frames have not been
+// made dirty since the last stackwalk.
+//
+
+bool CordbThread::OwnsFrame(CordbFrame * pFrame)
+{
+ // preliminary checking
+ if ( (pFrame != NULL) &&
+ (!pFrame->IsNeutered()) &&
+ (pFrame->m_pThread == this)
+ )
+ {
+ //
+ // Note that this is one of the two remaining places where we need to use the cached stack frames.
+ // Theoretically, since this is not an exact check anyway, we could just use the thread's stack
+ // range instead of looping through all the individual frames. However, since we need to maintain
+ // the stack frame cache for code:CordbThread::GetActiveFunctions, we might as well use the cache here.
+ //
+
+ // make sure this thread actually have frames to check
+ if (m_stackFrames.Count() != 0)
+ {
+ // get the stack range of this thread
+ FramePointer fpLeaf = (*(m_stackFrames.Get(0)))->GetFramePointer();
+ FramePointer fpRoot = (*(m_stackFrames.Get(m_stackFrames.Count() - 1)))->GetFramePointer();
+
+ FramePointer fpCurrent = pFrame->GetFramePointer();
+
+ // compare the stack range against the frame pointer of the specified frame
+ if (IsEqualOrCloserToLeaf(fpLeaf, fpCurrent) && IsEqualOrCloserToRoot(fpRoot, fpCurrent))
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// This routine is a internal helper function for ICorDebugThread2::GetTaskId.
+//
+// Arguments:
+// pHandle - return thread handle here after fetching from the left side. Can return SWITCHOUT_HANDLE_VALUE.
+//
+// Return Value:
+// hr - It can fail with CORDBG_E_THREAD_NOT_SCHEDULED.
+//
+// Notes:
+// This method will most likely be deprecated in V3.0. We can't always return the thread handle.
+// For example, what does it mean to return a thread handle in remote debugging scenarios?
+//
+void CordbThread::RefreshHandle(HANDLE * phThread)
+{
+ // here is where we will put code in to fetch the thread handle from the left side.
+ // This should only happen when CLRTask is hosted.
+ // Make sure that we are setting the right HR when thread is being switched out.
+ THROW_IF_NEUTERED(this);
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+
+ if (phThread == NULL)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+ *phThread = INVALID_HANDLE_VALUE;
+
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ HANDLE hThread = pDAC->GetThreadHandle(m_vmThreadToken);
+
+ if (hThread == SWITCHOUT_HANDLE_VALUE)
+ {
+ *phThread = SWITCHOUT_HANDLE_VALUE;
+ ThrowHR(CORDBG_E_THREAD_NOT_SCHEDULED);
+ }
+
+ _ASSERTE(hThread != INVALID_HANDLE_VALUE);
+ PREFAST_ASSUME(hThread != NULL);
+
+ // need to dup handle here
+ if (hThread == m_hCachedOutOfProcThread)
+ {
+ *phThread = m_hCachedThread;
+ }
+ else
+ {
+ BOOL fSuccess = TRUE;
+ if (m_hCachedThread != INVALID_HANDLE_VALUE)
+ {
+ // clear the previous cache
+ CloseHandle(m_hCachedThread);
+ m_hCachedOutOfProcThread = INVALID_HANDLE_VALUE;
+ m_hCachedThread = INVALID_HANDLE_VALUE;
+ }
+
+ // now duplicate the out-of-proc handle
+ fSuccess = DuplicateHandle(GetProcess()->UnsafeGetProcessHandle(),
+ hThread,
+ GetCurrentProcess(),
+ &m_hCachedThread,
+ NULL,
+ FALSE,
+ DUPLICATE_SAME_ACCESS);
+ *phThread = m_hCachedThread;
+
+ if (fSuccess)
+ {
+ m_hCachedOutOfProcThread = hThread;
+ }
+ else
+ {
+ ThrowLastError();
+ }
+ }
+} // CordbThread::RefreshHandle
+
+
+//---------------------------------------------------------------------------------------
+//
+// This routine sets the debug state of a thread.
+//
+// Arguments:
+// state - The debug state to set to.
+//
+// Return Value:
+// Normal HRESULT semantics.
+//
+HRESULT CordbThread::SetDebugState(CorDebugThreadState state)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ LOG((LF_CORDB, LL_INFO1000, "CT::SDS: thread=0x%08x 0x%x, state=%d\n", this, m_id, state));
+
+ // @dbgtodo- , sync - decide on how to suspend a thread. V2 leverages synchronization
+ // (see below). For V3, do we just hard suspend the thread?
+ if (GetProcess()->GetShim() == NULL)
+ {
+ return E_NOTIMPL;
+ }
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ hr = EnsureThreadIsAlive();
+
+ if (SUCCEEDED(hr))
+ {
+ // This lets the debugger suspend / resume threads. This is only called when when the
+ // target is already synchronized. That means all the threads are already suspended. So
+ // setting the suspend bit here just means that the debugger's continue logic won't resume
+ // this thread when we do a Continue.
+ if ((state != THREAD_SUSPEND) && (state != THREAD_RUN))
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ pDAC->SetDebugState(m_vmThreadToken, state);
+
+ m_debugState = state;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+HRESULT CordbThread::GetDebugState(CorDebugThreadState * pState)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ VALIDATE_POINTER_TO_OBJECT(pState, CorDebugThreadState *);
+
+ *pState = m_debugState;
+
+ return S_OK;
+}
+
+
+// Public implementation of ICorDebugThread::GetUserState
+// Arguments:
+// pState - out parameter; return the user state
+//
+// Return Value:
+// Return S_OK if the operation is successful.
+// Return E_INVALIDARG if the out parameter is NULL.
+// Return other failure HRs returned by the call to the DDI.
+HRESULT CordbThread::GetUserState(CorDebugUserState * pState)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pState, CorDebugUserState *);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ if (pState == NULL)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+ *pState = GetUserState();
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Retrieve the user state of the current thread.
+//
+// Notes:
+// This caches results between continues. The cache is cleared when the target continues or is flushed.
+// See code:CordbThread::CleanupStack, code:CordbThread::MarkStackFramesDirty
+//
+CorDebugUserState CordbThread::GetUserState()
+{
+ if (m_userState == kInvalidUserState)
+ {
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ m_userState = pDAC->GetUserState(m_vmThreadToken);
+ }
+
+ return m_userState;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// This routine finds and returns the current exception off of a thread.
+//
+// Arguments:
+// ppExceptionObject - OUT: Space for storing the exception found on the thread as a value.
+//
+// Return Value:
+// Normal HRESULT semantics.
+//
+HRESULT CordbThread::GetCurrentException(ICorDebugValue ** ppExceptionObject)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ HRESULT hr = S_OK;
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ VALIDATE_POINTER_TO_OBJECT(ppExceptionObject, ICorDebugValue **);
+ *ppExceptionObject = NULL;
+
+ EX_TRY
+ {
+ if (!HasException())
+ {
+ //
+ // Go to the LS and retrieve any exception object.
+ //
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ VMPTR_OBJECTHANDLE vmObjHandle = pDAC->GetCurrentException(m_vmThreadToken);
+
+ if (vmObjHandle.IsNull())
+ {
+ hr = S_FALSE;
+ }
+ else
+ {
+#if defined(_DEBUG)
+ // Since we know an exception is in progress on this thread, our assumption about the
+ // thread's current AppDomain should be correct
+ VMPTR_AppDomain vmAppDomain = pDAC->GetCurrentAppDomain(m_vmThreadToken);
+ _ASSERTE(GetAppDomain()->GetADToken() == vmAppDomain);
+#endif // _DEBUG
+
+ m_vmExcepObjHandle = vmObjHandle;
+ }
+ }
+
+ if (hr == S_OK)
+ {
+ // We've believe this assert may fire in the wild.
+ // We've seen m_vmExcepObjHandle null in retail builds after stack overflow.
+ _ASSERTE(!m_vmExcepObjHandle.IsNull());
+
+ ICorDebugReferenceValue * pRefValue = NULL;
+ hr = CordbReferenceValue::BuildFromGCHandle(GetAppDomain(), m_vmExcepObjHandle, &pRefValue);
+ *ppExceptionObject = pRefValue;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+HRESULT CordbThread::ClearCurrentException()
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ // This API is not implemented. For Continuable Exceptions, see InterceptCurrentException.
+ // @todo - should it return E_NOTIMPL?
+ return S_OK;
+}
+
+HRESULT CordbThread::CreateStepper(ICorDebugStepper ** ppStepper)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ VALIDATE_POINTER_TO_OBJECT(ppStepper, ICorDebugStepper **);
+
+ CordbStepper * pStepper = new (nothrow) CordbStepper(this, NULL);
+
+ if (pStepper == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ pStepper->ExternalAddRef();
+ *ppStepper = pStepper;
+
+ return S_OK;
+}
+
+//Returns true if current user state of a thread is USER_WAIT_SLEEP_JOIN
+bool CordbThread::IsThreadWaitingOrSleeping()
+{
+ CorDebugUserState userState = m_userState;
+ if (userState == kInvalidUserState)
+ {
+ //If m_userState is not ready, we'll read from DAC only part of it which
+ //is important for us now, bacuase we don't want possible side effects
+ //of reading USER_UNSAFE_POINT flag.
+ //We don't cache the value, because it's potentially incomplete.
+ IDacDbiInterface *pDAC = GetProcess()->GetDAC();
+ userState = pDAC->GetPartialUserState(m_vmThreadToken);
+ }
+
+ return (userState & USER_WAIT_SLEEP_JOIN) != 0;
+}
+
+//----------------------------------------------------------------------------
+// check if the thread is dead
+//
+// Returns: true if the thread is dead.
+//
+bool CordbThread::IsThreadDead()
+{
+ return GetProcess()->GetDAC()->IsThreadMarkedDead(m_vmThreadToken);
+}
+
+// Helper to return CORDBG_E_BAD_THREAD_STATE if IsThreadDead
+//
+// Notes:
+// IsThreadDead queries the VM Thread's actual state, regardless of what ExitThread
+// callbacks have or have not been sent / queued / dispatched.
+HRESULT CordbThread::EnsureThreadIsAlive()
+{
+ if (IsThreadDead())
+ {
+ return CORDBG_E_BAD_THREAD_STATE;
+ }
+ else
+ {
+ return S_OK;
+ }
+}
+
+// ----------------------------------------------------------------------------
+// CordbThread::EnumerateChains
+//
+// Description:
+// Create and return an ICDChainEnum for enumerating chains on the stack. Since chains have been
+// deprecated in Arrowhead, this function returns E_NOTIMPL unless there is a shim.
+//
+// Arguments:
+// * ppChains - out parameter; return the ICDChainEnum
+//
+// Return Value:
+// Return S_OK on success.
+// Return E_INVALIDARG if ppChains is NULL.
+// Return CORDBG_E_OBJECT_NEUTERED if the CordbThread is neutered.
+// Return E_NOTIMPL if there is no shim.
+//
+
+HRESULT CordbThread::EnumerateChains(ICorDebugChainEnum ** ppChains)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ VALIDATE_POINTER_TO_OBJECT(ppChains, ICorDebugChainEnum **);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ *ppChains = NULL;
+
+ if (GetProcess()->GetShim() != NULL)
+ {
+ hr = EnsureThreadIsAlive();
+
+ if (SUCCEEDED(hr))
+ {
+ // use the shim to create an ICDChainEnum
+ PRIVATE_SHIM_CALLBACK_IN_THIS_SCOPE0(GetProcess());
+ ShimStackWalk * pSW = GetProcess()->GetShim()->LookupOrCreateShimStackWalk(this);
+ pSW->EnumerateChains(ppChains);
+ }
+ }
+ else
+ {
+ // This is the Arrowhead case, where ICDChain has been deprecated.
+ hr = E_NOTIMPL;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+// ----------------------------------------------------------------------------
+// CordbThread::GetActiveChain
+//
+// Description:
+// Retrieve the leaf chain on this thread. Since chains have been deprecated in Arrowhead,
+// this function returns E_NOTIMPL unless there is a shim.
+//
+// Arguments:
+// * ppChain - out parameter; return the leaf chain
+//
+// Return Value:
+// Return S_OK on success.
+// Return E_INVALIDARG if ppChain is NULL.
+// Return CORDBG_E_OBJECT_NEUTERED if the CordbThread is neutered.
+// Return E_NOTIMPL if there is no shim.
+//
+
+HRESULT CordbThread::GetActiveChain(ICorDebugChain ** ppChain)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ VALIDATE_POINTER_TO_OBJECT(ppChain, ICorDebugChain **);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ *ppChain = NULL;
+ hr = EnsureThreadIsAlive();
+
+ if (SUCCEEDED(hr))
+ {
+ if (GetProcess()->GetShim() != NULL)
+ {
+ // use the shim to retrieve the leaf chain
+ PRIVATE_SHIM_CALLBACK_IN_THIS_SCOPE0(GetProcess());
+ ShimStackWalk * pSSW = GetProcess()->GetShim()->LookupOrCreateShimStackWalk(this);
+ pSSW->GetActiveChain(ppChain);
+ }
+ else
+ {
+ // This is the Arrowhead case, where ICDChain has been deprecated.
+ hr = E_NOTIMPL;
+ }
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+// ----------------------------------------------------------------------------
+// CordbThread::GetActiveFrame
+//
+// Description:
+// Retrieve the leaf frame on this thread. Unfortunately, this is one of the cases where we need to
+// do different things depending on whether there is a shim. See the Notes below.
+//
+// Arguments:
+// * ppFrame - out parameter; return the leaf frame
+//
+// Return Value:
+// Return S_OK on success.
+// Return E_INVALIDARG if ppFrame is NULL.
+// Return CORDBG_E_OBJECT_NEUTERED if the CordbThread is neutered.
+// Also return whatever CreateStackWalk() and GetFrame() return if they fail.
+//
+// Notes:
+// In V2, we return NULL if the leaf frame is not in the leaf chain, i.e. if the leaf chain is
+// empty. Note that managed chains are never empty. Also, in V2 it is possible that this API
+// will return an internal frame as the active frame on a thread.
+//
+// The Arrowhead implementation two breaking changes:
+// 1) It never returns an internal frame.
+// 2) We return a frame if the leaf frame is managed. Otherwise, we return NULL.
+//
+
+HRESULT CordbThread::GetActiveFrame(ICorDebugFrame ** ppFrame)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ VALIDATE_POINTER_TO_OBJECT(ppFrame, ICorDebugFrame **);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ *ppFrame = NULL;
+ hr = EnsureThreadIsAlive();
+
+ if (SUCCEEDED(hr))
+ {
+ if (GetProcess()->GetShim() != NULL)
+ {
+ PRIVATE_SHIM_CALLBACK_IN_THIS_SCOPE0(GetProcess());
+ ShimStackWalk * pSSW = GetProcess()->GetShim()->LookupOrCreateShimStackWalk(this);
+ pSSW->GetActiveFrame(ppFrame);
+ }
+ else
+ {
+ // This is the Arrowhead case. We could call RefreshStack() here, but since we only need the
+ // leaf frame, there is no point in walking the entire stack.
+ RSExtSmartPtr<ICorDebugStackWalk> pSW;
+ hr = CreateStackWalk(&pSW);
+ IfFailThrow(hr);
+
+ hr = pSW->GetFrame(ppFrame);
+ IfFailThrow(hr);
+ }
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+// ----------------------------------------------------------------------------
+// CordbThread::GetActiveRegister
+//
+// Description:
+// In V2, retrieve the ICDRegisterSet for the leaf chain. In Arrowhead, retrieve the ICDRegisterSet
+// for the leaf CONTEXT.
+//
+// Arguments:
+// * ppRegisters - out parameter; return the ICDRegister
+//
+// Return Value:
+// Return S_OK on success.
+// Return E_INVALIDARG if ppFrame is NULL.
+// Return CORDBG_E_OBJECT_NEUTERED if the CordbThread is neutered.
+// Also return whatever CreateStackWalk() and GetContext() return if they fail.
+//
+
+HRESULT CordbThread::GetRegisterSet(ICorDebugRegisterSet ** ppRegisters)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ VALIDATE_POINTER_TO_OBJECT(ppRegisters, ICorDebugRegisterSet **);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ *ppRegisters = NULL;
+ hr = EnsureThreadIsAlive();
+
+ if (SUCCEEDED(hr))
+ {
+ if (GetProcess()->GetShim() != NULL)
+ {
+ // use the shim to retrieve the active ICDRegisterSet
+ PRIVATE_SHIM_CALLBACK_IN_THIS_SCOPE0(GetProcess());
+ ShimStackWalk * pSSW = GetProcess()->GetShim()->LookupOrCreateShimStackWalk(this);
+ pSSW->GetActiveRegisterSet(ppRegisters);
+ }
+ else
+ {
+ // This is the Arrowhead case. We could call RefreshStack() here, but since we only need the
+ // leaf frame, there is no point in walking the entire stack.
+ RSExtSmartPtr<ICorDebugStackWalk> pSW;
+ hr = CreateStackWalk(&pSW);
+ IfFailThrow(hr);
+
+ // retrieve the leaf CONTEXT
+ DT_CONTEXT ctx;
+ hr = pSW->GetContext(CONTEXT_FULL, sizeof(ctx), NULL, reinterpret_cast<BYTE *>(&ctx));
+ IfFailThrow(hr);
+
+ // the CordbRegisterSet is responsible for freeing this memory
+ NewHolder<DebuggerREGDISPLAY> pDRD(new DebuggerREGDISPLAY());
+
+ // convert the CONTEXT to a DebuggerREGDISPLAY
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ pDAC->ConvertContextToDebuggerRegDisplay(&ctx, pDRD, true);
+
+ // create the CordbRegisterSet
+ RSInitHolder<CordbRegisterSet> pRS(new CordbRegisterSet(pDRD,
+ this,
+ true, // active
+ false, // !fQuickUnwind
+ true)); // own DRD memory
+ pDRD.SuppressRelease();
+
+ pRS.TransferOwnershipExternal(ppRegisters);
+ }
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+HRESULT CordbThread::CreateEval(ICorDebugEval ** ppEval)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ VALIDATE_POINTER_TO_OBJECT(ppEval, ICorDebugEval **);
+
+ CordbEval * pEval = new (nothrow) CordbEval(this);
+ if (pEval == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ pEval->ExternalAddRef();
+ *ppEval = static_cast<ICorDebugEval *>(pEval);
+
+ return S_OK;
+}
+
+// DAC check
+
+// Double check our results w/ DAC.
+// This gives DAC some great coverage.
+// Given an IP and the md token (that the RS obtained), use DAC to lookup the md token. Then
+// we can compare DAC & the RS and make sure DACs working.
+void CheckAgainstDAC(CordbFunction * pFunc, void * pIP, mdMethodDef mdExpected)
+{
+ // This is a hook to add DAC checks agaisnt a {function, ip}
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Internal function to build up a stack trace.
+//
+//
+// Return Value:
+// S_OK on success.
+//
+// Assumptions:
+// Process is stopped.
+//
+// Notes:
+// Send a IPC events to the LS to build up the stack.
+//
+//---------------------------------------------------------------------------------------
+void CordbThread::RefreshStack()
+{
+ THROW_IF_NEUTERED(this);
+
+ // We must have the Stop-Go lock to change our thread's stack-state.
+ // Also, our caller should have guaranteed that we're synced. And b/c we hold the stop-go lock,
+ // that shouldn't have changed.
+ // INTERNAL_SYNC_API_ENTRY() checks that we have the lock and that we are synced.
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+
+ // bail out early if the stack hasn't changed
+ if (m_fFramesFresh)
+ {
+ return;
+ }
+
+ HRESULT hr = S_OK;
+
+ //
+ // Clean up old snapshot.
+ //
+
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+
+ // clear the stack frame cache
+ ClearStackFrameCache();
+
+ //
+ // If we don't have a debugger thread token, then this thread has never
+ // executed managed code and we have no frame information for it.
+ //
+ if (m_vmThreadToken.IsNull())
+ {
+ ThrowHR(E_FAIL);
+ }
+
+ // walk the stack using the V3 API and populate the stack frame cache
+ RSInitHolder<CordbStackWalk> pSW(new CordbStackWalk(this));
+ pSW->Init();
+ do
+ {
+ RSExtSmartPtr<ICorDebugFrame> pIFrame;
+ hr = pSW->GetFrame(&pIFrame);
+ IfFailThrow(hr);
+
+ if (pIFrame != NULL)
+ {
+ // add the stack frame to the cache
+ CordbFrame ** ppCFrame = m_stackFrames.AppendThrowing();
+ *ppCFrame = CordbFrame::GetCordbFrameFromInterface(pIFrame);
+
+ // Now that we have saved the pointer, increment the ref count.
+ // This has to match the InternalRelease() in code:CordbThread::ClearStackFrameCache.
+ (*ppCFrame)->InternalAddRef();
+ }
+
+ // advance to the next frame
+ hr = pSW->Next();
+ IfFailThrow(hr);
+ }
+ while (hr != CORDBG_S_AT_END_OF_STACK);
+
+ m_fFramesFresh = true;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// This function is used to invalidate and clean up the cached stack trace.
+//
+
+void CordbThread::CleanupStack()
+{
+ _ASSERTE(GetProcess()->GetProcessLock()->HasLock());
+
+ // Neuter outstanding CordbChainEnums, CordbFrameEnums, some CordbTypeEnums, and some CordbValueEnums.
+ m_RefreshStackNeuterList.NeuterAndClear(GetProcess());
+
+ m_fContextFresh = false; // invalidate the cached active CONTEXT
+ m_vmLeftSideContext = VMPTR_CONTEXT::NullPtr(); // set the LS pointer to the active CONTEXT to NULL
+ m_fFramesFresh = false; // invalidate the cached stack trace (frames & chains)
+ m_userState = kInvalidUserState; // clear the cached user state
+
+ // tell the shim to flush its caches as well
+ if (GetProcess()->GetShim() != NULL)
+ {
+ GetProcess()->GetShim()->NotifyOnStackInvalidate();
+ }
+}
+
+// Notifying the thread that the process is being continued.
+// This will cause our caches to get invalidated without actually cleaning the caches.
+void CordbThread::MarkStackFramesDirty()
+{
+ LIMITED_METHOD_CONTRACT;
+
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+
+#if !defined(DBG_TARGET_ARM) // @ARMTODO
+ // invalidate the cached floating point state
+ m_fFloatStateValid = false;
+#endif // !defined(DBG_TARGET_ARM) @ARMTODO
+
+ // This flag is only true between the window when we get an exception callback and
+ // when we call continue. Since this function is only called when we continue, we
+ // need to reset this flag here. Note that in the case of an outstanding funceval,
+ // we'll set this flag again when the funceval is completed.
+ m_fException = false;
+
+ // Clear the stashed EnC remap IP address if any
+ // This is important to ensure we don't try to write into LS memory which is no longer
+ // being used to hold the remap IP.
+ m_EnCRemapFunctionIP = NULL;
+
+ m_fContextFresh = false; // invalidate the cached active CONTEXT
+ m_vmLeftSideContext = VMPTR_CONTEXT::NullPtr(); // set the LS pointer to the active CONTEXT to NULL
+ m_fFramesFresh = false; // invalidate the cached stack trace (frames & chains)
+ m_userState = kInvalidUserState; // clear the cached user state
+
+ m_RefreshStackNeuterList.NeuterAndClear(GetProcess());
+
+ // tell the shim to flush its caches as well
+ if (GetProcess()->GetShim() != NULL)
+ {
+ GetProcess()->GetShim()->NotifyOnStackInvalidate();
+ }
+}
+
+// Set that there's an outstanding exception on this thread.
+// This can be called when the process object receives an exception notification.
+// This is cleared in code:CordbThread::MarkStackFramesDirty.
+void CordbThread::SetExInfo(VMPTR_OBJECTHANDLE vmExcepObjHandle)
+{
+ m_fException = true;
+ m_vmExcepObjHandle = vmExcepObjHandle;
+
+ // CordbThread::GetCurrentException assumes that we always have a m_vmExcepObjHandle when at an exception.
+ // Push that assert up here.
+ _ASSERTE(!m_vmExcepObjHandle.IsNull());
+}
+
+
+// ----------------------------------------------------------------------------
+// CordbThread::FindFrame
+//
+// Description:
+// Given a FramePointer, find the matching CordbFrame.
+//
+// Arguments:
+// * ppFrame - out parameter; the CordbFrame to be returned
+// * fp - the input FramePointer
+//
+// Return Value:
+// Return S_OK on success.
+// Return E_FAIL on failure.
+//
+// Assumptions:
+// * This function is only called from the shim.
+//
+// Notes:
+// * Currently this function is only used by the shim to map the FramePointer it gets via the
+// DB_IPCE_EXCEPTION_CALLBACK2 callback. When we figure out what to do with the
+// DB_IPCE_EXCEPTION_CALLBACK2, we should remove this function.
+//
+
+HRESULT CordbThread::FindFrame(ICorDebugFrame ** ppFrame, FramePointer fp)
+{
+ FAIL_IF_NEUTERED(this);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ _ASSERTE(ppFrame != NULL);
+ *ppFrame = NULL;
+
+ _ASSERTE(GetProcess()->GetShim() != NULL);
+
+ PRIVATE_SHIM_CALLBACK_IN_THIS_SCOPE0(GetProcess());
+ ShimStackWalk * pSSW = GetProcess()->GetShim()->LookupOrCreateShimStackWalk(this);
+
+ for (UINT32 i = 0; i < pSSW->GetFrameCount(); i++)
+ {
+ ICorDebugFrame * pIFrame = pSSW->GetFrame(i);
+ CordbFrame * pCFrame = CordbFrame::GetCordbFrameFromInterface(pIFrame);
+
+#if defined(_WIN64)
+ // On 64-bit we can simply compare the FramePointer.
+ if (pCFrame->GetFramePointer() == fp)
+#else // !_WIN64
+ // On other platforms, we need to do a more elaborate check.
+ if (pCFrame->IsContainedInFrame(fp))
+#endif // _WIN64
+ {
+ *ppFrame = pIFrame;
+ (*ppFrame)->AddRef();
+ return S_OK;
+ }
+ }
+
+ // Cannot find the frame.
+ return E_FAIL;
+}
+
+
+#if !defined(DBG_TARGET_ARM) // @ARMTODO
+
+#if defined(CROSS_COMPILE) && defined(_TARGET_ARM64_)
+extern "C" double FPFillR8(void* pFillSlot)
+{
+ _ASSERTE(!"nyi for platform");
+ return 0;
+}
+#elif defined(_TARGET_AMD64_) || defined(_TARGET_ARM64_)
+extern "C" double FPFillR8(void* pFillSlot);
+#endif
+
+
+#if defined(_TARGET_X86_)
+
+// CordbThread::Get32bitFPRegisters
+// Converts the values in the floating point register area of the context to real number values. See
+// code:CordbThread::LoadFloatState for more details.
+// Arguments:
+// input: pContext
+// output: none (initializes m_floatValues)
+
+void CordbThread::Get32bitFPRegisters(CONTEXT * pContext)
+{
+ // On X86, we get the values by saving our current FPU state, loading
+ // the other thread's FPU state into our own, saving out each
+ // value off the FPU stack, and then restoring our FPU state.
+ //
+ FLOATING_SAVE_AREA floatarea = pContext->FloatSave; // copy FloatSave
+
+ //
+ // Take the TOP out of the FPU status word. Note, our version of the
+ // stack runs from 0->7, not 7->0...
+ //
+ unsigned int floatStackTop = 7 - ((floatarea.StatusWord & 0x3800) >> 11);
+
+ FLOATING_SAVE_AREA currentFPUState;
+
+ __asm fnsave currentFPUState // save the current FPU state.
+
+ floatarea.StatusWord &= 0xFF00; // remove any error codes.
+ floatarea.ControlWord |= 0x3F; // mask all exceptions.
+
+ // the x86 FPU stores real numbers as 10 byte values in IEEE format. Here we use
+ // the hardware to convert these to doubles.
+
+ // @dbgtodo Microsoft crossplat: the conversion from a series of bytes to a floating
+ // point value will need to be done with an explicit conversion routine to unpack
+ // the IEEE format and compute the real number value represented.
+
+ __asm
+ {
+ fninit
+ frstor floatarea ;; reload the threads FPU state.
+ }
+
+ unsigned int i;
+
+ for (i = 0; i <= floatStackTop; i++)
+ {
+ long double td;
+ __asm fstp td // copy out the double
+ m_floatValues[i] = td;
+ }
+
+ __asm
+ {
+ fninit
+ frstor currentFPUState ;; restore our saved FPU state.
+ }
+
+ m_fFloatStateValid = true;
+ m_floatStackTop = floatStackTop;
+} // CordbThread::Get32bitFPRegisters
+
+#elif defined(_TARGET_AMD64_) || defined(_TARGET_ARM64_)
+
+// CordbThread::Get64bitFPRegisters
+// Converts the values in the floating point register area of the context to real number values. See
+// code:CordbThread::LoadFloatState for more details.
+// Arguments:
+// input: pFPRegisterBase - starting address of the floating point register storage of the CONTEXT
+// registerSize - the size of a floating point register
+// start - the index into m_floatValues where we start initializing. For amd64, we start
+// at the beginning, but for ia64, the first two registers have fixed values,
+// so we start at two.
+// nRegisters - the number of registers to be initialized
+// output: none (initializes m_floatValues)
+
+void CordbThread::Get64bitFPRegisters(FPRegister64 * rgContextFPRegisters, int start, int nRegisters)
+{
+ // make sure no one has changed the type definition for 64-bit FP registers
+ _ASSERTE(sizeof(FPRegister64) == 16);
+ // We convert and copy all the fp registers.
+ for (int reg = start; reg < nRegisters; reg++)
+ {
+ // @dbgtodo Microsoft crossplat: the conversion from a FLOAT128 or M128A struct to a floating
+ // point value will need to be done with an explicit conversion routine instead
+ // of the call to FPFillR8
+ m_floatValues[reg] = FPFillR8(&rgContextFPRegisters[reg - start]);
+ }
+} // CordbThread::Get64bitFPRegisters
+
+#endif // _TARGET_X86_
+
+// CordbThread::LoadFloatState
+// Initializes the float state members of this instance of CordbThread. This function gets the context and
+// converts the floating point values from their context representation to a real number value. Floating
+// point numbers are represented in IEEE format on all current platforms. We store them in the context as a
+// pair of 64-bit integers (IA64 and AMD64) or a series of bytes (x86). Rather than unpack them explicitly
+// and do the appropriate mathematical operations to produce the corresponding floating point value, we let
+// the hardware do it instead. We load a floating point register with the representation from the context
+// and then store it in m_floatValues. Using the hardware is obviously a huge perf win. If/when we make
+// cross-plat work, we should at least code necessary conversion routines in assembly. Even with cross-plat,
+// we can probably still use the hardware in most cases, as long as the size is appropriate.
+//
+// Arguments: none
+// Return Value: none (initializes data members)
+// Note: Throws
+
+void CordbThread::LoadFloatState()
+{
+ THROW_IF_NEUTERED(this);
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+
+ DT_CONTEXT tempContext;
+ GetProcess()->GetDAC()->GetContext(m_vmThreadToken, &tempContext);
+
+#if defined(_TARGET_X86_)
+ Get32bitFPRegisters((CONTEXT*) &tempContext);
+#elif defined(_TARGET_AMD64_)
+ // we have no fixed-value registers, so we begin with the first one and initialize all 16
+ Get64bitFPRegisters((FPRegister64*) &(tempContext.Xmm0), 0, 16);
+#elif defined(_TARGET_ARM64_)
+ Get64bitFPRegisters((FPRegister64*) &(tempContext.V), 0, 32);
+#else
+ _ASSERTE(!"nyi for platform");
+#endif // !_TARGET_X86_
+
+ m_fFloatStateValid = true;
+} // CordbThread::LoadFloatState
+
+#endif // !DBG_TARGET_ARM @ARMTODO
+
+const bool SetIP_fCanSetIPOnly = TRUE;
+const bool SetIP_fSetIP = FALSE;
+
+const bool SetIP_fIL = TRUE;
+const bool SetIP_fNative = FALSE;
+
+//---------------------------------------------------------------------------------------
+//
+// Issues a SetIP command to the left-side and returns the result
+//
+// Arguments:
+// fCanSetIPOnly - TRUE if only to do the setip command and not refresh stacks as well.
+// debuggerModule - LS token to the debugger module.
+// mdMethod - Metadata token for the method.
+// nativeCodeJITInfoToken - LS token to the DebuggerJitInfo for the method.
+// offset - Offset within the method to set the IP to.
+// fIsIl - Is this an IL offset?
+//
+// Return Value:
+// S_OK on success.
+//
+HRESULT CordbThread::SetIP(bool fCanSetIPOnly,
+ CordbNativeCode * pNativeCode,
+ SIZE_T offset,
+ bool fIsIL)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ VMPTR_DomainFile vmDomainFile = pNativeCode->GetModule()->m_vmDomainFile;
+ _ASSERTE(!vmDomainFile.IsNull());
+
+ // If this thread is stopped due to an exception, never allow SetIP
+ if (HasException())
+ {
+ return (CORDBG_E_SET_IP_NOT_ALLOWED_ON_EXCEPTION);
+ }
+
+ DebuggerIPCEvent event;
+ GetProcess()->InitIPCEvent(&event, DB_IPCE_SET_IP, true, GetAppDomain()->GetADToken());
+ event.SetIP.fCanSetIPOnly = fCanSetIPOnly;
+ event.SetIP.vmThreadToken = m_vmThreadToken;
+ event.SetIP.vmDomainFile = vmDomainFile;
+ event.SetIP.mdMethod = pNativeCode->GetMetadataToken();
+ event.SetIP.vmMethodDesc = pNativeCode->GetVMNativeCodeMethodDescToken();
+ event.SetIP.startAddress = pNativeCode->GetAddress();
+ event.SetIP.offset = offset;
+ event.SetIP.fIsIL = fIsIL;
+
+
+ LOG((LF_CORDB, LL_INFO10000, "[%x] CT::SIP: Info:thread:0x%x"
+ "mod:0x%x MethodDef:0x%x offset:0x%x il?:0x%x\n",
+ GetCurrentThreadId(),
+ VmPtrToCookie(m_vmThreadToken),
+ VmPtrToCookie(vmDomainFile),
+ pNativeCode->GetMetadataToken(),
+ offset,
+ fIsIL));
+
+ LOG((LF_CORDB, LL_INFO10000, "[%x] CT::SIP: sizeof(DebuggerIPCEvent):0x%x **********\n",
+ sizeof(DebuggerIPCEvent)));
+
+ HRESULT hr = GetProcess()->m_cordb->SendIPCEvent(GetProcess(), &event, sizeof(DebuggerIPCEvent));
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ _ASSERTE(event.type == DB_IPCE_SET_IP);
+
+ if (!fCanSetIPOnly && SUCCEEDED(event.hr))
+ {
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+ CleanupStack();
+ }
+
+ return ErrWrapper(event.hr);
+}
+
+// Get the context from a thread in managed code.
+// This thread should be stopped gracefully by the LS in managed code.
+HRESULT CordbThread::GetManagedContext(DT_CONTEXT ** ppContext)
+{
+ FAIL_IF_NEUTERED(this);
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+
+ if (ppContext == NULL)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ *ppContext = NULL;
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ // Each CordbThread object allocates the m_pContext's DT_CONTEXT structure only once, the first time GetContext is
+ // invoked.
+ if(m_pContext == NULL)
+ {
+ // Throw if the allocation fails.
+ m_pContext = reinterpret_cast<DT_CONTEXT *>(new BYTE[sizeof(DT_CONTEXT)]);
+ }
+
+ HRESULT hr = S_OK;
+
+ if (m_fContextFresh == false)
+ {
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ m_vmLeftSideContext = pDAC->GetManagedStoppedContext(m_vmThreadToken);
+
+ if (m_vmLeftSideContext.IsNull())
+ {
+ // We don't have a context in managed code.
+ ThrowHR(CORDBG_E_CONTEXT_UNVAILABLE);
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CT::GC: getting context from left side pointer.\n"));
+
+ // The thread we're examining IS handling an exception, So grab the CONTEXT of the exception, NOT the
+ // currently executing thread's CONTEXT (which would be the context of the exception handler.)
+ hr = GetProcess()->SafeReadThreadContext(m_vmLeftSideContext.ToLsPtr(), m_pContext);
+ IfFailThrow(hr);
+ }
+
+ // m_fContextFresh should be marked false when CleanupStack, MarkAllFramesAsDirty, etc get called.
+ m_fContextFresh = true;
+ }
+
+ _ASSERTE(SUCCEEDED(hr));
+ (*ppContext) = m_pContext;
+
+ return hr;
+}
+
+HRESULT CordbThread::SetManagedContext(DT_CONTEXT * pContext)
+{
+ INTERNAL_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ if(pContext == NULL)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ m_vmLeftSideContext = pDAC->GetManagedStoppedContext(m_vmThreadToken);
+
+ if (m_vmLeftSideContext.IsNull())
+ {
+ ThrowHR(CORDBG_E_CONTEXT_UNVAILABLE);
+ }
+ else
+ {
+ // The thread we're examining IS handling an exception, So set the CONTEXT of the exception, NOT the currently
+ // executing thread's CONTEXT (which would be the context of the exception handler.)
+ //
+ // Note: we read the remote context and merge the new one in, then write it back. This ensures that we don't
+ // write too much information into the remote process.
+ DT_CONTEXT tempContext = { 0 };
+ hr = GetProcess()->SafeReadThreadContext(m_vmLeftSideContext.ToLsPtr(), &tempContext);
+ IfFailThrow(hr);
+
+ CORDbgCopyThreadContext(&tempContext, pContext);
+
+ hr = GetProcess()->SafeWriteThreadContext(m_vmLeftSideContext.ToLsPtr(), &tempContext);
+ IfFailThrow(hr);
+
+ // @todo - who's updating the regdisplay to guarantee that's in sync w/ our new context?
+ }
+
+ _ASSERTE(SUCCEEDED(hr));
+ if (m_fContextFresh && (m_pContext != NULL))
+ {
+ *m_pContext = *pContext;
+ }
+
+ return hr;
+}
+
+
+HRESULT CordbThread::GetAppDomain(ICorDebugAppDomain ** ppAppDomain)
+{
+ // We don't use the cached m_pAppDomain pointer here because it might be incorrect
+ // if the thread has transitioned to another domain but we haven't received any events
+ // from it yet. So we need to ask the left-side for the current domain.
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+ {
+ ValidateOrThrow(ppAppDomain);
+ *ppAppDomain = NULL;
+ hr = EnsureThreadIsAlive();
+
+ if (SUCCEEDED(hr))
+ {
+ CordbAppDomain * pAppDomain = NULL;
+ hr = GetCurrentAppDomain(&pAppDomain);
+ IfFailThrow(hr);
+ _ASSERTE( pAppDomain != NULL );
+
+ *ppAppDomain = static_cast<ICorDebugAppDomain *> (pAppDomain);
+ pAppDomain->ExternalAddRef();
+ }
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Issues a get appdomain command and returns it.
+//
+// Arguments:
+// ppAppDomain - OUT: Space for storing the app domain of this thread.
+//
+// Return Value:
+// S_OK on success.
+//
+HRESULT CordbThread::GetCurrentAppDomain(CordbAppDomain ** ppAppDomain)
+{
+ FAIL_IF_NEUTERED(this);
+ INTERNAL_API_ENTRY(GetProcess());
+
+ *ppAppDomain = NULL;
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // @dbgtodo ICDThread - push this up
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+
+ hr = EnsureThreadIsAlive();
+
+ if (SUCCEEDED(hr))
+ {
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ VMPTR_AppDomain vmAppDomain = pDAC->GetCurrentAppDomain(m_vmThreadToken);
+
+ CordbAppDomain * pAppDomain = GetProcess()->LookupOrCreateAppDomain(vmAppDomain);
+ _ASSERTE(pAppDomain != NULL); // we should be aware of all AppDomains
+
+ *ppAppDomain = pAppDomain;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return S_OK;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Issues a get_object command and returns the thread object as a value.
+//
+// Arguments:
+// ppThreadObject - OUT: Space for storing the thread object of this thread as a value
+//
+// Return Value:
+// S_OK on success.
+//
+HRESULT CordbThread::GetObject(ICorDebugValue ** ppThreadObject)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ VALIDATE_POINTER_TO_OBJECT(ppThreadObject, ICorDebugObjectValue **);
+
+ // Default to NULL
+ *ppThreadObject = NULL;
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // @dbgtodo ICDThread - push this up
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+
+ hr = EnsureThreadIsAlive();
+
+ if (SUCCEEDED(hr))
+ {
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ VMPTR_OBJECTHANDLE vmObjHandle = pDAC->GetThreadObject(m_vmThreadToken);
+ if (vmObjHandle.IsNull())
+ {
+ ThrowHR(E_FAIL);
+ }
+
+ // We create the object relative to the current AppDomain of the thread
+ // Thread objects aren't really agile (eg. their m_Context field is domain-bound and
+ // fixed up manually during transitions). This means that a thread object can only
+ // be used in the domain the thread was in when the object was created.
+ VMPTR_AppDomain vmAppDomain = pDAC->GetCurrentAppDomain(m_vmThreadToken);
+
+ CordbAppDomain * pThreadCurrentDomain = NULL;
+ pThreadCurrentDomain = GetProcess()->m_appDomains.GetBaseOrThrow(VmPtrToCookie(vmAppDomain));
+ _ASSERTE(pThreadCurrentDomain != NULL); // we should be aware of all AppDomains
+
+ if (pThreadCurrentDomain == NULL)
+ {
+ // fall back to some domain to avoid crashes in retail -
+ // safe enough for getting the name of the thread etc.
+ pThreadCurrentDomain = GetProcess()->GetDefaultAppDomain();
+ }
+
+ lockHolder.Release();
+
+ ICorDebugReferenceValue * pRefValue = NULL;
+ hr = CordbReferenceValue::BuildFromGCHandle(pThreadCurrentDomain, vmObjHandle, &pRefValue);
+ *ppThreadObject = pRefValue;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ // Don't return a null pointer with S_OK.
+ _ASSERTE((hr != S_OK) || (*ppThreadObject != NULL));
+ return hr;
+}
+
+/*
+ *
+ * GetActiveFunctions
+ *
+ * This routine is the interface function for ICorDebugThread2::GetActiveFunctions.
+ *
+ * Parameters:
+ * cFunctions - the count of the number of COR_ACTIVE_FUNCTION in pFunctions. Zero
+ * indicates no pFunctions buffer.
+ * pcFunctions - pointer to storage for the count of elements filled in to pFunctions, or
+ * count that would be needed to fill pFunctions, if cFunctions is 0.
+ * pFunctions - buffer to store results. May be NULL.
+ *
+ * Return Value:
+ * HRESULT from the helper routine.
+ *
+ */
+
+HRESULT CordbThread::GetActiveFunctions(
+ ULONG32 cFunctions,
+ ULONG32 * pcFunctions,
+ COR_ACTIVE_FUNCTION pFunctions[])
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ ULONG32 index;
+ ULONG32 iRealIndex;
+ ULONG32 last;
+
+ if (((cFunctions != 0) && (pFunctions == NULL)) || (pcFunctions == NULL))
+ {
+ return E_INVALIDARG;
+ }
+
+ //
+ // Default to 0
+ //
+ *pcFunctions = 0;
+
+ // @dbgtodo synchronization - The ATT macro may slip the thread to a sychronized state. The
+ // synchronization feature crew needs to figure out what to do here. Then we can use the
+ // PUBLIC_API_BEGIN macro in this function.
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+
+ if (IsThreadDead())
+ {
+ //
+ // Return zero active functions on this thread.
+ //
+ hr = S_OK;
+ }
+ else
+ {
+ ULONG32 cAllFrames = 0; // the total number of frames (stack frames and internal frames)
+ ULONG32 cStackFrames = 0; // the number of stack frames
+ ShimStackWalk * pSSW = NULL;
+
+ if (GetProcess()->GetShim() != NULL)
+ {
+ PRIVATE_SHIM_CALLBACK_IN_THIS_SCOPE0(GetProcess());
+ pSSW = GetProcess()->GetShim()->LookupOrCreateShimStackWalk(this);
+
+ // initialize the frame counts
+ cAllFrames = pSSW->GetFrameCount();
+ for (ULONG32 i = 0; i < cAllFrames; i++)
+ {
+ // filter out internal frames
+ if (CordbFrame::GetCordbFrameFromInterface(pSSW->GetFrame(i))->GetAsNativeFrame() != NULL)
+ {
+ cStackFrames += 1;
+ }
+ }
+
+ _ASSERTE(cStackFrames <= cAllFrames);
+ }
+ else
+ {
+ RefreshStack();
+
+ cAllFrames = m_stackFrames.Count();
+ cStackFrames = cAllFrames;
+
+ // In Arrowhead, the stackwalking API doesn't return internal frames,
+ // so the frame counts should be equal.
+ _ASSERTE(cStackFrames == cAllFrames);
+ }
+
+ *pcFunctions = cStackFrames;
+
+ //
+ // If all we want is the count, then return that.
+ //
+ if ((pFunctions == NULL) || (cFunctions == 0))
+ {
+ hr = S_OK;
+ }
+ else
+ {
+ //
+ // Now go down list of frames, storing information
+ //
+ last = (cFunctions < cStackFrames) ? cFunctions : cStackFrames;
+ iRealIndex = 0;
+ index =0;
+
+ while((index < last) && (iRealIndex < cAllFrames))
+ {
+ CordbFrame * pThisFrame = NULL;
+ if (GetProcess()->GetShim())
+ {
+ _ASSERTE(pSSW != NULL);
+ pThisFrame = CordbFrame::GetCordbFrameFromInterface(pSSW->GetFrame(iRealIndex));
+ }
+ else
+ {
+ pThisFrame = *(m_stackFrames.Get(iRealIndex));
+ _ASSERTE(pThisFrame->GetAsNativeFrame() != NULL);
+ }
+
+ iRealIndex++;
+
+ CordbNativeFrame * pNativeFrame = pThisFrame->GetAsNativeFrame();
+ if (pNativeFrame == NULL)
+ {
+ // filter out internal frames
+ _ASSERTE(pThisFrame->GetAsInternalFrame() != NULL);
+ continue;
+ }
+
+ //
+ // Fill in the easy stuff.
+ //
+ CordbFunction * pFunction;
+
+ pFunction = (static_cast<CordbFrame *>(pNativeFrame))->GetFunction();
+ ASSERT(pFunction != NULL);
+
+ hr = pFunction->QueryInterface(IID_ICorDebugFunction2,
+ reinterpret_cast<void **>(&(pFunctions[index].pFunction)));
+ ASSERT(!FAILED(hr));
+
+ CordbModule * pModule = pFunction->GetModule();
+ pFunctions[index].pModule = pModule;
+ pModule->ExternalAddRef();
+
+ CordbAppDomain * pAppDomain = pNativeFrame->GetCurrentAppDomain();
+ pFunctions[index].pAppDomain = pAppDomain;
+ pAppDomain->ExternalAddRef();
+
+ pFunctions[index].flags = 0;
+
+ //
+ // Now go to the IL frame (if one exists) to the get the offset.
+ //
+ CordbJITILFrame * pJITILFrame;
+
+ pJITILFrame = pNativeFrame->m_JITILFrame;
+
+ if (pJITILFrame != NULL)
+ {
+ hr = pJITILFrame->GetIP(&(pFunctions[index].ilOffset), NULL);
+ ASSERT(!FAILED(hr));
+ }
+ else
+ {
+ pFunctions[index].ilOffset = (DWORD) NO_MAPPING;
+ }
+
+ // Update to the next count.
+ index++;
+ }
+
+ // @todo - The spec says that pcFunctions == # of elements in pFunctions,
+ // but the behavior here is that it's always the total.
+ // If we want to fix that, we should uncomment the assignment here:
+ //*pcFunctions = index;
+ }
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// This is the entry point for continuable exceptions.
+// It implements ICorDebugThread2::InterceptCurrentException.
+//
+// Arguments:
+// pFrame - the stack frame to intercept at
+//
+// Return Value:
+// HRESULT indicating success or failure
+//
+// Notes:
+// Since we cannot intercept an exception at an internal frame,
+// pFrame should not be an ICorDebugInternalFrame.
+//
+
+HRESULT CordbThread::InterceptCurrentException(ICorDebugFrame * pFrame)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+#if defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ // Continuable exceptions are not implemented on rotor and Mac.
+ return E_NOTIMPL;
+
+#else // !FEATURE_DBGIPC_TRANSPORT_DI
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ DebuggerIPCEvent event;
+
+ if (pFrame == NULL)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ //
+ // Verify we were passed a real stack frame, and not an internal
+ // CLR mocked up one.
+ //
+ {
+ RSExtSmartPtr<ICorDebugInternalFrame> pInternalFrame;
+ hr = pFrame->QueryInterface(IID_ICorDebugInternalFrame, (void **)&pInternalFrame);
+
+ if (!FAILED(hr))
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+ }
+
+
+ //
+ // If the thread is detached, then there should be no frames on its stack.
+ //
+ hr = EnsureThreadIsAlive();
+
+ if (SUCCEEDED(hr))
+ {
+ //
+ // Refresh the stack frames for this thread and verify pFrame is on it.
+ //
+
+ RefreshStack();
+
+ //
+ // Now check if the frame actually lives on the stack of the current thread.
+ //
+
+ // "Cast" the ICDFrame pointer to a CordbFrame pointer.
+ CordbFrame * pRealFrame = CordbFrame::GetCordbFrameFromInterface(pFrame);
+ if (!OwnsFrame(pRealFrame))
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ //
+ // pFrame is on the stack - good. Now tell the LS to intercept at that frame.
+ //
+
+ GetProcess()->InitIPCEvent(&event, DB_IPCE_INTERCEPT_EXCEPTION, true, VMPTR_AppDomain::NullPtr());
+
+ event.InterceptException.vmThreadToken = m_vmThreadToken;
+ event.InterceptException.frameToken = pRealFrame->GetFramePointer();
+
+ hr = GetProcess()->m_cordb->SendIPCEvent(GetProcess(), &event, sizeof(DebuggerIPCEvent));
+
+ //
+ // Stop now if we can't even send the event.
+ //
+ if (!SUCCEEDED(hr))
+ {
+ ThrowHR(hr);
+ }
+
+ _ASSERTE(event.type == DB_IPCE_INTERCEPT_EXCEPTION_RESULT);
+
+ hr = event.hr;
+ // Since we are going to exit anyway, we don't need to throw here.
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+#endif // FEATURE_DBGIPC_TRANSPORT_DI
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Return S_OK if there is a current exception and it is unhandled, otherwise
+// return S_FALSE
+//
+HRESULT CordbThread::HasUnhandledException()
+{
+ FAIL_IF_NEUTERED(this);
+
+ HRESULT hr = S_FALSE;
+ PUBLIC_REENTRANT_API_BEGIN(this)
+ {
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ if(pDAC->HasUnhandledException(m_vmThreadToken))
+ {
+ hr = S_OK;
+ }
+ }
+ PUBLIC_REENTRANT_API_END(hr);
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Create a stackwalker on the current thread. Initially, the stackwalker is stopped at the
+// managed filter CONTEXT if there is one. Otherwise it is stopped at the leaf CONTEXT.
+//
+// Arguments:
+// ppStackWalk - out parameter; return the new stackwalker
+//
+// Return Value:
+// Return S_OK on succcess.
+// Return E_FAIL on error.
+//
+// Notes:
+// The filter CONTEXT will be removed in V3.0.
+//
+
+HRESULT CordbThread::CreateStackWalk(ICorDebugStackWalk ** ppStackWalk)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ VALIDATE_POINTER_TO_OBJECT(ppStackWalk, ICorDebugStackWalk **);
+
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+ hr = EnsureThreadIsAlive();
+
+ if (SUCCEEDED(hr))
+ {
+ RSInitHolder<CordbStackWalk> pSW(new CordbStackWalk(this));
+ pSW->Init();
+ pSW.TransferOwnershipExternal(ppStackWalk);
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// This is a callback function used to enumerate the internal frames on a thread.
+// Each time this callback is invoked, we'll create a new CordbInternalFrame and store it
+// in an array. See code:DacDbiInterfaceImpl::EnumerateInternalFrames for more information.
+//
+// Arguments:
+// pFrameData - contains information about the current internal frame in the enumeration
+// pUserData - This is a GetActiveInternalFramesData.
+// It contains an array of internl frames to be filled.
+//
+
+// static
+void CordbThread::GetActiveInternalFramesCallback(const DebuggerIPCE_STRData * pFrameData,
+ void * pUserData)
+{
+ // Retrieve the CordbThread.
+ GetActiveInternalFramesData * pCallbackData = reinterpret_cast<GetActiveInternalFramesData *>(pUserData);
+ CordbThread * pThis = pCallbackData->pThis;
+ INTERNAL_DAC_CALLBACK(pThis->GetProcess());
+
+ // Make sure we are getting invoked for internal frames.
+ _ASSERTE(pFrameData->eType == DebuggerIPCE_STRData::cStubFrame);
+
+ // Look up the CordbAppDomain.
+ CordbAppDomain * pAppDomain = NULL;
+ VMPTR_AppDomain vmCurrentAppDomain = pFrameData->vmCurrentAppDomainToken;
+ if (!vmCurrentAppDomain.IsNull())
+ {
+ pAppDomain = pThis->GetProcess()->LookupOrCreateAppDomain(vmCurrentAppDomain);
+ }
+
+ // Create a CordbInternalFrame.
+ CordbInternalFrame * pInternalFrame = new CordbInternalFrame(pThis,
+ pFrameData->fp,
+ pAppDomain,
+ pFrameData);
+
+ // Store the internal frame in the array and update the index to prepare for the next one.
+ pCallbackData->pInternalFrames.Assign(pCallbackData->uIndex, pInternalFrame);
+ pCallbackData->uIndex++;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// This function returns an array of ICDInternalFrame2. Each element represents an internal frame
+// on the thread. If ppInternalFrames is NULL or cInternalFrames is 0, then we just return
+// the number of internal frames on the thread.
+//
+// Arguments:
+// cInternalFrames - the number of elements in ppInternalFrames
+// pcInternalFrames - out parameter; return the number of internal frames on the thread
+// ppInternalFrames - a buffer to store the array of internal frames
+//
+// Return Value:
+// S_OK on success.
+// E_INVALIDARG if
+// - ppInternalFrames is NULL but cInternalFrames is not 0
+// - pcInternalFrames is NULL
+// - cInternalFrames is smaller than the number of internal frames actually on the thread
+//
+
+HRESULT CordbThread::GetActiveInternalFrames(ULONG32 cInternalFrames,
+ ULONG32 * pcInternalFrames,
+ ICorDebugInternalFrame2 * ppInternalFrames[])
+{
+ HRESULT hr = S_OK;
+ PUBLIC_REENTRANT_API_BEGIN(this);
+ {
+ if ( ((cInternalFrames != 0) && (ppInternalFrames == NULL)) ||
+ (pcInternalFrames == NULL) )
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ *pcInternalFrames = 0;
+
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ ULONG32 cActiveInternalFrames = pDAC->GetCountOfInternalFrames(m_vmThreadToken);
+
+ // Set the count.
+ *pcInternalFrames = cActiveInternalFrames;
+
+ // Don't need to do anything else if the user is only asking for the count.
+ if ((cInternalFrames != 0) && (ppInternalFrames != NULL))
+ {
+ if (cInternalFrames < cActiveInternalFrames)
+ {
+ ThrowWin32(ERROR_INSUFFICIENT_BUFFER);
+ }
+ else
+ {
+ // initialize the callback data
+ GetActiveInternalFramesData data;
+ data.pThis = this;
+ data.uIndex = 0;
+ data.pInternalFrames.AllocOrThrow(cActiveInternalFrames);
+ // We want to ensure it's automatically cleaned up in all cases
+ // e.g. if we're debugging a MiniDumpNormal and we fail to
+ // retrieve memory from the target. The exception will be
+ // caught above this frame.
+ data.pInternalFrames.EnableAutoClear();
+
+ pDAC->EnumerateInternalFrames(m_vmThreadToken,
+ &CordbThread::GetActiveInternalFramesCallback,
+ &data);
+ _ASSERTE(cActiveInternalFrames == data.pInternalFrames.Length());
+
+ // Copy the internal frames we have accumulated in GetActiveInternalFramesData to the out
+ // argument.
+ for (unsigned int i = 0; i < data.pInternalFrames.Length(); i++)
+ {
+ RSInitHolder<CordbInternalFrame> pInternalFrame(data.pInternalFrames[i]);
+ pInternalFrame.TransferOwnershipExternal(&(ppInternalFrames[i]));
+ }
+ }
+ }
+ }
+ PUBLIC_REENTRANT_API_END(hr);
+ return hr;
+}
+
+
+// ICorDebugThread4
+
+// -------------------------------------------------------------------------------
+// Gets the current custom notification on this thread or NULL if no such object exists
+// Arguments:
+// output: ppNotificationObject - current CustomNotification object.
+// if we aren't currently inside a CustomNotification callback, this will
+// always return NULL.
+// return value:
+// S_OK on success
+// S_FALSE if no object exists
+// CORDBG_E_BAD_REFERENCE_VALUE if the reference is bad
+HRESULT CordbThread::GetCurrentCustomDebuggerNotification(ICorDebugValue ** ppNotificationObject)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_NO_LOCK_BEGIN(this);
+ {
+ ATT_REQUIRE_STOPPED_MAY_FAIL_OR_THROW(GetProcess(), ThrowHR);
+
+ if (ppNotificationObject == NULL)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ *ppNotificationObject = NULL;
+
+ //
+ // Go to the LS and retrieve any notification object.
+ //
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ VMPTR_OBJECTHANDLE vmObjHandle = pDAC->GetCurrentCustomDebuggerNotification(m_vmThreadToken);
+
+#if defined(_DEBUG)
+ // Since we know a notification has occurred on this thread, our assumption about the
+ // thread's current AppDomain should be correct
+ VMPTR_AppDomain vmAppDomain = pDAC->GetCurrentAppDomain(m_vmThreadToken);
+
+ _ASSERTE(GetAppDomain()->GetADToken() == vmAppDomain);
+#endif // _DEBUG
+
+ if (!vmObjHandle.IsNull())
+ {
+ ICorDebugReferenceValue * pRefValue = NULL;
+ IfFailThrow(CordbReferenceValue::BuildFromGCHandle(GetAppDomain(), vmObjHandle, &pRefValue));
+ *ppNotificationObject = pRefValue;
+ }
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+/*
+ *
+ * SetRemapIP
+ *
+ * This routine communicate the EnC remap IP to the LS by writing it to process memory using
+ * the pointer that was set in the thread. If the address is null, then we haven't seen
+ * a RemapOpportunity call for this frame/function combo yet, so invalid to Remap the function.
+ *
+ * Parameters:
+ * offset - the IL offset to set the IP to
+ *
+ * Return Value:
+ * S_OK or CORDBG_E_NO_REMAP_BREAKPIONT.
+ *
+ */
+HRESULT CordbThread::SetRemapIP(SIZE_T offset)
+{
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+
+ // This is only set when we're prepared to do a remap
+ if (! m_EnCRemapFunctionIP)
+ {
+ return CORDBG_E_NO_REMAP_BREAKPIONT;
+ }
+
+ // Write the value of the remap offset into the left side
+ HRESULT hr = GetProcess()->SafeWriteStruct(PTR_TO_CORDB_ADDRESS(m_EnCRemapFunctionIP), &offset);
+
+ // Prevent SetRemapIP from being called twice for the same RemapOpportunity
+ // If we don't get any calls to RemapFunction, this member will be cleared in
+ // code:CordbThread::MarkStackFramesDirty when Continue is called
+ m_EnCRemapFunctionIP = NULL;
+
+ return hr;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// This routine is the interface function for ICorDebugThread2::GetConnectionID.
+//
+// Arguments:
+// pdwConnectionId - return connection id set on the thread. Can return INVALID_CONNECTION_ID
+//
+// Return Value:
+// HRESULT indicating success or failure
+//
+HRESULT CordbThread::GetConnectionID(CONNID * pConnectionID)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ // now retrieve the connection id
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ if (pConnectionID == NULL)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ *pConnectionID = pDAC->GetConnectionID(m_vmThreadToken);
+
+ if (*pConnectionID == INVALID_CONNECTION_ID)
+ {
+ hr = S_FALSE;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+} // CordbThread::GetConnectionID
+
+//---------------------------------------------------------------------------------------
+//
+// This routine is the interface function for ICorDebugThread2::GetTaskID.
+//
+// Arguments:
+// pTaskId - return task id set on the thread. Can return INVALID_TASK_ID
+//
+// Return Value:
+// HRESULT indicating success or failure
+//
+HRESULT CordbThread::GetTaskID(TASKID * pTaskID)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ // now retrieve the task id
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ if (pTaskID == NULL)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ *pTaskID = this->GetTaskID();
+
+ if (*pTaskID == INVALID_TASK_ID)
+ {
+ hr = S_FALSE;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+} // CordbThread::GetTaskID
+
+//---------------------------------------------------------------------------------------
+// Get the task ID for this thread
+//
+// return:
+// task id set on the thread. Can return INVALID_TASK_ID
+//
+TASKID CordbThread::GetTaskID()
+{
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ return pDAC->GetTaskID(m_vmThreadToken);
+}
+
+
+
+//---------------------------------------------------------------------------------------
+//
+// This routine is the interface function for ICorDebugThread2::GetVolatileOSThreadID.
+//
+// Arguments:
+// pdwTid - return os thread id
+//
+// Return Value:
+// HRESULT indicating success or failure
+//
+// Notes:
+// Compare with code:CordbThread::GetID
+HRESULT CordbThread::GetVolatileOSThreadID(DWORD * pdwTID)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ // now retrieve the OS thread ID
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ if (pdwTID == NULL)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ *pdwTID = pDAC->TryGetVolatileOSThreadID(m_vmThreadToken);
+
+ if (*pdwTID == 0)
+ {
+ hr = S_FALSE; // Switched out
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+} // CordbThread::GetOSThreadID
+
+//---------------------------------------------------------------------------------------
+// Get the thread's volatile OS ID. (this is fiber aware)
+//
+// Returns:
+// Thread's current OS id. For fibers / "logical threads", This may change as a thread executes.
+// Throws if the managed thread currently is not mapped to an OS thread (ie, not scheduled)
+//
+DWORD CordbThread::GetVolatileOSThreadID()
+{
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ DWORD dwThreadID = pDAC->TryGetVolatileOSThreadID(m_vmThreadToken);
+
+ if (dwThreadID == 0)
+ {
+ ThrowHR(CORDBG_E_THREAD_NOT_SCHEDULED);
+ }
+ return dwThreadID;
+}
+
+// ----------------------------------------------------------------------------
+// CordbThread::ClearStackFrameCache
+//
+// Description:
+// Clear the cache of stack frames maintained by the CordbThread.
+//
+// Notes:
+// We are doing an InternalRelease() here to match the InternalAddRef() in code:CordbThread::RefreshStack.
+//
+
+void CordbThread::ClearStackFrameCache()
+{
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+
+ for (int i = 0; i < m_stackFrames.Count(); i++)
+ {
+ (*m_stackFrames.Get(i))->Neuter();
+ (*m_stackFrames.Get(i))->InternalRelease();
+ }
+ m_stackFrames.Clear();
+}
+
+// ----------------------------------------------------------------------------
+// EnumerateBlockingObjectsCallback
+//
+// Description:
+// A small helper used by CordbThread::GetBlockingObjects. This callback adds the enumerated items
+// to a list
+//
+// Arguments:
+// blockingObject - the object to add to the list
+// pUserData - the list to add it to
+
+VOID EnumerateBlockingObjectsCallback(DacBlockingObject blockingObject, CALLBACK_DATA pUserData)
+{
+ CQuickArrayList<DacBlockingObject>* pDacBlockingObjs = (CQuickArrayList<DacBlockingObject>*)pUserData;
+ pDacBlockingObjs->Push(blockingObject);
+}
+
+// ----------------------------------------------------------------------------
+// CordbThread::GetBlockingObjects
+//
+// Description:
+// Returns a list of objects that a thread is blocking on by using Monitor.Enter and
+// Monitor.Wait
+//
+// Arguments:
+// ppBlockingObjectEnum - on return this is an enumerator for the list of blocking objects
+//
+// Return:
+// S_OK on success or an appropriate failing HRESULT
+
+HRESULT CordbThread::GetBlockingObjects(ICorDebugBlockingObjectEnum **ppBlockingObjectEnum)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ VALIDATE_POINTER_TO_OBJECT(ppBlockingObjectEnum, ICorDebugBlockingObjectEnum **);
+
+ HRESULT hr = S_OK;
+ CorDebugBlockingObject* blockingObjs = NULL;
+ EX_TRY
+ {
+ CQuickArrayList<DacBlockingObject> dacBlockingObjects;
+ IDacDbiInterface* pDac = GetProcess()->GetDAC();
+ pDac->EnumerateBlockingObjects(m_vmThreadToken,
+ (IDacDbiInterface::FP_BLOCKINGOBJECT_ENUMERATION_CALLBACK) EnumerateBlockingObjectsCallback,
+ (CALLBACK_DATA) &dacBlockingObjects);
+ blockingObjs = new CorDebugBlockingObject[dacBlockingObjects.Size()];
+ for(SIZE_T i = 0 ; i < dacBlockingObjects.Size(); i++)
+ {
+ // ICorDebug API needs to flip the direction of the list from the way DAC stores it
+ SIZE_T dacObjIndex = dacBlockingObjects.Size()-i-1;
+ switch(dacBlockingObjects[dacObjIndex].blockingReason)
+ {
+ case DacBlockReason_MonitorCriticalSection:
+ blockingObjs[i].blockingReason = BLOCKING_MONITOR_CRITICAL_SECTION;
+ break;
+ case DacBlockReason_MonitorEvent:
+ blockingObjs[i].blockingReason = BLOCKING_MONITOR_EVENT;
+ break;
+ default:
+ _ASSERTE(!"Should not get here");
+ ThrowHR(E_FAIL);
+ break;
+ }
+ blockingObjs[i].dwTimeout = dacBlockingObjects[dacObjIndex].dwTimeout;
+ CordbAppDomain* pAppDomain;
+ {
+ RSLockHolder holder(GetProcess()->GetProcessLock());
+ pAppDomain = GetProcess()->LookupOrCreateAppDomain(dacBlockingObjects[dacObjIndex].vmAppDomain);
+ }
+ blockingObjs[i].pBlockingObject = CordbValue::CreateHeapValue(pAppDomain,
+ dacBlockingObjects[dacObjIndex].vmBlockingObject);
+ }
+
+ CordbBlockingObjectEnumerator* objEnum = new CordbBlockingObjectEnumerator(GetProcess(),
+ blockingObjs,
+ (DWORD)dacBlockingObjects.Size());
+ GetProcess()->GetContinueNeuterList()->Add(GetProcess(), objEnum);
+ hr = objEnum->QueryInterface(__uuidof(ICorDebugBlockingObjectEnum), (void**)ppBlockingObjectEnum);
+ _ASSERTE(SUCCEEDED(hr));
+ }
+ EX_CATCH_HRESULT(hr);
+ delete [] blockingObjs;
+ return hr;
+}
+
+// ----------------------------------------------------------------------------
+// CordbThread::SetCreateEventQueued
+void CordbThread::SetCreateEventQueued()
+{
+ m_fCreationEventQueued = true;
+}
+
+// ----------------------------------------------------------------------------
+// CordbThread::CreateEventWasQueued
+bool CordbThread::CreateEventWasQueued()
+{
+ return m_fCreationEventQueued;
+}
+
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+/* ------------------------------------------------------------------------- *
+ * Unmanaged Thread classes
+ * ------------------------------------------------------------------------- */
+
+CordbUnmanagedThread::CordbUnmanagedThread(CordbProcess *pProcess, DWORD dwThreadId, HANDLE hThread, void *lpThreadLocalBase)
+ : CordbBase(pProcess, dwThreadId, enumCordbUnmanagedThread),
+ m_handle(hThread),
+ m_threadLocalBase(lpThreadLocalBase),
+ m_pTLSArray(NULL),
+ m_pTLSExtendedArray(NULL),
+ m_state(CUTS_None),
+ m_originalHandler(NULL),
+#ifdef DBG_TARGET_X86
+ m_pSavedLeafSeh(NULL),
+#endif
+ m_stackBase(0),
+ m_stackLimit(0),
+ m_continueCountCached(0)
+{
+ m_pLeftSideContext.Set(NULL);
+
+ IBEvent()->m_state = CUES_None;
+ IBEvent()->m_next = NULL;
+ IBEvent()->m_owner = this;
+
+ IBEvent2()->m_state = CUES_None;
+ IBEvent2()->m_next = NULL;
+ IBEvent2()->m_owner = this;
+
+ OOBEvent()->m_state = CUES_None;
+ OOBEvent()->m_next = NULL;
+ OOBEvent()->m_owner = this;
+
+ m_pPatchSkipAddress = NULL;
+
+ this->GetStackRange(NULL, NULL);
+}
+
+CordbUnmanagedThread::~CordbUnmanagedThread()
+{
+ // CordbUnmanagedThread objects will:
+ // - never send IPC events.
+ // - never be exposed to the public. (we assert external-ref is always == 0)
+ // - always manipulated on W32ET (where we can't do IPC stuff)
+
+ UnsafeNeuterDeadObject();
+
+ _ASSERTE(this->IsNeutered());
+
+ // by the time the thread is deleted, it shouldn't have any outstanding debug events.
+
+ // Actually, the thread could get deleted while we have an outstanding IB debug event. We could get the IB event, hijack that thread,
+ // and then since the process is continued, something could go off and kill the hijacked thread.
+ // If the event is still in the process's queued list, and it still refers back to a thread, then we'll AV when we try to access the event
+ // (or continue it).
+ CONSISTENCY_CHECK_MSGF(!HasIBEvent(), ("Deleting thread w/ outstanding IB event:this=%p,event-code=%d\n", this, IBEvent()->m_currentDebugEvent.dwDebugEventCode));
+
+ CONSISTENCY_CHECK_MSGF(!HasOOBEvent(), ("Deleting thread w/ outstanding OOB event:this=%p,event-code=%d\n", this, OOBEvent()->m_currentDebugEvent.dwDebugEventCode));
+}
+
+#define WINNT_TLS_OFFSET_X86 0xe10 // TLS[0] at fs:[WINNT_TLS_OFFSET]
+#define WINNT_TLS_OFFSET_AMD64 0x1480
+#define WINNT_TLS_OFFSET_ARM 0xe10
+#define WINNT5_TLSEXPANSIONPTR_OFFSET_X86 0xf94 // TLS[64] at [fs:[WINNT5_TLSEXPANSIONPTR_OFFSET]]
+#define WINNT5_TLSEXPANSIONPTR_OFFSET_AMD64 0x1780
+#define WINNT5_TLSEXPANSIONPTR_OFFSET_ARM 0xf94
+
+HRESULT CordbUnmanagedThread::LoadTLSArrayPtr(void)
+{
+ FAIL_IF_NEUTERED(this);
+
+ HRESULT hr = S_OK;
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+
+
+ // Just simple math on NT with a small tls index.
+ // The TLS slots for 0-63 are embedded in the TIB.
+#if defined(DBG_TARGET_X86)
+ m_pTLSArray = (BYTE*) m_threadLocalBase + WINNT_TLS_OFFSET_X86;
+#elif defined(DBG_TARGET_AMD64)
+ m_pTLSArray = (BYTE*) m_threadLocalBase + WINNT_TLS_OFFSET_AMD64;
+#elif defined(DBG_TARGET_ARM)
+ m_pTLSArray = (BYTE*) m_threadLocalBase + WINNT_TLS_OFFSET_ARM;
+#elif defined(DBG_TARGET_ARM64)
+ m_pTLSArray = (BYTE*) m_threadLocalBase + WINNT_TLS_OFFSET_ARM64;
+#else
+ PORTABILITY_ASSERT("Implement OOP TLS on your platform");
+#endif
+
+ // Extended slot is lazily initialized, so check every time.
+ if (m_pTLSExtendedArray == NULL)
+ {
+ // On NT 5 you can have TLS index's greater than 63, so we
+ // have to grab the ptr to the TLS expansion array first,
+ // then use that as the base to index off of. This will
+ // never move once we find it for a given thread, so we
+ // cache it here so we don't always have to perform two
+ // ReadProcessMemory's.
+#if defined(DBG_TARGET_X86)
+ void *ppTLSArray = (BYTE*) m_threadLocalBase + WINNT5_TLSEXPANSIONPTR_OFFSET_X86;
+#elif defined(DBG_TARGET_AMD64)
+ void *ppTLSArray = (BYTE*) m_threadLocalBase + WINNT5_TLSEXPANSIONPTR_OFFSET_AMD64;
+#elif defined(DBG_TARGET_ARM)
+ void *ppTLSArray = (BYTE*) m_threadLocalBase + WINNT5_TLSEXPANSIONPTR_OFFSET_ARM;
+#elif defined(DBG_TARGET_ARM64)
+ void *ppTLSArray = (BYTE*) m_threadLocalBase + WINNT5_TLSEXPANSIONPTR_OFFSET_ARM64;
+#else
+ PORTABILITY_ASSERT("Implement OOP TLS on your platform");
+#endif
+
+ hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(ppTLSArray), &m_pTLSExtendedArray);
+ }
+
+
+ return hr;
+}
+
+/*
+VOID CordbUnmanagedThread::VerifyFSChain()
+{
+#if defined(DBG_TARGET_X86)
+ DT_CONTEXT temp;
+ temp.ContextFlags = DT_CONTEXT_FULL;
+ DbiGetThreadContext(m_handle, &temp);
+ LOG((LF_CORDB, LL_INFO1000, "CUT::VFSC: 0x%x fs=0x%x TIB=0x%x\n",
+ m_id, temp.SegFs, m_threadLocalBase));
+ REMOTE_PTR pExceptionRegRecordPtr;
+ HRESULT hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(m_threadLocalBase), &pExceptionRegRecordPtr);
+ if(FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::VFSC: ERROR 0x%x failed to read fs:0 value: computed addr=0x%p err=%x\n",
+ m_id, m_threadLocalBase, hr));
+ _ASSERTE(FALSE);
+ return;
+ }
+ while(pExceptionRegRecordPtr != EXCEPTION_CHAIN_END && pExceptionRegRecordPtr != NULL)
+ {
+ REMOTE_PTR prev;
+ REMOTE_PTR handler;
+ hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(pExceptionRegRecordPtr), &prev);
+ if(FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::VFSC: ERROR 0x%x failed to read prev value: computed addr=0x%p err=%x\n",
+ m_id, pExceptionRegRecordPtr, hr));
+ return;
+ }
+ hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS( (VOID*)((DWORD)pExceptionRegRecordPtr+4) ), &handler);
+ if(FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::VFSC: ERROR 0x%x failed to read handler value: computed addr=0x%p err=%x\n",
+ m_id, (DWORD)pExceptionRegRecordPtr+4, hr));
+ return;
+ }
+ LOG((LF_CORDB, LL_INFO1000, "CUT::VFSC: OK 0x%x record=0x%x prev=0x%x handler=0x%x\n",
+ m_id, pExceptionRegRecordPtr, prev, handler));
+ if(handler == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::VFSC: ERROR 0x%x NULL handler found\n", m_id));
+ _ASSERTE(FALSE);
+ return;
+ }
+ if(prev == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::VFSC: ERROR 0x%x NULL prev found\n", m_id));
+ _ASSERTE(FALSE);
+ return;
+ }
+ if(prev == pExceptionRegRecordPtr)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::VFSC: ERROR 0x%x cyclic prev found\n", m_id));
+ _ASSERTE(FALSE);
+ return;
+ }
+ pExceptionRegRecordPtr = prev;
+ }
+
+ LOG((LF_CORDB, LL_INFO1000, "CUT::VFSC: OK 0x%x\n", m_id));
+#endif
+ return;
+}*/
+
+#ifdef DBG_TARGET_X86
+HRESULT CordbUnmanagedThread::SaveCurrentLeafSeh()
+{
+ _ASSERTE(m_pSavedLeafSeh == NULL);
+ REMOTE_PTR pExceptionRegRecordPtr;
+ HRESULT hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(m_threadLocalBase), &pExceptionRegRecordPtr);
+ if(FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::SCLS: failed to read fs:0 value: computed addr=0x%p err=%x\n", m_threadLocalBase, hr));
+ return hr;
+ }
+ m_pSavedLeafSeh = pExceptionRegRecordPtr;
+ return S_OK;
+}
+
+HRESULT CordbUnmanagedThread::RestoreLeafSeh()
+{
+ _ASSERTE(m_pSavedLeafSeh != NULL);
+ HRESULT hr = GetProcess()->SafeWriteStruct(PTR_TO_CORDB_ADDRESS(m_threadLocalBase), &m_pSavedLeafSeh);
+ if(FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::RLS: failed to write fs:0 value: computed addr=0x%p err=%x\n", m_threadLocalBase, hr));
+ return hr;
+ }
+ m_pSavedLeafSeh = NULL;
+ return S_OK;
+}
+#endif
+
+// Read the contents from the LS's Predefined TLS block.
+// This is an auxillary TLS storage array-of-void*, indexed off the TLS.
+// pRead is optional. This makes sense when '0' is a valid default value.
+// 1) On success (block exists in LS, we can read it),
+// return value of data in the slot, *pRead = true
+// 2) On failure to read block (block doens't exist yet, any other failure)
+// return value == 0 (assumed default, *pRead = false
+REMOTE_PTR CordbUnmanagedThread::GetPreDefTlsSlot(SIZE_T slot, bool * pRead)
+{
+#ifdef FEATURE_IMPLICIT_TLS
+ REMOTE_PTR pBlock = (REMOTE_PTR) GetEETlsDataBlock();
+#else
+ DebuggerIPCRuntimeOffsets *pRO = &(GetProcess()->m_runtimeOffsets);
+ REMOTE_PTR pBlock = (REMOTE_PTR) GetTlsSlot(pRO->m_TLSIndexOfPredefs);
+#endif
+
+ REMOTE_PTR data = 0;
+
+ // We don't have a maximum size, but we know it's less than ~200. This assert
+ // will catch if we're just passsing Garbage.
+ _ASSERTE(slot < 200);
+
+ bool dummy;
+ if (pRead == NULL)
+ {
+ pRead = &dummy;
+ }
+
+ if (pBlock != NULL)
+ {
+ REMOTE_PTR p = ((BYTE*) pBlock) + slot * sizeof(data);
+
+ // Now read the "special" status out of the PreDef block.
+ HRESULT hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(p), &data);
+
+ // The predef block should be valid at this point, so the ReadProcessMemory ought to work.
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+
+ if (SUCCEEDED(hr))
+ {
+ *pRead = true;
+ return data;
+ }
+ }
+
+ *pRead = false;
+ return 0;
+}
+
+#ifndef FEATURE_IMPLICIT_TLS
+
+// Read the contents from a LS threads's TLS slot.
+DWORD_PTR CordbUnmanagedThread::GetTlsSlot(SIZE_T slot)
+{
+ DWORD_PTR ret = 0;
+
+ // Compute the address of the necessary TLS value.
+ if (FAILED(LoadTLSArrayPtr()))
+ {
+ return NULL;
+ }
+
+
+ void * pBase = NULL;
+ SIZE_T slotAdjusted = slot;
+
+ if (slot < TLS_MINIMUM_AVAILABLE)
+ {
+ pBase = m_pTLSArray;
+ }
+ else if (slot < TLS_MINIMUM_AVAILABLE + TLS_EXPANSION_SLOTS)
+ {
+ pBase = m_pTLSExtendedArray;
+ slotAdjusted -= TLS_MINIMUM_AVAILABLE;
+
+ // Expansion slot is lazily allocated. If we're trying to read from it, but hasn't been allocated,
+ // then the TLS slot is still the default value, which is 0 (NULL).
+ if (pBase == NULL)
+ {
+ return NULL;
+ }
+ }
+ else
+ {
+ // Slot is out of range. Shouldn't happen unless debuggee is corrupted.
+ _ASSERTE(!"Invalid TLS slot");
+ return NULL;
+ }
+
+ void *pEEThreadTLS = (BYTE*) pBase + (slotAdjusted * sizeof(void*));
+
+
+ // Read the thread's TLS value.
+ HRESULT hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(pEEThreadTLS), &ret);
+ if (FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::GEETV: failed to read TLS value: computed addr=0x%p index=%d, err=%x\n",
+ pEEThreadTLS, slot, hr));
+
+ return NULL;
+ }
+
+ LOG((LF_CORDB, LL_INFO1000000, "CUT::GEETV: EE Thread TLS value is 0x%p for thread 0x%x, slot 0x%x\n", ret, m_id, slot));
+
+ return ret;
+}
+
+// This does a WriteProcessMemory to write to the debuggee's TLS slot allotted to EEThread
+//
+// Arguments:
+// EETlsValue - the value to write to the remote TLS slot.
+//
+// Notes:
+// The TLS slot is m_TLSIndex.
+//
+// This is very brittle because the OS can lazily allocates storage for TLS slots.
+// In order to gaurantee the storage is available, it must have been written to by the debuggee.
+// For managed threads, that's easy because the Thread* is already written to the slot.
+// But for pure native threads where GetThread() == NULL, the storage may not yet be allocated.
+//
+// The saving grace is that the debuggee's hijack filters will force the TLS to be allocated before it
+// sends a flare.
+//
+// Therefore, this function can only be called:
+// 1) on a managed thread
+// 2) on a native thread after that thread has been hijacked and sent a flare.
+//
+// This is brittle reasoning, but so is the rest of interop-debugging.
+//
+HRESULT CordbUnmanagedThread::SetEEThreadValue(REMOTE_PTR EETlsValue)
+{
+ FAIL_IF_NEUTERED(this);
+
+ // Compute the address of the necessary TLS value.
+ DebuggerIPCRuntimeOffsets *pRO = &(GetProcess()->m_runtimeOffsets);
+
+ // Compute the address of the necessary TLS value.
+ HRESULT hr = LoadTLSArrayPtr();
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+
+ DWORD slot = (DWORD) pRO->m_TLSIndex;
+
+ void * pBase = NULL;
+ SIZE_T slotAdjusted = slot;
+ if (slot < TLS_MINIMUM_AVAILABLE)
+ {
+ pBase = m_pTLSArray;
+ }
+ else if (slot < TLS_MINIMUM_AVAILABLE+TLS_EXPANSION_SLOTS)
+ {
+ pBase = m_pTLSExtendedArray;
+ slotAdjusted -= TLS_MINIMUM_AVAILABLE;
+
+ // Expansion slot is lazily allocated. If we're trying to read from it, but hasn't been allocated,
+ // then the TLS slot is still the default value, which is 0.
+ if (pBase == NULL)
+ {
+ // See reasoning in header for why this should succeed.
+ _ASSERTE(!"Can't set to expansion slots because they haven't been allocated");
+ return E_FAIL;
+ }
+ }
+ else
+ {
+ // Slot is out of range. Shouldn't happen unless debuggee is corrupted.
+ _ASSERTE(!"Invalid TLS slot");
+ return E_INVALIDARG;
+ }
+
+
+ void *pEEThreadTLS = (BYTE*) pBase + (slotAdjusted * sizeof(void*));
+
+
+ // Write the thread's TLS value.
+ hr = GetProcess()->SafeWriteStruct(PTR_TO_CORDB_ADDRESS(pEEThreadTLS), &EETlsValue);
+
+ if (FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::SEETV: failed to set TLS value: "
+ "computed addr=0x%p index=%d, err=%x\n",
+ pEEThreadTLS, pRO->m_TLSIndex, hr));
+
+ return hr;
+ }
+
+ LOG((LF_CORDB, LL_INFO1000000,
+ "CUT::SEETV: EE Thread TLS value is now 0x%p for thread 0x%x\n",
+ EETlsValue, m_id));
+
+ return S_OK;
+}
+#else // FEATURE_IMPLICIT_TLS
+
+#ifdef DBG_TARGET_X86
+#define WINNT_OFFSETOF__TEB__ThreadLocalStoragePointer 0x2c
+#elif defined(DBG_TARGET_AMD64)
+#define WINNT_OFFSETOF__TEB__ThreadLocalStoragePointer 0x58
+#elif defined(DBG_TARGET_ARM)
+#define WINNT_OFFSETOF__TEB__ThreadLocalStoragePointer 0x2c
+#elif defined(DBG_TARGET_ARM64)
+#define WINNT_OFFSETOF__TEB__ThreadLocalStoragePointer 0x58
+#endif
+
+// sets the value of gCurrentThreadInfo.m_pThread
+HRESULT CordbUnmanagedThread::SetEEThreadValue(REMOTE_PTR EETlsValue)
+{
+ FAIL_IF_NEUTERED(this);
+
+ HRESULT hr = S_OK;
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+
+ REMOTE_PTR EEThreadAddr = (BYTE*) GetClrModuleTlsDataAddress() + OFFSETOF__TLS__tls_CurrentThread;
+ if(EEThreadAddr == NULL)
+ return E_FAIL;
+
+ // Write the thread's TLS value.
+ hr = GetProcess()->SafeWriteStruct(PTR_TO_CORDB_ADDRESS(EEThreadAddr), &EETlsValue);
+
+ if (FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::SEETV: failed to set TLS value: "
+ "computed addr=0x%p index=%d, err=%x\n",
+ EEThreadAddr, GetProcess()->m_runtimeOffsets.m_TLSIndex, hr));
+
+ return hr;
+ }
+
+ LOG((LF_CORDB, LL_INFO1000000,
+ "CUT::SEETV: EE Thread TLS value is now 0x%p for thread 0x%x\n",
+ EETlsValue, m_id));
+
+ return S_OK;
+
+}
+
+// gets the value of gCurrentThreadInfo.m_pThread
+DWORD_PTR CordbUnmanagedThread::GetEEThreadValue()
+{
+ DWORD_PTR ret = NULL;
+
+ REMOTE_PTR EEThreadAddr = (BYTE*) GetClrModuleTlsDataAddress() + OFFSETOF__TLS__tls_CurrentThread;
+ if(EEThreadAddr == NULL)
+ return NULL;
+
+ // Read the thread's TLS value.
+ HRESULT hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(EEThreadAddr), &ret);
+
+ if (FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::GEETV: failed to get TLS value: "
+ "computed addr=0x%p index=%d, err=%x\n",
+ EEThreadAddr, GetProcess()->m_runtimeOffsets.m_TLSIndex, hr));
+
+ return NULL;
+ }
+
+ LOG((LF_CORDB, LL_INFO1000000,
+ "CUT::GEETV: EE Thread TLS value is 0x%p for thread 0x%x\n",
+ ret, m_id));
+
+ return ret;
+}
+
+// returns the remote address of gCurrentThreadInfo
+REMOTE_PTR CordbUnmanagedThread::GetClrModuleTlsDataAddress()
+{
+ HRESULT hr = S_OK;
+
+ REMOTE_PTR tlsArrayAddr;
+ hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS((BYTE*)m_threadLocalBase + WINNT_OFFSETOF__TEB__ThreadLocalStoragePointer), &tlsArrayAddr);
+ if (FAILED(hr))
+ {
+ return NULL;
+ }
+
+ if (tlsArrayAddr == NULL)
+ {
+ _ASSERTE(!"ThreadLocalStoragePointer is NULL");
+ return NULL;
+ }
+
+ DWORD slot = (DWORD)(GetProcess()->m_runtimeOffsets.m_TLSIndex);
+
+ REMOTE_PTR clrModuleTlsDataAddr;
+ hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS((BYTE*)tlsArrayAddr + slot * sizeof(void*)), &clrModuleTlsDataAddr);
+ if (FAILED(hr))
+ {
+ return NULL;
+ }
+
+ if (clrModuleTlsDataAddr == NULL)
+ {
+ _ASSERTE(!"No clr module data present at _tls_index for this thread");
+ return NULL;
+ }
+
+ return clrModuleTlsDataAddr;
+}
+
+// gets the value of gCurrentThreadInfo.m_EETlsData
+REMOTE_PTR CordbUnmanagedThread::GetEETlsDataBlock()
+{
+ REMOTE_PTR ret;
+
+ REMOTE_PTR blockAddr = (BYTE*) GetClrModuleTlsDataAddress() + OFFSETOF__TLS__tls_EETlsData;
+
+
+ HRESULT hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(blockAddr), &ret);
+ if (FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::GEETDB: failed to read EETlsData address: computed addr=0x%p offset=%d, err=%x\n",
+ blockAddr, OFFSETOF__TLS__tls_EETlsData, hr));
+
+ return NULL;
+ }
+
+ LOG((LF_CORDB, LL_INFO1000000, "CUT::GEETDB: EETlsData address value is 0x%p for thread 0x%x\n", ret, m_id));
+
+ return ret;
+}
+
+#endif // FEATURE_IMPLICIT_TLS
+
+/*
+ * CacheEEDebuggerWord
+ *
+ * NOTE: This routine is inappropriately named at this time because we dont
+ * actually cache any values. This is because we dont have a way to invalidate
+ * the cache between purely-native continues.
+ *
+ * This routine grabs two pieces of information from the target process via
+ * ReadProcessMemory. First, if the runtime does not have a thread object for
+ * this thread it grabs the debugger's value from the TLS slot. If there is a
+ * runtime thread object, then it saves that away and grabs the debugger's value
+ * from the thread object.
+ *
+ * Parameters:
+ * None.
+ *
+ * Returns:
+ * None. If it fails, then the Get/Set functions will fail.
+ */
+void CordbUnmanagedThread::CacheEEDebuggerWord()
+{
+ LOG((LF_CORDB, LL_INFO1000, "CacheEEDW: Entered\n"));
+
+#ifdef FEATURE_IMPLICIT_TLS
+ REMOTE_PTR value = (REMOTE_PTR)GetEEThreadValue();
+#else
+ REMOTE_PTR value = (REMOTE_PTR)GetTlsSlot(GetProcess()->m_runtimeOffsets.m_TLSIndex);
+#endif
+
+ if ((((DWORD)value) & 0x1) == 1)
+ {
+ m_pEEThread = NULL;
+ m_pdwTlsValue = (REMOTE_PTR)((BYTE*)value - 0x1);
+ m_fValidTlsData = TRUE;
+ }
+ else if (value != NULL)
+ {
+ m_pEEThread = value;
+
+ // Compute the address of the debugger word #2.
+ void *pEEDebuggerWord = (BYTE*)m_pEEThread + GetProcess()->m_runtimeOffsets.m_EEThreadDebuggerWordOffset;
+
+ // Update the word.
+ HRESULT hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(pEEDebuggerWord), &m_pdwTlsValue);
+ m_fValidTlsData = SUCCEEDED(hr);
+
+ if (!m_fValidTlsData)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "EEDW: failed to read debugger word: 0x%08x + 0x%x = 0x%p, err=%d\n",
+ m_pEEThread, GetProcess()->m_runtimeOffsets.m_EEThreadDebuggerWordOffset, pEEDebuggerWord, GetLastError()));
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CacheEEDW: Debugger word is 0x%p\n", m_pdwTlsValue));
+ }
+ }
+ else
+ {
+ m_fValidTlsData = TRUE;
+ m_pEEThread = NULL;
+ m_pdwTlsValue = NULL;
+ }
+
+ LOG((LF_CORDB, LL_INFO1000, "CacheEEDW: Exited\n"));
+}
+
+/*
+ * GetEEDebuggerWord
+ *
+ * This routine returns the value read from the thread
+ *
+ * Parameters:
+ * pValue - Location to store value.
+ *
+ * Returns:
+ * E_INVALIDARG, E_FAIL, S_OK
+ */
+HRESULT CordbUnmanagedThread::GetEEDebuggerWord(REMOTE_PTR *pValue)
+{
+ if (pValue == NULL)
+ {
+ return E_INVALIDARG;
+ }
+
+ CacheEEDebuggerWord();
+
+ if (!m_fValidTlsData)
+ {
+ *pValue = NULL;
+ return E_FAIL;
+ }
+
+ *pValue = m_pdwTlsValue;
+
+ return S_OK;
+}
+
+// SetEEDebuggerWord
+//
+// This routine writes the value to the thread
+//
+// Parameters:
+// pValue - Value to write.
+//
+// Returns:
+// HRESULT failure code or S_OK
+//
+// Notes:
+// This function is very dangerous. See code:CordbUnmanagedThread::SetEETlsValue for why.
+HRESULT CordbUnmanagedThread::SetEEDebuggerWord(REMOTE_PTR value)
+{
+ LOG((LF_CORDB, LL_INFO1000, "CUT::SEEDW: Entered - value is 0x%p\n", value));
+
+ CacheEEDebuggerWord();
+
+ if (!m_fValidTlsData)
+ {
+ return E_FAIL;
+ }
+
+ m_pdwTlsValue = value;
+
+ //
+ // If the thread is NULL, bit-or on a 1 and store that.
+ //
+ if (m_pEEThread == NULL)
+ {
+ REMOTE_PTR pdwTemp = m_pdwTlsValue;
+
+ if (pdwTemp != 0)
+ {
+ // actually we add 1, but we only use it for pointers which are
+ // 8 byte aligned so it is the same thing
+ _ASSERTE( ((UINT_PTR)pdwTemp & 0x1) == 0);
+ pdwTemp = (REMOTE_PTR) ((BYTE*)pdwTemp + 0x01);
+ }
+ // This will write to the TLS slot. It's only safe to do this after a Flare has been sent from the
+ // LS (since that's what guarantees the slot is allocated).
+ return SetEEThreadValue(pdwTemp);
+ }
+ else
+ {
+ // Compute the address of the debugger word #2.
+ void *pEEDebuggerWord = (BYTE*)m_pEEThread + GetProcess()->m_runtimeOffsets.m_EEThreadDebuggerWordOffset;
+
+ // Update the word.
+ HRESULT hr = GetProcess()->SafeWriteStruct(PTR_TO_CORDB_ADDRESS(pEEDebuggerWord), &m_pdwTlsValue);
+
+ if (FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::SEETDW: failed to write debugger word: 0x%08x + 0x%x = 0x%08x, err=%x\n",
+ m_pEEThread, GetProcess()->m_runtimeOffsets.m_EEThreadDebuggerWordOffset, pEEDebuggerWord, hr));
+
+ return hr;
+ }
+ }
+
+ LOG((LF_CORDB, LL_INFO1000, "CUT::SEEDW: Exited\n"));
+ return S_OK;
+}
+
+/*
+ * GetEEThreadPtr
+ *
+ * This routine returns the value read from the thread
+ *
+ * Parameters:
+ * ppEEThread - Location to store value.
+ *
+ * Returns:
+ * E_INVALIDARG, E_FAIL, S_OK
+ */
+HRESULT CordbUnmanagedThread::GetEEThreadPtr(REMOTE_PTR *ppEEThread)
+{
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+
+ if (ppEEThread == NULL)
+ {
+ return E_INVALIDARG;
+ }
+
+ CacheEEDebuggerWord();
+
+ if (!m_fValidTlsData)
+ {
+ *ppEEThread = NULL;
+ return E_FAIL;
+ }
+
+ *ppEEThread = m_pEEThread;
+
+ return S_OK;
+}
+
+
+
+void CordbUnmanagedThread::GetEEState(bool *threadStepping, bool *specialManagedException)
+{
+ REMOTE_PTR pEEThread;
+
+ HRESULT hr = GetEEThreadPtr(&pEEThread);
+
+ _ASSERTE(SUCCEEDED(hr));
+ _ASSERTE(pEEThread != NULL);
+
+ *threadStepping = false;
+ *specialManagedException = false;
+
+ // Compute the address of the thread's state
+ DebuggerIPCRuntimeOffsets *pRO = &(GetProcess()->m_runtimeOffsets);
+ void *pEEThreadStateNC = (BYTE*) pEEThread + pRO->m_EEThreadStateNCOffset;
+
+ // Grab the thread state out of the EE Thread.
+ DWORD EEThreadStateNC;
+ hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(pEEThreadStateNC), &EEThreadStateNC);
+
+ if (FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::GEETS: failed to read thread state NC: 0x%p + 0x%x = 0x%p, err=%d\n",
+ pEEThread, pRO->m_EEThreadStateNCOffset, pEEThreadStateNC, GetLastError()));
+
+ return;
+ }
+
+ LOG((LF_CORDB, LL_INFO1000000, "CUT::GEETS: EE Thread state NC is 0x%08x\n", EEThreadStateNC));
+
+ // Looks like we've got the state of the thread.
+ *threadStepping = ((EEThreadStateNC & pRO->m_EEThreadSteppingStateMask) != 0);
+ *specialManagedException = ((EEThreadStateNC & pRO->m_EEIsManagedExceptionStateMask) != 0);
+
+ return;
+}
+
+// Currently, the EE manually tracks its "can't-stop" regions. This retrieves that manual tracking value.
+// @todo - This should eventually become deprecated since the Entire EE will be a can't-stop region.
+bool CordbUnmanagedThread::GetEEThreadCantStopHelper()
+{
+ // Note: any failure to read memory is okay for this method. We simply say that the thread is not is a can't stop
+ // state, and that's okay.
+
+ REMOTE_PTR pEEThread;
+
+ HRESULT hr = GetEEThreadPtr(&pEEThread);
+
+ _ASSERTE(SUCCEEDED(hr));
+ _ASSERTE(pEEThread != NULL);
+
+ // Compute the address of the thread's debugger word #1
+ DebuggerIPCRuntimeOffsets *pRO = &(GetProcess()->m_runtimeOffsets);
+ void *pEEThreadCantStop = (BYTE*) pEEThread + pRO->m_EEThreadCantStopOffset;
+
+ // Grab the debugger word #1 out of the EE Thread.
+ DWORD EEThreadCantStop;
+ hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(pEEThreadCantStop), &EEThreadCantStop);
+
+ if (FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::GEETS: failed to read thread cant stop: 0x%08x + 0x%x = 0x%08x, err=%d\n",
+ pEEThread, pRO->m_EEThreadCantStopOffset, pEEThreadCantStop, GetLastError()));
+
+ return false;
+ }
+
+ LOG((LF_CORDB, LL_INFO1000000, "CUT::GEETS: EE Thread cant stop is 0x%08x\n", EEThreadCantStop));
+
+ // Looks like we've got it.
+ if (EEThreadCantStop != 0)
+ return true;
+ else
+ return false;
+}
+
+
+// Is the thread in a "can't stop" region?
+// "Can't-Stop" regions include anything that's "inside" the runtime; ie, the runtime has some
+// synchronization mechanism that will halt this thread, and so we don't need to suspend it.
+// The interop debugger should leave anything in a can't-stop region alone and just let the runtime
+// handle it.
+bool CordbUnmanagedThread::IsCantStop()
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ }
+ CONTRACTL_END;
+
+ // Definition of a can't stop region:
+ // - Any "Special" thread that doesn't have an EE Thread (includes the real Helper Thread,
+ // Concurrent GC thread, ThreadPool thread, etc).
+ // - Any thread in Cooperative code.
+ // - Any thread w/ a can't-stop count > 0.
+ // - Any thread holding a "Debugger" Crst. (This is actually a subset of the
+ // can't-stop count b/c Enter/Leave adjust that count).
+ // - Any generic, first chance or RaiseException hijacked thread
+
+ // If the runtime isn't init yet, not a can't-stop.
+ // We don't even have the DCB yet.
+ if (!GetProcess()->m_initialized)
+ {
+ return false;
+ }
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+
+ if(IsRaiseExceptionHijacked())
+ {
+ return true;
+ }
+
+ REMOTE_PTR pEEThread;
+ HRESULT hr = this->GetEEThreadPtr(&pEEThread);
+
+ if (FAILED(hr))
+ {
+ _ASSERTE(!"Failed to EEThreadPtr in IsCantStop");
+ return true;
+ }
+
+ DebuggerIPCRuntimeOffsets *pRO = &(GetProcess()->m_runtimeOffsets);
+
+ // @todo- remove this and use the CantStop index below.
+ // Is this a "special" thread?
+ // Any thread that can take CLR locks w/o having an EE Thread object should
+ // be marked as special. These threads are in "can't-stop" regions b/c if we suspend
+ // them, they may be holding a lock that blocks the helper thread.
+ // The helper thread is marked as "special".
+ {
+ SIZE_T idx = pRO->m_TLSIsSpecialIndex;
+ REMOTE_PTR special = GetPreDefTlsSlot(idx, NULL);
+
+ // If it's a special thread
+ if ((special != 0) && (pEEThread == NULL))
+ {
+ return true;
+ }
+ }
+
+ // Check for CantStop regions off the FLS.
+ // This is the biggest way to describe can't-stop regions when we're in preemptive mode
+ // (or when we don't have a thread object).
+ // If a LS thread takes a debugger lock, it will increment the Can't-Stop count.
+ {
+ SIZE_T idx = pRO->m_TLSCantStopIndex;
+ REMOTE_PTR count = (REMOTE_PTR) GetPreDefTlsSlot(idx, NULL);
+
+ // Just a sanity check here. There's nothing special about 1000, but if the
+ // stop-count gets this big, 99% chance it's:
+ // - we're accessing the wrong memory (an issue)
+ // - someone on the LS is leaking stop-counts. (an issue).
+ _ASSERTE(count < (REMOTE_PTR)1000);
+
+ if (count > 0)
+ {
+ LOG((LF_CORDB, LL_INFO1000000, "Thread 0x%x is can't-stop b/c count=%d\n", m_id, count));
+ return true;
+ }
+ }
+
+ EX_TRY
+ {
+ GetProcess()->UpdateRightSideDCB();
+ }
+ EX_CATCH
+ {
+ _ASSERTE(!"IsCantStop: Failed updating debugger control block");
+ }
+ EX_END_CATCH(SwallowAllExceptions);
+
+ // Helper's canary thread is always can't-stop.
+ if (this->m_id == GetProcess()->GetDCB()->m_CanaryThreadId)
+ {
+ return true;
+ }
+
+ // Check helper thread / or anyone pretending to be the helper thread.
+ if ((this->m_id == GetProcess()->GetDCB()->m_helperThreadId) ||
+ (this->m_id == GetProcess()->GetDCB()->m_temporaryHelperThreadId) ||
+ (this->m_id == GetProcess()->m_helperThreadId))
+ {
+ return true;
+ }
+
+ if (IsGenericHijacked() || IsFirstChanceHijacked())
+ return true;
+
+ // If this isn't a EE thread (and not the helper thread, and not hijacked), then it's ok to stop.
+ if (pEEThread == NULL)
+ return false;
+
+ // This checks for an explicit "can't" stop region.
+ // Eventually, these explicit regions should become a complete subset of the other checks.
+ if (GetEEThreadCantStopHelper())
+ return true;
+
+
+ // If we're in cooperative mode (either managed code or parts inside the runtime), then don't stop.
+ // Note we could remove this since the check is made in side of the DAC request below,
+ // but it's faster to look here.
+ if (GetEEPGCDisabled())
+ return true;
+
+ return false;
+}
+
+bool CordbUnmanagedThread::GetEEPGCDisabled()
+{
+ // Note: any failure to read memory is okay for this method. We simply say that the thread has PGC disabled, which
+ // is always the worst case scenario.
+
+ REMOTE_PTR pEEThread;
+
+ HRESULT hr = GetEEThreadPtr(&pEEThread);
+
+ _ASSERTE(SUCCEEDED(hr));
+
+ // Compute the address of the thread's PGC disabled word
+ DebuggerIPCRuntimeOffsets *pRO = &(GetProcess()->m_runtimeOffsets);
+ void *pEEThreadPGCDisabled = (BYTE*) pEEThread + pRO->m_EEThreadPGCDisabledOffset;
+
+ // Grab the PGC disabled word out of the EE Thread.
+ DWORD EEThreadPGCDisabled;
+ hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(pEEThreadPGCDisabled), &EEThreadPGCDisabled);
+
+ if (FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::GEETS: failed to read thread PGC Disabled: 0x%p + 0x%x = 0x%p, err=%d\n",
+ pEEThread, pRO->m_EEThreadPGCDisabledOffset, pEEThreadPGCDisabled, GetLastError()));
+
+ return true;
+ }
+
+ LOG((LF_CORDB, LL_INFO1000000, "CUT::GEETS: EE Thread PGC Disabled is 0x%08x\n", EEThreadPGCDisabled));
+
+ // Looks like we've got it.
+ if (EEThreadPGCDisabled == pRO->m_EEThreadPGCDisabledValue)
+ return true;
+ else
+ return false;
+}
+
+bool CordbUnmanagedThread::GetEEFrame()
+{
+ REMOTE_PTR pEEThread;
+
+ HRESULT hr = GetEEThreadPtr(&pEEThread);
+
+ _ASSERTE(SUCCEEDED(hr));
+ _ASSERTE(pEEThread != NULL);
+
+ // Compute the address of the thread's frame ptr
+ DebuggerIPCRuntimeOffsets *pRO = &(GetProcess()->m_runtimeOffsets);
+ void *pEEThreadFrame = (BYTE*) pEEThread + pRO->m_EEThreadFrameOffset;
+
+ // Grab the thread's frame out of the EE Thread.
+ DWORD EEThreadFrame;
+ hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(pEEThreadFrame), &EEThreadFrame);
+
+ if (FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::GEETF: failed to read thread frame: 0x%p + 0x%x = 0x%p, err=%d\n",
+ pEEThread, pRO->m_EEThreadFrameOffset, pEEThreadFrame, GetLastError()));
+
+ return false;
+ }
+
+ LOG((LF_CORDB, LL_INFO1000000, "CUT::GEETF: EE Thread's frame is 0x%08x\n", EEThreadFrame));
+
+ // Looks like we've got the frame of the thread.
+ if (EEThreadFrame != pRO->m_EEMaxFrameValue)
+ return true;
+ else
+ return false;
+}
+
+// Gets the thread context as if the thread were unhijacked, regardless
+// of whether it really is
+HRESULT CordbUnmanagedThread::GetThreadContext(DT_CONTEXT* pContext)
+{
+ // While hijacked there are 3 potential contexts we could be resuming back to
+ // 1) A context provided in SetThreadContext that we defered applying
+ // 2) The LS copy of the context on the stack being modified in the handler
+ // 3) The original context present when the hijack was started
+ //
+ // Both #1 and #3 are stored in the GetHijackCtx() space so of course you can't
+ // have them both. You have have #1 if IsContextSet() is true, otherwise it holds #3
+ //
+ // GenericHijack, FirstChanceHijackForSync, and RaiseExceptionHijack use #1 if available
+ // and fallback to #3 if not. In other words they use GetHijackCtx() regardless of which thing it holds
+ // M2UHandoff uses #1 if available and then falls back to #2.
+ //
+ // The reasoning here is that the first three hijacks are intended to be transparent. Since
+ // the debugger shouldn't know they are occuring then it shouldn't see changes potentially
+ // made on the LS. The M2UHandoff is not transparent, it has to update the context in order
+ // to get clear of a bp.
+ //
+ // If not hijacked call the normal Win32 function.
+
+ HRESULT hr = S_OK;
+
+ LOG((LF_CORDB, LL_INFO10000, "CUT::GTC: thread=0x%p, flags=0x%x.\n", this, pContext->ContextFlags));
+
+ if(IsContextSet() || IsGenericHijacked() || (IsFirstChanceHijacked() && IsBlockingForSync())
+ || IsRaiseExceptionHijacked())
+ {
+ _ASSERTE(IsFirstChanceHijacked() || IsGenericHijacked() || IsRaiseExceptionHijacked());
+ LOG((LF_CORDB, LL_INFO10000, "CUT::GTC: hijackCtx case IsContextSet=%d IsGenericHijacked=%d"
+ "HijackedForSync=%d RaiseExceptionHijacked=%d.\n",
+ IsContextSet(), IsGenericHijacked(), IsBlockingForSync(), IsRaiseExceptionHijacked()));
+ LOG((LF_CORDB, LL_INFO10000, "CUT::GTC: hijackCtx is:\n"));
+ LogContext(GetHijackCtx());
+ CORDbgCopyThreadContext(pContext, GetHijackCtx());
+ }
+ // use the LS for M2UHandoff
+ else if (IsFirstChanceHijacked() && !IsBlockingForSync())
+ {
+ LOG((LF_CORDB, LL_INFO10000, "CUT::GTC: getting LS context for first chance hijack, addr=0x%08x.\n",
+ m_pLeftSideContext.UnsafeGet()));
+
+ // Read the context into a temp context then copy to the out param.
+ DT_CONTEXT tempContext = { 0 };
+
+ hr = GetProcess()->SafeReadThreadContext(m_pLeftSideContext, &tempContext);
+
+ if (SUCCEEDED(hr))
+ CORDbgCopyThreadContext(pContext, &tempContext);
+ }
+ // no hijack in place so just call straight through
+ else
+ {
+ LOG((LF_CORDB, LL_INFO10000, "CUT::GTC: getting context from win32.\n"));
+
+ BOOL succ = DbiGetThreadContext(m_handle, pContext);
+
+ if (!succ)
+ hr = HRESULT_FROM_GetLastError();
+ }
+
+ if(IsSSFlagHidden())
+ {
+ UnsetSSFlag(pContext);
+ }
+ LogContext(pContext);
+
+ return hr;
+}
+
+// Sets the thread context as if the thread were unhijacked, regardless
+// of whether it really is. See GetThreadContext above for more details
+// on this abstraction
+HRESULT CordbUnmanagedThread::SetThreadContext(DT_CONTEXT* pContext)
+{
+ HRESULT hr = S_OK;
+
+ LOG((LF_CORDB, LL_INFO10000,
+ "CUT::STC: thread=0x%p, flags=0x%x.\n", this, pContext->ContextFlags));
+
+ LogContext(pContext);
+
+ // If the thread is first chance hijacked, then write the context into the remote process. If the thread is generic
+ // hijacked, then update the copy of the context that we already have. Otherwise call the normal Win32 function.
+
+ if (IsGenericHijacked() || IsFirstChanceHijacked() || IsRaiseExceptionHijacked())
+ {
+ if(IsGenericHijacked())
+ {
+ LOG((LF_CORDB, LL_INFO10000, "CUT::STC: setting context from generic/2nd chance hijack.\n"));
+ }
+ else if(IsFirstChanceHijacked())
+ {
+ LOG((LF_CORDB, LL_INFO10000, "CUT::STC: setting context from 1st chance hijack.\n"));
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO10000, "CUT::STC: setting context from RaiseException hijack.\n"));
+ }
+ SetState(CUTS_HasContextSet);
+ CORDbgCopyThreadContext(GetHijackCtx(), pContext);
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO10000, "CUT::STC: setting context from win32.\n"));
+
+ // If the user is also setting the SS flag then we no longer have to hide it
+ if(IsSSFlagEnabled(pContext))
+ {
+ ClearState(CUTS_IsSSFlagHidden);
+ }
+ // if the user is turning off the SS flag but we still want it on then leave it on
+ // but hidden
+ if(!IsSSFlagEnabled(pContext) && IsSSFlagNeeded())
+ {
+ SetState(CUTS_IsSSFlagHidden);
+ SetSSFlag(pContext);
+ }
+
+ BOOL succ = DbiSetThreadContext(m_handle, pContext);
+
+ if (!succ)
+ {
+ hr = HRESULT_FROM_GetLastError();
+ }
+ }
+
+ return hr;
+}
+
+// Turns on the stepping flag internally and tracks whether or not the flag
+// should also be seen by the user
+VOID CordbUnmanagedThread::BeginStepping()
+{
+ _ASSERTE(!IsGenericHijacked() && !IsFirstChanceHijacked());
+ _ASSERTE(!IsSSFlagNeeded());
+ _ASSERTE(!IsSSFlagHidden());
+
+ DT_CONTEXT tempContext;
+ tempContext.ContextFlags = DT_CONTEXT_FULL;
+ BOOL succ = DbiGetThreadContext(m_handle, &tempContext);
+ _ASSERTE(succ);
+
+ if(!IsSSFlagEnabled(&tempContext))
+ {
+ SetSSFlag(&tempContext);
+ SetState(CUTS_IsSSFlagHidden);
+ }
+ SetState(CUTS_IsSSFlagNeeded);
+
+ succ = DbiSetThreadContext(m_handle, &tempContext);
+ _ASSERTE(succ);
+}
+
+// Turns off the stepping flag internally. If the user was also not using it then
+// the flag is turned off on the context
+VOID CordbUnmanagedThread::EndStepping()
+{
+ _ASSERTE(!IsGenericHijacked() && !IsFirstChanceHijacked());
+ _ASSERTE(IsSSFlagNeeded());
+
+ DT_CONTEXT tempContext;
+ tempContext.ContextFlags = DT_CONTEXT_FULL;
+ BOOL succ = DbiGetThreadContext(m_handle, &tempContext);
+ _ASSERTE(succ);
+
+ if(IsSSFlagHidden())
+ {
+ UnsetSSFlag(&tempContext);
+ ClearState(CUTS_IsSSFlagHidden);
+ }
+ ClearState(CUTS_IsSSFlagNeeded);
+
+ succ = DbiSetThreadContext(m_handle, &tempContext);
+ _ASSERTE(succ);
+}
+
+
+// Writes some details of the given context into the debugger log
+VOID CordbUnmanagedThread::LogContext(DT_CONTEXT* pContext)
+{
+#if defined(DBG_TARGET_X86)
+ LOG((LF_CORDB, LL_INFO10000,
+ "CUT::LC: Eip=0x%08x, Esp=0x%08x, Eflags=0x%08x\n", pContext->Eip, pContext->Esp,
+ pContext->EFlags));
+#elif defined(DBG_TARGET_AMD64)
+ LOG((LF_CORDB, LL_INFO10000,
+ "CUT::LC: Rip=" FMT_ADDR ", Rsp=" FMT_ADDR ", Eflags=0x%08x\n",
+ DBG_ADDR(pContext->Rip),
+ DBG_ADDR(pContext->Rsp),
+ pContext->EFlags)); // EFlags is still 32bits on AMD64
+#else // DBG_TARGET_X86
+ PORTABILITY_ASSERT("LogContext needs a PC and stack pointer.");
+#endif // DBG_TARGET_X86
+}
+
+// Hijacks this thread using the FirstChanceSuspend hijack
+HRESULT CordbUnmanagedThread::SetupFirstChanceHijackForSync()
+{
+ HRESULT hr = S_OK;
+
+ CONSISTENCY_CHECK(!IsBlockingForSync()); // Shouldn't double hijack
+ CONSISTENCY_CHECK(!IsCantStop()); // must be in stoppable-region.
+ _ASSERTE(HasIBEvent());
+
+ // We used to hijack for real here but now we have a vectored exception handler that will always be
+ // triggered. So we don't have hijack in the sense that we overwrite the thread's IP. However we still
+ // set the flag so that when we receive the HijackStartedSignal from the LS we know that this thread
+ // should block in there rather than continuing.
+ //hr = SetupFirstChanceHijack(EHijackReason::kFirstChanceSuspend, &(IBEvent()->m_currentDebugEvent.u.Exception.ExceptionRecord));
+
+ _ASSERTE(!IsFirstChanceHijacked());
+ _ASSERTE(!IsGenericHijacked());
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+
+ // We'd better not be hijacking in a can't stop region!
+ // This also means we can't hijack in coopeative (since that's a can't-stop)
+ _ASSERTE(!IsCantStop());
+
+ // we should not be stepping into hijacks
+ _ASSERTE(!IsSSFlagHidden());
+ _ASSERTE(!IsSSFlagNeeded());
+ _ASSERTE(!IsContextSet());
+
+ // snapshot the current context so we can start spoofing it
+ LOG((LF_CORDB, LL_INFO10000, "CUT::SFCHFS: hijackCtx started as:\n"));
+ LogContext(GetHijackCtx());
+
+ // Save the thread's full context.
+ DT_CONTEXT context;
+ context.ContextFlags = DT_CONTEXT_FULL;
+ BOOL succ = DbiGetThreadContext(m_handle, &context);
+ _ASSERTE(succ);
+ // for debugging when GetThreadContext fails
+ if(!succ)
+ {
+ DWORD error = GetLastError();
+ LOG((LF_CORDB, LL_ERROR, "CUT::SFCHFS: DbiGetThreadContext error=0x%x\n", error));
+ }
+
+ GetHijackCtx()->ContextFlags = DT_CONTEXT_FULL;
+ CORDbgCopyThreadContext(GetHijackCtx(), &context);
+ LOG((LF_CORDB, LL_INFO10000, "CUT::SFCHFS: thread=0x%x Hijacking for sync. Original context is:\n", this));
+ LogContext(GetHijackCtx());
+
+ // We're hijacking now...
+ SetState(CUTS_FirstChanceHijacked);
+ GetProcess()->m_state |= CordbProcess::PS_HIJACKS_IN_PLACE;
+
+ // We'll decrement this once the hijack returns
+ GetProcess()->m_cFirstChanceHijackedThreads++;
+ this->SetState(CUTS_BlockingForSync);
+
+ // we don't want to single step into the vectored exception handler
+ // we will restore the SS flag after returning from the hijack
+ if(IsSSFlagEnabled(&context))
+ {
+ LOG((LF_CORDB, LL_INFO10000, "CUT::SFCHFS: thread=0x%x Clearing SS flag\n", this));
+ UnsetSSFlag(&context);
+ succ = DbiSetThreadContext(m_handle, &context);
+ _ASSERTE(succ);
+ }
+
+
+
+ // There's a bizarre race where the thread was suspended right as the thread was about to dispatch a
+ // debug event. We still get the debug event, and then may try to hijack. Resume the thread so that
+ // it can run to the hijack.
+ if (this->IsSuspended())
+ {
+ LOG((LF_CORDB, LL_ERROR, "CUT::SFCHFS: thread was suspended... resuming\n"));
+ DWORD success = ResumeThread(this->m_handle);
+
+ if (success == 0xFFFFFFFF)
+ {
+ // Since we suspended it, we should be able to resume it in this window.
+ CONSISTENCY_CHECK_MSGF(false, ("Failed to resume thread: tid=0x%x!", this->m_id));
+ }
+ else
+ {
+ this->ClearState(CUTS_Suspended);
+ }
+ }
+
+ return hr;
+
+}
+
+HRESULT CordbUnmanagedThread::SetupFirstChanceHijack(EHijackReason::EHijackReason reason, const EXCEPTION_RECORD * pExceptionRecord)
+{
+ _ASSERTE(!IsFirstChanceHijacked());
+ _ASSERTE(!IsGenericHijacked());
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+
+ // We'd better not be hijacking in a can't stop region!
+ // This also means we can't hijack in coopeative (since that's a can't-stop)
+ _ASSERTE(!IsCantStop());
+
+ // we should not be stepping into hijacks
+ _ASSERTE(!IsSSFlagHidden());
+ _ASSERTE(!IsSSFlagNeeded());
+
+ // There's a bizarre race where the thread was suspended right as the thread was about to dispatch a
+ // debug event. We still get the debug event, and then may try to hijack. Resume the thread so that
+ // it can run to the hijack.
+ if (this->IsSuspended())
+ {
+ DWORD succ = ResumeThread(this->m_handle);
+
+ if (succ == 0xFFFFFFFF)
+ {
+ // Since we suspended it, we should be able to resume it in this window.
+ CONSISTENCY_CHECK_MSGF(false, ("Failed to resume thread: tid=0x%x!", this->m_id));
+ }
+ else
+ {
+ this->ClearState(CUTS_Suspended);
+ }
+ }
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+// We save off the SEH handler on X86 to make sure we restore it properly after the hijack is complete
+// The hijacks don't return normally and the SEH chain might have handlers added that don't get removed by default
+#ifdef DBG_TARGET_X86
+ hr = SaveCurrentLeafSeh();
+ if(FAILED(hr))
+ ThrowHR(hr);
+#endif
+ CORDB_ADDRESS LSContextAddr;
+ GetProcess()->GetDAC()->Hijack(VMPTR_Thread::NullPtr(),
+ GetOSTid(),
+ pExceptionRecord,
+ (CONTEXT*) GetHijackCtx(),
+ sizeof(CONTEXT),
+ reason,
+ NULL,
+ &LSContextAddr);
+ LOG((LF_CORDB, LL_INFO10000, "CUT::SFCH: pLeftSideContext=0x%p\n", LSContextAddr));
+ m_pLeftSideContext.Set(CORDB_ADDRESS_TO_PTR(LSContextAddr));
+ }
+ EX_CATCH_HRESULT(hr);
+ if(FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO10000, "CUT::SFCH: Error setting up hijack context hr=0x%x\n", hr));
+ return hr;
+ }
+
+
+ // We're hijacked now...
+ SetState(CUTS_FirstChanceHijacked);
+ GetProcess()->m_state |= CordbProcess::PS_HIJACKS_IN_PLACE;
+
+ // We'll decrement this once the hijack returns
+ GetProcess()->m_cFirstChanceHijackedThreads++;
+
+ return S_OK;
+}
+
+HRESULT CordbUnmanagedThread::SetupGenericHijack(DWORD eventCode, const EXCEPTION_RECORD * pRecord)
+{
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+
+ _ASSERTE(eventCode == EXCEPTION_DEBUG_EVENT);
+
+ _ASSERTE(!IsFirstChanceHijacked());
+ _ASSERTE(!IsGenericHijacked());
+ _ASSERTE(!IsContextSet());
+
+ // Save the thread's full context.
+ GetHijackCtx()->ContextFlags = DT_CONTEXT_FULL;
+
+ BOOL succ = DbiGetThreadContext(m_handle, GetHijackCtx());
+
+ if (!succ)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::SGH: couldn't get thread context: %d\n", GetLastError()));
+ return HRESULT_FROM_WIN32(GetLastError());
+ }
+
+#if defined(DBG_TARGET_AMD64)
+
+ // On X86 Debugger::GenericHijackFunc() ensures the stack is walkable
+ // by simply using the EBP chain, therefore we can execute the hijack
+ // by setting the thread's context EIP to point to this function.
+ // On X64, however, we first attempt to set up a "proper" hijack, with
+ // a function that allows the OS to unwind the stack (ExceptionHijack).
+ // If this fails we'll use the same method as on X86, even though the
+ // stack will become un-walkable
+
+ ULONG32 dwThreadId = GetOSTid();
+ CordbThread * pThread = GetProcess()->TryLookupOrCreateThreadByVolatileOSId(dwThreadId);
+
+ // For threads in the thread store we set up the full size
+ // hijack, otherwise we fallback to hijacking by SetIP.
+ if (pThread != NULL)
+ {
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // Note that the data-target is not atomic, and we have no rollback mechanism.
+ // We have to do several writes. If the data-target fails the writes half-way through the
+ // target will be inconsistent.
+ GetProcess()->GetDAC()->Hijack(
+ pThread->m_vmThreadToken,
+ dwThreadId,
+ pRecord,
+ (CONTEXT*) GetHijackCtx(),
+ sizeof(CONTEXT),
+ EHijackReason::kGenericHijack,
+ NULL,
+ NULL);
+ }
+ EX_CATCH_HRESULT(hr);
+ if (SUCCEEDED(hr))
+ {
+ // Remember that we've hijacked the thread.
+ SetState(CUTS_GenericHijacked);
+
+ return S_OK;
+ }
+
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CUT::SGH: Error setting up hijack context hr=0x%x\n", hr);
+ // fallthrough (above hijack might have failed due to stack overflow, for example)
+
+ }
+ // else (non-threadstore threads) fallthrough
+
+#endif // DBG_TARGET_AMD64
+
+ // Remember that we've hijacked the guy.
+ SetState(CUTS_GenericHijacked);
+
+ LOG((LF_CORDB, LL_INFO1000000, "CUT::SGH: Current IP is 0x%08x\n", CORDbgGetIP(GetHijackCtx())));
+
+ DebuggerIPCRuntimeOffsets *pRO = &(GetProcess()->m_runtimeOffsets);
+
+ // Wack the IP over to our generic hijack function.
+ LPVOID holdIP = CORDbgGetIP(GetHijackCtx());
+ CORDbgSetIP(GetHijackCtx(), pRO->m_genericHijackFuncAddr);
+
+ LOG((LF_CORDB, LL_INFO1000000, "CUT::SGH: New IP is 0x%08x\n", CORDbgGetIP(GetHijackCtx())));
+
+ // We should never single step into the hijack
+ BOOL isSSFlagOn = IsSSFlagEnabled(GetHijackCtx());
+ if(isSSFlagOn)
+ {
+ UnsetSSFlag(GetHijackCtx());
+ }
+
+ succ = DbiSetThreadContext(m_handle, GetHijackCtx());
+
+ if (!succ)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::SGH: couldn't set thread context: %d\n", GetLastError()));
+
+ return HRESULT_FROM_WIN32(GetLastError());
+ }
+
+ // Put the original IP back into the local context copy for later.
+ CORDbgSetIP(GetHijackCtx(), holdIP);
+ // Set the original SS flag into the local context copy for later
+ if(isSSFlagOn)
+ {
+ SetSSFlag(GetHijackCtx());
+ }
+ return S_OK;
+}
+
+HRESULT CordbUnmanagedThread::FixupFromGenericHijack()
+{
+ LOG((LF_CORDB, LL_INFO1000, "CUT::FFGH: fixing up from generic hijack. Eip=0x%p, Esp=0x%p\n",
+ CORDbgGetIP(GetHijackCtx()), CORDbgGetSP(GetHijackCtx())));
+
+ // We're no longer hijacked
+ _ASSERTE(IsGenericHijacked());
+ ClearState(CUTS_GenericHijacked);
+
+ // Clear the exception so we do a DBG_CONTINUE with the original context. Note: we only do generic hijacks on
+ // in-band events.
+ IBEvent()->SetState(CUES_ExceptionCleared);
+
+ // Using the context we saved when the event came in originally or the new context if set by user,
+ // reset the thread as if it were never hijacked.
+ BOOL succ = DbiSetThreadContext(m_handle, GetHijackCtx());
+ // if the user set the context it has been applied now
+ ClearState(CUTS_HasContextSet);
+
+ if (!succ)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CUT::FFGH: couldn't set thread context: %d\n", GetLastError()));
+
+ return HRESULT_FROM_WIN32(GetLastError());
+ }
+
+ return S_OK;
+}
+
+DT_CONTEXT * CordbUnmanagedThread::GetHijackCtx()
+{
+ return &m_context;
+}
+
+
+// Enable Single-Step (and bump the eip back one)
+// This can only be called after a bp. (because we assume that we executed a bp when we adjust the eip).
+HRESULT CordbUnmanagedThread::EnableSSAfterBP()
+{
+ DT_CONTEXT c;
+ c.ContextFlags = DT_CONTEXT_FULL;
+
+ BOOL succ = DbiGetThreadContext(m_handle, &c);
+
+ if (!succ)
+ return HRESULT_FROM_WIN32(GetLastError());
+
+ SetSSFlag(&c);
+
+ // Backup IP to point to the instruction we need to execute. Continuing from a breakpoint exception
+ // continues execution at the instruction after the breakpoint, but we need to continue where the
+ // breakpoint was.
+ CORDbgAdjustPCForBreakInstruction(&c);
+
+ succ = DbiSetThreadContext(m_handle, &c);
+
+ if (!succ)
+ {
+ return HRESULT_FROM_WIN32(GetLastError());
+ }
+
+ return S_OK;
+}
+
+//
+// FixupAfterOOBException automatically gets the debuggee past an OOB exception event. These are only BP or SS
+// events. For SS, we just clear it, assuming that the only reason the thread was stepped in such place was to get it
+// off of a BP. For a BP, we clear and backup the IP by one, and turn the trace flag on under the assumption that the
+// only thing a debugger is allowed to do with an OOB BP exception is to get us off of it.
+//
+HRESULT CordbUnmanagedThread::FixupAfterOOBException(CordbUnmanagedEvent *ue)
+{
+ // We really should only be doing things to single steps and breakpoint exceptions.
+ if (ue->m_currentDebugEvent.dwDebugEventCode == EXCEPTION_DEBUG_EVENT)
+ {
+ DWORD ec = ue->m_currentDebugEvent.u.Exception.ExceptionRecord.ExceptionCode;
+
+ if ((ec == STATUS_BREAKPOINT) || (ec == STATUS_SINGLE_STEP))
+ {
+ // Automatically clear the exception.
+ ue->SetState(CUES_ExceptionCleared);
+
+ // Don't bother about toggling the single-step flag. OOB BPs should only be called
+ // for raw int3 instructions, so no need to rewind and reexecute.
+ }
+ }
+
+ return S_OK;
+}
+
+
+//-----------------------------------------------------------------------------
+// Setup to skip an native breakpoint
+//-----------------------------------------------------------------------------
+void CordbUnmanagedThread::SetupForSkipBreakpoint(NativePatch * pNativePatch)
+{
+ _ASSERTE(pNativePatch != NULL);
+ _ASSERTE(!IsSkippingNativePatch());
+ _ASSERTE(m_pPatchSkipAddress == NULL);
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+
+ SetState(CUTS_SkippingNativePatch);
+
+#ifdef _DEBUG
+ // For debugging, provide a way that Cordbg devs can see if we're silently skipping BPs.
+ static DWORD fTrapOnSkip = -1;
+ if (fTrapOnSkip == -1)
+ fTrapOnSkip = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgTrapOnSkip);
+
+ if (fTrapOnSkip)
+ {
+ CONSISTENCY_CHECK_MSGF(false, ("The CLR is skipping a native BP at %p on thread 0x%x (%d)."
+ "\nYou're getting this notification in debug builds b/c you have com+ var 'DbgTrapOnSkip' enabled.",
+ pNativePatch->pAddress, this->m_id, this->m_id));
+
+ // We skipped this BP b/c IsCantStop was true. For debugging convenience, call IsCantStop here
+ // (in case we break at the assert above and want to trace why we're in a CS region)
+ bool fCantStop = this->IsCantStop();
+ LOG((LF_CORDB, LL_INFO1000, "In Can'tStopRegion = %d\n", fCantStop));
+
+ // Refresh the reg key
+ fTrapOnSkip = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgTrapOnSkip);
+ }
+#endif
+#if defined(DBG_TARGET_X86)
+ STRESS_LOG2(LF_CORDB, LL_INFO100, "CUT::SetupSkip. adddr=%p. Opcode=%x\n", pNativePatch->pAddress, (DWORD) pNativePatch->opcode);
+#endif
+
+ // Replace the BP w/ the opcode.
+ RemoveRemotePatch(GetProcess(), pNativePatch->pAddress, pNativePatch->opcode);
+
+ // Enable the SS flag & Adjust IP.
+ HRESULT hr = this->EnableSSAfterBP();
+ SIMPLIFYING_ASSUMPTION(SUCCEEDED(hr));
+
+
+ // Now we return,
+ // Process continues, LS will single step past BP, and fire a SS exception.
+ // When we get the SS, we res
+
+
+ // We need to remember this so we can make sure we fixup at the proper address.
+ // The address of a ss exception is the instruction we finish on, not where
+ // we originally placed the BP. Since instructions can be variable length,
+ // we can't work backwards.
+ m_pPatchSkipAddress = pNativePatch->pAddress;
+}
+
+//-----------------------------------------------------------------------------
+// Second half of skipping a native bp.
+// Note we pass the address in b/c our caller has (from the debug_evet), and
+// we don't want to waste storage to remember it ourselves.
+//-----------------------------------------------------------------------------
+void CordbUnmanagedThread::FixupForSkipBreakpoint()
+{
+ _ASSERTE(m_pPatchSkipAddress != NULL);
+ _ASSERTE(IsSkippingNativePatch());
+ _ASSERTE(GetProcess()->ThreadHoldsProcessLock());
+
+ ClearState(CUTS_SkippingNativePatch);
+
+ // Only reapply the int3 if it hasn't been removed yet.
+ if (GetProcess()->GetNativePatch(m_pPatchSkipAddress) != NULL)
+ {
+ ApplyRemotePatch(GetProcess(), m_pPatchSkipAddress);
+ STRESS_LOG1(LF_CORDB, LL_INFO100, "CUT::FixupSetupSkip. adddr=%p\n", m_pPatchSkipAddress);
+ }
+ else
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO100, "CUT::FixupSetupSkip. Patch removed. Not-readding. adddr=%p\n", m_pPatchSkipAddress);
+ }
+
+ m_pPatchSkipAddress = NULL;
+}
+
+inline TADDR GetSP(DT_CONTEXT* context)
+{
+#if defined(DBG_TARGET_X86)
+ return (TADDR)context->Esp;
+#elif defined(DBG_TARGET_AMD64)
+ return (TADDR)context->Rsp;
+#elif defined(DBG_TARGET_ARM) || defined(DBG_TARGET_ARM64)
+ return (TADDR)context->Sp;
+#else
+ _ASSERTE(!"nyi for platform");
+#endif
+}
+
+BOOL CordbUnmanagedThread::GetStackRange(CORDB_ADDRESS *pBase, CORDB_ADDRESS *pLimit)
+{
+#if !defined(FEATURE_DBGIPC_TRANSPORT)
+
+ if (m_stackBase == 0 && m_stackLimit == 0)
+ {
+ HANDLE hProc;
+ DT_CONTEXT tempContext;
+ MEMORY_BASIC_INFORMATION mbi;
+
+ tempContext.ContextFlags = DT_CONTEXT_FULL;
+ if (SUCCEEDED(GetProcess()->GetHandle(&hProc)) &&
+ SUCCEEDED(GetThreadContext(&tempContext)) &&
+ ::VirtualQueryEx(hProc, (LPCVOID)GetSP(&tempContext), &mbi, sizeof(mbi)) != 0)
+ {
+ // the lowest stack address is the AllocationBase
+ TADDR limit = PTR_TO_TADDR(mbi.AllocationBase);
+
+ // Now, on to find the stack base:
+ // Closest to the AllocationBase we might have a MEM_RESERVED block
+ // for all the as yet unallocated pages...
+ TADDR regionBase = limit;
+ if (::VirtualQueryEx(hProc, (LPCVOID) regionBase, &mbi, sizeof(mbi)) == 0
+ || mbi.Type != MEM_PRIVATE)
+ goto Exit;
+
+ if (mbi.State == MEM_RESERVE)
+ regionBase += mbi.RegionSize;
+
+ // Next we might have a few guard pages
+ if (::VirtualQueryEx(hProc, (LPCVOID) regionBase, &mbi, sizeof(mbi)) == 0
+ || mbi.Type != MEM_PRIVATE)
+ goto Exit;
+
+ if (mbi.State == MEM_COMMIT && (mbi.Protect & PAGE_GUARD) != 0)
+ regionBase += mbi.RegionSize;
+
+ // And finally the "regular" stack region
+ if (::VirtualQueryEx(hProc, (LPCVOID) regionBase, &mbi, sizeof(mbi)) == 0
+ || mbi.Type != MEM_PRIVATE)
+ goto Exit;
+
+ if (mbi.State == MEM_COMMIT && (mbi.Protect & PAGE_READWRITE) != 0)
+ regionBase += mbi.RegionSize;
+
+ if (limit == regionBase)
+ goto Exit;
+
+ m_stackLimit = limit;
+ m_stackBase = regionBase;
+ }
+ }
+
+Exit:
+ if (pBase != NULL)
+ *pBase = m_stackBase;
+ if (pLimit != NULL)
+ *pLimit = m_stackLimit;
+
+ return (m_stackBase != 0 || m_stackLimit != 0);
+
+#else
+
+ if (pBase != NULL)
+ *pBase = 0;
+ if (pLimit != NULL)
+ *pLimit = 0;
+
+ return FALSE;
+
+#endif // FEATURE_DBGIPC_TRANSPORT
+}
+
+//-----------------------------------------------------------------------------
+// Returns the thread context to the state it was in when it last entered RaiseException
+// This allows the thread to retrigger an exception caused by RaiseException
+//-----------------------------------------------------------------------------
+void CordbUnmanagedThread::HijackToRaiseException()
+{
+ LOG((LF_CORDB, LL_INFO1000, "CP::HTRE: hijacking to RaiseException\n"));
+ _ASSERTE(HasRaiseExceptionEntryCtx());
+ _ASSERTE(!IsRaiseExceptionHijacked());
+ _ASSERTE(!IsGenericHijacked());
+ _ASSERTE(!IsFirstChanceHijacked());
+ _ASSERTE(!IsContextSet());
+
+ BOOL succ = DbiGetThreadContext(m_handle, GetHijackCtx());
+ _ASSERTE(succ);
+ succ = DbiSetThreadContext(m_handle, &m_raiseExceptionEntryContext);
+ _ASSERTE(succ);
+ SetState(CUTS_IsRaiseExceptionHijacked);
+}
+
+//----------------------------------------------------------------------------
+// Returns the context to its unhijacked state.
+//----------------------------------------------------------------------------
+void CordbUnmanagedThread::RestoreFromRaiseExceptionHijack()
+{
+ LOG((LF_CORDB, LL_INFO1000, "CP::RFREH: ending RaiseException hijack\n"));
+ _ASSERTE(IsRaiseExceptionHijacked());
+
+ DT_CONTEXT restoreContext;
+ restoreContext.ContextFlags = DT_CONTEXT_FULL;
+ HRESULT hr = GetThreadContext(&restoreContext);
+ _ASSERTE(SUCCEEDED(hr));
+
+ ClearState(CUTS_IsRaiseExceptionHijacked);
+ hr = SetThreadContext(&restoreContext);
+ _ASSERTE(SUCCEEDED(hr));
+}
+
+//-----------------------------------------------------------------------------
+// Attempts to store the state of a thread currently entering RaiseException
+// This grabs both a full context and enough state to determine what exception
+// RaiseException should be raising. If any of the state can not be retrieved
+// then this entrance to RaiseException is silently ignored
+//-----------------------------------------------------------------------------
+void CordbUnmanagedThread::SaveRaiseExceptionEntryContext()
+{
+ _ASSERTE(FALSE); // should be unused now
+ LOG((LF_CORDB, LL_INFO1000, "CP::SREEC: saving raise exception context.\n"));
+ _ASSERTE(!HasRaiseExceptionEntryCtx());
+ _ASSERTE(!IsRaiseExceptionHijacked());
+ HRESULT hr = S_OK;
+ DT_CONTEXT context;
+ context.ContextFlags = DT_CONTEXT_FULL;
+ DbiGetThreadContext(m_handle, &context);
+ // if the flag is set, unset it
+ // we don't want to be single stepping through RaiseException the second time
+ // sending out OOB SS events. Ultimately we will rethrow the exception which would
+ // cleared the SS flag anyways.
+ UnsetSSFlag(&context);
+ memcpy(&m_raiseExceptionEntryContext, &context, sizeof(DT_CONTEXT));
+
+ // calculate the exception that we would expect to come from this invocation of RaiseException
+ REMOTE_PTR pExceptionInformation = NULL;
+#if defined(DBG_TARGET_AMD64)
+ m_raiseExceptionExceptionCode = (DWORD)m_raiseExceptionEntryContext.Rcx;
+ m_raiseExceptionExceptionFlags = (DWORD)m_raiseExceptionEntryContext.Rdx;
+ m_raiseExceptionNumberParameters = (DWORD)m_raiseExceptionEntryContext.R8;
+ pExceptionInformation = (REMOTE_PTR)m_raiseExceptionEntryContext.R9;
+#elif defined(DBG_TARGET_X86)
+ hr = m_pProcess->SafeReadStruct(PTR_TO_CORDB_ADDRESS((BYTE*)m_raiseExceptionEntryContext.Esp+4), &m_raiseExceptionExceptionCode);
+ if(FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::SREEC: failed to read exception code.\n"));
+ return;
+ }
+ hr = m_pProcess->SafeReadStruct(PTR_TO_CORDB_ADDRESS((BYTE*)m_raiseExceptionEntryContext.Esp+8), &m_raiseExceptionExceptionFlags);
+ if(FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::SREEC: failed to read exception flags.\n"));
+ return;
+ }
+ hr = m_pProcess->SafeReadStruct(PTR_TO_CORDB_ADDRESS((BYTE*)m_raiseExceptionEntryContext.Esp+12), &m_raiseExceptionNumberParameters);
+ if(FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::SREEC: failed to read number of parameters.\n"));
+ return;
+ }
+ hr = m_pProcess->SafeReadStruct(PTR_TO_CORDB_ADDRESS((BYTE*)m_raiseExceptionEntryContext.Esp+16), &pExceptionInformation);
+ if(FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::SREEC: failed to read exception information pointer.\n"));
+ return;
+ }
+#elif
+ _ASSERTE(!"Implement this for your platform");
+ return;
+#endif
+ LOG((LF_CORDB, LL_INFO1000, "CP::SREEC: RaiseException parameters are 0x%x 0x%x 0x%x 0x%p.\n",
+ m_raiseExceptionExceptionCode, m_raiseExceptionExceptionFlags,
+ m_raiseExceptionNumberParameters, pExceptionInformation));
+ TargetBuffer exceptionInfoTargetBuffer(pExceptionInformation, sizeof(REMOTE_PTR)*m_raiseExceptionNumberParameters);
+ EX_TRY
+ {
+ m_pProcess->SafeReadBuffer(exceptionInfoTargetBuffer, (BYTE*)m_raiseExceptionExceptionInformation);
+ }
+ EX_CATCH_HRESULT(hr);
+ if(FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::SREEC: failed to read exception information.\n"));
+ return;
+ }
+
+ // If everything was succesful then set this flag, otherwise none of the above data is considered valid
+ SetState(CUTS_HasRaiseExceptionEntryCtx);
+ return;
+}
+
+//-----------------------------------------------------------------------------
+// Clears all the state saved in SaveRaiseExceptionContext and returns the thread
+// to the state as if RaiseException has yet to be called. This is typically called
+// after an exception retriggers or after determining that the exception never will
+// retrigger.
+//-----------------------------------------------------------------------------
+void CordbUnmanagedThread::ClearRaiseExceptionEntryContext()
+{
+ _ASSERTE(FALSE); // should be unused now
+ LOG((LF_CORDB, LL_INFO1000, "CP::CREEC: clearing raise exception context.\n"));
+ _ASSERTE(HasRaiseExceptionEntryCtx());
+ ClearState(CUTS_HasRaiseExceptionEntryCtx);
+}
+
+//-----------------------------------------------------------------------------
+// Uses a heuristic to determine if the given exception record is likely to be the exception
+// raised by the last invocation of RaiseException on this thread. The current heuristic compares
+// ExceptionCode, ExceptionFlags, and all ExceptionInformation.
+//-----------------------------------------------------------------------------
+BOOL CordbUnmanagedThread::IsExceptionFromLastRaiseException(const EXCEPTION_RECORD* pExceptionRecord)
+{
+ _ASSERTE(FALSE); // should be unused now
+ if(!HasRaiseExceptionEntryCtx())
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::IEFLRE: not a match - no previous raise context\n"));
+ return FALSE;
+ }
+
+ if (pExceptionRecord->ExceptionCode != m_raiseExceptionExceptionCode)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::IEFLRE: not a match - exception codes differ 0x%x 0x%x\n",
+ pExceptionRecord->ExceptionCode, m_raiseExceptionExceptionCode));
+ return FALSE;
+ }
+
+ if (pExceptionRecord->ExceptionFlags != m_raiseExceptionExceptionFlags)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::IEFLRE: not a match - exception flags differ 0x%x 0x%x\n",
+ pExceptionRecord->ExceptionFlags, m_raiseExceptionExceptionFlags));
+ return FALSE;
+ }
+
+ if (pExceptionRecord->NumberParameters != m_raiseExceptionNumberParameters)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::IEFLRE: not a match - number parameters differ 0x%x 0x%x\n",
+ pExceptionRecord->NumberParameters, m_raiseExceptionNumberParameters));
+ return FALSE;
+ }
+
+ for(DWORD i = 0; i < pExceptionRecord->NumberParameters; i++)
+ {
+ if(m_raiseExceptionExceptionInformation[i] != pExceptionRecord->ExceptionInformation[i])
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::IEFLRE: not a match - param %d differs 0x%x 0x%x\n",
+ i, pExceptionRecord->ExceptionInformation[i], m_raiseExceptionExceptionInformation[i]));
+ return FALSE;
+ }
+ }
+
+ LOG((LF_CORDB, LL_INFO1000, "CP::IEFLRE: match\n"));
+ return TRUE;
+}
+
+
+//-----------------------------------------------------------------------------
+// Inject an int3 at the given remote address
+//-----------------------------------------------------------------------------
+
+// This flavor is assuming our caller already knows the opcode.
+HRESULT ApplyRemotePatch(CordbProcess * pProcess, const void * pRemoteAddress)
+{
+#if defined(DBG_TARGET_X86) || defined(DBG_TARGET_AMD64)
+ const BYTE patch = CORDbg_BREAK_INSTRUCTION;
+ HRESULT hr = pProcess->SafeWriteStruct(PTR_TO_CORDB_ADDRESS(pRemoteAddress), &patch);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+#else
+ PORTABILITY_ASSERT("NYI: ApplyRemotePatch for this platform");
+#endif
+ return S_OK;
+}
+
+
+// Get the opcode that we're replacing.
+HRESULT ApplyRemotePatch(CordbProcess * pProcess, const void * pRemoteAddress, PRD_TYPE * pOpcode)
+{
+#if defined(DBG_TARGET_X86) || defined(DBG_TARGET_AMD64)
+ // Read out opcode. 1 byte on x86
+ BYTE opcode;
+
+ HRESULT hr = pProcess->SafeReadStruct(PTR_TO_CORDB_ADDRESS(pRemoteAddress), &opcode);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ *pOpcode = (PRD_TYPE) opcode;
+#else
+ PORTABILITY_ASSERT("NYI: ApplyRemotePatch for this platform");
+#endif
+ ApplyRemotePatch(pProcess, pRemoteAddress);
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Remove the int3 from the remote address
+//-----------------------------------------------------------------------------
+HRESULT RemoveRemotePatch(CordbProcess * pProcess, const void * pRemoteAddress, PRD_TYPE opcode)
+{
+#if defined(DBG_TARGET_X86) || defined(DBG_TARGET_AMD64)
+ // Replace the BP w/ the opcode.
+ BYTE opcode2 = (BYTE) opcode;
+
+ pProcess->SafeWriteStruct(PTR_TO_CORDB_ADDRESS(pRemoteAddress), &opcode2);
+
+ // This may fail because the module has been unloaded. In which case, the patch is also
+ // gone so it makes sense to return success.
+#else
+ PORTABILITY_ASSERT("NYI: RemoveRemotePatch for this platform");
+#endif
+ return S_OK;
+}
+#endif // FEATURE_INTEROP_DEBUGGING
+
+//---------------------------------------------------------------------------------------
+//
+// Simple helper to return the SP value stored in a DebuggerREGDISPLAY.
+//
+// Arguments:
+// pDRD - the DebuggerREGDISPLAY in question
+//
+// Return Value:
+// the SP value
+//
+
+inline CORDB_ADDRESS GetSPFromDebuggerREGDISPLAY(DebuggerREGDISPLAY* pDRD)
+{
+ return pDRD->SP;
+}
+
+
+HRESULT CordbContext::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugContext)
+ *pInterface = static_cast<ICorDebugContext*>(this);
+ else if (id == IID_IUnknown)
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugContext*>(this));
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+
+/* ------------------------------------------------------------------------- *
+ * Frame class
+ * ------------------------------------------------------------------------- */
+
+
+// This is just used as a proxy object to pass a FramePointer around.
+CordbFrame::CordbFrame(CordbProcess * pProcess, FramePointer fp)
+ : CordbBase(pProcess, 0, enumCordbFrame),
+ m_fp(fp)
+{
+ UnsafeNeuterDeadObject(); // mark as neutered.
+}
+
+
+CordbFrame::CordbFrame(CordbThread * pThread,
+ FramePointer fp,
+ SIZE_T ip,
+ CordbAppDomain * pCurrentAppDomain)
+ : CordbBase(pThread->GetProcess(), 0, enumCordbFrame),
+ m_ip(ip),
+ m_pThread(pThread),
+ m_currentAppDomain(pCurrentAppDomain),
+ m_fp(fp)
+{
+#ifdef _DEBUG
+ // For debugging purposes, track what Continue session these frames were created in.
+ m_DbgContinueCounter = GetProcess()->m_continueCounter;
+#endif
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ m_pThread->GetRefreshStackNeuterList()->Add(GetProcess(), this);
+ }
+ EX_CATCH_HRESULT(hr);
+ SetUnrecoverableIfFailed(GetProcess(), hr);
+}
+
+
+CordbFrame::~CordbFrame()
+{
+ _ASSERTE(IsNeutered());
+}
+
+// Neutered by DerivedClasses
+void CordbFrame::Neuter()
+{
+ CordbBase::Neuter();
+}
+
+
+HRESULT CordbFrame::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugFrame)
+ *pInterface = static_cast<ICorDebugFrame*>(this);
+ else if (id == IID_IUnknown)
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugFrame*>(this));
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+// ----------------------------------------------------------------------------
+// CordbFrame::GetChain
+//
+// Description:
+// Return the owning chain. Since chains have been deprecated in Arrowhead,
+// this function returns E_NOTIMPL unless there is a shim.
+//
+// Arguments:
+// * ppChain - out parameter; return the owning chain
+//
+// Return Value:
+// Return S_OK on success.
+// Return E_INVALIDARG if ppChain is NULL.
+// Return CORDBG_E_OBJECT_NEUTERED if the CordbFrame is neutered.
+// Return E_NOTIMPL if there is no shim.
+// Return E_FAIL if failed to find the chain
+//
+
+HRESULT CordbFrame::GetChain(ICorDebugChain **ppChain)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_REENTRANT_API_BEGIN(this)
+ {
+ ValidateOrThrow(ppChain);
+ *ppChain = NULL;
+
+ if (GetProcess()->GetShim() != NULL)
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE0(GetProcess(), GET_PUBLIC_LOCK_HOLDER());
+ ShimStackWalk * pSSW = GetProcess()->GetShim()->LookupOrCreateShimStackWalk(m_pThread);
+ pSSW->GetChainForFrame(static_cast<ICorDebugFrame *>(this), ppChain);
+
+ if (*ppChain == NULL)
+ hr = E_FAIL;
+ }
+ else
+ {
+ // This is the Arrowhead case, where ICDChain has been deprecated.
+ hr = E_NOTIMPL;
+ }
+ }
+ PUBLIC_REENTRANT_API_END(hr);
+ return hr;
+}
+
+
+// Return the stack range taken up by this frame.
+// Note that this is not implemented in the base CordbFrame class.
+// Instead, this is implemented by the derived classes.
+// The start of the stack range is the leafmost boundary, and the end is the rootmost boundary.
+//
+// Notes: see code:#GetStackRange
+HRESULT CordbFrame::GetStackRange(CORDB_ADDRESS *pStart, CORDB_ADDRESS *pEnd)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_REENTRANT_API_BEGIN(this)
+ {
+ ValidateOrThrow(pStart);
+ ValidateOrThrow(pEnd);
+
+ hr = E_NOTIMPL;
+ }
+ PUBLIC_REENTRANT_API_END(hr);
+ return hr;
+}
+
+// Return the ICorDebugFunction associated with this frame.
+// There is one ICorDebugFunction for each EnC version of a method.
+HRESULT CordbFrame::GetFunction(ICorDebugFunction **ppFunction)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_REENTRANT_API_BEGIN(this)
+ {
+ ValidateOrThrow(ppFunction);
+
+ CordbFunction * pFunc = this->GetFunction();
+
+ if (pFunc == NULL)
+ {
+ ThrowHR(CORDBG_E_CODE_NOT_AVAILABLE);
+ }
+
+ // @dbgtodo LCG methods, IL stubs, dynamic language debugging
+ // Don't return an ICDFunction if we are dealing with a dynamic method.
+ // The dynamic debugging feature crew needs to decide exactly what to hand out for dynamic methods.
+ if (pFunc->GetMetadataToken() == mdMethodDefNil)
+ {
+ ThrowHR(CORDBG_E_CODE_NOT_AVAILABLE);
+ }
+
+ *ppFunction = static_cast<ICorDebugFunction *>(pFunc);
+ pFunc->ExternalAddRef();
+ }
+ PUBLIC_REENTRANT_API_END(hr);
+ return hr;
+}
+
+// Return the token of the ICorDebugFunction associated with this frame.
+// There is one ICorDebugFunction for each EnC version of a method.
+HRESULT CordbFrame::GetFunctionToken(mdMethodDef *pToken)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_REENTRANT_API_BEGIN(this)
+ {
+ ValidateOrThrow(pToken);
+
+ CordbFunction * pFunc = GetFunction();
+ if (pFunc == NULL)
+ {
+ hr = CORDBG_E_CODE_NOT_AVAILABLE;
+ }
+ else
+ {
+ *pToken = pFunc->GetMetadataToken();
+ }
+ }
+ PUBLIC_REENTRANT_API_END(hr);
+ return hr;
+}
+
+// ----------------------------------------------------------------------------
+// CordbFrame::GetCaller
+//
+// Description:
+// Return the caller of this frame. The caller is closer to the root.
+// This function has been deprecated in Arrowhead, and so it returns E_NOTIMPL unless there is a shim.
+//
+// Arguments:
+// * ppFrame - out parameter; return the caller frame
+//
+// Return Value:
+// Return S_OK on success.
+// Return E_INVALIDARG if ppFrame is NULL.
+// Return CORDBG_E_OBJECT_NEUTERED if the CordbFrame is neutered.
+// Return E_NOTIMPL if there is no shim.
+//
+
+HRESULT CordbFrame::GetCaller(ICorDebugFrame **ppFrame)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_REENTRANT_API_BEGIN(this)
+ {
+ ValidateOrThrow(ppFrame);
+
+ *ppFrame = NULL;
+
+ if (GetProcess()->GetShim() != NULL)
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE0(GetProcess(), GET_PUBLIC_LOCK_HOLDER());
+ ShimStackWalk * pSSW = GetProcess()->GetShim()->LookupOrCreateShimStackWalk(m_pThread);
+ pSSW->GetCallerForFrame(this, ppFrame);
+ }
+ else
+ {
+ *ppFrame = NULL;
+ hr = E_NOTIMPL;
+ }
+ }
+ PUBLIC_REENTRANT_API_END(hr);
+ return hr;
+}
+
+// ----------------------------------------------------------------------------
+// CordbFrame::GetCallee
+//
+// Description:
+// Return the callee of this frame. The callee is closer to the leaf.
+// This function has been deprecated in Arrowhead, and so it returns E_NOTIMPL unless there is a shim.
+//
+// Arguments:
+// * ppFrame - out parameter; return the callee frame
+//
+// Return Value:
+// Return S_OK on success.
+// Return E_INVALIDARG if ppFrame is NULL.
+// Return CORDBG_E_OBJECT_NEUTERED if the CordbFrame is neutered.
+// Return E_NOTIMPL if there is no shim.
+//
+
+HRESULT CordbFrame::GetCallee(ICorDebugFrame **ppFrame)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_REENTRANT_API_BEGIN(this)
+ {
+ ValidateOrThrow(ppFrame);
+
+ *ppFrame = NULL;
+
+ if (GetProcess()->GetShim() != NULL)
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE0(GetProcess(), GET_PUBLIC_LOCK_HOLDER());
+ ShimStackWalk * pSSW = GetProcess()->GetShim()->LookupOrCreateShimStackWalk(m_pThread);
+ pSSW->GetCalleeForFrame(static_cast<ICorDebugFrame *>(this), ppFrame);
+ }
+ else
+ {
+ *ppFrame = NULL;
+ hr = E_NOTIMPL;
+ }
+ }
+ PUBLIC_REENTRANT_API_END(hr);
+ return hr;
+}
+
+// Create a stepper on the frame.
+HRESULT CordbFrame::CreateStepper(ICorDebugStepper **ppStepper)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ VALIDATE_POINTER_TO_OBJECT(ppStepper, ICorDebugStepper **);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ RSInitHolder<CordbStepper> pStepper(new CordbStepper(m_pThread, this));
+ pStepper.TransferOwnershipExternal(ppStepper);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Given a frame pointer, determine if it is in the stack range owned by the frame.
+//
+// Arguments:
+// fp - frame pointer to check
+//
+// Return Value:
+// whether the specified frame pointer is in the stack range or not
+//
+
+bool CordbFrame::IsContainedInFrame(FramePointer fp)
+{
+ CORDB_ADDRESS stackStart;
+ CORDB_ADDRESS stackEnd;
+
+ // get the stack range
+ HRESULT hr;
+ hr = GetStackRange(&stackStart, &stackEnd);
+ _ASSERTE(SUCCEEDED(hr));
+
+ CORDB_ADDRESS sp = PTR_TO_CORDB_ADDRESS(fp.GetSPValue());
+
+ if ((stackStart <= sp) && (sp <= stackEnd))
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Given an ICorDebugFrame interface pointer, return a pointer to the base class CordbFrame.
+//
+// Arguments:
+// pFrame - the ICorDebugFrame interface pointer
+//
+// Return Value:
+// the CordbFrame pointer corresponding to the specified interface pointer
+//
+// Note:
+// This is currently only used for continuable exceptions.
+//
+
+// static
+CordbFrame* CordbFrame::GetCordbFrameFromInterface(ICorDebugFrame *pFrame)
+{
+ CordbFrame* pTargetFrame = NULL;
+
+ if (pFrame != NULL)
+ {
+ // test for CordbNativeFrame
+ RSExtSmartPtr<ICorDebugNativeFrame> pNativeFrame;
+ pFrame->QueryInterface(IID_ICorDebugNativeFrame, (void**)&pNativeFrame);
+ if (pNativeFrame != NULL)
+ {
+ pTargetFrame = static_cast<CordbFrame*>(static_cast<CordbNativeFrame*>(pNativeFrame.GetValue()));
+ }
+ else
+ {
+ // test for CordbJITILFrame
+ RSExtSmartPtr<ICorDebugILFrame> pILFrame;
+ pFrame->QueryInterface(IID_ICorDebugILFrame, (void**)&pILFrame);
+ if (pILFrame != NULL)
+ {
+ pTargetFrame = (static_cast<CordbJITILFrame*>(pILFrame.GetValue()))->m_nativeFrame;
+ }
+ else
+ {
+ // test for CordbInternalFrame
+ RSExtSmartPtr<ICorDebugInternalFrame> pInternalFrame;
+ pFrame->QueryInterface(IID_ICorDebugInternalFrame, (void**)&pInternalFrame);
+ if (pInternalFrame != NULL)
+ {
+ pTargetFrame = static_cast<CordbFrame*>(static_cast<CordbInternalFrame*>(pInternalFrame.GetValue()));
+ }
+ else
+ {
+ // when all else fails, this is just a CordbFrame
+ pTargetFrame = static_cast<CordbFrame*>(pFrame);
+ }
+ }
+ }
+ }
+ return pTargetFrame;
+}
+
+
+/* ------------------------------------------------------------------------- *
+
+ * Value Enumerator class
+ *
+ * Used by CordbJITILFrame for EnumLocalVars & EnumArgs.
+ * NOTE NOTE NOTE WE ASSUME that the 'frame' argument is actually the
+ * CordbJITILFrame's native frame member variable.
+ * ------------------------------------------------------------------------- */
+
+CordbValueEnum::CordbValueEnum(CordbNativeFrame *frame, ValueEnumMode mode) :
+ CordbBase(frame->GetProcess(), 0)
+{
+ _ASSERTE( frame != NULL );
+ _ASSERTE( mode == LOCAL_VARS_ORIGINAL_IL || mode == LOCAL_VARS_REJIT_IL || mode == ARGS);
+
+ m_frame = frame;
+ m_mode = mode;
+ m_iCurrent = 0;
+ m_iMax = 0;
+}
+
+/*
+ * CordbValueEnum::Init
+ *
+ * Initialize a CordbValueEnum object. Must be called after allocating the object and before using it. If Init
+ * fails, then destroy the object and release the memory.
+ *
+ * Parameters:
+ * none.
+ *
+ * Returns:
+ * HRESULT for success or failure.
+ *
+ */
+HRESULT CordbValueEnum::Init()
+{
+ HRESULT hr = S_OK;
+ CordbNativeFrame *nil = m_frame;
+ CordbJITILFrame *jil = nil->m_JITILFrame;
+
+ switch (m_mode)
+ {
+ case ARGS:
+ {
+ // Get the function signature
+ CordbFunction *func = m_frame->GetFunction();
+ ULONG methodArgCount;
+
+ IfFailRet(func->GetSig(NULL, &methodArgCount, NULL));
+
+ // Grab the argument count for the size of the enumeration.
+ m_iMax = methodArgCount;
+ if (jil->m_fVarArgFnx && !jil->m_sigParserCached.IsNull())
+ {
+ m_iMax = jil->m_allArgsCount;
+ }
+ break;
+ }
+ case LOCAL_VARS_ORIGINAL_IL:
+ {
+ // Get the locals signature.
+ ULONG localsCount;
+ IfFailRet(jil->GetOriginalILCode()->GetLocalVarSig(NULL, &localsCount));
+
+ // Grab the number of locals for the size of the enumeration.
+ m_iMax = localsCount;
+ break;
+ }
+ case LOCAL_VARS_REJIT_IL:
+ {
+ // Get the locals signature.
+ ULONG localsCount;
+ CordbReJitILCode* pCode = jil->GetReJitILCode();
+ if (pCode == NULL)
+ {
+ m_iMax = 0;
+ }
+ else
+ {
+ IfFailRet(pCode->GetLocalVarSig(NULL, &localsCount));
+
+ // Grab the number of locals for the size of the enumeration.
+ m_iMax = localsCount;
+ }
+ break;
+ }
+ }
+ // Everything worked okay, so add this object to the neuter list for objects that are tied to the stack trace.
+ EX_TRY
+ {
+ m_frame->m_pThread->GetRefreshStackNeuterList()->Add(GetProcess(), this);
+ }
+ EX_CATCH_HRESULT(hr);
+ SetUnrecoverableIfFailed(GetProcess(), hr);
+
+ return hr;
+}
+
+CordbValueEnum::~CordbValueEnum()
+{
+ _ASSERTE(this->IsNeutered());
+ _ASSERTE(m_frame == NULL);
+}
+
+void CordbValueEnum::Neuter()
+{
+ m_frame = NULL;
+ CordbBase::Neuter();
+}
+
+
+
+HRESULT CordbValueEnum::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugEnum)
+ *pInterface = static_cast<ICorDebugEnum*>(this);
+ else if (id == IID_ICorDebugValueEnum)
+ *pInterface = static_cast<ICorDebugValueEnum*>(this);
+ else if (id == IID_IUnknown)
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugValueEnum*>(this));
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+HRESULT CordbValueEnum::Skip(ULONG celt)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = E_FAIL;
+ if ( (m_iCurrent+celt) < m_iMax ||
+ celt == 0)
+ {
+ m_iCurrent += celt;
+ hr = S_OK;
+ }
+
+ return hr;
+}
+
+HRESULT CordbValueEnum::Reset()
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ m_iCurrent = 0;
+ return S_OK;
+}
+
+HRESULT CordbValueEnum::Clone(ICorDebugEnum **ppEnum)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ VALIDATE_POINTER_TO_OBJECT(ppEnum, ICorDebugEnum **);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ *ppEnum = NULL;
+ RSInitHolder<CordbValueEnum> pCVE(new CordbValueEnum(m_frame, m_mode));
+
+ // Initialize the new enum
+ hr = pCVE->Init();
+ IfFailThrow(hr);
+
+ pCVE.TransferOwnershipExternal(ppEnum);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+HRESULT CordbValueEnum::GetCount(ULONG *pcelt)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ VALIDATE_POINTER_TO_OBJECT(pcelt, ULONG *);
+
+ if( pcelt == NULL)
+ {
+ return E_INVALIDARG;
+ }
+
+ (*pcelt) = m_iMax;
+ return S_OK;
+}
+
+//
+// In the event of failure, the current pointer will be left at
+// one element past the troublesome element. Thus, if one were
+// to repeatedly ask for one element to iterate through the
+// array, you would iterate exactly m_iMax times, regardless
+// of individual failures.
+HRESULT CordbValueEnum::Next(ULONG celt, ICorDebugValue *values[], ULONG *pceltFetched)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(values, ICorDebugValue *,
+ celt, true, true);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pceltFetched, ULONG *);
+
+ if ((pceltFetched == NULL) && (celt != 1))
+ {
+ return E_INVALIDARG;
+ }
+
+ if (celt == 0)
+ {
+ if (pceltFetched != NULL)
+ {
+ *pceltFetched = 0;
+ }
+ return S_OK;
+ }
+
+ HRESULT hr = S_OK;
+
+ int iMax = min( m_iMax, m_iCurrent+celt);
+ int i;
+ for (i = m_iCurrent; i< iMax;i++)
+ {
+ switch ( m_mode )
+ {
+ case ARGS:
+ {
+ hr = m_frame->m_JITILFrame->GetArgument( i, &(values[i-m_iCurrent]) );
+ break;
+ }
+ case LOCAL_VARS_ORIGINAL_IL:
+ {
+ hr = m_frame->m_JITILFrame->GetLocalVariableEx(ILCODE_ORIGINAL_IL, i, &(values[i-m_iCurrent]) );
+ break;
+ }
+ case LOCAL_VARS_REJIT_IL:
+ {
+ hr = m_frame->m_JITILFrame->GetLocalVariableEx(ILCODE_REJIT_IL, i, &(values[i - m_iCurrent]));
+ break;
+ }
+ }
+ if ( FAILED( hr ) )
+ {
+ break;
+ }
+ }
+
+ int count = (i - m_iCurrent);
+
+ if ( FAILED( hr ) )
+ {
+ //
+ // we failed: +1 pushes us past troublesome element
+ //
+ m_iCurrent += 1 + count;
+ }
+ else
+ {
+ m_iCurrent += count;
+ }
+
+ if (pceltFetched != NULL)
+ {
+ *pceltFetched = count;
+ }
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+
+ //
+ // If we reached the end of the enumeration, but not the end
+ // of the number of requested items, we return S_FALSE.
+ //
+ if (((ULONG)count) < celt)
+ {
+ return S_FALSE;
+ }
+
+ return hr;
+}
+
+
+//-----------------------------------------------------------------------------
+// CordbInternalFrame
+//-----------------------------------------------------------------------------
+CordbInternalFrame::CordbInternalFrame(CordbThread * pThread,
+ FramePointer fp,
+ CordbAppDomain * pCurrentAppDomain,
+ const DebuggerIPCE_STRData * pData)
+ : CordbFrame(pThread, fp, 0, pCurrentAppDomain)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ m_eFrameType = pData->stubFrame.frameType;
+ m_funcMetadataToken = pData->stubFrame.funcMetadataToken;
+ m_vmMethodDesc = pData->stubFrame.vmMethodDesc;
+
+ // Some internal frames may not have a Function associated w/ them.
+ if (!IsNilToken(m_funcMetadataToken))
+ {
+ // Find the module of the function. Note that this module isn't necessarily in the same domain as our frame.
+ // FuncEval frames can point to methods they are going to invoke in another domain.
+ CordbModule * pModule = NULL;
+ pModule = GetProcess()->LookupOrCreateModule(pData->stubFrame.vmDomainFile);
+ _ASSERTE(pModule != NULL);
+
+ //
+ if( pModule != NULL )
+ {
+ _ASSERTE( (pModule->GetAppDomain() == pCurrentAppDomain) || (m_eFrameType == STUBFRAME_FUNC_EVAL) );
+
+
+
+ mdMethodDef token = pData->stubFrame.funcMetadataToken;
+
+ // @dbgtodo synchronization - push this up.
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+
+ // CordbInternalFrame could handle a null function.
+ // But if we fail to lookup, things are not in a good state anyways.
+ CordbFunction * pFunction = pModule->LookupOrCreateFunctionLatestVersion(token);
+ m_function.Assign(pFunction);
+
+ }
+ }
+}
+
+CordbInternalFrame::CordbInternalFrame(CordbThread * pThread,
+ FramePointer fp,
+ CordbAppDomain * pCurrentAppDomain,
+ CorDebugInternalFrameType frameType,
+ mdMethodDef funcMetadataToken,
+ CordbFunction * pFunction,
+ VMPTR_MethodDesc vmMethodDesc)
+ : CordbFrame(pThread, fp, 0, pCurrentAppDomain)
+{
+ m_eFrameType = frameType;
+ m_funcMetadataToken = funcMetadataToken;
+ m_function.Assign(pFunction);
+ m_vmMethodDesc = vmMethodDesc;
+}
+
+HRESULT CordbInternalFrame::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugFrame)
+ {
+ *pInterface = static_cast<ICorDebugFrame*>(static_cast<ICorDebugInternalFrame*>(this));
+ }
+ else if (id == IID_ICorDebugInternalFrame)
+ {
+ *pInterface = static_cast<ICorDebugInternalFrame*>(this);
+ }
+ else if (id == IID_ICorDebugInternalFrame2)
+ {
+ *pInterface = static_cast<ICorDebugInternalFrame2*>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugInternalFrame*>(this));
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+void CordbInternalFrame::Neuter()
+{
+ m_function.Clear();
+ CordbFrame::Neuter();
+}
+
+
+// ----------------------------------------------------------------------------
+// CordbInternalFrame::GetStackRange
+//
+// Description:
+// Return the stack range owned by this frame.
+// The start of the stack range is the leafmost boundary, and the end is the rootmost boundary.
+//
+// Arguments:
+// * pStart - out parameter; return the leaf end of the frame
+// * pEnd - out parameter; return the root end of the frame
+//
+// Return Value:
+// Return S_OK on success.
+//
+// Notes:
+// #GetStackRange
+// This is a virtual function and so there are multiple implementations for different types of frames.
+// It's very important to note that GetStackRange() can work when even after the frame is neutered.
+// Debuggers may rely on this to map old frames up to new frames across Continue() calls.
+//
+
+HRESULT CordbInternalFrame::GetStackRange(CORDB_ADDRESS *pStart,
+ CORDB_ADDRESS *pEnd)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+
+ // Callers explicit require GetStackRange() to be callable when neutered so that they
+ // can line up ICorDebugFrame objects across continues. We only return stack ranges
+ // here and don't access any special data.
+ OK_IF_NEUTERED(this);
+
+ if (GetProcess()->GetShim() != NULL)
+ {
+ CORDB_ADDRESS pFramePointer = PTR_TO_CORDB_ADDRESS(GetFramePointer().GetSPValue());
+ if (pStart)
+ {
+ *pStart = pFramePointer;
+ }
+ if (pEnd)
+ {
+ *pEnd = pFramePointer;
+ }
+ return S_OK;
+ }
+ else
+ {
+ if (pStart != NULL)
+ {
+ *pStart = NULL;
+ }
+ if (pEnd != NULL)
+ {
+ *pEnd = NULL;
+ }
+ return E_NOTIMPL;
+ }
+}
+
+
+// This may return NULL if there's no Method associated w/ this Frame.
+// For FuncEval frames, the function returned might also be in a different AppDomain
+// than the frame itself.
+CordbFunction * CordbInternalFrame::GetFunction()
+{
+ return m_function;
+}
+
+// Accessor for the shim private hook code:CordbThread::ConvertFrameForILMethodWithoutMetadata.
+// Refer to that function for comments on the return value, the argument, etc.
+BOOL CordbInternalFrame::ConvertInternalFrameForILMethodWithoutMetadata(
+ ICorDebugInternalFrame2 ** ppInternalFrame2)
+{
+ _ASSERTE(ppInternalFrame2 != NULL);
+ *ppInternalFrame2 = NULL;
+
+ // The only internal frame conversion we need to perform is from STUBFRAME_JIT_COMPILATION to
+ // STUBFRAME_LIGTHWEIGHT_FUNCTION.
+ if (m_eFrameType != STUBFRAME_JIT_COMPILATION)
+ {
+ return FALSE;
+ }
+
+ // Check whether the internal frame has an associated MethodDesc.
+ // Currently, the only STUBFRAME_JIT_COMPILATION frame with a NULL MethodDesc is ComPrestubMethodFrame,
+ // which is not exposed in Whidbey. So convert it according to rule #2 below.
+ if (m_vmMethodDesc.IsNull())
+ {
+ return TRUE;
+ }
+
+ // Retrieve the type of the method associated with the STUBFRAME_JIT_COMPILATION.
+ IDacDbiInterface::DynamicMethodType type = GetProcess()->GetDAC()->IsILStubOrLCGMethod(m_vmMethodDesc);
+
+ // Here are the conversion rules:
+ // 1) For a normal managed method, we don't convert, and we return FALSE.
+ // 2) For an IL stub, we convert to NULL, and we return TRUE.
+ // 3) For a dynamic method, we convert to a STUBFRAME_LIGHTWEIGHT_FUNCTION, and we return TRUE.
+ if (type == IDacDbiInterface::kNone)
+ {
+ return FALSE;
+ }
+ else if (type == IDacDbiInterface::kILStub)
+ {
+ return TRUE;
+ }
+ else if (type == IDacDbiInterface::kLCGMethod)
+ {
+ // Here we are basically cloning another CordbInternalFrame.
+ RSInitHolder<CordbInternalFrame> pInternalFrame(new CordbInternalFrame(m_pThread,
+ m_fp,
+ m_currentAppDomain,
+ STUBFRAME_LIGHTWEIGHT_FUNCTION,
+ m_funcMetadataToken,
+ m_function.GetValue(),
+ m_vmMethodDesc));
+ pInternalFrame.TransferOwnershipExternal(ppInternalFrame2);
+ return TRUE;
+ }
+
+ UNREACHABLE();
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Returns the address of an internal frame. The address is a stack pointer, even on IA64.
+//
+// Arguments:
+// pAddress - out parameter; return the frame marker address
+//
+// Return Value:
+// S_OK on success.
+// E_INVALIDARG if pAddress is NULL.
+//
+
+HRESULT CordbInternalFrame::GetAddress(CORDB_ADDRESS * pAddress)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ if (pAddress == NULL)
+ {
+ return E_INVALIDARG;
+ }
+
+ *pAddress = PTR_TO_CORDB_ADDRESS(GetFramePointer().GetSPValue());
+ return S_OK;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Refer to the comment for code:CordbInternalFrame::IsCloserToLeaf
+//
+
+BOOL CordbInternalFrame::IsCloserToLeafWorker(ICorDebugFrame * pFrameToCompare)
+{
+ // Get the address of the "this" internal frame.
+ CORDB_ADDRESS thisFrameAddr = PTR_TO_CORDB_ADDRESS(this->GetFramePointer().GetSPValue());
+
+ // Note that a QI on ICorDebugJITILFrame for ICorDebugNativeFrame will work.
+ RSExtSmartPtr<ICorDebugNativeFrame> pNativeFrame;
+ pFrameToCompare->QueryInterface(IID_ICorDebugNativeFrame, (void **)&pNativeFrame);
+ if (pNativeFrame != NULL)
+ {
+ // The frame to compare is a CordbNativeFrame.
+ CordbNativeFrame * pCNativeFrame = static_cast<CordbNativeFrame *>(pNativeFrame.GetValue());
+
+ // Compare the address of the "this" internal frame to the SP of the stack frame.
+ // We can't compare frame pointers because the frame pointer means different things on
+ // different platforms.
+ CORDB_ADDRESS stackFrameSP = GetSPFromDebuggerREGDISPLAY(&(pCNativeFrame->m_rd));
+ return (thisFrameAddr < stackFrameSP);
+ }
+
+ RSExtSmartPtr<ICorDebugRuntimeUnwindableFrame> pRUFrame;
+ pFrameToCompare->QueryInterface(IID_ICorDebugRuntimeUnwindableFrame, (void **)&pRUFrame);
+ if (pRUFrame != NULL)
+ {
+ // The frame to compare is a CordbRuntimeUnwindableFrame.
+ CordbRuntimeUnwindableFrame * pCRUFrame =
+ static_cast<CordbRuntimeUnwindableFrame *>(pRUFrame.GetValue());
+
+ DT_CONTEXT * pResumeContext = const_cast<DT_CONTEXT *>(pCRUFrame->GetContext());
+ CORDB_ADDRESS stackFrameSP = PTR_TO_CORDB_ADDRESS(CORDbgGetSP(pResumeContext));
+ return (thisFrameAddr < stackFrameSP);
+ }
+
+ RSExtSmartPtr<ICorDebugInternalFrame> pInternalFrame;
+ pFrameToCompare->QueryInterface(IID_ICorDebugInternalFrame, (void **)&pInternalFrame);
+ if (pInternalFrame != NULL)
+ {
+ // The frame to compare is a CordbInternalFrame.
+ CordbInternalFrame * pCInternalFrame =
+ static_cast<CordbInternalFrame *>(pInternalFrame.GetValue());
+
+ CORDB_ADDRESS frameAddr = PTR_TO_CORDB_ADDRESS(pCInternalFrame->GetFramePointer().GetSPValue());
+ return (thisFrameAddr < frameAddr);
+ }
+
+ // What does this mean? This is unexpected.
+ _ASSERTE(!"CIF::ICTLW - Unexpected frame type.\n");
+ ThrowHR(E_FAIL);
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Checks whether the "this" internal frame is closer to the leaf than the specified ICDFrame.
+// If the specified ICDFrame represents a stack frame, then we compare the address of the "this"
+// internal frame against the SP of the stack frame.
+//
+// Arguments:
+// pFrameToCompare - the ICDFrame to compare against
+// pIsCloser - out parameter; returns TRUE if the "this" internal frame is closer to the leaf
+//
+// Return Value:
+// S_OK on success.
+// E_INVALIDARG if pFrameToCompare or pIsCloser is NULL.
+// E_FAIL if pFrameToCompare is bogus.
+//
+// Notes:
+// This function doesn't deal with the backing store at all.
+//
+
+HRESULT CordbInternalFrame::IsCloserToLeaf(ICorDebugFrame * pFrameToCompare,
+ BOOL * pIsCloser)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_REENTRANT_API_BEGIN(this);
+ {
+ ValidateOrThrow(pFrameToCompare);
+ ValidateOrThrow(pIsCloser);
+
+ *pIsCloser = IsCloserToLeafWorker(pFrameToCompare);
+ }
+ PUBLIC_REENTRANT_API_END(hr);
+ return hr;
+}
+
+
+CordbRuntimeUnwindableFrame::CordbRuntimeUnwindableFrame(CordbThread * pThread,
+ FramePointer fp,
+ CordbAppDomain * pCurrentAppDomain,
+ DT_CONTEXT * pContext)
+ : CordbFrame(pThread, fp, 0, pCurrentAppDomain),
+ m_context(*pContext)
+{
+}
+
+void CordbRuntimeUnwindableFrame::Neuter()
+{
+ CordbFrame::Neuter();
+}
+
+HRESULT CordbRuntimeUnwindableFrame::QueryInterface(REFIID id, void ** ppInterface)
+{
+ if (id == IID_ICorDebugFrame)
+ {
+ *ppInterface = static_cast<ICorDebugFrame *>(static_cast<ICorDebugRuntimeUnwindableFrame *>(this));
+ }
+ else if (id == IID_ICorDebugRuntimeUnwindableFrame)
+ {
+ *ppInterface = static_cast<ICorDebugRuntimeUnwindableFrame *>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *ppInterface = static_cast<IUnknown *>(static_cast<ICorDebugRuntimeUnwindableFrame *>(this));
+ }
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Returns the CONTEXT corresponding to this CordbRuntimeUnwindableFrame.
+//
+// Return Value:
+// Return a pointer to the CONTEXT.
+//
+
+const DT_CONTEXT * CordbRuntimeUnwindableFrame::GetContext() const
+{
+ return &m_context;
+}
+
+
+// default constructor to make the compiler happy
+CordbMiscFrame::CordbMiscFrame()
+{
+#if defined(DBG_TARGET_WIN64) || defined(DBG_TARGET_ARM)
+ this->parentIP = 0;
+ this->fpParentOrSelf = LEAF_MOST_FRAME;
+ this->fIsFilterFunclet = false;
+#endif // _WIN64
+}
+
+// the real constructor which stores the funclet-related information in the CordbMiscFrame
+CordbMiscFrame::CordbMiscFrame(DebuggerIPCE_JITFuncData * pJITFuncData)
+{
+#if defined(DBG_TARGET_WIN64) || defined(DBG_TARGET_ARM)
+ this->parentIP = pJITFuncData->parentNativeOffset;
+ this->fpParentOrSelf = pJITFuncData->fpParentOrSelf;
+ this->fIsFilterFunclet = (pJITFuncData->fIsFilterFrame == TRUE);
+#endif // DBG_TARGET_WIN64 || DBG_TARGET_ARM
+}
+
+/* ------------------------------------------------------------------------- *
+ * Native Frame class
+ * ------------------------------------------------------------------------- */
+
+
+CordbNativeFrame::CordbNativeFrame(CordbThread * pThread,
+ FramePointer fp,
+ CordbNativeCode * pNativeCode,
+ SIZE_T ip,
+ DebuggerREGDISPLAY * pDRD,
+ TADDR taAmbientESP,
+ bool fQuicklyUnwound,
+ CordbAppDomain * pCurrentAppDomain,
+ CordbMiscFrame * pMisc /*= NULL*/,
+ DT_CONTEXT * pContext /*= NULL*/)
+ : CordbFrame(pThread, fp, ip, pCurrentAppDomain),
+ m_rd(*pDRD),
+ m_quicklyUnwound(fQuicklyUnwound),
+ m_JITILFrame(NULL),
+ m_nativeCode(pNativeCode), // implicit InternalAddRef
+ m_taAmbientESP(taAmbientESP)
+{
+ m_misc = *pMisc;
+
+ // Only new CordbNativeFrames created by the new stackwalk contain a CONTEXT.
+ _ASSERTE(pContext != NULL);
+ m_context = *pContext;
+}
+
+/*
+ A list of which resources owned by this object are accounted for.
+
+ RESOLVED:
+ CordbJITILFrame* m_JITILFrame; // Neutered
+*/
+
+CordbNativeFrame::~CordbNativeFrame()
+{
+ _ASSERTE(IsNeutered());
+}
+
+// Neutered by CordbThread::CleanupStack
+void CordbNativeFrame::Neuter()
+{
+ // Neuter may be called multiple times so be sure to set ptrs to NULL so that we don't
+ // double release them.
+ if (IsNeutered())
+ {
+ return;
+ }
+
+ m_nativeCode.Clear();
+
+ if (m_JITILFrame != NULL)
+ {
+ m_JITILFrame->Neuter();
+ m_JITILFrame.Clear();
+ }
+
+ CordbFrame::Neuter();
+}
+
+// CordbNativeFrame::QueryInterface
+//
+// Description
+// interface query for this COM object
+//
+// NOTE: the COM object associated with this CordbNativeFrame may consist of
+// two C++ objects (the CordbNativeFrame and the CordbJITILFrame).
+//
+// Parameters
+// id the GUID associated with the requested interface
+// pInterface [out] the interface pointer
+//
+// Returns
+// HRESULT
+// S_OK If this CordbJITILFrame supports the interface
+// E_NOINTERFACE If this object does not support the interface
+//
+// Exceptions
+// None
+//
+//
+HRESULT CordbNativeFrame::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugFrame)
+ {
+ *pInterface = static_cast<ICorDebugFrame*>(static_cast<ICorDebugNativeFrame*>(this));
+ }
+ else if (id == IID_ICorDebugNativeFrame)
+ {
+ *pInterface = static_cast<ICorDebugNativeFrame*>(this);
+ }
+ else if (id == IID_ICorDebugNativeFrame2)
+ {
+ *pInterface = static_cast<ICorDebugNativeFrame2*>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugNativeFrame*>(this));
+ }
+ else
+ {
+ // might be searching for an IL Frame. delegate that search to the
+ // JITILFrame
+ if (m_JITILFrame != NULL)
+ {
+ return m_JITILFrame->QueryInterfaceInternal(id, pInterface);
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+// Return the CordbNativeCode object associated with this native frame.
+// This is just a wrapper around the real helper.
+HRESULT CordbNativeFrame::GetCode(ICorDebugCode **ppCode)
+{
+ PUBLIC_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(ppCode, ICorDebugCode **);
+ FAIL_IF_NEUTERED(this);
+
+ CordbNativeCode * pCode = GetNativeCode();
+ *ppCode = static_cast<ICorDebugCode*> (pCode);
+ pCode->ExternalAddRef();
+
+ return S_OK;;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Returns the CONTEXT corresponding to this CordbNativeFrame.
+//
+// Return Value:
+// Return a pointer to the CONTEXT.
+//
+
+const DT_CONTEXT * CordbNativeFrame::GetContext() const
+{
+ return &m_context;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// This is an internal helper to get the CordbNativeCode object associated with this native frame.
+//
+// Return Value:
+// the associated CordbNativeCode object
+//
+
+CordbNativeCode * CordbNativeFrame::GetNativeCode()
+{
+ return this->m_nativeCode;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// This is an internal helper to get the CordbFunction object associated with this native frame.
+//
+// Return Value:
+// the associated CordbFunction object
+//
+
+CordbFunction *CordbNativeFrame::GetFunction()
+{
+ return this->m_nativeCode->GetFunction();
+}
+
+
+
+// Return the native offset.
+HRESULT CordbNativeFrame::GetIP(ULONG32 *pnOffset)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pnOffset, ULONG32 *);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ *pnOffset = (ULONG32)m_ip;
+
+ return S_OK;
+}
+
+ULONG32 CordbNativeFrame::GetIPOffset()
+{
+ return (ULONG32)m_ip;
+}
+
+TADDR CordbNativeFrame::GetReturnRegisterValue()
+{
+#if defined(DBG_TARGET_X86)
+ return (TADDR)m_context.Eax;
+#elif defined(DBG_TARGET_AMD64)
+ return (TADDR)m_context.Rax;
+#elif defined(DBG_TARGET_ARM)
+ return (TADDR)m_context.R0;
+#elif defined(DBG_TARGET_ARM64)
+ return (TADDR)m_context.X0;
+#else
+ _ASSERTE(!"nyi for platform");
+ return 0;
+#endif
+}
+
+// Determine if we can set IP at this point. The specified offset is the native offset.
+HRESULT CordbNativeFrame::CanSetIP(ULONG32 nOffset)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ if (!IsLeafFrame())
+ {
+ ThrowHR(CORDBG_E_SET_IP_NOT_ALLOWED_ON_NONLEAF_FRAME);
+ }
+
+ hr = m_pThread->SetIP(SetIP_fCanSetIPOnly,
+ m_nativeCode,
+ nOffset,
+ SetIP_fNative );
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+// Try to set the IP to the specified offset. The specified offset is the native offset.
+HRESULT CordbNativeFrame::SetIP(ULONG32 nOffset)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ if (!IsLeafFrame())
+ {
+ ThrowHR(CORDBG_E_SET_IP_NOT_ALLOWED_ON_NONLEAF_FRAME);
+ }
+
+ hr = m_pThread->SetIP(SetIP_fSetIP,
+ m_nativeCode,
+ nOffset,
+ SetIP_fNative );
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+
+// Given a (register,offset) description of a stack location, compute
+// the real memory address for it.
+// This will also handle ambient SP values (which are encoded with regNum == REGNUM_AMBIENT_SP).
+CORDB_ADDRESS CordbNativeFrame::GetLSStackAddress(
+ ICorDebugInfo::RegNum regNum,
+ signed offset)
+{
+ UINT_PTR *pRegAddr;
+
+ CORDB_ADDRESS pRemoteValue;
+
+ if (regNum != DBG_TARGET_REGNUM_AMBIENT_SP)
+ {
+ // Even if we're inside a funclet, variables (in both x64 and ARM) are still
+ // relative to the frame pointer or stack pointer, which are accurate in the
+ // funclet, after the funclet prolog; the frame pointer is re-established in the
+ // funclet prolog using the PSP. Thus, we just look up the frame pointer in the
+ // current native frame.
+
+ pRegAddr = this->GetAddressOfRegister(
+ ConvertRegNumToCorDebugRegister(regNum));
+
+ // This should never be null as long as regNum is a member of the RegNum enum.
+ // If it is, an AV dereferencing a null-pointer in retail builds, or an assert in debug
+ // builds is exactly the behavior we want.
+ PREFIX_ASSUME(pRegAddr != NULL);
+
+ pRemoteValue = PTR_TO_CORDB_ADDRESS(*pRegAddr + offset);
+ }
+ else
+ {
+ // Use the ambient ESP. At this point we're decoding an ambient-sp var, so
+ // we should definitely have an ambient-sp. If this is null, then the jit
+ // likely gave us an inconsistent data.
+ TADDR taAmbient = this->GetAmbientESP();
+ _ASSERTE(taAmbient != NULL);
+
+ pRemoteValue = PTR_TO_CORDB_ADDRESS(taAmbient + offset);
+ }
+
+ return pRemoteValue;
+}
+
+
+// ----------------------------------------------------------------------------
+// CordbNativeFrame::GetStackRange
+//
+// Description:
+// Return the stack range owned by this native frame.
+// The start of the stack range is the leafmost boundary, and the end is the rootmost boundary.
+//
+// Arguments:
+// * pStart - out parameter; return the leaf end of the frame
+// * pEnd - out parameter; return the root end of the frame
+//
+// Return Value:
+// Return S_OK on success.
+//
+// Notes: see code:#GetStackRange
+
+HRESULT CordbNativeFrame::GetStackRange(CORDB_ADDRESS *pStart,
+ CORDB_ADDRESS *pEnd)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+
+ // Callers explicit require GetStackRange() to be callable when neutered so that they
+ // can line up ICorDebugFrame objects across continues. We only return stack ranges
+ // here and don't access any special data.
+ OK_IF_NEUTERED(this);
+
+ if (GetProcess()->GetShim() != NULL)
+ {
+ if (pStart)
+ {
+ // From register set.
+ *pStart = GetSPFromDebuggerREGDISPLAY(&m_rd);
+ }
+
+ if (pEnd)
+ {
+ // The rootmost boundary is the frame pointer.
+ // <NOTE>
+ // This is not true on AMD64, on which we use the stack pointer as the frame pointer.
+ // </NOTE>
+ *pEnd = PTR_TO_CORDB_ADDRESS(GetFramePointer().GetSPValue());
+ }
+ return S_OK;
+ }
+ else
+ {
+ if (pStart != NULL)
+ {
+ *pStart = NULL;
+ }
+ if (pEnd != NULL)
+ {
+ *pEnd = NULL;
+ }
+ return E_NOTIMPL;
+ }
+}
+
+// Return the register set of the native frame.
+HRESULT CordbNativeFrame::GetRegisterSet(ICorDebugRegisterSet **ppRegisters)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ VALIDATE_POINTER_TO_OBJECT(ppRegisters, ICorDebugRegisterSet **);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // allocate a new CordbRegisterSet object
+ RSInitHolder<CordbRegisterSet> pRegisterSet(new CordbRegisterSet(&m_rd,
+ m_pThread,
+ IsLeafFrame(),
+ m_quicklyUnwound));
+
+ pRegisterSet.TransferOwnershipExternal(ppRegisters);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Checks whether the frame is a child frame or not.
+//
+// Arguments:
+// pIsChild - out parameter; returns whether the frame is a child frame
+//
+// Return Value:
+// S_OK on success.
+// E_INVALIDARG if the out parmater is NULL.
+//
+
+HRESULT CordbNativeFrame::IsChild(BOOL * pIsChild)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_REENTRANT_API_BEGIN(this)
+ {
+ if (pIsChild == NULL)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+ else
+ {
+ *pIsChild = ((this->IsFunclet() && !this->IsFilterFunclet()) ? TRUE : FALSE);
+ }
+ }
+ PUBLIC_REENTRANT_API_END(hr);
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Given an ICDNativeFrame2, check whether it is the parent frame of the current frame.
+//
+// Arguments:
+// pPotentialParentFrame - the ICDNativeFrame2 to check
+// pIsParent - out paramter; returns whether the specified frame is indeed the parent frame
+//
+// Return Value:
+// S_OK on success.
+// CORDBG_E_NOT_CHILD_FRAME if the current frame is not a child frame.
+// E_INVALIDARG if either of the incoming argument is NULL.
+// E_FAIL on other failures.
+//
+
+HRESULT CordbNativeFrame::IsMatchingParentFrame(ICorDebugNativeFrame2 * pPotentialParentFrame,
+ BOOL * pIsParent)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pPotentialParentFrame, ICorDebugNativeFrame2 *);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ if ((pPotentialParentFrame == NULL) || (pIsParent == NULL))
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ *pIsParent = FALSE;
+
+ if (!this->IsFunclet())
+ {
+ ThrowHR(CORDBG_E_NOT_CHILD_FRAME);
+ }
+
+#if defined(DBG_TARGET_WIN64) || defined(DBG_TARGET_ARM)
+ CordbNativeFrame * pFrameToCheck = static_cast<CordbNativeFrame *>(pPotentialParentFrame);
+ if (pFrameToCheck->IsFunclet())
+ {
+ *pIsParent = FALSE;
+ }
+ else
+ {
+ FramePointer fpParent = this->m_misc.fpParentOrSelf;
+ FramePointer fpToCheck = pFrameToCheck->m_misc.fpParentOrSelf;
+
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ *pIsParent = pDAC->IsMatchingParentFrame(fpToCheck, fpParent);
+ }
+#endif // DBG_TARGET_WIN64 || DBG_TARGET_ARM
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Return the stack parameter size of the current frame. Since this information is only used on x86,
+// we return S_FALSE and a size of 0 on WIN64 platforms.
+//
+// Arguments:
+// pSize - out parameter; return the size of the stack parameter
+//
+// Return Value:
+// S_OK on success.
+// S_FALSE on WIN64 platforms.
+// E_INVALIDARG if pSize is NULL.
+//
+// Notes:
+// Always return S_FALSE on WIN64.
+//
+
+HRESULT CordbNativeFrame::GetStackParameterSize(ULONG32 * pSize)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ if (pSize == NULL)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+#if defined(_TARGET_X86_)
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ *pSize = pDAC->GetStackParameterSize(PTR_TO_CORDB_ADDRESS(CORDbgGetIP(&m_context)));
+#else // !_TARGET_X86_
+ hr = S_FALSE;
+ *pSize = 0;
+#endif // _TARGET_X86_
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+//
+// GetAddressOfRegister returns the address of the given register in the
+// frame's current register display (eg, a local address). This is usually used to build a
+// ICorDebugValue from.
+//
+UINT_PTR * CordbNativeFrame::GetAddressOfRegister(CorDebugRegister regNum) const
+{
+ UINT_PTR* ret = NULL;
+
+ switch (regNum)
+ {
+ case REGISTER_STACK_POINTER:
+ ret = (UINT_PTR*)GetSPAddress(&m_rd);
+ break;
+
+#if !defined(DBG_TARGET_AMD64) && !defined(DBG_TARGET_ARM) // @ARMTODO
+ case REGISTER_FRAME_POINTER:
+ ret = (UINT_PTR*)GetFPAddress(&m_rd);
+ break;
+#endif
+
+#if defined(DBG_TARGET_X86)
+ case REGISTER_X86_EAX:
+ ret = (UINT_PTR*)&m_rd.Eax;
+ break;
+
+ case REGISTER_X86_ECX:
+ ret = (UINT_PTR*)&m_rd.Ecx;
+ break;
+
+ case REGISTER_X86_EDX:
+ ret = (UINT_PTR*)&m_rd.Edx;
+ break;
+
+ case REGISTER_X86_EBX:
+ ret = (UINT_PTR*)&m_rd.Ebx;
+ break;
+
+ case REGISTER_X86_ESI:
+ ret = (UINT_PTR*)&m_rd.Esi;
+ break;
+
+ case REGISTER_X86_EDI:
+ ret = (UINT_PTR*)&m_rd.Edi;
+ break;
+
+#elif defined(DBG_TARGET_AMD64)
+ case REGISTER_AMD64_RBP:
+ ret = (UINT_PTR*)&m_rd.Rbp;
+ break;
+
+ case REGISTER_AMD64_RAX:
+ ret = (UINT_PTR*)&m_rd.Rax;
+ break;
+
+ case REGISTER_AMD64_RCX:
+ ret = (UINT_PTR*)&m_rd.Rcx;
+ break;
+
+ case REGISTER_AMD64_RDX:
+ ret = (UINT_PTR*)&m_rd.Rdx;
+ break;
+
+ case REGISTER_AMD64_RBX:
+ ret = (UINT_PTR*)&m_rd.Rbx;
+ break;
+
+ case REGISTER_AMD64_RSI:
+ ret = (UINT_PTR*)&m_rd.Rsi;
+ break;
+
+ case REGISTER_AMD64_RDI:
+ ret = (UINT_PTR*)&m_rd.Rdi;
+ break;
+
+ case REGISTER_AMD64_R8:
+ ret = (UINT_PTR*)&m_rd.R8;
+ break;
+
+ case REGISTER_AMD64_R9:
+ ret = (UINT_PTR*)&m_rd.R9;
+ break;
+
+ case REGISTER_AMD64_R10:
+ ret = (UINT_PTR*)&m_rd.R10;
+ break;
+
+ case REGISTER_AMD64_R11:
+ ret = (UINT_PTR*)&m_rd.R11;
+ break;
+
+ case REGISTER_AMD64_R12:
+ ret = (UINT_PTR*)&m_rd.R12;
+ break;
+
+ case REGISTER_AMD64_R13:
+ ret = (UINT_PTR*)&m_rd.R13;
+ break;
+
+ case REGISTER_AMD64_R14:
+ ret = (UINT_PTR*)&m_rd.R14;
+ break;
+
+ case REGISTER_AMD64_R15:
+ ret = (UINT_PTR*)&m_rd.R15;
+ break;
+#elif defined(DBG_TARGET_ARM)
+ case REGISTER_ARM_R0:
+ ret = (UINT_PTR*)&m_rd.R0;
+ break;
+
+ case REGISTER_ARM_R1:
+ ret = (UINT_PTR*)&m_rd.R1;
+ break;
+
+ case REGISTER_ARM_R2:
+ ret = (UINT_PTR*)&m_rd.R2;
+ break;
+
+ case REGISTER_ARM_R3:
+ ret = (UINT_PTR*)&m_rd.R3;
+ break;
+
+ case REGISTER_ARM_R4:
+ ret = (UINT_PTR*)&m_rd.R4;
+ break;
+
+ case REGISTER_ARM_R5:
+ ret = (UINT_PTR*)&m_rd.R5;
+ break;
+
+ case REGISTER_ARM_R6:
+ ret = (UINT_PTR*)&m_rd.R6;
+ break;
+
+ case REGISTER_ARM_R7:
+ ret = (UINT_PTR*)&m_rd.R7;
+ break;
+
+ case REGISTER_ARM_R8:
+ ret = (UINT_PTR*)&m_rd.R8;
+ break;
+
+ case REGISTER_ARM_R9:
+ ret = (UINT_PTR*)&m_rd.R9;
+ break;
+
+ case REGISTER_ARM_R10:
+ ret = (UINT_PTR*)&m_rd.R10;
+ break;
+
+ case REGISTER_ARM_R11:
+ ret = (UINT_PTR*)&m_rd.R11;
+ break;
+
+ case REGISTER_ARM_R12:
+ ret = (UINT_PTR*)&m_rd.R12;
+ break;
+
+ case REGISTER_ARM_LR:
+ ret = (UINT_PTR*)&m_rd.LR;
+ break;
+
+ case REGISTER_ARM_PC:
+ ret = (UINT_PTR*)&m_rd.PC;
+ break;
+#elif defined(DBG_TARGET_ARM64)
+ case REGISTER_ARM64_X0:
+ case REGISTER_ARM64_X1:
+ case REGISTER_ARM64_X2:
+ case REGISTER_ARM64_X3:
+ case REGISTER_ARM64_X4:
+ case REGISTER_ARM64_X5:
+ case REGISTER_ARM64_X6:
+ case REGISTER_ARM64_X7:
+ case REGISTER_ARM64_X8:
+ case REGISTER_ARM64_X9:
+ case REGISTER_ARM64_X10:
+ case REGISTER_ARM64_X11:
+ case REGISTER_ARM64_X12:
+ case REGISTER_ARM64_X13:
+ case REGISTER_ARM64_X14:
+ case REGISTER_ARM64_X15:
+ case REGISTER_ARM64_X16:
+ case REGISTER_ARM64_X17:
+ case REGISTER_ARM64_X18:
+ case REGISTER_ARM64_X19:
+ case REGISTER_ARM64_X20:
+ case REGISTER_ARM64_X21:
+ case REGISTER_ARM64_X22:
+ case REGISTER_ARM64_X23:
+ case REGISTER_ARM64_X24:
+ case REGISTER_ARM64_X25:
+ case REGISTER_ARM64_X26:
+ case REGISTER_ARM64_X27:
+ case REGISTER_ARM64_X28:
+ ret = (UINT_PTR*)&m_rd.X[regNum - REGISTER_ARM64_X0];
+ break;
+
+ case REGISTER_ARM64_LR:
+ ret = (UINT_PTR*)&m_rd.LR;
+ break;
+
+ case REGISTER_ARM64_PC:
+ ret = (UINT_PTR*)&m_rd.PC;
+ break;
+#endif
+
+ default:
+ _ASSERT(!"Invalid register number!");
+ }
+
+ return ret;
+}
+
+//
+// GetLeftSideAddressOfRegister returns the Left Side address of the given register in the frames current register
+// display.
+//
+CORDB_ADDRESS CordbNativeFrame::GetLeftSideAddressOfRegister(CorDebugRegister regNum) const
+{
+#if !defined(USE_REMOTE_REGISTER_ADDRESS)
+ // Use marker values as the register address. This is to implement the funceval breaking change.
+ //
+ if (IsLeafFrame())
+ {
+ return kLeafFrameRegAddr;
+ }
+ else
+ {
+ return kNonLeafFrameRegAddr;
+ }
+
+#else // USE_REMOTE_REGISTER_ADDRESS
+ void* ret = 0;
+
+ switch (regNum)
+ {
+
+#if !defined(DBG_TARGET_AMD64)
+ case REGISTER_FRAME_POINTER:
+ ret = m_rd.pFP;
+ break;
+#endif
+
+#if defined(DBG_TARGET_X86)
+ case REGISTER_X86_EAX:
+ ret = m_rd.pEax;
+ break;
+
+ case REGISTER_X86_ECX:
+ ret = m_rd.pEcx;
+ break;
+
+ case REGISTER_X86_EDX:
+ ret = m_rd.pEdx;
+ break;
+
+ case REGISTER_X86_EBX:
+ ret = m_rd.pEbx;
+ break;
+
+ case REGISTER_X86_ESI:
+ ret = m_rd.pEsi;
+ break;
+
+ case REGISTER_X86_EDI:
+ ret = m_rd.pEdi;
+ break;
+
+#elif defined(DBG_TARGET_AMD64)
+ case REGISTER_AMD64_RBP:
+ ret = m_rd.pRbp;
+ break;
+
+ case REGISTER_AMD64_RAX:
+ ret = m_rd.pRax;
+ break;
+
+ case REGISTER_AMD64_RCX:
+ ret = m_rd.pRcx;
+ break;
+
+ case REGISTER_AMD64_RDX:
+ ret = m_rd.pRdx;
+ break;
+
+ case REGISTER_AMD64_RBX:
+ ret = m_rd.pRbx;
+ break;
+
+ case REGISTER_AMD64_RSI:
+ ret = m_rd.pRsi;
+ break;
+
+ case REGISTER_AMD64_RDI:
+ ret = m_rd.pRdi;
+ break;
+
+ case REGISTER_AMD64_R8:
+ ret = m_rd.pR8;
+ break;
+
+ case REGISTER_AMD64_R9:
+ ret = m_rd.pR9;
+ break;
+
+ case REGISTER_AMD64_R10:
+ ret = m_rd.pR10;
+ break;
+
+ case REGISTER_AMD64_R11:
+ ret = m_rd.pR11;
+ break;
+
+ case REGISTER_AMD64_R12:
+ ret = m_rd.pR12;
+ break;
+
+ case REGISTER_AMD64_R13:
+ ret = m_rd.pR13;
+ break;
+
+ case REGISTER_AMD64_R14:
+ ret = m_rd.pR14;
+ break;
+
+ case REGISTER_AMD64_R15:
+ ret = m_rd.pR15;
+ break;
+#endif
+ default:
+ _ASSERT(!"Invalid register number!");
+ }
+
+ return PTR_TO_CORDB_ADDRESS(ret);
+#endif // !USE_REMOTE_REGISTER_ADDRESS
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Given the native variable information of a variable, return its value.
+//
+// Arguments:
+// pNativeVarInfo - the variable information of the variable to be retrieved
+//
+// Returns:
+// Return the specified value.
+// Throw on error.
+//
+// Assumption:
+// This function assumes that the value is either in a register or on the stack
+// (i.e. VLT_REG or VLT_STK).
+//
+// Notes:
+// Eventually we should make this more general-purpose.
+//
+
+SIZE_T CordbNativeFrame::GetRegisterOrStackValue(const ICorDebugInfo::NativeVarInfo * pNativeVarInfo)
+{
+ SIZE_T uResult;
+
+ if (pNativeVarInfo->loc.vlType == ICorDebugInfo::VLT_REG)
+ {
+ CorDebugRegister reg = ConvertRegNumToCorDebugRegister(pNativeVarInfo->loc.vlReg.vlrReg);
+ uResult = *(reinterpret_cast<SIZE_T *>(GetAddressOfRegister(reg)));
+ }
+ else if (pNativeVarInfo->loc.vlType == ICorDebugInfo::VLT_STK)
+ {
+ CORDB_ADDRESS remoteAddr = GetLSStackAddress(pNativeVarInfo->loc.vlStk.vlsBaseReg,
+ pNativeVarInfo->loc.vlStk.vlsOffset);
+
+ HRESULT hr = GetProcess()->SafeReadStruct(remoteAddr, &uResult);
+ IfFailThrow(hr);
+ }
+ else
+ {
+ ThrowHR(E_FAIL);
+ }
+
+ return uResult;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Looks in a register and retrieves the value as a specific type, returning it
+// as an ICorDebugValue.
+//
+// Arguments:
+// reg - The register to use.
+// cbSigBlob - The number of bytes in the signature given.
+// pvSigBlob - A signature stream that describes the type of the value in the register.
+// ppValue - OUT: Space to store the resulting ICorDebugValue
+//
+// Returns:
+// S_OK on success, else an error code.
+//
+HRESULT CordbNativeFrame::GetLocalRegisterValue(CorDebugRegister reg,
+ ULONG cbSigBlob,
+ PCCOR_SIGNATURE pvSigBlob,
+ ICorDebugValue ** ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(pvSigBlob, BYTE, cbSigBlob, true, false);
+
+ CordbType * pType;
+
+ SigParser sigParser(pvSigBlob, cbSigBlob);
+
+ Instantiation emptyInst;
+
+ HRESULT hr = CordbType::SigToType(m_JITILFrame->GetModule(), &sigParser, &emptyInst, &pType);
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ return GetLocalRegisterValue(reg, pType, ppValue);
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Looks in two registers and retrieves the value as a specific type, returning it
+// as an ICorDebugValue.
+//
+// Arguments:
+// highWordReg - The register to use for the high word.
+// lowWordReg - The register to use for the low word.
+// cbSigBlob - The number of bytes in the signature given.
+// pvSigBlob - A signature stream that describes the type of the value in the register.
+// ppValue - OUT: Space to store the resulting ICorDebugValue
+//
+// Returns:
+// S_OK on success, else an error code.
+//
+HRESULT CordbNativeFrame::GetLocalDoubleRegisterValue(CorDebugRegister highWordReg,
+ CorDebugRegister lowWordReg,
+ ULONG cbSigBlob,
+ PCCOR_SIGNATURE pvSigBlob,
+ ICorDebugValue ** ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ if (cbSigBlob == 0)
+ {
+ return E_INVALIDARG;
+ }
+
+ CordbType * pType;
+
+ SigParser sigParser(pvSigBlob, cbSigBlob);
+
+ Instantiation emptyInst;
+
+ HRESULT hr = CordbType::SigToType(m_JITILFrame->GetModule(), &sigParser, &emptyInst, &pType);
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ return GetLocalDoubleRegisterValue(highWordReg, lowWordReg, pType, ppValue);
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Uses an address and retrieves the value as a specific type, returning it
+// as an ICorDebugValue.
+//
+// Arguments:
+// address - A local memory address.
+// cbSigBlob - The number of bytes in the signature given.
+// pvSigBlob - A signature stream that describes the type of the value in the register.
+// ppValue - OUT: Space to store the resulting ICorDebugValue
+//
+// Returns:
+// S_OK on success, else an error code.
+//
+HRESULT CordbNativeFrame::GetLocalMemoryValue(CORDB_ADDRESS address,
+ ULONG cbSigBlob,
+ PCCOR_SIGNATURE pvSigBlob,
+ ICorDebugValue ** ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(pvSigBlob, BYTE, cbSigBlob, true, false);
+
+ CordbType * pType;
+
+ SigParser sigParser(pvSigBlob, cbSigBlob);
+
+ Instantiation emptyInst;
+
+ HRESULT hr = CordbType::SigToType(m_JITILFrame->GetModule(), &sigParser, &emptyInst, &pType);
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ return GetLocalMemoryValue(address, pType, ppValue);
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Uses a register and an address, retrieving the value as a specific type, returning it
+// as an ICorDebugValue.
+//
+// Arguments:
+// highWordReg - Register to use as the high word.
+// lowWordAddress - A local memory address containing the low word.
+// cbSigBlob - The number of bytes in the signature given.
+// pvSigBlob - A signature stream that describes the type of the value in the register.
+// ppValue - OUT: Space to store the resulting ICorDebugValue
+//
+// Returns:
+// S_OK on success, else an error code.
+//
+HRESULT CordbNativeFrame::GetLocalRegisterMemoryValue(CorDebugRegister highWordReg,
+ CORDB_ADDRESS lowWordAddress,
+ ULONG cbSigBlob,
+ PCCOR_SIGNATURE pvSigBlob,
+ ICorDebugValue ** ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ if (cbSigBlob == 0)
+ {
+ return E_INVALIDARG;
+ }
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(pvSigBlob, BYTE, cbSigBlob, true, true);
+
+ CordbType * pType;
+
+ SigParser sigParser(pvSigBlob, cbSigBlob);
+
+ Instantiation emptyInst;
+
+ HRESULT hr = CordbType::SigToType(m_JITILFrame->GetModule(), &sigParser, &emptyInst, &pType);
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ return GetLocalRegisterMemoryValue(highWordReg, lowWordAddress, pType, ppValue);
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Uses a register and an address, retrieving the value as a specific type, returning it
+// as an ICorDebugValue.
+//
+// Arguments:
+// highWordReg - A local memory address to use as the high word.
+// lowWordAddress - Register containing the low word.
+// cbSigBlob - The number of bytes in the signature given.
+// pvSigBlob - A signature stream that describes the type of the value in the register.
+// ppValue - OUT: Space to store the resulting ICorDebugValue
+//
+// Returns:
+// S_OK on success, else an error code.
+//
+HRESULT CordbNativeFrame::GetLocalMemoryRegisterValue(CORDB_ADDRESS highWordAddress,
+ CorDebugRegister lowWordRegister,
+ ULONG cbSigBlob,
+ PCCOR_SIGNATURE pvSigBlob,
+ ICorDebugValue ** ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ if (cbSigBlob == 0)
+ {
+ return E_INVALIDARG;
+ }
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(pvSigBlob, BYTE, cbSigBlob, true, true);
+
+ CordbType * pType;
+
+ SigParser sigParser(pvSigBlob, cbSigBlob);
+
+ Instantiation emptyInst;
+
+ HRESULT hr = CordbType::SigToType(m_JITILFrame->GetModule(), &sigParser, &emptyInst, &pType);
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ return GetLocalMemoryRegisterValue(highWordAddress, lowWordRegister, pType, ppValue);
+}
+
+
+
+HRESULT CordbNativeFrame::GetLocalRegisterValue(CorDebugRegister reg,
+ CordbType * pType,
+ ICorDebugValue **ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+#if defined(DBG_TARGET_X86) || defined(DBG_TARGET_WIN64)
+#if defined(DBG_TARGET_X86)
+ if ((reg >= REGISTER_X86_FPSTACK_0) && (reg <= REGISTER_X86_FPSTACK_7))
+#elif defined(DBG_TARGET_AMD64)
+ if ((reg >= REGISTER_AMD64_XMM0) && (reg <= REGISTER_AMD64_XMM15))
+#elif defined(DBG_TARGET_ARM64)
+ if ((reg >= REGISTER_ARM64_V0) && (reg <= REGISTER_ARM64_V31))
+#endif
+ {
+ return GetLocalFloatingPointValue(reg, pType, ppValue);
+ }
+#endif
+
+ // The address of the given register is the address of the value
+ // in this process. We have no remote address here.
+ void *pLocalValue = (void*)GetAddressOfRegister(reg);
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+ // Provide the register info as we create the value. CreateValueByType will transfer ownership of this to
+ // the new instance of CordbValue.
+ EnregisteredValueHomeHolder pRemoteReg(new RegValueHome(this, reg));
+ EnregisteredValueHomeHolder * pRegHolder = pRemoteReg.GetAddr();
+
+ ICorDebugValue *pValue;
+ CordbValue::CreateValueByType(GetCurrentAppDomain(),
+ pType,
+ false,
+ EMPTY_BUFFER,
+ MemoryRange(pLocalValue, REG_SIZE),
+ pRegHolder,
+ &pValue); // throws
+
+ *ppValue = pValue;
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+HRESULT CordbNativeFrame::GetLocalDoubleRegisterValue(
+ CorDebugRegister highWordReg,
+ CorDebugRegister lowWordReg,
+ CordbType * pType,
+ ICorDebugValue **ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // Provide the register info as we create the value. CreateValueByType will transfer ownership of this to
+ // the new instance of CordbValue.
+ EnregisteredValueHomeHolder pRemoteReg(new RegRegValueHome(this, highWordReg, lowWordReg));
+ EnregisteredValueHomeHolder * pRegHolder = pRemoteReg.GetAddr();
+
+ CordbValue::CreateValueByType(GetCurrentAppDomain(),
+ pType,
+ false,
+ EMPTY_BUFFER,
+ MemoryRange(NULL, 0),
+ pRegHolder,
+ ppValue); // throws
+ }
+ EX_CATCH_HRESULT(hr);
+
+#ifdef _DEBUG
+ {
+ // sanity check object size
+ if (SUCCEEDED(hr))
+ {
+ ULONG32 objectSize;
+ hr = (*ppValue)->GetSize(&objectSize);
+ _ASSERTE(SUCCEEDED(hr));
+ //
+ // nickbe
+ // 10/31/2002 11:09:42
+ //
+ // This assert assumes that the JIT will only partially enregister
+ // objects that have a size equal to twice the size of a register.
+ //
+ _ASSERTE(objectSize == 2 * sizeof(void*));
+ }
+ }
+#endif
+ return hr;
+}
+
+HRESULT
+CordbNativeFrame::GetLocalMemoryValue(CORDB_ADDRESS address,
+ CordbType * pType,
+ ICorDebugValue **ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ _ASSERTE(m_nativeCode->GetFunction() != NULL);
+ HRESULT hr = S_OK;
+
+ ICorDebugValue *pValue;
+ EX_TRY
+ {
+ CordbValue::CreateValueByType(GetCurrentAppDomain(),
+ pType,
+ false,
+ TargetBuffer(address, CordbValue::GetSizeForType(pType, kUnboxed)),
+ MemoryRange(NULL, 0),
+ NULL,
+ &pValue); // throws
+ }
+ EX_CATCH_HRESULT(hr);
+
+ if (SUCCEEDED(hr))
+ *ppValue = pValue;
+
+ return hr;
+}
+
+HRESULT
+CordbNativeFrame::GetLocalByRefMemoryValue(CORDB_ADDRESS address,
+ CordbType * pType,
+ ICorDebugValue **ppValue)
+{
+ INTERNAL_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ LPVOID actualAddress = NULL;
+ HRESULT hr = GetProcess()->SafeReadStruct(address, &actualAddress);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ return GetLocalMemoryValue(PTR_TO_CORDB_ADDRESS(actualAddress), pType, ppValue);
+}
+
+HRESULT
+CordbNativeFrame::GetLocalRegisterMemoryValue(CorDebugRegister highWordReg,
+ CORDB_ADDRESS lowWordAddress,
+ CordbType * pType,
+ ICorDebugValue **ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // Provide the register info as we create the value. CreateValueByType will transfer ownership of this to
+ // the new instance of CordbValue.
+ EnregisteredValueHomeHolder pRemoteReg(new RegMemValueHome(this,
+ highWordReg,
+ lowWordAddress));
+ EnregisteredValueHomeHolder * pRegHolder = pRemoteReg.GetAddr();
+
+ CordbValue::CreateValueByType(GetCurrentAppDomain(),
+ pType,
+ false,
+ EMPTY_BUFFER,
+ MemoryRange(NULL, 0),
+ pRegHolder,
+ ppValue); // throws
+ }
+ EX_CATCH_HRESULT(hr);
+
+#ifdef _DEBUG
+ {
+ if (SUCCEEDED(hr))
+ {
+ ULONG32 objectSize;
+ hr = (*ppValue)->GetSize(&objectSize);
+ _ASSERTE(SUCCEEDED(hr));
+ // See the comment in CordbNativeFrame::GetLocalDoubleRegisterValue
+ // for more information on this assertion
+ _ASSERTE(objectSize == 2 * sizeof(void*));
+ }
+ }
+#endif
+ return hr;
+}
+
+HRESULT
+CordbNativeFrame::GetLocalMemoryRegisterValue(CORDB_ADDRESS highWordAddress,
+ CorDebugRegister lowWordRegister,
+ CordbType * pType,
+ ICorDebugValue **ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // Provide the register info as we create the value. CreateValueByType will transfer ownership of this to
+ // the new instance of CordbValue.
+ EnregisteredValueHomeHolder pRemoteReg(new MemRegValueHome(this,
+ lowWordRegister,
+ highWordAddress));
+ EnregisteredValueHomeHolder * pRegHolder = pRemoteReg.GetAddr();
+
+ CordbValue::CreateValueByType(GetCurrentAppDomain(),
+ pType,
+ false,
+ EMPTY_BUFFER,
+ MemoryRange(NULL, 0),
+ pRegHolder,
+ ppValue); // throws
+ }
+ EX_CATCH_HRESULT(hr);
+
+#ifdef _DEBUG
+ {
+ if (SUCCEEDED(hr))
+ {
+ ULONG32 objectSize;
+ hr = (*ppValue)->GetSize(&objectSize);
+ _ASSERTE(SUCCEEDED(hr));
+ // See the comment in CordbNativeFrame::GetLocalDoubleRegisterValue
+ // for more information on this assertion
+ _ASSERTE(objectSize == 2 * sizeof(void*));
+ }
+ }
+#endif
+ return hr;
+}
+
+#if !defined(DBG_TARGET_ARM) // @ARMTODO
+HRESULT CordbNativeFrame::GetLocalFloatingPointValue(DWORD index,
+ CordbType * pType,
+ ICorDebugValue **ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ HRESULT hr = S_OK;
+
+ CorElementType et = pType->m_elementType;
+
+ if ((et != ELEMENT_TYPE_R4) &&
+ (et != ELEMENT_TYPE_R8))
+ return E_INVALIDARG;
+
+#if defined(DBG_TARGET_AMD64)
+ if (!((index >= REGISTER_AMD64_XMM0) &&
+ (index <= REGISTER_AMD64_XMM15)))
+ return E_INVALIDARG;
+ index -= REGISTER_AMD64_XMM0;
+#elif defined(DBG_TARGET_ARM64)
+ if (!((index >= REGISTER_ARM64_V0) &&
+ (index <= REGISTER_ARM64_V31)))
+ return E_INVALIDARG;
+ index -= REGISTER_ARM64_V0;
+#else
+ if (!((index >= REGISTER_X86_FPSTACK_0) &&
+ (index <= REGISTER_X86_FPSTACK_7)))
+ return E_INVALIDARG;
+ index -= REGISTER_X86_FPSTACK_0;
+#endif
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+
+ // Make sure the thread's floating point stack state is loaded
+ // over from the left side.
+ //
+ CordbThread *pThread = m_pThread;
+
+ EX_TRY
+ {
+ if (!pThread->m_fFloatStateValid)
+ {
+ pThread->LoadFloatState();
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ if (SUCCEEDED(hr))
+ {
+#if !defined(DBG_TARGET_WIN64)
+ // This is needed on x86 because we are dealing with a stack.
+ index = pThread->m_floatStackTop - index;
+#endif
+
+ if (index >= (sizeof(pThread->m_floatValues) /
+ sizeof(pThread->m_floatValues[0])))
+ return E_INVALIDARG;
+
+#ifdef DBG_TARGET_X86
+ // A workaround (sort of) to get around the difference in format between
+ // a float value and a double value. We can't simply cast a double pointer to
+ // a float pointer. Instead, we have to cast the double itself to a float.
+ if (pType->m_elementType == ELEMENT_TYPE_R4)
+ *(float *)&(pThread->m_floatValues[index]) = (float)pThread->m_floatValues[index];
+#endif
+
+ ICorDebugValue* pValue;
+
+ EX_TRY
+ {
+ // Provide the register info as we create the value. CreateValueByType will transfer ownership of this to
+ // the new instance of CordbValue.
+ EnregisteredValueHomeHolder pRemoteReg(new FloatRegValueHome(this, index));
+ EnregisteredValueHomeHolder * pRegHolder = pRemoteReg.GetAddr();
+
+ CordbValue::CreateValueByType(GetCurrentAppDomain(),
+ pType,
+ false,
+ EMPTY_BUFFER,
+ MemoryRange(&(pThread->m_floatValues[index]), sizeof(double)),
+ pRegHolder,
+ &pValue); // throws
+
+ *ppValue = pValue;
+ }
+ EX_CATCH_HRESULT(hr);
+
+ }
+
+ return hr;
+}
+#endif // !DBG_TARGET_ARM @ARMTODO
+
+//---------------------------------------------------------------------------------------
+//
+// Quick accessor to tell if we're the leaf frame.
+//
+// Return Value:
+// whether we are the leaf frame or not
+//
+
+bool CordbNativeFrame::IsLeafFrame() const
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ // Should only be called by non-neutered stuff.
+ // Also, since we're not neutered, we know we have a Thread object, and we know it's state is current.
+ _ASSERTE(!this->IsNeutered());
+
+ // If the thread's state is sleeping, then there's no frame below us, but we're actually
+ // not the leaf frame.
+ // @todo- consider having Sleep / Wait / Join be an ICDInternalFrame.
+ _ASSERTE(m_pThread != NULL); // not neutered, so should have a thread
+ if (m_pThread->IsThreadWaitingOrSleeping())
+ {
+ return false;
+ }
+
+ if (!m_optfIsLeafFrame.HasValue())
+ {
+ if (GetProcess()->GetShim() != NULL)
+ {
+ // In V2, the definition of "leaf frame" is the leaf frame in the leaf chain in the stackwalk.
+ PRIVATE_SHIM_CALLBACK_IN_THIS_SCOPE0(GetProcess());
+ ShimStackWalk * pSW = GetProcess()->GetShim()->LookupOrCreateShimStackWalk(m_pThread);
+
+ // check if there is any chain
+ if (pSW->GetChainCount() > 0)
+ {
+ // check if the leaf chain has any frame
+ if (pSW->GetChain(0)->GetLastFrameIndex() > 0)
+ {
+ CordbFrame * pCFrame = GetCordbFrameFromInterface(pSW->GetFrame(0));
+ CordbNativeFrame * pNFrame = pCFrame->GetAsNativeFrame();
+ if (pNFrame != NULL)
+ {
+ // check if the leaf frame in the leaf chain is "this"
+ if (CompareControlRegisters(GetContext(), pNFrame->GetContext()))
+ {
+ m_optfIsLeafFrame = TRUE;
+ }
+ }
+ }
+ }
+
+ if (!m_optfIsLeafFrame.HasValue())
+ {
+ m_optfIsLeafFrame = FALSE;
+ }
+ }
+ else
+ {
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ m_optfIsLeafFrame = (pDAC->IsLeafFrame(m_pThread->m_vmThreadToken, &m_context) == TRUE);
+ }
+ }
+ return m_optfIsLeafFrame.GetValue();
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Get the offset used to determine if a variable is live in a particular method frame.
+//
+// Return Value:
+// the offset used for inspection purposes
+//
+// Notes:
+// On WIN64, variables used in funclets are always homed on the stack. Morever, the variable lifetime
+// information only covers the parent method. The idea is that the variables which are live in a funclet
+// will be the variables which are live in the parent method at the offset at which the exception occurs.
+// Thus, to determine if a variable is live in a funclet frame, we need to use the offset of the parent
+// method frame at which the exception occurs.
+//
+
+SIZE_T CordbNativeFrame::GetInspectionIP()
+{
+#if defined(DBG_TARGET_WIN64) || defined(DBG_TARGET_ARM)
+ // On 64-bit, if this is a funclet, then return the offset of the parent method frame at which
+ // the exception occurs. Otherwise just return the normal offset.
+ return (IsFunclet() ? GetParentIP() : m_ip);
+#else
+ // Always return the normal offset on all other platforms.
+ return m_ip;
+#endif // DBG_TARGET_WIN64 || DBG_TARGET_ARM
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Return whether this is a funclet method frame.
+//
+// Return Value:
+// whether this is a funclet method frame.
+//
+
+bool CordbNativeFrame::IsFunclet()
+{
+#if defined(DBG_TARGET_WIN64) || defined(DBG_TARGET_ARM)
+ return (m_misc.parentIP != NULL);
+#else // !DBG_TARGET_WIN64 && !DBG_TARGET_ARM
+ return false;
+#endif // DBG_TARGET_WIN64 || DBG_TARGET_ARM
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Return whether this is a filter funclet method frame.
+//
+// Return Value:
+// whether this is a filter funclet method frame.
+//
+
+bool CordbNativeFrame::IsFilterFunclet()
+{
+#if defined(DBG_TARGET_WIN64) || defined(DBG_TARGET_ARM)
+ return (IsFunclet() && m_misc.fIsFilterFunclet);
+#else // !DBG_TARGET_WIN64 && !DBG_TARGET_ARM
+ return false;
+#endif // DBG_TARGET_WIN64
+}
+
+
+#if defined(DBG_TARGET_WIN64) || defined(DBG_TARGET_ARM)
+//---------------------------------------------------------------------------------------
+//
+// Return the offset of the parent method frame at which the exception occurs.
+//
+// Return Value:
+// the offset of the parent method frame at which the exception occurs
+//
+
+SIZE_T CordbNativeFrame::GetParentIP()
+{
+ return m_misc.parentIP;
+}
+#endif // DBG_TARGET_WIN64 || DBG_TARGET_ARM
+
+// Accessor for the shim private hook code:CordbThread::ConvertFrameForILMethodWithoutMetadata.
+// Refer to that function for comments on the return value, the argument, etc.
+BOOL CordbNativeFrame::ConvertNativeFrameForILMethodWithoutMetadata(
+ ICorDebugInternalFrame2 ** ppInternalFrame2)
+{
+ _ASSERTE(ppInternalFrame2 != NULL);
+ *ppInternalFrame2 = NULL;
+
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ IDacDbiInterface::DynamicMethodType type =
+ pDAC->IsILStubOrLCGMethod(GetNativeCode()->GetVMNativeCodeMethodDescToken());
+
+ // Here are the conversion rules:
+ // 1) For a normal managed method, we don't convert, and we return FALSE.
+ // 2) For an IL stub, we convert to NULL, and we return TRUE.
+ // 3) For a dynamic method, we convert to a STUBFRAME_LIGHTWEIGHT_FUNCTION, and we return TRUE.
+ if (type == IDacDbiInterface::kNone)
+ {
+ return FALSE;
+ }
+ else if (type == IDacDbiInterface::kILStub)
+ {
+ return TRUE;
+ }
+ else if (type == IDacDbiInterface::kLCGMethod)
+ {
+ RSInitHolder<CordbInternalFrame> pInternalFrame(
+ new CordbInternalFrame(m_pThread,
+ m_fp,
+ m_currentAppDomain,
+ STUBFRAME_LIGHTWEIGHT_FUNCTION,
+ GetNativeCode()->GetMetadataToken(),
+ GetNativeCode()->GetFunction(),
+ GetNativeCode()->GetVMNativeCodeMethodDescToken()));
+
+ pInternalFrame.TransferOwnershipExternal(ppInternalFrame2);
+ return TRUE;
+ }
+
+ UNREACHABLE();
+}
+
+/* ------------------------------------------------------------------------- *
+ * JIT-IL Frame class
+ * ------------------------------------------------------------------------- */
+
+CordbJITILFrame::CordbJITILFrame(CordbNativeFrame * pNativeFrame,
+ CordbILCode * pCode,
+ UINT_PTR ip,
+ CorDebugMappingResult mapping,
+ GENERICS_TYPE_TOKEN exactGenericArgsToken,
+ DWORD dwExactGenericArgsTokenIndex,
+ bool fVarArgFnx,
+ CordbReJitILCode * pRejitCode)
+ : CordbBase(pNativeFrame->GetProcess(), 0, enumCordbJITILFrame),
+ m_nativeFrame(pNativeFrame),
+ m_ilCode(pCode),
+ m_ip(ip),
+ m_mapping(mapping),
+ m_fVarArgFnx(fVarArgFnx),
+ m_allArgsCount(0),
+ m_rgbSigParserBuf(NULL),
+ m_FirstArgAddr(NULL),
+ m_rgNVI(NULL),
+ m_genericArgs(),
+ m_genericArgsLoaded(false),
+ m_frameParamsToken(exactGenericArgsToken),
+ m_dwFrameParamsTokenIndex(dwExactGenericArgsTokenIndex),
+ m_pReJitCode(pRejitCode)
+{
+ // We'll initialize the SigParser in CordbJITILFrame::Init().
+ m_sigParserCached = SigParser(NULL, 0);
+ _ASSERTE(m_sigParserCached.IsNull());
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ m_nativeFrame->m_pThread->GetRefreshStackNeuterList()->Add(GetProcess(), this);
+ }
+ EX_CATCH_HRESULT(hr);
+ SetUnrecoverableIfFailed(GetProcess(), hr);
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Initialize a CordbJITILFrame object. Must be called after allocating the object and before using it.
+// If Init fails, then destroy the object and release the memory.
+//
+// Return Value:
+// HRESULT for the operation
+//
+// Notes:
+// This is a nop if the function is not a vararg function.
+//
+
+HRESULT CordbJITILFrame::Init()
+{
+ // ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ HRESULT hr = S_OK;
+
+
+ EX_TRY
+ {
+ _ASSERTE(m_ilCode != NULL);
+
+ if (m_fVarArgFnx)
+ {
+ // First, we need to find the VASigCookie. Use the native var info to do so.
+ const ICorDebugInfo::NativeVarInfo * pNativeVarInfo = NULL;
+ CordbNativeFrame * pNativeFrame = this->m_nativeFrame;
+
+ pNativeFrame->m_nativeCode->LoadNativeInfo();
+ hr = pNativeFrame->m_nativeCode->ILVariableToNative((DWORD)ICorDebugInfo::VARARGS_HND_ILNUM,
+ pNativeFrame->GetInspectionIP(),
+ &pNativeVarInfo);
+ IfFailThrow(hr);
+
+ // Check for the case where the VASigCookie isn't pushed on the stack yet.
+ // This should only be a problem with optimized code.
+ if (pNativeVarInfo->loc.vlType != ICorDebugInfo::VLT_STK)
+ {
+ ThrowHR(E_FAIL);
+ }
+
+ // Retrieve the target address.
+ CORDB_ADDRESS pRemoteValue = pNativeFrame->GetLSStackAddress(
+ pNativeVarInfo->loc.vlStk.vlsBaseReg,
+ pNativeVarInfo->loc.vlStk.vlsOffset);
+
+ CORDB_ADDRESS argBase;
+ // Now is the time to ask DacDbi to retrieve the information based on the VASigCookie.
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ TargetBuffer sigTargetBuf = pDAC->GetVarArgSig(pRemoteValue, &argBase);
+
+ // make sure we are not leaking any memory
+ _ASSERTE(m_rgbSigParserBuf == NULL);
+
+ m_rgbSigParserBuf = new BYTE[sigTargetBuf.cbSize];
+ GetProcess()->SafeReadBuffer(sigTargetBuf, m_rgbSigParserBuf);
+ m_sigParserCached = SigParser(m_rgbSigParserBuf, sigTargetBuf.cbSize);
+
+ // Note that we should never mutate the SigParser.
+ // Instead, make a copy and work with the copy instead.
+ if (!m_sigParserCached.IsNull())
+ {
+ SigParser sigParser = m_sigParserCached;
+
+ // get the actual count of arguments, including the var args
+ IfFailThrow(sigParser.SkipMethodHeaderSignature(&m_allArgsCount));
+
+ BOOL methodIsStatic;
+
+ m_ilCode->GetSig(NULL, NULL, &methodIsStatic); // throws
+
+ if (!methodIsStatic)
+ {
+ m_allArgsCount++; // skip the "this" object
+ }
+
+ // initialize the variable lifetime information
+ m_rgNVI = new ICorDebugInfo::NativeVarInfo[m_allArgsCount]; // throws
+
+ _ASSERTE(ICorDebugInfo::VLT_COUNT <= ICorDebugInfo::VLT_INVALID);
+
+ for (ULONG i = 0; i < m_allArgsCount; i++)
+ {
+ m_rgNVI[i].loc.vlType = ICorDebugInfo::VLT_INVALID;
+ }
+ }
+
+ // GetVarArgSig gets the address of the beginning of the arguments pushed for this frame.
+ // We'll need the address of the first argument, which will depend on its size and the
+ // calling convention, so we'll commpute that now that we have the SigParser.
+ CordbType * pArgType;
+ IfFailThrow(GetArgumentType(0, &pArgType));
+ ULONG32 argSize = 0;
+ IfFailThrow(pArgType->GetUnboxedObjectSize(&argSize));
+#if defined(_TARGET_X86_) // (STACK_GROWS_DOWN_ON_ARGS_WALK)
+ m_FirstArgAddr = argBase - argSize;
+#else // !_TARGET_X86_ (STACK_GROWS_UP_ON_ARGS_WALK)
+ AlignAddressForType(pArgType, argBase);
+ m_FirstArgAddr = argBase;
+#endif // !_TARGET_X86_ (STACK_GROWS_UP_ON_ARGS_WALK)
+ }
+
+ // The stackwalking code can't always successfully retrieve the generics type token.
+ // For example, on 64-bit, the JIT only encodes the generics type token location if
+ // a method has catch clause for a generic exception (e.g. "catch(MyException<string> e)").
+ if ((m_dwFrameParamsTokenIndex != (DWORD)ICorDebugInfo::MAX_ILNUM) && (m_frameParamsToken == NULL))
+ {
+ // All variables are unavailable in the prolog and the epilog.
+ // This includes the generics type token. Failing to get the token just means that
+ // we won't have full generics information. This should not be a disastrous failure.
+ //
+ // Currently, on X64, the JIT is reporting that the variables are live even in the epilog.
+ // That's why we need this check here. I need to follow up on this.
+ if ((m_mapping != MAPPING_PROLOG) && (m_mapping != MAPPING_EPILOG))
+ {
+ // Find the generics type token using the variable lifetime information.
+ const ICorDebugInfo::NativeVarInfo * pNativeVarInfo = NULL;
+ CordbNativeFrame * pNativeFrame = this->m_nativeFrame;
+
+ pNativeFrame->m_nativeCode->LoadNativeInfo();
+ HRESULT hrTmp = pNativeFrame->m_nativeCode->ILVariableToNative(m_dwFrameParamsTokenIndex,
+ pNativeFrame->GetInspectionIP(),
+ &pNativeVarInfo);
+
+ // It's not a disaster if we can't find the generics token, so don't throw an exception here.
+ // In fact, it's fairly common in retail code. Even if we can't find the generics token,
+ // we may still be able to look up the generics type information later by using the MethodDesc,
+ // the "this" object, etc. If not, we'll at least get the representative type information
+ // (e.g. Foo<T> instead of Foo<string>).
+ if (SUCCEEDED(hrTmp))
+ {
+ _ASSERTE(pNativeVarInfo != NULL);
+
+ // The generics type token should be stored either in a register or on the stack.
+ SIZE_T uRawToken = pNativeFrame->GetRegisterOrStackValue(pNativeVarInfo);
+
+ // Ask DAC to resolve the token for us. We really don't want to deal with all the logic here.
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+ // On a minidump, we'll throw if we're missing the memory.
+ ALLOW_DATATARGET_MISSING_MEMORY(
+ m_frameParamsToken = pDAC->ResolveExactGenericArgsToken(m_dwFrameParamsTokenIndex, uRawToken);
+ );
+ }
+ }
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+/*
+ A list of which resources owned by this object are accounted for.
+
+ UNKNOWN:
+ CordbNativeFrame* m_nativeFrame;
+ CordbILCode * m_ilCode;
+ CorDebugMappingResult m_mapping;
+ CORDB_ADDRESS m_FirstArgAddr;
+ ICorDebugInfo::NativeVarInfo * m_rgNVI; // Deleted in neuter
+ CordbClass **m_genericArgs;
+*/
+
+CordbJITILFrame::~CordbJITILFrame()
+{
+ _ASSERTE(IsNeutered());
+}
+
+// Neutered by CordbNativeFrame
+void CordbJITILFrame::Neuter()
+{
+ // Since neutering here calls Release directly, we don't want to double-release
+ // if neuter is called multiple times.
+ if (IsNeutered())
+ {
+ return;
+ }
+
+ // Frames include pointers across to other types that specify the
+ // representation instantiation - reduce the reference counts on these....
+ for (unsigned int i = 0; i < m_genericArgs.m_cInst; i++)
+ {
+ m_genericArgs.m_ppInst[i]->Release();
+ }
+
+ if (m_rgNVI != NULL)
+ {
+ delete [] m_rgNVI;
+ m_rgNVI = NULL;
+ }
+
+ if (m_rgbSigParserBuf != NULL)
+ {
+ delete [] m_rgbSigParserBuf;
+ m_rgbSigParserBuf = NULL;
+ }
+
+ m_pReJitCode.Clear();
+
+ // If this class ever inherits from the CordbFrame we'll need a call
+ // to CordbFrame::Neuter() here instead of to CordbBase::Neuter();
+ CordbBase::Neuter();
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Load the generic type and method arguments and store them into the frame if possible.
+//
+// Return Value:
+// HRESULT for the operation
+//
+
+void CordbJITILFrame::LoadGenericArgs()
+{
+ THROW_IF_NEUTERED(this);
+ INTERNAL_SYNC_API_ENTRY(GetProcess()); //
+
+ // The case where there are no type parameters, or the case where we've
+ // already feched the realInst, is easy.
+ if (m_genericArgsLoaded)
+ {
+ return;
+ }
+
+ _ASSERTE(m_nativeFrame->m_nativeCode != NULL);
+
+ if (!m_nativeFrame->m_nativeCode->IsInstantiatedGeneric())
+ {
+ m_genericArgs = Instantiation(0, NULL,0);
+ m_genericArgsLoaded = true;
+ return;
+ }
+
+ // Find the exact generic arguments for a frame that is executing
+ // a generic method. The left-side will fetch these from arguments
+ // given on the stack and/or from the IP.
+
+
+ IDacDbiInterface * pDAC = GetProcess()->GetDAC();
+
+ UINT32 cGenericClassTypeParams = 0;
+ DacDbiArrayList<DebuggerIPCE_ExpandedTypeData> rgGenericTypeParams;
+
+ pDAC->GetMethodDescParams(GetCurrentAppDomain()->GetADToken(),
+ m_nativeFrame->GetNativeCode()->GetVMNativeCodeMethodDescToken(),
+ m_frameParamsToken,
+ &cGenericClassTypeParams,
+ &rgGenericTypeParams);
+
+ UINT32 cTotalGenericTypeParams = rgGenericTypeParams.Count();
+
+ // @dbgtodo reliability - This holder doesn't actually work in this case because it just deletes
+ // each element on error. The RS classes are all expected to be neutered before the destructor is called.
+ NewArrayHolder<CordbType *> ppGenericArgs(new CordbType *[cTotalGenericTypeParams]);
+
+ for (UINT32 i = 0; i < cTotalGenericTypeParams;i++)
+ {
+ // creates a CordbType object for the generic argument
+ HRESULT hr = CordbType::TypeDataToType(GetCurrentAppDomain(),
+ &(rgGenericTypeParams[i]),
+ &ppGenericArgs[i]);
+ IfFailThrow(hr);
+
+ // We add a ref as the instantiation will be stored away in the
+ // ref-counted data structure associated with the JITILFrame
+ ppGenericArgs[i]->AddRef();
+ }
+
+ // initialize the generics information
+ m_genericArgs = Instantiation(cTotalGenericTypeParams, ppGenericArgs, cGenericClassTypeParams);
+ m_genericArgsLoaded = true;
+
+ ppGenericArgs.SuppressRelease();
+}
+
+
+//
+// CordbJITILFrame::QueryInterface
+//
+// Description
+// Interface query for this COM object
+//
+// NOTE: the COM object associated with this CordbJITILFrame may consist of two
+// C++ objects (a CordbJITILFrame and its associated CordbNativeFrame)
+//
+// Parameters
+// id the GUID associated with the requested interface
+// pInterface [out] the interface pointer
+//
+// Returns
+// HRESULT
+// S_OK If this CordbJITILFrame supports the interface
+// E_NOINTERFACE If this object does not support the interface
+//
+// Exceptions
+// None
+//
+HRESULT CordbJITILFrame::QueryInterface(REFIID id, void **pInterface)
+{
+ if (NULL != m_nativeFrame)
+ {
+ // If the native frame does not support the requested interface, then
+ // the native fram is responsible for delegating the query back to this
+ // object through QueryInterfaceInternal(...)
+ return m_nativeFrame->QueryInterface(id, pInterface);
+ }
+
+ // no native frame. Check for interfaces common to CordbNativeFrame and
+ // CordbJITILFrame
+ if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugILFrame*>(this));
+ }
+ else if (id == IID_ICorDebugFrame)
+ {
+ *pInterface = static_cast<ICorDebugFrame*>(this);
+ }
+ else
+ {
+ // didn't find an interface yet. Since there's no native frame
+ // associated with this IL frame, go ahead and check for the IL frame
+ return this->QueryInterfaceInternal(id, pInterface);
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+//
+// CordbJITILFrame::QueryInterfaceInternal
+//
+// Description
+// Interface query for interfaces implemented ONLY by CordbJITILFrame (as
+// opposed to interfaces implemented by both CordbNativeFrame and
+// CordbJITILFrame)
+//
+// Parameters
+// id the GUID associated with the requested interface
+// pInterface [out] the interface pointer
+// NOTE: id must not be IUnknown or ICorDebugFrame
+// NOTE: if this object is in "forward compatibility mode", passing in
+// IID_ICorDebugILFrame2 for the id will result in a failure (returns
+// E_NOINTERFACE)
+//
+// Returns
+// HRESULT
+// S_OK If this CordbJITILFrame supports the interface
+// E_NOINTERFACE If this object does not support the interface
+//
+// Exceptions
+// None
+//
+HRESULT
+CordbJITILFrame::QueryInterfaceInternal(REFIID id, void** pInterface)
+{
+ _ASSERTE(IID_ICorDebugFrame != id);
+ _ASSERTE(IID_IUnknown != id);
+
+ // don't query for IUnknown or ICorDebugFrame! Someone else should have
+ // already taken care of that.
+ if (id == IID_ICorDebugILFrame)
+ {
+ *pInterface = static_cast<ICorDebugILFrame*>(this);
+ }
+ else if (id == IID_ICorDebugILFrame2)
+ {
+ *pInterface = static_cast<ICorDebugILFrame2*>(this);
+ }
+ else if (id == IID_ICorDebugILFrame3)
+ {
+ *pInterface = static_cast<ICorDebugILFrame3*>(this);
+ }
+ else if (id == IID_ICorDebugILFrame4)
+ {
+ *pInterface = static_cast<ICorDebugILFrame4*>(this);
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Get an enumerator for the generic type and method arguments on this frame.
+//
+// Arguments:
+// ppTypeParameterEnum - out parameter; return the enumerator
+//
+// Return Value:
+// HRESULT for the operation
+//
+HRESULT CordbJITILFrame::EnumerateTypeParameters(ICorDebugTypeEnum **ppTypeParameterEnum)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppTypeParameterEnum, ICorDebugTypeEnum **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ (*ppTypeParameterEnum) = NULL;
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+
+ // load the generic arguments, which may be cached
+ LoadGenericArgs();
+
+ // create the enumerator
+ RSInitHolder<CordbTypeEnum> pEnum(
+ CordbTypeEnum::Build(GetCurrentAppDomain(), m_nativeFrame->m_pThread->GetRefreshStackNeuterList(), m_genericArgs.m_cInst, m_genericArgs.m_ppInst));
+ if ( pEnum == NULL )
+ {
+ ThrowOutOfMemory();
+ }
+
+ pEnum.TransferOwnershipExternal(ppTypeParameterEnum);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+
+// ----------------------------------------------------------------------------
+// CordbJITILFrame::GetChain
+//
+// Description:
+// Return the owning chain. Since chains have been deprecated in Arrowhead,
+// this function returns E_NOTIMPL unless there is a shim.
+//
+// Arguments:
+// * ppChain - out parameter; return the owning chain
+//
+// Return Value:
+// Return S_OK on success.
+// Return E_INVALIDARG if ppChain is NULL.
+// Return CORDBG_E_OBJECT_NEUTERED if the CordbJITILFrame is neutered.
+// Return E_NOTIMPL if there is no shim.
+//
+
+HRESULT CordbJITILFrame::GetChain(ICorDebugChain **ppChain)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppChain, ICorDebugChain **);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ hr = m_nativeFrame->GetChain(ppChain);
+ // Since we are returning anyway, let's not throw even if the call fails.
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+// Return the IL code blob associated with this IL frame.
+// Each IL frame corresponds to exactly one IL code blob.
+HRESULT CordbJITILFrame::GetCode(ICorDebugCode **ppCode)
+{
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppCode, ICorDebugCode **);
+
+ *ppCode = static_cast<ICorDebugCode*> (m_ilCode);
+ m_ilCode->ExternalAddRef();
+
+ return S_OK;;
+}
+
+// Return the function associated with this IL frame.
+// Each IL frame corresponds to exactly one function.
+HRESULT CordbJITILFrame::GetFunction(ICorDebugFunction **ppFunction)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this)
+ {
+ ValidateOrThrow(ppFunction);
+
+ CordbFunction * pFunc = m_nativeFrame->GetFunction();
+ *ppFunction = static_cast<ICorDebugFunction *>(pFunc);
+ pFunc->ExternalAddRef();
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+// Return the token of the function associated with this IL frame.
+// Each IL frame corresponds to exactly one function.
+HRESULT CordbJITILFrame::GetFunctionToken(mdMethodDef *pToken)
+{
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pToken, mdMethodDef *);
+
+ *pToken = m_nativeFrame->m_nativeCode->GetMetadataToken();
+
+ return S_OK;
+}
+
+// ----------------------------------------------------------------------------
+// CordJITILFrame::GetStackRange
+//
+// Description:
+// Get the stack range owned by the associated native frame.
+// IL frames and native frames are 1:1 for normal jitted managed methods.
+// Dynamic methods are an exception.
+//
+// Arguments:
+// * pStart - out parameter; return the leaf end of the frame
+// * pEnd - out parameter; return the root end of the frame
+//
+// Return Value:
+// Return S_OK on success.
+//
+// Notes: see code:#GetStackRange
+
+HRESULT CordbJITILFrame::GetStackRange(CORDB_ADDRESS *pStart, CORDB_ADDRESS *pEnd)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+
+ // The access of m_nativeFrame is not safe here. It's a weak reference.
+ OK_IF_NEUTERED(this);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ hr = m_nativeFrame->GetStackRange(pStart, pEnd);
+ // Since we are returning anyway, let's not throw even if the call fails.
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+// ----------------------------------------------------------------------------
+// CordbJITILFrame::GetCaller
+//
+// Description:
+// Delegate to the associated native frame to return the caller, which is closer to the root.
+// This function has been deprecated in Arrowhead, and so it returns E_NOTIMPL unless there is a shim.
+//
+// Arguments:
+// * ppFrame - out parameter; return the caller frame
+//
+// Return Value:
+// Return S_OK on success.
+// Return E_INVALIDARG if ppFrame is NULL.
+// Return CORDBG_E_OBJECT_NEUTERED if the CordbJITILFrame is neutered.
+// Return E_NOTIMPL if there is no shim.
+//
+
+HRESULT CordbJITILFrame::GetCaller(ICorDebugFrame **ppFrame)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppFrame, ICorDebugFrame **);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ hr = m_nativeFrame->GetCaller(ppFrame);
+ // Since we are returning anyway, let's not throw even if the call fails.
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+// ----------------------------------------------------------------------------
+// CordbJITILFrame::GetCallee
+//
+// Description:
+// Delegate to the associated native frame to return the callee, which is closer to the leaf.
+// This function has been deprecated in Arrowhead, and so it returns E_NOTIMPL unless there is a shim.
+//
+// Arguments:
+// * ppFrame - out parameter; return the callee frame
+//
+// Return Value:
+// Return S_OK on success.
+// Return E_INVALIDARG if ppFrame is NULL.
+// Return CORDBG_E_OBJECT_NEUTERED if the CordbJITILFrame is neutered.
+// Return E_NOTIMPL if there is no shim.
+//
+
+HRESULT CordbJITILFrame::GetCallee(ICorDebugFrame **ppFrame)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppFrame, ICorDebugFrame **);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ hr = m_nativeFrame->GetCallee(ppFrame);
+ // Since we are returning anyway, let's not throw even if the call fails.
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+// Create a stepper on the frame.
+HRESULT CordbJITILFrame::CreateStepper(ICorDebugStepper **ppStepper)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ // by default, a stepper operates on the IL level, using IL offsets
+ return m_nativeFrame->CreateStepper(ppStepper);
+}
+
+// Return the IL offset and the mapping result.
+HRESULT CordbJITILFrame::GetIP(ULONG32 *pnOffset,
+ CorDebugMappingResult *pMappingResult)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pnOffset, ULONG32 *);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pMappingResult, CorDebugMappingResult *);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ *pnOffset = (ULONG32)m_ip;
+ if (pMappingResult)
+ *pMappingResult = m_mapping;
+
+ return S_OK;
+}
+
+// Determine if we can set IP at this point. The specified offset is the IL offset.
+HRESULT CordbJITILFrame::CanSetIP(ULONG32 nOffset)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // Check to see that this is a leaf frame
+ if (!m_nativeFrame->IsLeafFrame())
+ {
+ ThrowHR(CORDBG_E_SET_IP_NOT_ALLOWED_ON_NONLEAF_FRAME);
+ }
+
+ // delegate to the associated native frame
+ CordbNativeCode * pNativeCode = m_nativeFrame->m_nativeCode;
+ hr = m_nativeFrame->m_pThread->SetIP(SetIP_fCanSetIPOnly, // specify that this is for checking only
+ pNativeCode,
+ nOffset,
+ SetIP_fIL );
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+// Try to set the IP to the specified offset. The specified offset is the IL offset.
+HRESULT CordbJITILFrame::SetIP(ULONG32 nOffset)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // Check to see that this is a leaf frame
+ if (!m_nativeFrame->IsLeafFrame())
+ {
+ ThrowHR(CORDBG_E_SET_IP_NOT_ALLOWED_ON_NONLEAF_FRAME);
+ }
+
+ // delegate to the native frame
+ CordbNativeCode * pNativeCode = m_nativeFrame->m_nativeCode;
+ hr = m_nativeFrame->m_pThread->SetIP(SetIP_fSetIP, // specify that this is a real SetIP operation
+ pNativeCode,
+ nOffset,
+ SetIP_fIL );
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// This routine creates backing native info for a local variable, returning an ICorDebugInfo
+// object for the local variable when successful.
+//
+// Arguments:
+// dwIndex - Index of the local variable to create native info for.
+// ppNativeInfo - OUT: Space for storing the resulting pointer to native variable info.
+//
+// Return Value:
+// HRESULT for the operation
+//
+HRESULT CordbJITILFrame::FabricateNativeInfo(DWORD dwIndex,
+ const ICorDebugInfo::NativeVarInfo ** ppNativeInfo)
+{
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ THROW_IF_NEUTERED(this);
+ INTERNAL_SYNC_API_ENTRY(this->GetProcess());
+ _ASSERTE(m_fVarArgFnx);
+
+ // This array should have been populated in CordbJITILFrame::Init().
+ _ASSERTE(m_rgNVI != NULL);
+
+ // check if we have already fabricated all the information
+ if (m_rgNVI[dwIndex].loc.vlType != ICorDebugInfo::VLT_INVALID)
+ {
+ (*ppNativeInfo) = &m_rgNVI[dwIndex];
+ }
+ else
+ {
+ // We'll initialize everything at once
+ ULONG cbArchitectureMin;
+
+ // m_FirstArgAddr will already be aligned on platforms that require alignment
+ CORDB_ADDRESS rpCur = m_FirstArgAddr;
+
+#if defined(DBG_TARGET_X86) || defined(DBG_TARGET_ARM)
+ cbArchitectureMin = 4;
+#elif defined(DBG_TARGET_WIN64)
+ cbArchitectureMin = 8;
+#else
+ cbArchitectureMin = 8; //REVISIT_TODO not sure if this is correct
+ PORTABILITY_ASSERT("What is the architecture-dependent minimum word size?");
+#endif // DBG_TARGET_X86
+
+ // make a copy of the cached SigParser
+ SigParser sigParser = m_sigParserCached;
+
+ IfFailThrow(sigParser.SkipMethodHeaderSignature(NULL));
+
+ ULONG32 cbType;
+
+ CordbType * pArgType;
+
+ // make sure all the generic type and method arguments are loaded
+ LoadGenericArgs();
+
+ // get a CordbType object for the generic argument
+ IfFailThrow(CordbType::SigToType(GetModule(), &sigParser, &(this->m_genericArgs), &pArgType));
+
+ IfFailThrow(pArgType->GetUnboxedObjectSize(&cbType));
+
+#if defined(DBG_TARGET_X86) // STACK_GROWS_DOWN_ON_ARGS_WALK
+ // The the rpCur pointer starts off in the right spot for the
+ // first argument, but thereafter we have to decrement it
+ // before getting the variable's location from it. So increment
+ // it here to be consistent later.
+ rpCur += max(cbType, cbArchitectureMin);
+#endif
+
+ // Grab the IL code's function's method signature so we can see if it's static.
+ BOOL fMethodIsStatic;
+
+ m_ilCode->GetSig(NULL, NULL, &fMethodIsStatic); // throws
+
+ ULONG i;
+
+ if (fMethodIsStatic)
+ {
+ i = 0;
+ }
+ else
+ {
+ i = 1;
+ }
+
+ for ( ; i < m_allArgsCount; i++)
+ {
+ m_rgNVI[i].startOffset = 0;
+ m_rgNVI[i].endOffset = 0xFFffFFff;
+ m_rgNVI[i].varNumber = i;
+ m_rgNVI[i].loc.vlType = ICorDebugInfo::VLT_FIXED_VA;
+
+ LoadGenericArgs();
+
+ IfFailThrow(CordbType::SigToType(GetModule(), &sigParser, &(this->m_genericArgs), &pArgType));
+
+ IfFailThrow(pArgType->GetUnboxedObjectSize(&cbType));
+
+#if defined(DBG_TARGET_X86) // STACK_GROWS_DOWN_ON_ARGS_WALK
+ rpCur -= max(cbType, cbArchitectureMin);
+ m_rgNVI[i].loc.vlFixedVarArg.vlfvOffset =
+ (unsigned)(m_FirstArgAddr - rpCur);
+
+ // Since the JIT adds in the size of this field, we do too to
+ // be consistent.
+ m_rgNVI[i].loc.vlFixedVarArg.vlfvOffset += sizeof(((CORINFO_VarArgInfo*)0)->argBytes);
+#else // STACK_GROWS_UP_ON_ARGS_WALK
+ m_rgNVI[i].loc.vlFixedVarArg.vlfvOffset =
+ (unsigned)(rpCur - m_FirstArgAddr);
+ rpCur += max(cbType, cbArchitectureMin);
+ AlignAddressForType(pArgType, rpCur);
+#endif
+
+ IfFailThrow(sigParser.SkipExactlyOne());
+ } // for ( ; i M m_allArgsCount; i++)
+
+ (*ppNativeInfo) = &m_rgNVI[dwIndex];
+ } // else (m_rgNVI[dwIndex].loc.vlType == ICorDebugInfo::VLT_INVALID)
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+HRESULT CordbJITILFrame::ILVariableToNative(DWORD dwVarNumber,
+ const ICorDebugInfo::NativeVarInfo **ppNativeInfo)
+{
+ FAIL_IF_NEUTERED(this);
+ INTERNAL_SYNC_API_ENTRY(GetProcess()); //
+
+ _ASSERTE(m_nativeFrame->m_nativeCode->IsNativeCodeValid());
+ // We keep the fixed argument native var infos in the
+ // CordbFunction, which only is an issue for var args info:
+ if (!m_fVarArgFnx || //not a var args function
+ (dwVarNumber < m_nativeFrame->m_nativeCode->GetFixedArgCount()) || // var args,fixed arg
+ // note that this include the implicit 'this' for nonstatic fnxs
+ (dwVarNumber >= m_allArgsCount) ||// var args, local variable
+ (m_sigParserCached.IsNull())) //we don't have any VA info
+ {
+ // If we're in a var args fnx, but we're actually looking
+ // for a local variable, then we want to use the variable
+ // index as the function sees it - fixed (but not var)
+ // args are added to local var number to get native info
+ // We are really trying to find a variable by it's number,
+ // but "special" variables have a negative number which we
+ // don't use. We "number" them conceptually between the
+ // arguments and locals:
+ //
+ // arguments special locals
+ // -----------------------------------------
+ // Actual numbers: 1 2 3 . . . 4 5 6 7
+ // Logical numbers: 0 1 2 3 4 5 6 7 8
+ //
+ // We have two different counts for the number of arguments: the fixedArgCount
+ // gives the actual number of arguments and the allArgsCount is the number of
+ // of fixed arguments plus the number of var args.
+ //
+ // Thus, to get the correct actual number for locals we have to compute it as
+ // logicalNumber - allArgsCount + fixedArgCount
+
+ if (m_fVarArgFnx && (dwVarNumber >= m_allArgsCount) && !m_sigParserCached.IsNull())
+ {
+ dwVarNumber -= m_allArgsCount;
+ dwVarNumber += m_nativeFrame->m_nativeCode->GetFixedArgCount();
+ }
+
+ return m_nativeFrame->m_nativeCode->ILVariableToNative(dwVarNumber,
+ m_nativeFrame->GetInspectionIP(),
+ ppNativeInfo);
+ }
+
+ return FabricateNativeInfo(dwVarNumber,ppNativeInfo);
+}
+
+//---------------------------------------------------------------------------------------
+//
+// This routine get the type of a particular argument.
+//
+// Arguments:
+// dwIndex - Index of the argument.
+// ppResultType - OUT: Space for storing the type of the argument.
+//
+// Return Value:
+// HRESULT for the operation
+//
+HRESULT CordbJITILFrame::GetArgumentType(DWORD dwIndex,
+ CordbType ** ppResultType)
+{
+ HRESULT hr = S_OK;
+ THROW_IF_NEUTERED(this);
+ INTERNAL_SYNC_API_ENTRY(GetProcess());
+
+ LoadGenericArgs();
+
+ if (m_fVarArgFnx && !m_sigParserCached.IsNull())
+ {
+ SigParser sigParser = m_sigParserCached;
+
+ IfFailThrow(sigParser.SkipMethodHeaderSignature(NULL));
+
+ // Grab the IL code's function's method signature so we can see if it's static.
+ BOOL fMethodIsStatic;
+
+ m_ilCode->GetSig(NULL, NULL, &fMethodIsStatic); // throws
+ if (!fMethodIsStatic)
+ {
+ if (dwIndex == 0)
+ {
+ // Return the signature for the 'this' pointer for the
+ // class this method is in.
+
+ IfFailThrow(m_ilCode->GetClass()->GetThisType(&(this->m_genericArgs), ppResultType));
+ return hr;
+ }
+ else
+ {
+ dwIndex--;
+ }
+ }
+ for (ULONG i = 0; i < dwIndex; i++)
+ {
+ IfFailThrow(sigParser.SkipExactlyOne());
+ }
+
+ IfFailThrow(sigParser.SkipFunkyAndCustomModifiers());
+
+ IfFailThrow(sigParser.SkipAnyVASentinel());
+
+ IfFailThrow(CordbType::SigToType(GetModule(), &sigParser, &(this->m_genericArgs), ppResultType));
+ }
+ else // (!m_fVarArgFnx || m_sigParserCached.IsNull())
+ {
+ m_nativeFrame->m_nativeCode->GetArgumentType(dwIndex, &(this->m_genericArgs), ppResultType);
+ }
+
+ return hr;
+}
+
+//
+// GetNativeVariable uses the JIT variable information to delegate to
+// the native frame when the value is really created.
+//
+HRESULT CordbJITILFrame::GetNativeVariable(CordbType *type,
+ const ICorDebugInfo::NativeVarInfo *pNativeVarInfo,
+ ICorDebugValue **ppValue)
+{
+ INTERNAL_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ HRESULT hr = S_OK;
+
+#if defined(DBG_TARGET_WIN64) || defined(DBG_TARGET_ARM)
+ if (m_nativeFrame->IsFunclet())
+ {
+ if ( (pNativeVarInfo->loc.vlType != ICorDebugInfo::VLT_STK) &&
+ (pNativeVarInfo->loc.vlType != ICorDebugInfo::VLT_STK2) &&
+ (pNativeVarInfo->loc.vlType != ICorDebugInfo::VLT_STK_BYREF) )
+ {
+ _ASSERTE(!"CordbJITILFrame::GetNativeVariable()"
+ " - Variables used in funclets should always be homed on the stack.\n");
+ return E_FAIL;
+ }
+ }
+#endif // DBG_TARGET_WIN64 || DBG_TARGET_ARM
+
+ switch (pNativeVarInfo->loc.vlType)
+ {
+ case ICorDebugInfo::VLT_REG:
+ hr = m_nativeFrame->GetLocalRegisterValue(
+ ConvertRegNumToCorDebugRegister(pNativeVarInfo->loc.vlReg.vlrReg),
+ type, ppValue);
+ break;
+
+ case ICorDebugInfo::VLT_REG_BYREF:
+ {
+ CORDB_ADDRESS pRemoteByRefAddr = PTR_TO_CORDB_ADDRESS(
+ *( m_nativeFrame->GetAddressOfRegister(ConvertRegNumToCorDebugRegister(pNativeVarInfo->loc.vlReg.vlrReg))) );
+
+ hr = m_nativeFrame->GetLocalMemoryValue(pRemoteByRefAddr,
+ type,
+ ppValue);
+ }
+ break;
+
+#if defined(DBG_TARGET_WIN64) || defined(DBG_TARGET_ARM)
+ case ICorDebugInfo::VLT_REG_FP:
+#if defined(DBG_TARGET_ARM) // @ARMTODO
+ hr = E_NOTIMPL;
+#else // DBG_TARGET_ARM @ARMTODO
+ hr = m_nativeFrame->GetLocalFloatingPointValue(pNativeVarInfo->loc.vlReg.vlrReg + REGISTER_AMD64_XMM0,
+ type, ppValue);
+
+#endif // DBG_TARGET_ARM @ARMTODO
+ break;
+#endif // DBG_TARGET_WIN64 || DBG_TARGET_ARM
+
+ case ICorDebugInfo::VLT_STK_BYREF:
+ {
+ CORDB_ADDRESS pRemoteByRefAddr = m_nativeFrame->GetLSStackAddress(
+ pNativeVarInfo->loc.vlStk.vlsBaseReg, pNativeVarInfo->loc.vlStk.vlsOffset) ;
+
+ hr = m_nativeFrame->GetLocalByRefMemoryValue(pRemoteByRefAddr,
+ type,
+ ppValue);
+ }
+ break;
+
+ case ICorDebugInfo::VLT_STK:
+ {
+ CORDB_ADDRESS pRemoteValue = m_nativeFrame->GetLSStackAddress(
+ pNativeVarInfo->loc.vlStk.vlsBaseReg, pNativeVarInfo->loc.vlStk.vlsOffset) ;
+
+ hr = m_nativeFrame->GetLocalMemoryValue(pRemoteValue,
+ type,
+ ppValue);
+ }
+ break;
+
+ case ICorDebugInfo::VLT_REG_REG:
+ hr = m_nativeFrame->GetLocalDoubleRegisterValue(
+ ConvertRegNumToCorDebugRegister(pNativeVarInfo->loc.vlRegReg.vlrrReg2),
+ ConvertRegNumToCorDebugRegister(pNativeVarInfo->loc.vlRegReg.vlrrReg1),
+ type, ppValue);
+ break;
+
+ case ICorDebugInfo::VLT_REG_STK:
+ {
+ CORDB_ADDRESS pRemoteValue = m_nativeFrame->GetLSStackAddress(
+ pNativeVarInfo->loc.vlRegStk.vlrsStk.vlrssBaseReg, pNativeVarInfo->loc.vlRegStk.vlrsStk.vlrssOffset);
+
+ hr = m_nativeFrame->GetLocalMemoryRegisterValue(
+ pRemoteValue,
+ ConvertRegNumToCorDebugRegister(pNativeVarInfo->loc.vlRegStk.vlrsReg),
+ type, ppValue);
+ }
+ break;
+
+ case ICorDebugInfo::VLT_STK_REG:
+ {
+ CORDB_ADDRESS pRemoteValue = m_nativeFrame->GetLSStackAddress(
+ pNativeVarInfo->loc.vlStkReg.vlsrStk.vlsrsBaseReg, pNativeVarInfo->loc.vlStkReg.vlsrStk.vlsrsOffset);
+
+ hr = m_nativeFrame->GetLocalRegisterMemoryValue(
+ ConvertRegNumToCorDebugRegister(pNativeVarInfo->loc.vlStkReg.vlsrReg),
+ pRemoteValue, type, ppValue);
+ }
+ break;
+
+ case ICorDebugInfo::VLT_STK2:
+ {
+ CORDB_ADDRESS pRemoteValue = m_nativeFrame->GetLSStackAddress(
+ pNativeVarInfo->loc.vlStk2.vls2BaseReg, pNativeVarInfo->loc.vlStk2.vls2Offset);
+
+ hr = m_nativeFrame->GetLocalMemoryValue(pRemoteValue,
+ type,
+ ppValue);
+ }
+ break;
+
+ case ICorDebugInfo::VLT_FPSTK:
+#if defined(DBG_TARGET_ARM) // @ARMTODO
+ hr = E_NOTIMPL;
+#else
+ /*
+ @TODO [Microsoft] We have to make this work!!!!!!!!!!!!!
+ hr = m_nativeFrame->GetLocalFloatingPointValue(
+ pNativeVarInfo->loc.vlFPstk.vlfReg + REGISTER_X86_FPSTACK_0,
+ type, ppValue);
+ */
+ hr = CORDBG_E_IL_VAR_NOT_AVAILABLE;
+#endif
+ break;
+
+ case ICorDebugInfo::VLT_FIXED_VA:
+ if (m_sigParserCached.IsNull()) //no var args info
+ return CORDBG_E_IL_VAR_NOT_AVAILABLE;
+
+ CORDB_ADDRESS pRemoteValue;
+
+
+#if defined(DBG_TARGET_X86) // STACK_GROWS_DOWN_ON_ARGS_WALK
+ pRemoteValue = m_FirstArgAddr - pNativeVarInfo->loc.vlFixedVarArg.vlfvOffset;
+ // Remember to subtract out this amount
+ pRemoteValue += sizeof(((CORINFO_VarArgInfo*)0)->argBytes);
+#else // STACK_GROWS_UP_ON_ARGS_WALK
+ pRemoteValue = m_FirstArgAddr + pNativeVarInfo->loc.vlFixedVarArg.vlfvOffset;
+#endif
+
+ hr = m_nativeFrame->GetLocalMemoryValue(pRemoteValue,
+ type,
+ ppValue);
+
+ break;
+
+
+ default:
+ _ASSERTE(!"Invalid locVarType");
+ hr = E_FAIL;
+ break;
+ }
+
+ return hr;
+}
+
+HRESULT CordbJITILFrame::EnumerateLocalVariables(ICorDebugValueEnum **ppValueEnum)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppValueEnum, ICorDebugValueEnum **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ return EnumerateLocalVariablesEx(ILCODE_ORIGINAL_IL, ppValueEnum);
+}
+
+HRESULT CordbJITILFrame::GetLocalVariable(DWORD dwIndex,
+ ICorDebugValue **ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ return GetLocalVariableEx(ILCODE_ORIGINAL_IL, dwIndex, ppValue);
+}
+
+
+HRESULT CordbJITILFrame::EnumerateArguments(ICorDebugValueEnum **ppValueEnum)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppValueEnum, ICorDebugValueEnum **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+ RSInitHolder<CordbValueEnum> cdVE(new CordbValueEnum(m_nativeFrame, CordbValueEnum::ARGS));
+
+ // Initialize the new enum
+ hr = cdVE->Init();
+ IfFailThrow(hr);
+
+ cdVE.TransferOwnershipExternal(ppValueEnum);
+
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// This routine gets the value of a particular argument
+//
+// Arguments:
+// dwIndex - Index of the argument.
+// ppValue - OUT: Space for storing the value of the argument
+//
+// Return Value:
+// HRESULT for the operation
+//
+HRESULT CordbJITILFrame::GetArgument(DWORD dwIndex, ICorDebugValue ** ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ const ICorDebugInfo::NativeVarInfo * pNativeInfo;
+
+ //
+ // First, make sure that we've got the jitted variable location data
+ // loaded from the left side.
+ //
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ m_nativeFrame->m_nativeCode->LoadNativeInfo(); //throws
+
+ hr = ILVariableToNative(dwIndex, &pNativeInfo);
+ IfFailThrow(hr);
+
+ // Get the type of this argument from the function
+ CordbType * pType;
+
+ hr = GetArgumentType(dwIndex, &pType);
+ IfFailThrow(hr);
+
+ hr = GetNativeVariable(pType, pNativeInfo, ppValue);
+ IfFailThrow(hr);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+
+HRESULT CordbJITILFrame::GetStackDepth(ULONG32 *pDepth)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pDepth, ULONG32 *);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+
+ /* !!! */
+
+ return E_NOTIMPL;
+}
+
+HRESULT CordbJITILFrame::GetStackValue(DWORD dwIndex, ICorDebugValue **ppValue)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ /* !!! */
+
+ return E_NOTIMPL;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Remaps the active frame to the latest EnC version of the function, preserving the
+// execution state of the method such as the values of locals.
+// Can only be called when the leaf frame is at a remap opportunity.
+//
+// Arguments:
+// nOffset - the IL offset in the new version of the function to remap to
+//
+
+HRESULT CordbJITILFrame::RemapFunction(ULONG32 nOffset)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this)
+ {
+#if !defined(EnC_SUPPORTED)
+ ThrowHR(E_NOTIMPL);
+
+#else // EnC_SUPPORTED
+ // Can only be called on leaf frame.
+ if (!m_nativeFrame->IsLeafFrame())
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ // mark frames as not fresh, because this frame has been updated.
+ m_nativeFrame->m_pThread->CleanupStack();
+
+ // Since we may have overwritten anything (objects, code, etc), we should mark
+ // everything as needing to be re-cached.
+ m_nativeFrame->m_pThread->GetProcess()->m_continueCounter++;
+
+ // Tell the left-side to do the remap
+ hr = m_nativeFrame->m_pThread->SetRemapIP(nOffset);
+
+#endif // EnC_SUPPORTED
+ }
+ PUBLIC_API_END(hr);
+
+ return hr;
+}
+HRESULT CordbJITILFrame::GetReturnValueForILOffset(ULONG32 ILoffset, ICorDebugValue** ppReturnValue)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+ EX_TRY
+ {
+ hr = GetReturnValueForILOffsetImpl(ILoffset, ppReturnValue);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+
+HRESULT CordbJITILFrame::BuildInstantiationForCallsite(CordbModule * pModule, NewArrayHolder<CordbType*> &types, Instantiation &inst, Instantiation *currentInstantiation, mdToken targetClass, SigParser genericSig)
+{
+ // This function builds an Instantiation object (and backing "types" array) for a given
+ // class and method signature.
+ HRESULT hr = S_OK;
+ RSExtSmartPtr<IMetaDataImport2> pImport2;
+ IfFailRet(pModule->GetMetaDataImporter()->QueryInterface(IID_IMetaDataImport2, (void**)&pImport2));
+
+ // If the targetClass is a TypeSpec that means its first element is GENERICINST.
+ // We only need to build types for the Instantiation if targetClass is a TypeSpec.
+ ULONG classGenerics = 0;
+ SigParser typeSig;
+ if (TypeFromToken(targetClass) == mdtTypeSpec)
+ {
+ // Our goal with this is to full "classGenerics" with the number of
+ // generics, and move "typeSig" to the start of the first generic type.
+ PCCOR_SIGNATURE sig = 0;
+ ULONG sigCount = 0;
+
+ IfFailRet(pImport2->GetTypeSpecFromToken(targetClass, &sig, &sigCount));
+
+ typeSig = SigParser(sig, sigCount);
+ CorElementType elemType;
+ IfFailRet(typeSig.GetElemType(&elemType));
+
+ if (elemType != ELEMENT_TYPE_GENERICINST)
+ return META_E_BAD_SIGNATURE;
+
+ IfFailRet(typeSig.GetElemType(&elemType));
+ if (elemType != ELEMENT_TYPE_VALUETYPE && elemType != ELEMENT_TYPE_CLASS)
+ return META_E_BAD_SIGNATURE;
+
+ IfFailRet(typeSig.GetToken(NULL));
+ IfFailRet(typeSig.GetData(&classGenerics));
+ }
+
+ // Similarly for method generics. Simply fill "methodGenerics" with the number
+ // of generics, and move "genericSig" to the start of the first generic param.
+ ULONG methodGenerics = 0;
+ if (!genericSig.IsNull())
+ {
+ ULONG callingConv = 0;
+ IfFailRet(genericSig.GetCallingConvInfo(&callingConv));
+ if (callingConv == IMAGE_CEE_CS_CALLCONV_GENERICINST)
+ IfFailRet(genericSig.GetData(&methodGenerics));
+ }
+
+
+ // Now build "types" and "inst".
+ CordbType *pType = 0;
+ types = new CordbType*[methodGenerics+classGenerics];
+ ULONG i = 0;
+ for (;i < classGenerics; ++i)
+ {
+ CorElementType et;
+ IfFailRet(typeSig.PeekElemType(&et));
+ if ((et == ELEMENT_TYPE_VAR || et == ELEMENT_TYPE_MVAR) && currentInstantiation->m_cInst == 0)
+ return E_FAIL;
+
+ CordbType::SigToType(pModule, &typeSig, currentInstantiation, &pType);
+ types[i] = pType;
+ typeSig.SkipExactlyOne();
+ }
+
+ for (; i < methodGenerics+classGenerics; ++i)
+ {
+ CorElementType et;
+ IfFailRet(genericSig.PeekElemType(&et));
+ if ((et == ELEMENT_TYPE_VAR || et == ELEMENT_TYPE_MVAR) && currentInstantiation->m_cInst == 0)
+ return E_FAIL;
+
+ CordbType::SigToType(pModule, &genericSig, currentInstantiation, &pType);
+ types[i] = pType;
+ genericSig.SkipExactlyOne();
+ }
+
+ inst = Instantiation(methodGenerics+classGenerics, types, classGenerics);
+ return S_OK;
+}
+
+HRESULT CordbJITILFrame::GetReturnValueForILOffsetImpl(ULONG32 ILoffset, ICorDebugValue** ppReturnValue)
+{
+ if (ppReturnValue == NULL)
+ return E_INVALIDARG;
+
+ if (!m_genericArgsLoaded)
+ LoadGenericArgs();
+
+ // First verify that we're stopped at the correct native offset
+ // by calling ICorDebugCode3::GetReturnValueLiveOffset and
+ // compare the returned native offset to our current location.
+ HRESULT hr = S_OK;
+ CordbNativeCode *pCode = m_nativeFrame->m_nativeCode;
+ pCode->LoadNativeInfo();
+
+ ULONG32 count = 0;
+ IfFailRet(pCode->GetReturnValueLiveOffsetImpl(&m_genericArgs, ILoffset, 0, &count, NULL));
+
+ NewArrayHolder<ULONG32> offsets(new ULONG32[count]);
+ IfFailRet(pCode->GetReturnValueLiveOffsetImpl(&m_genericArgs, ILoffset, count, &count, offsets));
+
+ bool found = false;
+ ULONG32 currentOffset = m_nativeFrame->GetIPOffset();
+ for (ULONG32 i = 0; i < count; ++i)
+ if ((found = currentOffset == offsets[i]))
+ break;
+
+ if (!found)
+ return E_UNEXPECTED;
+
+ // Get the signatures and mdToken for the callee.
+ SigParser methodSig, genericSig;
+ mdToken mdFunction = 0, targetClass = 0;
+ IfFailRet(pCode->GetCallSignature(ILoffset, &targetClass, &mdFunction, methodSig, genericSig));
+ IfFailRet(CordbNativeCode::SkipToReturn(methodSig));
+
+
+
+
+ // Create the Instantiation, type and then return value
+ NewArrayHolder<CordbType*> types;
+ Instantiation inst;
+ CordbType *pType = 0;
+ IfFailRet(BuildInstantiationForCallsite(GetModule(), types, inst, &m_genericArgs, targetClass, genericSig));
+ IfFailRet(CordbType::SigToType(GetModule(), &methodSig, &inst, &pType));
+ return GetReturnValueForType(pType, ppReturnValue);
+}
+
+
+HRESULT CordbJITILFrame::GetReturnValueForType(CordbType *pType, ICorDebugValue **ppReturnValue)
+{
+#if defined(DBG_TARGET_ARM)
+ return E_NOTIMPL;
+#else
+
+
+#if defined(DBG_TARGET_X86)
+ const CorDebugRegister floatRegister = REGISTER_X86_FPSTACK_0;
+#elif defined(DBG_TARGET_AMD64)
+ const CorDebugRegister floatRegister = REGISTER_AMD64_XMM0;
+#elif defined(DBG_TARGET_ARM64)
+ const CorDebugRegister floatRegister = REGISTER_ARM64_V0;
+#endif
+
+#if defined(DBG_TARGET_X86)
+ const CorDebugRegister ptrRegister = REGISTER_X86_EAX;
+#elif defined(DBG_TARGET_AMD64)
+ const CorDebugRegister ptrRegister = REGISTER_AMD64_RAX;
+#elif defined(DBG_TARGET_ARM64)
+ const CorDebugRegister ptrRegister = REGISTER_ARM64_X0;
+#endif
+
+ CorElementType corReturnType = pType->GetElementType();
+ switch (corReturnType)
+ {
+ default:
+ return m_nativeFrame->GetLocalRegisterValue(ptrRegister, pType, ppReturnValue);
+
+ case ELEMENT_TYPE_R4:
+ case ELEMENT_TYPE_R8:
+ return m_nativeFrame->GetLocalFloatingPointValue(floatRegister, pType, ppReturnValue);
+
+#ifdef DBG_TARGET_X86
+ case ELEMENT_TYPE_I8:
+ case ELEMENT_TYPE_U8:
+ return m_nativeFrame->GetLocalDoubleRegisterValue(REGISTER_X86_EDX, REGISTER_X86_EAX, pType, ppReturnValue);
+#endif
+ }
+#endif
+}
+
+HRESULT CordbJITILFrame::EnumerateLocalVariablesEx(ILCodeKind flags, ICorDebugValueEnum **ppValueEnum)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppValueEnum, ICorDebugValueEnum **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+ if (flags != ILCODE_ORIGINAL_IL && flags != ILCODE_REJIT_IL)
+ return E_INVALIDARG;
+
+ EX_TRY
+ {
+ RSInitHolder<CordbValueEnum> cdVE(new CordbValueEnum(m_nativeFrame,
+ flags == ILCODE_ORIGINAL_IL ? CordbValueEnum::LOCAL_VARS_ORIGINAL_IL : CordbValueEnum::LOCAL_VARS_REJIT_IL));
+
+ // Initialize the new enum
+ hr = cdVE->Init();
+ IfFailThrow(hr);
+
+ cdVE.TransferOwnershipExternal(ppValueEnum);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+HRESULT CordbJITILFrame::GetLocalVariableEx(ILCodeKind flags, DWORD dwIndex, ICorDebugValue **ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ if (flags != ILCODE_ORIGINAL_IL && flags != ILCODE_REJIT_IL)
+ return E_INVALIDARG;
+ if (flags == ILCODE_REJIT_IL && m_pReJitCode == NULL)
+ return E_INVALIDARG;
+
+ const ICorDebugInfo::NativeVarInfo *pNativeInfo;
+
+ //
+ // First, make sure that we've got the jitted variable location data
+ // loaded from the left side.
+ //
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ m_nativeFrame->m_nativeCode->LoadNativeInfo(); //throws
+
+ ULONG cArgs;
+ if (m_fVarArgFnx && (!m_sigParserCached.IsNull()))
+ {
+ cArgs = m_allArgsCount;
+ }
+ else
+ {
+ cArgs = m_nativeFrame->m_nativeCode->GetFixedArgCount();
+ }
+
+ hr = ILVariableToNative(dwIndex + cArgs, &pNativeInfo);
+ IfFailThrow(hr);
+
+ LoadGenericArgs();
+
+ // Get the type of this argument from the function
+ CordbType *type;
+ CordbILCode* pActiveCode = m_pReJitCode != NULL ? m_pReJitCode : m_ilCode;
+ hr = pActiveCode->GetLocalVariableType(dwIndex, &(this->m_genericArgs), &type);
+ IfFailThrow(hr);
+
+ // if the caller wants the original IL local, it should implicitly map to the same index
+ // variable in the profiler instrumented code. We can't determine whether the instrumented code
+ // really adhered to this, but we can check two things:
+ // a) the requested index was valid in the original signature
+ // (GetLocalVariableType will return E_INVALIDARG if not)
+ // b) the type of local in the original signature matches the type of local in the instrumented signature
+ // (the code below will return CORDBG_E_IL_VAR_NOT_AVAILABLE)
+ if (flags == ILCODE_ORIGINAL_IL && m_pReJitCode != NULL)
+ {
+ CordbType* pOriginalType;
+ hr = m_ilCode->GetLocalVariableType(dwIndex, &(this->m_genericArgs), &pOriginalType);
+ IfFailThrow(hr);
+ if (pOriginalType != type)
+ {
+ IfFailThrow(CORDBG_E_IL_VAR_NOT_AVAILABLE); // bad profiler, it shouldn't have changed types
+ }
+ }
+
+
+ hr = GetNativeVariable(type, pNativeInfo, ppValue);
+ IfFailThrow(hr);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+HRESULT CordbJITILFrame::GetCodeEx(ILCodeKind flags, ICorDebugCode **ppCode)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ if (flags != ILCODE_ORIGINAL_IL && flags != ILCODE_REJIT_IL)
+ return E_INVALIDARG;
+
+ if (flags == ILCODE_ORIGINAL_IL)
+ {
+ return GetCode(ppCode);
+ }
+ else
+ {
+ *ppCode = m_pReJitCode;
+ if (m_pReJitCode != NULL)
+ {
+ m_pReJitCode->ExternalAddRef();
+ }
+ }
+ return S_OK;
+}
+
+CordbILCode* CordbJITILFrame::GetOriginalILCode()
+{
+ return m_ilCode;
+}
+
+CordbReJitILCode* CordbJITILFrame::GetReJitILCode()
+{
+ return m_pReJitCode;
+}
+
+/* ------------------------------------------------------------------------- *
+ * Eval class
+ * ------------------------------------------------------------------------- */
+
+CordbEval::CordbEval(CordbThread *pThread)
+ : CordbBase(pThread->GetProcess(), 0, enumCordbEval),
+ m_thread(pThread), // implicit InternalAddRef
+ m_function(NULL),
+ m_complete(false),
+ m_successful(false),
+ m_aborted(false),
+ m_resultAddr(NULL),
+ m_evalDuringException(false)
+{
+ m_vmObjectHandle = VMPTR_OBJECTHANDLE::NullPtr();
+ m_debuggerEvalKey = LSPTR_DEBUGGEREVAL::NullPtr();
+
+ m_resultType.elementType = ELEMENT_TYPE_VOID;
+ m_resultAppDomainToken = VMPTR_AppDomain::NullPtr();
+
+ CordbAppDomain * pDomain = m_thread->GetAppDomain();
+ (void)pDomain; //prevent "unused variable" error from GCC
+#ifdef _DEBUG
+ // Remember what AD we started in so that we can check that we finish there too.
+ m_DbgAppDomainStarted = pDomain;
+#endif
+
+ // Place ourselves on the processes neuter-list.
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ GetProcess()->AddToLeftSideResourceCleanupList(this);
+ }
+ EX_CATCH_HRESULT(hr);
+ SetUnrecoverableIfFailed(GetProcess(), hr);
+}
+
+CordbEval::~CordbEval()
+{
+ _ASSERTE(IsNeutered());
+}
+
+// Free the left-side resources for the eval.
+void CordbEval::NeuterLeftSideResources()
+{
+ SendCleanup();
+
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+ Neuter();
+}
+
+// Neuter the CordbEval
+//
+// Assumptions:
+// By the time we neuter the eval, it's associated left-side resources
+// are already cleaned up (either explicitly from calling code:CordbEval::SendCleanup
+// or implicitly from the left-side exiting).
+//
+// Notes:
+// We place ourselves on a neuter list. This gets called when the neuterlist sweeps.
+void CordbEval::Neuter()
+{
+ // By now, we should have freed our target-resources (code:CordbEval::NeuterLeftSideResources
+ // or code:CordbEval::SendCleanup), unless the target is dead (terminated or about to exit).
+ BOOL fTargetIsDead = !GetProcess()->IsSafeToSendEvents() || GetProcess()->m_exiting;
+ (void)fTargetIsDead; //prevent "unused variable" error from GCC
+ _ASSERTE(fTargetIsDead || (m_debuggerEvalKey == NULL));
+
+ m_thread.Clear();
+
+ CordbBase::Neuter();
+}
+
+HRESULT CordbEval::SendCleanup()
+{
+ FAIL_IF_NEUTERED(this);
+ INTERNAL_SYNC_API_ENTRY(GetProcess()); //
+
+ HRESULT hr = S_OK;
+
+ // Send a message to the left side to release the eval object over
+ // there if one exists.
+ if ((m_debuggerEvalKey != NULL) &&
+ GetProcess()->IsSafeToSendEvents())
+ {
+ // Call Abort() before doing new CallFunction()
+ if (!m_complete)
+ return CORDBG_E_FUNC_EVAL_NOT_COMPLETE;
+
+ // Release the left side handle to the object
+ DebuggerIPCEvent event;
+
+ GetProcess()->InitIPCEvent(
+ &event,
+ DB_IPCE_FUNC_EVAL_CLEANUP,
+ true,
+ m_thread->GetAppDomain()->GetADToken());
+
+ event.FuncEvalCleanup.debuggerEvalKey = m_debuggerEvalKey;
+
+ hr = GetProcess()->SendIPCEvent(&event, sizeof(DebuggerIPCEvent));
+ IfFailRet(hr);
+
+#if _DEBUG
+ if (SUCCEEDED(hr))
+ _ASSERTE(event.type == DB_IPCE_FUNC_EVAL_CLEANUP_RESULT);
+#endif
+
+ // Null out the key so we don't try to do this again.
+ m_debuggerEvalKey = LSPTR_DEBUGGEREVAL::NullPtr();
+
+ hr = event.hr;
+ }
+
+ // Release the cached HandleValue for the result. This may cleanup resources,
+ // like our object handle to the func-eval result.
+ m_pHandleValue.Clear();
+
+
+ return hr;
+}
+
+HRESULT CordbEval::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugEval)
+ {
+ *pInterface = static_cast<ICorDebugEval*>(this);
+ }
+ else if (id == IID_ICorDebugEval2)
+ {
+ *pInterface = static_cast<ICorDebugEval2*>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugEval*>(this));
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+//
+// Gather data about an argument to either CallFunction or NewObject
+// and place it into a DebuggerIPCE_FuncEvalArgData struct for passing
+// to the Left Side.
+//
+HRESULT CordbEval::GatherArgInfo(ICorDebugValue *pValue,
+ DebuggerIPCE_FuncEvalArgData *argData)
+{
+ FAIL_IF_NEUTERED(this);
+ INTERNAL_SYNC_API_ENTRY(GetProcess()); //
+
+ HRESULT hr;
+ CORDB_ADDRESS addr;
+ CorElementType ty;
+ bool needRelease = false;
+
+ pValue->GetType(&ty);
+
+ // Note: if the value passed in is in fact a byref, then we need to dereference it to get to the real thing. Passing
+ // a byref as a byref to a func eval is never right.
+ if ((ty == ELEMENT_TYPE_BYREF) || (ty == ELEMENT_TYPE_TYPEDBYREF))
+ {
+ ICorDebugReferenceValue *prv = NULL;
+
+ // The value had better implement ICorDebugReference value.
+ IfFailRet(pValue->QueryInterface(IID_ICorDebugReferenceValue, (void**)&prv));
+
+ // This really should always work for a byref, unless we're out of memory.
+ hr = prv->Dereference(&pValue);
+ prv->Release();
+
+ IfFailRet(hr);
+
+ // Make sure to get the type we were referencing for use below.
+ pValue->GetType(&ty);
+ needRelease = true;
+ }
+
+ // We should never have a byref by this point.
+ _ASSERTE((ty != ELEMENT_TYPE_BYREF) && (ty != ELEMENT_TYPE_TYPEDBYREF));
+
+ pValue->GetAddress(&addr);
+
+ argData->argAddr = CORDB_ADDRESS_TO_PTR(addr);
+ argData->argElementType = ty;
+
+ argData->argIsHandleValue = false;
+ argData->argIsLiteral = false;
+ argData->fullArgType = NULL;
+ argData->fullArgTypeNodeCount = 0;
+
+ // We have to have knowledge of our value implementation here,
+ // which it would nice if we didn't have to know.
+ CordbValue *cv = NULL;
+
+ switch(ty)
+ {
+
+ case ELEMENT_TYPE_CLASS:
+ case ELEMENT_TYPE_OBJECT:
+ case ELEMENT_TYPE_STRING:
+ case ELEMENT_TYPE_PTR:
+ case ELEMENT_TYPE_ARRAY:
+ case ELEMENT_TYPE_SZARRAY:
+ {
+ ICorDebugHandleValue *pHandle = NULL;
+ pValue->QueryInterface(IID_ICorDebugHandleValue, (void **) &pHandle);
+ if (pHandle == NULL)
+ {
+ // A reference value
+ cv = static_cast<CordbValue*> (static_cast<CordbReferenceValue*> (pValue));
+ argData->argIsHandleValue = !(((CordbReferenceValue *)pValue)->m_valueHome.ObjHandleIsNull());
+
+ // Is this a literal value? If, we'll copy the data to the
+ // buffer area so the left side can get it.
+ CordbReferenceValue *rv;
+ rv = static_cast<CordbReferenceValue*>(pValue);
+ argData->argIsLiteral = rv->CopyLiteralData(argData->argLiteralData);
+ if (rv->GetValueHome())
+ {
+ rv->GetValueHome()->CopyToIPCEType(&(argData->argHome));
+ }
+ }
+ else
+ {
+ argData->argIsHandleValue = true;
+ argData->argIsLiteral = false;
+ pHandle->Release();
+ argData->argHome.kind = RAK_NONE;
+ }
+ }
+ break;
+
+ case ELEMENT_TYPE_VALUETYPE: // OK: this E_T_VALUETYPE comes ICorDebugValue::GetType
+
+ // A value class object
+ cv = static_cast<CordbValue*> (static_cast<CordbVCObjectValue*>(static_cast<ICorDebugObjectValue*> (pValue)));
+
+ // The EE does not guarantee to have exact type information
+ // available for all struct types, so we indicate the type by using a
+ // DebuggerIPCE_TypeArgData serialization of a type.
+ //
+ // At the moment the LHS only cares about this data
+ // when boxing the "this" pointer.
+ {
+ CordbVCObjectValue * pVCObjVal =
+ static_cast<CordbVCObjectValue *>(static_cast<ICorDebugObjectValue*> (pValue));
+
+ unsigned int fullArgTypeNodeCount = 0;
+ cv->m_type->CountTypeDataNodes(&fullArgTypeNodeCount);
+
+ _ASSERTE(fullArgTypeNodeCount > 0);
+ unsigned int bufferSize = sizeof(DebuggerIPCE_TypeArgData) * fullArgTypeNodeCount;
+ DebuggerIPCE_TypeArgData *bufferFrom = (DebuggerIPCE_TypeArgData *) _alloca(bufferSize);
+
+ DebuggerIPCE_TypeArgData *curr = bufferFrom;
+ CordbType::GatherTypeData(cv->m_type, &curr);
+
+ void *buffer = NULL;
+ IfFailRet(m_thread->GetProcess()->GetAndWriteRemoteBuffer(m_thread->GetAppDomain(), bufferSize, bufferFrom, &buffer));
+
+ argData->fullArgType = buffer;
+ argData->fullArgTypeNodeCount = fullArgTypeNodeCount;
+ // Is it enregistered?
+ if ((addr == NULL) && (pVCObjVal->GetValueHome() != NULL))
+ {
+ pVCObjVal->GetValueHome()->CopyToIPCEType(&(argData->argHome));
+ }
+
+ }
+ break;
+
+ default:
+
+ // A generic value
+ cv = static_cast<CordbValue*> (static_cast<CordbGenericValue*> (pValue));
+
+ // Is this a literal value? If, we'll copy the data to the
+ // buffer area so the left side can get it.
+ CordbGenericValue *gv = (CordbGenericValue*)pValue;
+ argData->argIsLiteral = gv->CopyLiteralData(argData->argLiteralData);
+ // Is it enregistered?
+ if ((addr == NULL) && (gv->GetValueHome() != NULL))
+ {
+ gv->GetValueHome()->CopyToIPCEType(&(argData->argHome));
+ }
+ break;
+ }
+
+
+ // Release pValue if we got it via a dereference from above.
+ if (needRelease)
+ pValue->Release();
+
+ return S_OK;
+}
+
+
+HRESULT CordbEval::SendFuncEval(unsigned int genericArgsCount,
+ ICorDebugType *genericArgs[],
+ void *argData1, unsigned int argData1Size,
+ void *argData2, unsigned int argData2Size,
+ DebuggerIPCEvent * event)
+{
+ FAIL_IF_NEUTERED(this);
+ INTERNAL_SYNC_API_ENTRY(GetProcess()); //
+ unsigned int genericArgsNodeCount = 0;
+
+ DebuggerIPCE_TypeArgData *tyargData = NULL;
+ CordbType::CountTypeDataNodesForInstantiation(genericArgsCount,genericArgs,&genericArgsNodeCount);
+
+ unsigned int tyargDataSize = sizeof(DebuggerIPCE_TypeArgData) * genericArgsNodeCount;
+
+ if (genericArgsNodeCount > 0)
+ {
+ tyargData = new (nothrow) DebuggerIPCE_TypeArgData[genericArgsNodeCount];
+ if (tyargData == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ DebuggerIPCE_TypeArgData *curr_tyargData = tyargData;
+ CordbType::GatherTypeDataForInstantiation(genericArgsCount, genericArgs, &curr_tyargData);
+
+ }
+ event->FuncEval.genericArgsNodeCount = genericArgsNodeCount;
+
+
+ // Are we doing an eval during an exception? If so, we need to remember
+ // that over here and also tell the Left Side.
+ event->FuncEval.evalDuringException = m_thread->HasException();
+ m_evalDuringException = !!event->FuncEval.evalDuringException;
+ m_vmThreadOldExceptionHandle = m_thread->GetThreadExceptionRawObjectHandle();
+
+ // Corresponding Release() on DB_IPCE_FUNC_EVAL_COMPLETE.
+ // If a func eval is aborted, the LHS may not complete the abort
+ // immediately and hence we cant do a SendCleanup(). Hence, we maintain
+ // an extra ref-count to determine when this can be done.
+ AddRef();
+
+ HRESULT hr = m_thread->GetProcess()->SendIPCEvent(event, sizeof(DebuggerIPCEvent));
+
+ // If the send failed, return that failure.
+ if (FAILED(hr))
+ goto LExit;
+
+ _ASSERTE(event->type == DB_IPCE_FUNC_EVAL_SETUP_RESULT);
+
+ hr = event->hr;
+
+ // Memory has been allocated to hold info about each argument on
+ // the left side now, so copy the argument data over to the left
+ // side. No need to send another event, since the left side won't
+ // take any more action on this evaluation until the process is
+ // continued anyway.
+ //
+ // The type arguments come first, followed by up to two blobs of data
+ // for other arguments.
+ if (SUCCEEDED(hr))
+ {
+ EX_TRY
+ {
+ CORDB_ADDRESS argdata = event->FuncEvalSetupComplete.argDataArea;
+
+ if ((tyargData != NULL) && (tyargDataSize != 0))
+ {
+
+ TargetBuffer tb(argdata, tyargDataSize);
+ m_thread->GetProcess()->SafeWriteBuffer(tb, (const BYTE*) tyargData); // throws
+
+ argdata += tyargDataSize;
+ }
+
+ if ((argData1 != NULL) && (argData1Size != 0))
+ {
+ TargetBuffer tb(argdata, argData1Size);
+ m_thread->GetProcess()->SafeWriteBuffer(tb, (const BYTE*) argData1); // throws
+
+ argdata += argData1Size;
+ }
+
+ if ((argData2 != NULL) && (argData2Size != 0))
+ {
+ TargetBuffer tb(argdata, argData2Size);
+ m_thread->GetProcess()->SafeWriteBuffer(tb, (const BYTE*) argData2); // throws
+
+ argdata += argData2Size;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ }
+
+LExit:
+ if (tyargData)
+ {
+ delete [] tyargData;
+ }
+
+ // Save the key to the eval on the left side for future reference.
+ if (SUCCEEDED(hr))
+ {
+ m_debuggerEvalKey = event->FuncEvalSetupComplete.debuggerEvalKey;
+ m_thread->GetProcess()->IncrementOutstandingEvalCount();
+ }
+ else
+ {
+ // We dont expect to receive a DB_IPCE_FUNC_EVAL_COMPLETE, so just release here
+ Release();
+ }
+
+ return hr;
+}
+
+
+// Get the AppDomain that an object lives in.
+// This does not adjust any reference counts.
+// Returns NULL if we can't determine the appdomain, or if the value is known to be agile.
+CordbAppDomain * GetAppDomainFromValue(ICorDebugValue * pValue)
+{
+ // Unfortunately, there's no direct way to cast from an ICDValue to a CordbValue.
+ // So we need to QI for the culprit interfaces and check specifically.
+
+ {
+ RSExtSmartPtr<ICorDebugHandleValue> handleP;
+ pValue->QueryInterface(IID_ICorDebugHandleValue, (void**)&handleP);
+ if (handleP != NULL)
+ {
+ CordbHandleValue * chp = static_cast<CordbHandleValue *> (handleP.GetValue());
+ return chp->GetAppDomain();
+ }
+ }
+
+ {
+ RSExtSmartPtr<ICorDebugReferenceValue> refP;
+ pValue->QueryInterface(IID_ICorDebugReferenceValue, (void**)&refP);
+ if (refP != NULL)
+ {
+ CordbReferenceValue * crp = static_cast<CordbReferenceValue *> (refP.GetValue());
+ return crp->GetAppDomain();
+ }
+ }
+
+ {
+ RSExtSmartPtr<ICorDebugObjectValue> objP;
+ pValue->QueryInterface(IID_ICorDebugObjectValue, (void**)&objP);
+ if (objP != NULL)
+ {
+ CordbVCObjectValue * crp = static_cast<CordbVCObjectValue*> (objP.GetValue());
+ return crp->GetAppDomain();
+ }
+ }
+
+ // Assume nothing else has AD affinity.
+ return NULL;
+}
+
+HRESULT CordbEval::CallFunction(ICorDebugFunction *pFunction,
+ ULONG32 nArgs,
+ ICorDebugValue *pArgs[])
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ if (GetProcess()->GetShim() == NULL)
+ {
+ return E_NOTIMPL;
+ }
+ return CallParameterizedFunction(pFunction,0,NULL,nArgs,pArgs);
+}
+
+//-----------------------------------------------------------------------------
+// See if we can convert general Func-eval failure HRs (which are usually based on EE-invariants that
+// may be meaningless to the user) into a more specific user-friendly hr.
+// Doing the conversions here in the RS (instead of in the LS) makes it more clear that these
+// HRs definitely map to concepts described by the ICorDebugAPI instead of EE-invariants.
+// It also lets us clearly prioritize the HRs in case of ambiguity.
+//-----------------------------------------------------------------------------
+HRESULT CordbEval::FilterHR(HRESULT hr)
+{
+ // Currently, we only make CORDBG_E_ILLEGAL_AT_GC_UNSAFE_POINT more specific.
+ // If it's not that HR, then shortcut our work.
+ if (hr != CORDBG_E_ILLEGAL_AT_GC_UNSAFE_POINT)
+ {
+ return hr;
+ }
+
+ // In the case of conflicting HRs (if the func-eval fails for multiple reasons),
+ // we'll try to give priority to the more general HR.
+ // This communicates the quickest action for the user to be able to get to a
+ // func-eval friendly spot. It also means less churn in the hrs we return
+ // because specific hrs are more likely to change than general ones.
+
+ // If we got CORDBG_E_ILLEGAL_AT_GC_UNSAFE_POINT, check the common reasons.
+ // We'll use the Right-Side's intimate knowledge of the Left-Side to guess _why_
+ // it's a GC-unsafe spot, and then we'll communicate that back w/ a more meaningful HR.
+ // If GC safe-spots change, then these errors should be updated.
+
+
+ //
+ // Most likely is if we're in native code. Check that first.
+ //
+ // In V2, we do this check by checking if the leaf chain is native. Since we have no chain in Arrowhead,
+ // we can't do this check. Instead, we check whether the active frame is NULL or not. If it's NULL,
+ // then we are stopped in native code.
+ //
+ HRESULT hrTemp = S_OK;
+ if (GetProcess()->GetShim() != NULL)
+ {
+ // the V2 case
+ RSExtSmartPtr<ICorDebugChain> pChain;
+ hrTemp = m_thread->GetActiveChain(&pChain);
+ if (FAILED(hrTemp))
+ {
+ // just return the original HR if this call fails
+ return hr;
+ }
+
+ // pChain should never be NULL here, since we should have at least one thread start chain even if
+ // there is no managed code on the stack, but let's just be extra careful here.
+ if (pChain == NULL)
+ {
+ return hr;
+ }
+
+ BOOL fManagedChain;
+ hrTemp = pChain->IsManaged(&fManagedChain);
+ if (FAILED(hrTemp))
+ {
+ // just return the original HR if this call fails
+ return hr;
+ }
+
+ if (fManagedChain == FALSE)
+ {
+ return CORDBG_E_ILLEGAL_IN_NATIVE_CODE;
+ }
+ }
+
+ RSExtSmartPtr<ICorDebugFrame> pIFrame;
+ hrTemp = m_thread->GetActiveFrame(&pIFrame);
+ if (FAILED(hrTemp))
+ {
+ // just return the original HR if this call fails
+ return hr;
+ }
+
+ CordbFrame * pFrame = NULL;
+ pFrame = CordbFrame::GetCordbFrameFromInterface(pIFrame);
+
+ if (GetProcess()->GetShim() == NULL)
+ {
+ // the Arrowhead case
+ if (pFrame == NULL)
+ {
+ return CORDBG_E_ILLEGAL_IN_NATIVE_CODE;
+ }
+ }
+
+ // Next, check if we're in optimized code.
+ // Optimized code doesn't directly mean that func-evals are illegal; but it greatly
+ // increases the odds of being at a GC-unsafe point.
+ // We give this failure higher precedence than the "Is in prolog" failure.
+
+ if (pFrame != NULL)
+ {
+ CordbNativeFrame * pNativeFrame = pFrame->GetAsNativeFrame();
+ if (pNativeFrame != NULL)
+ {
+ CordbNativeCode * pCode = pNativeFrame->GetNativeCode();
+ if (pCode != NULL)
+ {
+ DWORD flags;
+ hrTemp = pCode->GetModule()->GetJITCompilerFlags(&flags);
+
+ if (SUCCEEDED(hrTemp))
+ {
+ if ((flags & CORDEBUG_JIT_DISABLE_OPTIMIZATION) != CORDEBUG_JIT_DISABLE_OPTIMIZATION)
+ {
+ return CORDBG_E_ILLEGAL_IN_OPTIMIZED_CODE;
+ }
+
+ } // GetCompilerFlags
+ } // Code
+
+ CordbJITILFrame * pILFrame = pNativeFrame->m_JITILFrame;
+ if (pILFrame != NULL)
+ {
+ if (pILFrame->m_mapping == MAPPING_PROLOG)
+ {
+ return CORDBG_E_ILLEGAL_IN_PROLOG;
+ }
+ }
+ } // Native Frame
+ }
+
+ // No filtering.
+ return hr;
+
+}
+
+//---------------------------------------------------------------------------------------
+//
+// This routine calls a function with the given set of type arguments and actual arguments.
+// This is the jumping off point for func-eval.
+//
+// Arguments:
+// pFunction - The function to call.
+// nTypeArgs - The number of type-arguments for the method in rgpTypeArgs
+// rgpTypeArgs - An array of pointers to types.
+// nArgs - The number of arguments for the method in rgpArgs
+// rgpArgs - An array of pointers to values for the arguments to the method.
+//
+// Return Value:
+// HRESULT for the operation
+//
+HRESULT CordbEval::CallParameterizedFunction(ICorDebugFunction *pFunction,
+ ULONG32 nTypeArgs,
+ ICorDebugType * rgpTypeArgs[],
+ ULONG32 nArgs,
+ ICorDebugValue * rgpArgs[])
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ VALIDATE_POINTER_TO_OBJECT(pFunction, ICorDebugFunction *);
+
+ if (nArgs > 0)
+ {
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(rgpArgs, ICorDebugValue *, nArgs, true, true);
+ }
+
+ HRESULT hr = E_FAIL;
+
+ {
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ // The LS will assume that all of the ICorDebugValues and ICorDebugTypes are in
+ // the same appdomain as the function. Verify this.
+ CordbAppDomain * pMethodAppDomain = (static_cast<CordbFunction *> (pFunction))->GetAppDomain();
+
+ if (!DoAppDomainsMatch(pMethodAppDomain, nTypeArgs, rgpTypeArgs, nArgs, rgpArgs))
+ {
+ return ErrWrapper(CORDBG_E_APPDOMAIN_MISMATCH);
+ }
+
+ // Callers are free to reuse an ICorDebugEval object for multiple
+ // evals. Since we create a Left Side eval representation each
+ // time, we need to be sure to clean it up now that we know we're
+ // done with it.
+ hr = SendCleanup();
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+
+ // Must be locked to get a cookie
+ RsPtrHolder<CordbEval> hFuncEval(this);
+
+ if (hFuncEval.Ptr().IsNull())
+ {
+ return E_OUTOFMEMORY;
+ }
+ lockHolder.Release(); // release to send an IPC event.
+
+ // Remember the function that we're evaluating.
+ m_function = static_cast<CordbFunction *>(pFunction);
+ m_evalType = DB_IPCE_FET_NORMAL;
+
+
+ // Arrange the arguments into a form that the left side can deal
+ // with. We do this before starting the func eval setup to ensure
+ // that we can complete this step before mutating the left
+ // side.
+ DebuggerIPCE_FuncEvalArgData * pArgData = NULL;
+
+ if (nArgs > 0)
+ {
+ // We need to make the same type of array that the left side
+ // holds.
+ pArgData = new (nothrow) DebuggerIPCE_FuncEvalArgData[nArgs];
+
+ if (pArgData == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ // For each argument, convert its home into something the left
+ // side can understand.
+ for (unsigned int i = 0; i < nArgs; i++)
+ {
+ hr = GatherArgInfo(rgpArgs[i], &(pArgData[i]));
+
+ if (FAILED(hr))
+ {
+ delete [] pArgData;
+ return hr;
+ }
+ }
+ }
+
+ // Send over to the left side and get it to setup this eval.
+ DebuggerIPCEvent event;
+ m_thread->GetProcess()->InitIPCEvent(&event, DB_IPCE_FUNC_EVAL, true, m_thread->GetAppDomain()->GetADToken());
+
+ event.FuncEval.vmThreadToken = m_thread->m_vmThreadToken;
+ event.FuncEval.funcEvalType = m_evalType;
+ event.FuncEval.funcMetadataToken = m_function->GetMetadataToken();
+ event.FuncEval.vmDomainFile = m_function->GetModule()->GetRuntimeDomainFile();
+ event.FuncEval.funcEvalKey = hFuncEval.Ptr();
+ event.FuncEval.argCount = nArgs;
+ event.FuncEval.genericArgsCount = nTypeArgs;
+
+
+
+ hr = SendFuncEval(nTypeArgs,
+ rgpTypeArgs,
+ reinterpret_cast<void *>(pArgData),
+ sizeof(DebuggerIPCE_FuncEvalArgData) * nArgs,
+ NULL,
+ 0,
+ &event);
+
+ // Cleanup
+
+ if (pArgData)
+ {
+ delete [] pArgData;
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ hFuncEval.SuppressRelease(); // Now LS owns.
+ }
+ }
+
+ // Convert from LS EE-centric failure code to something more friendly to end-users.
+ // Success HRs will not be converted.
+ hr = FilterHR(hr);
+
+ // Return any failure the Left Side may have told us about.
+ return hr;
+}
+
+BOOL CordbEval::DoAppDomainsMatch( CordbAppDomain * pAppDomain,
+ ULONG32 nTypes,
+ ICorDebugType *pTypes[],
+ ULONG32 nValues,
+ ICorDebugValue *pValues[] )
+{
+ _ASSERTE( !(pTypes == NULL && nTypes != 0) );
+ _ASSERTE( !(pValues == NULL && nValues != 0) );
+
+ // Make sure each value is in the appdomain.
+ for(unsigned int i = 0; i < nValues; i++)
+ {
+ // Assuming that only Ref Values have AD affinity
+ CordbAppDomain * pValueAppDomain = GetAppDomainFromValue( pValues[i] );
+
+ if ((pValueAppDomain != NULL) && (pValueAppDomain != pAppDomain))
+ {
+ LOG((LF_CORDB,LL_INFO1000, "CordbEval::DADM - AD mismatch. appDomain=0x%08x, param #%d=0x%08x, must fail.\n",
+ pAppDomain, i, pValueAppDomain));
+ return FALSE;
+ }
+ }
+
+ for(unsigned int i = 0; i < nTypes; i++ )
+ {
+ CordbType* t = static_cast<CordbType*>( pTypes[i] );
+ CordbAppDomain * pTypeAppDomain = t->GetAppDomain();
+
+ if( pTypeAppDomain != NULL && pTypeAppDomain != pAppDomain )
+ {
+ LOG((LF_CORDB,LL_INFO1000, "CordbEval::DADM - AD mismatch. appDomain=0x%08x, type param #%d=0x%08x, must fail.\n",
+ pAppDomain, i, pTypeAppDomain));
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+HRESULT CordbEval::NewObject(ICorDebugFunction *pConstructor,
+ ULONG32 nArgs,
+ ICorDebugValue *pArgs[])
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ return NewParameterizedObject(pConstructor,0,NULL,nArgs,pArgs);
+}
+
+//---------------------------------------------------------------------------------------
+//
+// This routine calls a constructor with the given set of type arguments and actual arguments.
+// This is the jumping off point for func-evaling "new".
+//
+// Arguments:
+// pConstructor - The function to call.
+// nTypeArgs - The number of type-arguments for the method in rgpTypeArgs
+// rgpTypeArgs - An array of pointers to types.
+// nArgs - The number of arguments for the method in rgpArgs
+// rgpArgs - An array of pointers to values for the arguments to the method.
+//
+// Return Value:
+// HRESULT for the operation
+//
+HRESULT CordbEval::NewParameterizedObject(ICorDebugFunction * pConstructor,
+ ULONG32 nTypeArgs,
+ ICorDebugType * rgpTypeArgs[],
+ ULONG32 nArgs,
+ ICorDebugValue * rgpArgs[])
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pConstructor, ICorDebugFunction *);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(rgpArgs, ICorDebugValue *, nArgs, true, true);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ // The LS will assume that all of the ICorDebugValues and ICorDebugTypes are in
+ // the same appdomain as the constructor. Verify this.
+ CordbAppDomain * pConstructorAppDomain = (static_cast<CordbFunction *> (pConstructor))->GetAppDomain();
+
+ if (!DoAppDomainsMatch(pConstructorAppDomain, nTypeArgs, rgpTypeArgs, nArgs, rgpArgs))
+ {
+ return ErrWrapper(CORDBG_E_APPDOMAIN_MISMATCH);
+ }
+
+ // Callers are free to reuse an ICorDebugEval object for multiple
+ // evals. Since we create a Left Side eval representation each
+ // time, we need to be sure to clean it up now that we know we're
+ // done with it.
+ HRESULT hr = SendCleanup();
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+ RsPtrHolder<CordbEval> hFuncEval(this);
+
+ if (hFuncEval.Ptr().IsNull())
+ {
+ return E_OUTOFMEMORY;
+ }
+ lockHolder.Release();
+
+ // Remember the function that we're evaluating.
+ m_function = static_cast<CordbFunction *>(pConstructor);
+ m_evalType = DB_IPCE_FET_NEW_OBJECT;
+
+ // Arrange the arguments into a form that the left side can deal
+ // with. We do this before starting the func eval setup to ensure
+ // that we can complete this step before mutating up the left
+ // side.
+ DebuggerIPCE_FuncEvalArgData * pArgData = NULL;
+
+ if (nArgs > 0)
+ {
+ // We need to make the same type of array that the left side
+ // holds.
+ pArgData = new (nothrow) DebuggerIPCE_FuncEvalArgData[nArgs];
+
+ if (pArgData == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ // For each argument, convert its home into something the left
+ // side can understand.
+ for (unsigned int i = 0; i < nArgs; i++)
+ {
+ hr = GatherArgInfo(rgpArgs[i], &(pArgData[i]));
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+ }
+ }
+
+ // Send over to the left side and get it to setup this eval.
+ DebuggerIPCEvent event;
+
+ m_thread->GetProcess()->InitIPCEvent(&event, DB_IPCE_FUNC_EVAL, true, m_thread->GetAppDomain()->GetADToken());
+
+ event.FuncEval.vmThreadToken = m_thread->m_vmThreadToken;
+ event.FuncEval.funcEvalType = m_evalType;
+ event.FuncEval.funcMetadataToken = m_function->GetMetadataToken();
+ event.FuncEval.vmDomainFile = m_function->GetModule()->GetRuntimeDomainFile();
+ event.FuncEval.funcEvalKey = hFuncEval.Ptr();
+ event.FuncEval.argCount = nArgs;
+ event.FuncEval.genericArgsCount = nTypeArgs;
+
+ hr = SendFuncEval(nTypeArgs,
+ rgpTypeArgs,
+ reinterpret_cast<void *>(pArgData),
+ sizeof(DebuggerIPCE_FuncEvalArgData) * nArgs,
+ NULL,
+ 0,
+ &event);
+
+ // Cleanup
+
+ if (pArgData)
+ {
+ delete [] pArgData;
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ hFuncEval.SuppressRelease(); // Now LS owns.
+ }
+
+
+ // Return any failure the Left Side may have told us about.
+ return hr;
+}
+
+HRESULT CordbEval::NewObjectNoConstructor(ICorDebugClass *pClass)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ return NewParameterizedObjectNoConstructor(pClass,0,NULL);
+}
+
+//---------------------------------------------------------------------------------------
+//
+// This routine creates an object of a certain type, but does not call the constructor
+// for the type on the object.
+//
+// Arguments:
+// pClass - the type of the object to create.
+// nTypeArgs - The number of type-arguments for the method in rgpTypeArgs
+// rgpTypeArgs - An array of pointers to types.
+//
+// Return Value:
+// HRESULT for the operation
+//
+HRESULT CordbEval::NewParameterizedObjectNoConstructor(ICorDebugClass * pClass,
+ ULONG32 nTypeArgs,
+ ICorDebugType * rgpTypeArgs[])
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pClass, ICorDebugClass *);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ // The LS will assume that all of the ICorDebugTypes are in
+ // the same appdomain as the class. Verify this.
+ CordbAppDomain * pClassAppDomain = (static_cast<CordbClass *> (pClass))->GetAppDomain();
+
+ if (!DoAppDomainsMatch(pClassAppDomain, nTypeArgs, rgpTypeArgs, 0, NULL))
+ {
+ return ErrWrapper(CORDBG_E_APPDOMAIN_MISMATCH);
+ }
+
+ // Callers are free to reuse an ICorDebugEval object for multiple
+ // evals. Since we create a Left Side eval representation each
+ // time, we need to be sure to clean it up now that we know we're
+ // done with it.
+ HRESULT hr = SendCleanup();
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+ RsPtrHolder<CordbEval> hFuncEval(this);
+ lockHolder.Release(); // release to send an IPC event.
+
+ if (hFuncEval.Ptr().IsNull())
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ // Remember the function that we're evaluating.
+ m_class = (CordbClass*)pClass;
+ m_evalType = DB_IPCE_FET_NEW_OBJECT_NC;
+
+ // Send over to the left side and get it to setup this eval.
+ DebuggerIPCEvent event;
+
+ m_thread->GetProcess()->InitIPCEvent(&event, DB_IPCE_FUNC_EVAL, true, m_thread->GetAppDomain()->GetADToken());
+
+ event.FuncEval.vmThreadToken = m_thread->m_vmThreadToken;
+ event.FuncEval.funcEvalType = m_evalType;
+ event.FuncEval.funcMetadataToken = mdMethodDefNil;
+ event.FuncEval.funcClassMetadataToken = (mdTypeDef)m_class->m_id;
+ event.FuncEval.vmDomainFile = m_class->GetModule()->GetRuntimeDomainFile();
+ event.FuncEval.funcEvalKey = hFuncEval.Ptr();
+ event.FuncEval.argCount = 0;
+ event.FuncEval.genericArgsCount = nTypeArgs;
+
+ hr = SendFuncEval(nTypeArgs, rgpTypeArgs, NULL, 0, NULL, 0, &event);
+
+ if (SUCCEEDED(hr))
+ {
+ hFuncEval.SuppressRelease(); // Now LS owns.
+ }
+
+ // Return any failure the Left Side may have told us about.
+ return hr;
+}
+
+/*
+ *
+ * NewString
+ *
+ * This routine is the interface function for ICorDebugEval::NewString
+ *
+ * Parameters:
+ * string - the string to create - must be null-terminated
+ *
+ * Return Value:
+ * HRESULT from the helper routines on RS and LS.
+ *
+ */
+HRESULT CordbEval::NewString(LPCWSTR string)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ return NewStringWithLength(string, (UINT)wcslen(string));
+}
+
+//---------------------------------------------------------------------------------------
+//
+// This routine is the interface function for ICorDebugEval::NewStringWithLength.
+//
+// Arguments:
+// wszString - the string to create
+// iLength - the number of characters that you want to create. Can include embedded nulls.
+//
+// Return Value:
+// HRESULT for the operation
+//
+HRESULT CordbEval::NewStringWithLength(LPCWSTR wszString, UINT iLength)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(wszString, LPCWSTR); // Gotta have a string...
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ // Callers are free to reuse an ICorDebugEval object for multiple
+ // evals. Since we create a Left Side eval representation each
+ // time, we need to be sure to clean it up now that we know we're
+ // done with it.
+ HRESULT hr = SendCleanup();
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+ RsPtrHolder<CordbEval> hFuncEval(this);
+ lockHolder.Release(); // release to send an IPC event.
+
+ if (hFuncEval.Ptr().IsNull())
+ {
+ return E_OUTOFMEMORY;
+ }
+
+
+ // Length of the string? Don't account for null as COMString::NewString is length-based
+ SIZE_T cbString = iLength * sizeof(WCHAR);
+
+ // Remember that we're doing a func eval for a new string.
+ m_function = NULL;
+ m_evalType = DB_IPCE_FET_NEW_STRING;
+
+ // Send over to the left side and get it to setup this eval.
+ DebuggerIPCEvent event;
+
+ m_thread->GetProcess()->InitIPCEvent(&event, DB_IPCE_FUNC_EVAL, true, m_thread->GetAppDomain()->GetADToken());
+
+ event.FuncEval.vmThreadToken = m_thread->m_vmThreadToken;
+ event.FuncEval.funcEvalType = m_evalType;
+ event.FuncEval.funcEvalKey = hFuncEval.Ptr();
+ event.FuncEval.stringSize = cbString;
+
+ // Note: no function or module here...
+ event.FuncEval.funcMetadataToken = mdMethodDefNil;
+ event.FuncEval.funcClassMetadataToken = mdTypeDefNil;
+ event.FuncEval.vmDomainFile = VMPTR_DomainFile::NullPtr();
+ event.FuncEval.argCount = 0;
+ event.FuncEval.genericArgsCount = 0;
+ event.FuncEval.genericArgsNodeCount = 0;
+
+ hr = SendFuncEval(0, NULL, (void *)wszString, (unsigned int)cbString, NULL, 0, &event);
+
+ if (SUCCEEDED(hr))
+ {
+ hFuncEval.SuppressRelease(); // Now LS owns.
+ }
+
+ // Return any failure the Left Side may have told us about.
+ return hr;
+}
+
+HRESULT CordbEval::NewArray(CorElementType elementType,
+ ICorDebugClass *pElementClass,
+ ULONG32 rank,
+ ULONG32 dims[],
+ ULONG32 lowBounds[])
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pElementClass, ICorDebugClass *);
+
+ // If you want a class, you gotta pass a class.
+ if ((elementType == ELEMENT_TYPE_CLASS) && (pElementClass == NULL))
+ return E_INVALIDARG;
+
+ // If you want an array of objects, then why pass a class?
+ if ((elementType == ELEMENT_TYPE_OBJECT) && (pElementClass != NULL))
+ return E_INVALIDARG;
+
+ // Arg check...
+ if (elementType == ELEMENT_TYPE_VOID)
+ return E_INVALIDARG;
+
+ CordbType *typ;
+ HRESULT hr = S_OK;
+ hr = CordbType::MkUnparameterizedType(m_thread->GetAppDomain(), elementType, (CordbClass *) pElementClass, &typ);
+
+ if (FAILED(hr))
+ return hr;
+
+ return NewParameterizedArray(typ, rank,dims,lowBounds);
+
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// This routine sets up a func-eval to create a new array of the given type.
+//
+// Arguments:
+// pElementType - The type of each element of the array.
+// rank - Rank of the array.
+// rgDimensions - Array of dimensions for the array.
+// rmLowBounds - Array of lower bounds on the array.
+//
+// Return Value:
+// HRESULT for the operation
+//
+HRESULT CordbEval::NewParameterizedArray(ICorDebugType * pElementType,
+ ULONG32 rank,
+ ULONG32 rgDimensions[],
+ ULONG32 rgLowBounds[])
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pElementType, ICorDebugType *);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+
+ // Callers are free to reuse an ICorDebugEval object for multiple evals. Since we create a Left Side eval
+ // representation each time, we need to be sure to clean it up now that we know we're done with it.
+ HRESULT hr = SendCleanup();
+
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ // Arg check...
+ if ((rank == 0) || (rgDimensions == NULL))
+ {
+ return E_INVALIDARG;
+ }
+
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+ RsPtrHolder<CordbEval> hFuncEval(this);
+ lockHolder.Release(); // release to send an IPC event.
+
+ if (hFuncEval.Ptr().IsNull())
+ {
+ return E_OUTOFMEMORY;
+ }
+
+
+ // Remember that we're doing a func eval for a new string.
+ m_function = NULL;
+ m_evalType = DB_IPCE_FET_NEW_ARRAY;
+
+ // Send over to the left side and get it to setup this eval.
+ DebuggerIPCEvent event;
+
+ m_thread->GetProcess()->InitIPCEvent(&event, DB_IPCE_FUNC_EVAL, true, m_thread->GetAppDomain()->GetADToken());
+
+ event.FuncEval.vmThreadToken = m_thread->m_vmThreadToken;
+ event.FuncEval.funcEvalType = m_evalType;
+ event.FuncEval.funcEvalKey = hFuncEval.Ptr();
+
+ event.FuncEval.arrayRank = rank;
+
+ // Note: no function or module here...
+ event.FuncEval.funcMetadataToken = mdMethodDefNil;
+ event.FuncEval.funcClassMetadataToken = mdTypeDefNil;
+ event.FuncEval.vmDomainFile = VMPTR_DomainFile::NullPtr();
+ event.FuncEval.argCount = 0;
+ event.FuncEval.genericArgsCount = 1;
+
+ // Prefast overflow sanity check.
+ S_UINT32 allocSize = S_UINT32(rank) * S_UINT32(sizeof(SIZE_T));
+
+ if (allocSize.IsOverflow())
+ {
+ return E_INVALIDARG;
+ }
+
+ // Just in case sizeof(SIZE_T) != sizeof(ULONG32)
+ SIZE_T * rgDimensionsSizeT = reinterpret_cast<SIZE_T *>(_alloca(allocSize.Value()));
+
+ for (unsigned int i = 0; i < rank; i++)
+ {
+ rgDimensionsSizeT[i] = rgDimensions[i];
+ }
+
+ ICorDebugType * rgpGenericArgs[1];
+
+ rgpGenericArgs[0] = pElementType;
+
+ // @dbgtodo funceval : lower bounds were ignored in V1 - fix this.
+ hr = SendFuncEval(1,
+ rgpGenericArgs,
+ reinterpret_cast<void *>(rgDimensionsSizeT),
+ rank * sizeof(SIZE_T),
+ NULL, // (void*)lowBounds,
+ 0, // ((lowBounds == NULL) ? 0 : rank * sizeof(SIZE_T)),
+ &event);
+
+ if (SUCCEEDED(hr))
+ {
+ hFuncEval.SuppressRelease(); // Now LS owns.
+ }
+
+ // Return any failure the Left Side may have told us about.
+ return hr;
+}
+
+HRESULT CordbEval::IsActive(BOOL *pbActive)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pbActive, BOOL *);
+
+ *pbActive = (m_complete == true);
+ return S_OK;
+}
+
+/*
+ * This routine submits an abort request to the LS.
+ *
+ * Parameters:
+ * None.
+ *
+ * Returns:
+ * The HRESULT as returned by the LS.
+ *
+ */
+
+HRESULT
+CordbEval::Abort(
+ void
+ )
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ ATT_ALLOW_LIVE_DO_STOPGO(GetProcess());
+
+
+ //
+ // No need to abort if its already completed.
+ //
+ if (m_complete)
+ {
+ return S_OK;
+ }
+
+
+ //
+ // Can't abort if its never even been started.
+ //
+ if (m_debuggerEvalKey == NULL)
+ {
+ return E_INVALIDARG;
+ }
+
+ CORDBRequireProcessStateOK(m_thread->GetProcess());
+
+ //
+ // Send over to the left side to get the eval aborted.
+ //
+ DebuggerIPCEvent event;
+
+ m_thread->GetProcess()->InitIPCEvent(&event,
+ DB_IPCE_FUNC_EVAL_ABORT,
+ true,
+ m_thread->GetAppDomain()->GetADToken()
+ );
+
+ event.FuncEvalAbort.debuggerEvalKey = m_debuggerEvalKey;
+
+ HRESULT hr = m_thread->GetProcess()->SendIPCEvent(&event,
+ sizeof(DebuggerIPCEvent)
+ );
+
+
+ //
+ // If the send failed, return that failure.
+ //
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ _ASSERTE(event.type == DB_IPCE_FUNC_EVAL_ABORT_RESULT);
+
+ //
+ // Since we may have
+ // overwritten anything (objects, code, etc), we should mark
+ // everything as needing to be re-cached.
+ //
+ m_thread->GetProcess()->m_continueCounter++;
+
+ hr = event.hr;
+
+ return hr;
+}
+
+HRESULT CordbEval::GetResult(ICorDebugValue **ppResult)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppResult, ICorDebugValue **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ *ppResult = NULL;
+
+ // Is the evaluation complete?
+ if (!m_complete)
+ {
+ return CORDBG_E_FUNC_EVAL_NOT_COMPLETE;
+ }
+
+ if (m_aborted)
+ {
+ return CORDBG_S_FUNC_EVAL_ABORTED;
+ }
+
+ // Does the evaluation have a result?
+ if (m_resultType.elementType == ELEMENT_TYPE_VOID)
+ {
+ return CORDBG_S_FUNC_EVAL_HAS_NO_RESULT;
+ }
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // Make a ICorDebugValue out of the result.
+ CordbAppDomain * pAppDomain;
+
+ if (!m_resultAppDomainToken.IsNull())
+ {
+ // @dbgtodo funceval - push this up
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+
+ pAppDomain = m_thread->GetProcess()->LookupOrCreateAppDomain(m_resultAppDomainToken);
+ }
+ else
+ {
+ pAppDomain = m_thread->GetAppDomain();
+ }
+ PREFIX_ASSUME(pAppDomain != NULL);
+
+ CordbType * pType = NULL;
+ hr = CordbType::TypeDataToType(pAppDomain, &m_resultType, &pType);
+ IfFailThrow(hr);
+
+ bool resultInHandle =
+ ((m_resultType.elementType == ELEMENT_TYPE_CLASS) ||
+ (m_resultType.elementType == ELEMENT_TYPE_SZARRAY) ||
+ (m_resultType.elementType == ELEMENT_TYPE_OBJECT) ||
+ (m_resultType.elementType == ELEMENT_TYPE_ARRAY) ||
+ (m_resultType.elementType == ELEMENT_TYPE_STRING));
+
+ if (resultInHandle)
+ {
+ // if object handle is null here, something has gone wrong!!!
+ _ASSERTE(!m_vmObjectHandle.IsNull());
+
+ if (m_pHandleValue == NULL)
+ {
+ // Create CordbHandleValue for result
+ RSInitHolder<CordbHandleValue> pHandleValue(new CordbHandleValue(pAppDomain, pType, HANDLE_STRONG));
+
+ // Initialize the handle value object. The HandleValue will now
+ // own the m_objectHandle.
+ hr = pHandleValue->Init(m_vmObjectHandle);
+
+ if (!SUCCEEDED(hr))
+ {
+ // Neuter the new object we've been working on. This will
+ // call Dispose(), and that will go back to the left side
+ // and free the handle that we got above.
+ pHandleValue->NeuterLeftSideResources();
+
+ //
+
+ // Do not delete chv here. The neuter list still has a reference to it, and it will be cleaned up automatically.
+ ThrowHR(hr);
+ }
+ m_pHandleValue.Assign(pHandleValue);
+ pHandleValue.ClearAndMarkDontNeuter();
+ }
+
+ // This AddRef is for caller to release
+ //
+ *ppResult = m_pHandleValue;
+ m_pHandleValue->ExternalAddRef();
+ }
+ else if (CorIsPrimitiveType(m_resultType.elementType) && (m_resultType.elementType != ELEMENT_TYPE_STRING))
+ {
+ // create a CordbGenericValue flagged as a literal
+ hr = CordbEval::CreatePrimitiveLiteral(pType, ppResult);
+ }
+ else
+ {
+ TargetBuffer remoteValue(m_resultAddr, CordbValue::GetSizeForType(pType, kBoxed));
+ // Now that we have the module, go ahead and create the result.
+
+ CordbValue::CreateValueByType(pAppDomain,
+ pType,
+ true,
+ remoteValue,
+ MemoryRange(NULL, 0),
+ NULL,
+ ppResult); // throws
+ }
+
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+HRESULT CordbEval::GetThread(ICorDebugThread **ppThread)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppThread, ICorDebugThread **);
+
+ *ppThread = static_cast<ICorDebugThread*> (m_thread);
+ m_thread->ExternalAddRef();
+
+ return S_OK;
+}
+
+// Create a RS literal for primitive type funceval result. In case the result is used as an argument for
+// another funceval, we need to make sure that we're not relying on the LS value, which will be freed and
+// thus unavailable.
+// Arguments:
+// input: pType - CordbType instance representing the type of the primitive value
+// output: ppValue - ICorDebugValue representing the result as a literal CordbGenericValue
+// Return Value:
+// hr: may fail for OOM, ReadProcessMemory failures
+HRESULT CordbEval::CreatePrimitiveLiteral(CordbType * pType,
+ ICorDebugValue ** ppValue)
+{
+ CordbGenericValue * gv = NULL;
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // Create a generic value.
+ gv = new CordbGenericValue(pType);
+
+ // initialize the local value
+ int size = CordbValue::GetSizeForType(pType, kBoxed);
+ if (size > 8)
+ {
+ ThrowHR(HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER));
+ }
+ TargetBuffer remoteValue(m_resultAddr, size);
+ BYTE localBuffer[8] = {0};
+
+ GetProcess()->SafeReadBuffer (remoteValue, localBuffer);
+ gv->SetValue(localBuffer);
+
+ // Do not delete gv here even if the initialization fails.
+ // The neuter list still has a reference to it, and it will be cleaned up automatically.
+ gv->ExternalAddRef();
+ *ppValue = (ICorDebugValue*)(ICorDebugGenericValue*)gv;
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+HRESULT CordbEval::CreateValue(CorElementType elementType,
+ ICorDebugClass *pElementClass,
+ ICorDebugValue **ppValue)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ CordbType *typ;
+
+ // @todo: only primitive values right now.
+ if (((elementType < ELEMENT_TYPE_BOOLEAN) ||
+ (elementType > ELEMENT_TYPE_R8)) &&
+ !(elementType == ELEMENT_TYPE_CLASS))
+ return E_INVALIDARG;
+
+ HRESULT hr = S_OK;
+
+ // MkUnparameterizedType now works if you give it ELEMENT_TYPE_CLASS and
+ // a null pElementClass - it returns the type for ELEMENT_TYPE_OBJECT.
+
+ hr = CordbType::MkUnparameterizedType(m_thread->GetAppDomain(), elementType, (CordbClass *) pElementClass, &typ);
+
+ if (FAILED(hr))
+ return hr;
+
+ return CreateValueForType(typ, ppValue);
+}
+
+// create an ICDValue to represent a value for a funceval
+// Arguments:
+// input: pIType - the type for the new value
+// output: ppValue - the new ICDValue. If there is a failure of some sort, this will be NULL
+// ReturnValue: S_OK on success (ppValue should contain a non-NULL address)
+// E_OUTOFMEMORY, if we can't allocate space for the new ICDValue
+// Notes: We can also get read process memory errors or E_INVALIDARG if errors occur during initialization,
+// but in that case, we don't return the hresult. Instead, we just never update ppValue, so it will still be
+// NULL on exit.
+HRESULT CordbEval::CreateValueForType(ICorDebugType * pIType,
+ ICorDebugValue ** ppValue)
+{
+ HRESULT hr = S_OK;
+
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **);
+ VALIDATE_POINTER_TO_OBJECT(pIType, ICorDebugType*);
+
+ *ppValue = NULL;
+ CordbType *pType = static_cast<CordbType *> (pIType);
+
+ CorElementType elementType = pType->m_elementType;
+ // We don't support IntPtr and UIntPtr types as arguments here, but we do support these types as results
+ // (see code:CordbEval::CreatePrimitiveLiteral) and we have changed the LS to support them as well
+ if (((elementType < ELEMENT_TYPE_BOOLEAN) ||
+ (elementType > ELEMENT_TYPE_R8)) &&
+ !((elementType == ELEMENT_TYPE_CLASS) || (elementType == ELEMENT_TYPE_OBJECT)))
+ return E_INVALIDARG;
+
+ // Note: ELEMENT_TYPE_OBJECT is what we'll get for the null reference case, so allow that.
+ if ((elementType == ELEMENT_TYPE_CLASS) || (elementType == ELEMENT_TYPE_OBJECT))
+ {
+ EX_TRY
+ {
+ // create a reference value
+ CordbReferenceValue *rv = new CordbReferenceValue(pType);
+
+ if (SUCCEEDED(rv->InitRef(MemoryRange(NULL,0))))
+ {
+ // Do not delete rv here even if the initialization fails.
+ // The neuter list still has a reference to it, and it will be cleaned up automatically.
+ rv->ExternalAddRef();
+ *ppValue = (ICorDebugValue*)(ICorDebugReferenceValue*)rv;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ }
+ else
+ {
+ CordbGenericValue * gv = NULL;
+ EX_TRY
+ {
+ // Create a generic value.
+ gv = new CordbGenericValue(pType);
+
+ gv->Init(MemoryRange(NULL,0));
+ // Do not delete gv here even if the initialization fails.
+ // The neuter list still has a reference to it, and it will be cleaned up automatically.
+ gv->ExternalAddRef();
+ *ppValue = (ICorDebugValue*)(ICorDebugGenericValue*)gv;
+ }
+ EX_CATCH_HRESULT(hr);
+ }
+
+ return hr;
+} // CordbEval::CreateValueForType
+
+
+/* ------------------------------------------------------------------------- *
+ * CordbEval2
+ *
+ * Extentions to the CordbEval class for Whidbey
+ *
+ * ------------------------------------------------------------------------- */
+
+
+/*
+ * This routine submits a rude abort request to the LS.
+ *
+ * Parameters:
+ * None.
+ *
+ * Returns:
+ * The HRESULT as returned by the LS.
+ *
+ */
+
+HRESULT
+CordbEval::RudeAbort(
+ void
+ )
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+
+ ATT_ALLOW_LIVE_DO_STOPGO(GetProcess());
+
+ //
+ // No need to abort if its already completed.
+ //
+ if (m_complete)
+ {
+ return S_OK;
+ }
+
+ //
+ // Can't abort if its never even been started.
+ //
+ if (m_debuggerEvalKey == NULL)
+ {
+ return E_INVALIDARG;
+ }
+
+ CORDBRequireProcessStateOK(m_thread->GetProcess());
+
+ //
+ // Send over to the left side to get the eval aborted.
+ //
+ DebuggerIPCEvent event;
+
+ m_thread->GetProcess()->InitIPCEvent(&event,
+ DB_IPCE_FUNC_EVAL_RUDE_ABORT,
+ true,
+ m_thread->GetAppDomain()->GetADToken()
+ );
+
+ event.FuncEvalRudeAbort.debuggerEvalKey = m_debuggerEvalKey;
+
+ HRESULT hr = m_thread->GetProcess()->SendIPCEvent(&event,
+ sizeof(DebuggerIPCEvent)
+ );
+
+ //
+ // If the send failed, return that failure.
+ //
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ _ASSERTE(event.type == DB_IPCE_FUNC_EVAL_RUDE_ABORT_RESULT);
+
+ //
+ // Since we may have
+ // overwritten anything (objects, code, etc), we should mark
+ // everything as needing to be re-cached.
+ //
+ m_thread->GetProcess()->m_continueCounter++;
+
+ hr = event.hr;
+
+ return hr;
+}
+
+
+
+
+/* ------------------------------------------------------------------------- *
+ * CodeParameter Enumerator class
+ * ------------------------------------------------------------------------- */
+
+CordbCodeEnum::CordbCodeEnum(unsigned int cCodes, RSSmartPtr<CordbCode> * ppCodes) :
+ CordbBase(NULL, 0)
+{
+ // Because the array is of smart-ptrs, the elements are already reffed
+ // We now take ownership of the array itself too.
+ m_ppCodes = ppCodes;
+
+ m_iCurrent = 0;
+ m_iMax = cCodes;
+}
+
+
+CordbCodeEnum::~CordbCodeEnum()
+{
+ // This will invoke the SmartPtr dtors on each element and call release.
+ delete [] m_ppCodes;
+}
+
+HRESULT CordbCodeEnum::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugEnum)
+ *pInterface = static_cast<ICorDebugEnum*>(this);
+ else if (id == IID_ICorDebugCodeEnum)
+ *pInterface = static_cast<ICorDebugCodeEnum*>(this);
+ else if (id == IID_IUnknown)
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugCodeEnum*>(this));
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+HRESULT CordbCodeEnum::Skip(ULONG celt)
+{
+ HRESULT hr = E_FAIL;
+ if ( (m_iCurrent+celt) < m_iMax ||
+ celt == 0)
+ {
+ m_iCurrent += celt;
+ hr = S_OK;
+ }
+
+ return hr;
+}
+
+HRESULT CordbCodeEnum::Reset()
+{
+ m_iCurrent = 0;
+ return S_OK;
+}
+
+HRESULT CordbCodeEnum::Clone(ICorDebugEnum **ppEnum)
+{
+ VALIDATE_POINTER_TO_OBJECT(ppEnum, ICorDebugEnum **);
+ (*ppEnum) = NULL;
+
+ HRESULT hr = S_OK;
+
+ // Create a new copy of the array because the CordbCodeEnum will
+ // take ownership of it.
+ RSSmartPtr<CordbCode> * ppCodes = new (nothrow) RSSmartPtr<CordbCode> [m_iMax];
+ if (ppCodes == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+ for(UINT i = 0; i < m_iMax; i++)
+ {
+ ppCodes[i].Assign(m_ppCodes[i]);
+ }
+
+
+ CordbCodeEnum *pCVE = new (nothrow) CordbCodeEnum( m_iMax, ppCodes);
+ if ( pCVE == NULL )
+ {
+ delete [] ppCodes;
+ hr = E_OUTOFMEMORY;
+ goto LExit;
+ }
+
+ pCVE->ExternalAddRef();
+ (*ppEnum) = (ICorDebugEnum*)pCVE;
+
+LExit:
+ return hr;
+}
+
+HRESULT CordbCodeEnum::GetCount(ULONG *pcelt)
+{
+ VALIDATE_POINTER_TO_OBJECT(pcelt, ULONG *);
+
+ if( pcelt == NULL)
+ return E_INVALIDARG;
+
+ (*pcelt) = m_iMax;
+ return S_OK;
+}
+
+//
+// In the event of failure, the current pointer will be left at
+// one element past the troublesome element. Thus, if one were
+// to repeatedly ask for one element to iterate through the
+// array, you would iterate exactly m_iMax times, regardless
+// of individual failures.
+HRESULT CordbCodeEnum::Next(ULONG celt, ICorDebugCode *values[], ULONG *pceltFetched)
+{
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(values, ICorDebugClass *,
+ celt, true, true);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pceltFetched, ULONG *);
+
+ if ((pceltFetched == NULL) && (celt != 1))
+ {
+ return E_INVALIDARG;
+ }
+
+ if (celt == 0)
+ {
+ if (pceltFetched != NULL)
+ {
+ *pceltFetched = 0;
+ }
+ return S_OK;
+ }
+
+ HRESULT hr = S_OK;
+
+ int iMax = min( m_iMax, m_iCurrent+celt);
+ int i;
+
+ for (i = m_iCurrent; i < iMax; i++)
+ {
+ values[i-m_iCurrent] = m_ppCodes[i];
+ values[i-m_iCurrent]->AddRef();
+ }
+
+ int count = (i - m_iCurrent);
+
+ if ( FAILED( hr ) )
+ { //we failed: +1 pushes us past troublesome element
+ m_iCurrent += 1 + count;
+ }
+ else
+ {
+ m_iCurrent += count;
+ }
+
+ if (pceltFetched != NULL)
+ {
+ *pceltFetched = count;
+ }
+
+ //
+ // If we reached the end of the enumeration, but not the end
+ // of the number of requested items, we return S_FALSE.
+ //
+ if (((ULONG)count) < celt)
+ {
+ return S_FALSE;
+ }
+
+ return hr;
+}
+
diff --git a/src/debug/di/rstype.cpp b/src/debug/di/rstype.cpp
new file mode 100644
index 0000000000..b183fdf39e
--- /dev/null
+++ b/src/debug/di/rstype.cpp
@@ -0,0 +1,2815 @@
+// 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.
+//*****************************************************************************
+// File: rstype.cpp
+//
+
+//
+// Define implementation of ICorDebugType
+//*****************************************************************************
+
+
+#include "stdafx.h"
+#include "winbase.h"
+#include "corpriv.h"
+
+
+//-----------------------------------------------------------------------------
+// Public method to get the static field from a type.
+//
+// Parameters:
+// fieldDef - metadata token for which field on this type to retrieve.
+// pFrame - context for Thread/AppDomains statics.
+// ppValue - OUT: out-parameter to get value.
+//
+// Returns:
+// S_OK on success.
+//
+HRESULT CordbType::GetStaticFieldValue(mdFieldDef fieldDef,
+ ICorDebugFrame * pFrame,
+ ICorDebugValue ** ppValue)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+
+ IMetaDataImport * pImport = NULL;
+
+ EX_TRY
+ {
+ // Ensure we were actually passed a mdFieldDef. This is especially useful to protect
+ // against an accidental mdPropertyDef, because properties look like fields.
+ //
+ if (TypeFromToken(fieldDef) != mdtFieldDef)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ pImport = m_pClass->GetModule()->GetMetaDataImporter(); // throws
+
+ if (((m_elementType != ELEMENT_TYPE_CLASS) && (m_elementType != ELEMENT_TYPE_VALUETYPE)) || (m_pClass == NULL))
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+
+
+ BOOL fSyncBlockField = FALSE;
+
+ // If non generic type, then degenerate to CordbClass implementation.
+ if (m_inst.m_cInst == 0)
+ {
+ hr = m_pClass->GetStaticFieldValue(fieldDef, pFrame, ppValue);
+ }
+ else
+ {
+ *ppValue = NULL;
+
+ // Validate the token.
+ if (!pImport->IsValidToken(fieldDef))
+ {
+ ThrowHR(hr = E_INVALIDARG);
+ }
+
+ // Make sure we have enough info about the class.
+ hr = Init(FALSE);
+ IfFailThrow(hr);
+
+ // Lookup the field given its metadata token.
+ FieldData * pFieldData;
+
+ hr = GetFieldInfo(fieldDef, &pFieldData);
+
+ if (hr == CORDBG_E_ENC_HANGING_FIELD)
+ {
+ // Generics + EnC is Not supported.
+ hr = CORDBG_E_STATIC_VAR_NOT_AVAILABLE;
+ }
+
+ IfFailThrow(hr);
+
+ hr = CordbClass::GetStaticFieldValue2(m_pClass->GetModule(),
+ pFieldData,
+ fSyncBlockField,
+ &m_inst,
+ pFrame,
+ ppValue);
+ // fall through to translate HR
+ }
+
+ }
+ EX_CATCH_HRESULT(hr);
+ if (pImport != NULL)
+ {
+ hr = CordbClass::PostProcessUnavailableHRESULT(hr, pImport, fieldDef);
+ }
+ return hr;
+
+}
+
+// Combine E_T_s and rank together to get an id for the m_sharedtypes table
+#define CORDBTYPE_ID(elementType,rank) ((unsigned int) elementType * (rank + 1) + 1)
+
+
+//-----------------------------------------------------------------------------
+// Constructor
+// Builds a CordbType around a primitive.
+//-----------------------------------------------------------------------------
+CordbType::CordbType(CordbAppDomain *appdomain, CorElementType et, unsigned int rank)
+: CordbBase(appdomain->GetProcess(), CORDBTYPE_ID(et,rank) , enumCordbType),
+ m_elementType(et),
+ m_appdomain(appdomain),
+ m_pClass(NULL),
+ m_rank(rank),
+ m_spinetypes(2),
+ m_objectSize(0),
+ m_fieldInfoNeedsInit(TRUE)
+{
+ m_typeHandleExact = VMPTR_TypeHandle::NullPtr();
+
+ _ASSERTE(m_elementType != ELEMENT_TYPE_VALUETYPE);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ m_appdomain->AddToTypeList(this);
+ }
+ EX_CATCH_HRESULT(hr);
+ SetUnrecoverableIfFailed(GetProcess(), hr);
+}
+
+//-----------------------------------------------------------------------------
+// Constructor
+// Builds a CordbType around a class. This is an Open CordbType.
+// For a generic type, this CordbType will not have the generic parameters,
+// but it will be a subordinate type to another Closed (instantiated) CordbType
+//-----------------------------------------------------------------------------
+CordbType::CordbType(CordbAppDomain *appdomain, CorElementType et, CordbClass *cls)
+: CordbBase(appdomain->GetProcess(), et, enumCordbType),
+ m_elementType(et),
+ m_appdomain(appdomain),
+ m_pClass(cls),
+ m_rank(0),
+ m_spinetypes(2),
+ m_objectSize(0),
+ m_fieldInfoNeedsInit(TRUE)
+{
+ m_typeHandleExact = VMPTR_TypeHandle::NullPtr();
+ _ASSERTE(m_elementType != ELEMENT_TYPE_VALUETYPE);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ m_appdomain->AddToTypeList(this);
+ }
+ EX_CATCH_HRESULT(hr);
+ SetUnrecoverableIfFailed(GetProcess(), hr);
+}
+
+//-----------------------------------------------------------------------------
+// Constructor
+// Builds a Partial-Type, instantiation is tycon's instantation plus tyarg.
+// Eg, if tycon is "Dict<int>", and tyarg is "string", then this yields
+// "Dict<int, string>"
+//-----------------------------------------------------------------------------
+CordbType::CordbType(CordbType *tycon, CordbType *tyarg)
+: CordbBase(tycon->GetProcess(), (UINT_PTR)tyarg, enumCordbType),
+ m_elementType(tycon->m_elementType),
+ m_appdomain(tycon->m_appdomain),
+ m_pClass(tycon->m_pClass),
+ m_rank(tycon->m_rank),
+ m_spinetypes(2),
+ m_objectSize(0),
+ m_fieldInfoNeedsInit(TRUE)
+ // tyarg is added as part of instantiation -see below...
+{
+ m_typeHandleExact = VMPTR_TypeHandle::NullPtr();
+ _ASSERTE(m_elementType != ELEMENT_TYPE_VALUETYPE);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ m_appdomain->AddToTypeList(this);
+ }
+ EX_CATCH_HRESULT(hr);
+ SetUnrecoverableIfFailed(GetProcess(), hr);
+}
+
+
+ULONG STDMETHODCALLTYPE CordbType::AddRef()
+{
+ // This AddRef/Release pair creates a weak ref-counted reference to the class for this
+ // type. This avoids a circularity in ref-counted references between
+ // classes and types - if we had a circularity the objects would never get
+ // collected at all...
+ //if (m_class)
+ // m_class->AddRef();
+ return (BaseAddRef());
+}
+ULONG STDMETHODCALLTYPE CordbType::Release()
+{
+ // if (m_class)
+ // m_class->Release();
+ return (BaseRelease());
+}
+
+/*
+ A list of which resources owened by this object are accounted for.
+
+ HANDLED:
+ CordbClass *m_class; Weakly referenced by increasing count directly in AddRef() and Release()
+ Instantiation m_inst; // Internal pointers to CordbClass released in CordbClass::Neuter
+ CordbHashTable m_spinetypes; // Neutered
+ CordbHashTable m_fields; // Deleted in ~CordbType
+*/
+
+//-----------------------------------------------------------------------------
+// Cleanup memory for CordbTypes.
+//-----------------------------------------------------------------------------
+CordbType::~CordbType()
+{
+ _ASSERTE(IsNeutered());
+}
+
+//-----------------------------------------------------------------------------
+// Neutered by CordbModule
+// See CordbBase::Neuter for neuter semantics.
+//-----------------------------------------------------------------------------
+void CordbType::Neuter()
+{
+ _ASSERTE(GetProcess()->GetProcessLock()->HasLock());
+
+ // We have some direct releases below. If we call Neuter twice, that could
+ // result in double-releases. So check if we're already neutered, and
+ // if so, no work left to do.
+ if (IsNeutered())
+ {
+ return;
+ }
+
+ for (unsigned int i = 0; i < m_inst.m_cInst; i++)
+ {
+ m_inst.m_ppInst[i]->Release();
+ }
+
+ m_spinetypes.NeuterAndClear(GetProcess()->GetProcessLock());
+
+ if(m_inst.m_ppInst)
+ {
+ delete [] m_inst.m_ppInst;
+ m_inst.m_ppInst = NULL;
+ }
+ m_fieldList.Dealloc();
+
+ CordbBase::Neuter();
+}
+
+//-----------------------------------------------------------------------------
+// Public method for IUnknown::QueryInterface.
+// Has standard QI semantics.
+//-----------------------------------------------------------------------------
+HRESULT CordbType::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugType)
+ *pInterface = static_cast<ICorDebugType*>(this);
+ else if (id == IID_IUnknown)
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugType*>(this));
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+
+//-----------------------------------------------------------------------------
+// Make a simple type with no type arguments by specifying a CorElementType,
+// e.g. ELEMENT_TYPE_I1
+//
+// CordbType's are effectively a full representation of
+// structured types. They are hashed via a combination of their constituent
+// elements (e.g. CordbClass's or CordbType's) and the element type that is used to
+// combine the elements, or if they have no elements then via
+// the element type alone. The following is used to create all CordbTypes.
+//
+// An AppDomain holds a cache of CordbTypes for each of the basic CorElementTypes.
+//
+// Arguments:
+// pAppDomain - the AppDomain that the type lives in.
+// elementType - element_type to create the CordbType around.
+// ppResultType - OUT: out-parameter to get the CordbType.
+//
+// Returns:
+// S_OK on success.
+//
+//
+HRESULT CordbType::MkType(CordbAppDomain * pAppDomain,
+ CorElementType elementType,
+ CordbType ** ppResultType)
+{
+ _ASSERTE(pAppDomain != NULL);
+ _ASSERTE(ppResultType != NULL);
+
+ RSLockHolder lockHolder(pAppDomain->GetProcess()->GetProcessLock());
+
+ // Some points in the code create types via element types that are clearly objects but where
+ // no further information is given. This is always done when creating a CordbValue, prior
+ // to actually going over to the EE to discover what kind of value it is. In all these
+ // cases we can just use the type for "Object" - the code for dereferencing the value
+ // will update the type correctly once it has been determined. We don't do this for ELEMENT_TYPE_STRING
+ // as that is actually a NullaryType and at other places in the code we will want exactly that type!
+ if ((elementType == ELEMENT_TYPE_CLASS) ||
+ (elementType == ELEMENT_TYPE_SZARRAY) ||
+ (elementType == ELEMENT_TYPE_ARRAY))
+ {
+ elementType = ELEMENT_TYPE_OBJECT;
+ }
+
+ switch (elementType)
+ {
+ // this one is included because we need a "seed" type to uniquely hash FNPTR types,
+ // i.e. the nullary FNPTR type is used as the type constructor for all function pointer types,
+ // when combined with an approproiate instantiation.
+ case ELEMENT_TYPE_FNPTR:
+ // fall through ...
+
+ case ELEMENT_TYPE_VOID:
+ case ELEMENT_TYPE_BOOLEAN:
+ case ELEMENT_TYPE_CHAR:
+ case ELEMENT_TYPE_I1:
+ case ELEMENT_TYPE_U1:
+ case ELEMENT_TYPE_I2:
+ case ELEMENT_TYPE_U2:
+ case ELEMENT_TYPE_I4:
+ case ELEMENT_TYPE_U4:
+ case ELEMENT_TYPE_I8:
+ case ELEMENT_TYPE_U8:
+ case ELEMENT_TYPE_R4:
+ case ELEMENT_TYPE_R8:
+ case ELEMENT_TYPE_STRING:
+ case ELEMENT_TYPE_OBJECT:
+ case ELEMENT_TYPE_TYPEDBYREF:
+ case ELEMENT_TYPE_I:
+ case ELEMENT_TYPE_U:
+
+ *ppResultType = pAppDomain->m_sharedtypes.GetBase(CORDBTYPE_ID(elementType, 0));
+
+ if (*ppResultType == NULL)
+ {
+ CordbType * pNewType = new (nothrow) CordbType(pAppDomain, elementType, (unsigned int) 0);
+
+ if (pNewType == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ HRESULT hr = pAppDomain->m_sharedtypes.AddBase(pNewType);
+
+ if (SUCCEEDED(hr))
+ {
+ *ppResultType = pNewType;
+ }
+ else
+ {
+ _ASSERTE(!"unexpected failure!");
+ delete pNewType;
+ }
+
+ return hr;
+ }
+ return S_OK;
+
+ default:
+ _ASSERTE(!"unexpected element type!");
+ return E_FAIL;
+ }
+
+}
+
+//-----------------------------------------------------------------------------
+// Internal method to make a type with exactly one type argument by specifying
+// ELEMENT_TYPE_PTR, ELEMENT_TYPE_BYREF, ELEMENT_TYPE_SZARRAY or
+// ELEMENT_TYPE_ARRAY.
+//
+// Arguments:
+// pAppDomain - appdomain containing the type.
+// elementType - element type to create around. This is limited to: ELEMENT_TYPE_PTR,
+// ELEMENT_TYPE_BYREF, ELEMENT_TYPE_SZARRAY or ELEMENT_TYPE_ARRAY.
+// rank - for non-arrays, this must be 0. For szarray, this must be 1.
+// For multi-dimensional arrays, this is the rank.
+// pType - the single input type-parameter required for the specified element type.
+// ppResultType - OUT: the output parameter to get the corresponding CordbType
+//
+// Returns:
+// S_OK on success.
+//
+HRESULT CordbType::MkType(CordbAppDomain *pAppDomain,
+ CorElementType elementType,
+ ULONG rank,
+ CordbType * pType,
+ CordbType ** ppResultType)
+{
+ _ASSERTE(pAppDomain != NULL);
+ _ASSERTE(ppResultType != NULL);
+
+ RSLockHolder lockHolder(pAppDomain->GetProcess()->GetProcessLock());
+
+ switch (elementType)
+ {
+
+ case ELEMENT_TYPE_PTR:
+ case ELEMENT_TYPE_BYREF:
+ _ASSERTE(rank == 0);
+ goto LUnary;
+
+ case ELEMENT_TYPE_SZARRAY:
+ _ASSERTE(rank == 1);
+ goto LUnary;
+
+ case ELEMENT_TYPE_ARRAY:
+LUnary:
+ {
+ CordbType * pFoundType = pAppDomain->m_sharedtypes.GetBase(CORDBTYPE_ID(elementType, rank));
+
+ if (pFoundType == NULL)
+ {
+ pFoundType = new (nothrow) CordbType(pAppDomain, elementType, rank);
+
+ if (pFoundType == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ HRESULT hr = pAppDomain->m_sharedtypes.AddBase(pFoundType);
+
+ if (FAILED(hr))
+ {
+ _ASSERTE(!"unexpected failure!");
+ delete pFoundType;
+ return hr;
+ }
+ }
+
+ Instantiation inst(1, &pType);
+
+ return MkTyAppType(pAppDomain, pFoundType, &inst, ppResultType);
+
+ }
+
+ default:
+ _ASSERTE(!"unexpected element type!");
+ return E_FAIL;
+ }
+
+}
+
+//-----------------------------------------------------------------------------
+// Internal method to make a type for an instantiation of a class or value type, or just for the
+// class or value type if it accepts no type parameters.
+// Creates a CordbType instantiation around an uninstantiated CordbType and TypeParameter list.
+// In other words, this does:
+// CordbType(List<T>) + Instantiation({T=int}) --> CordbType(List<int>)
+//
+// This will create the subordinate types. Eg, for Triple<x,y,z>, it will create:
+// CordbType(Triple<x>), CordbType(Triple<x,y>), and CordbType(Triple<x,y,z)).
+// The fully instantiated one (the last one) is returned via the out parameter *pRes.
+//
+// Arguments:
+// pAppDomain - the appdomain that the type lives in.
+// pType - the open type to instantiate. Eg, CordbType(List<T>)
+// pInst - instantiation parameters.
+// ppResultType - OUT: out parameter to hold resulting type.
+//
+// Returns:
+// S_OK on success.
+//
+HRESULT CordbType::MkTyAppType(CordbAppDomain * pAppDomain,
+ CordbType * pType,
+ const Instantiation * pInst,
+ CordbType ** ppResultType)
+{
+ _ASSERTE(pAppDomain == pType->GetAppDomain());
+
+ CordbType * pCordbType = pType;
+
+ // Loop through and create each of the subordinate types, building up to the final fully Closed type.
+ for (unsigned int i = 0; i < pInst->m_cClassTyPars; i++)
+ {
+
+ CordbType * pCordbBaseType = pCordbType->m_spinetypes.GetBase((UINT_PTR) (pInst->m_ppInst[i]));
+
+ if (pCordbBaseType == NULL)
+ {
+ pCordbBaseType = new (nothrow) CordbType(pCordbType, pInst->m_ppInst[i]);
+
+ if (pCordbBaseType == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ HRESULT hr = pCordbType->m_spinetypes.AddBase(pCordbBaseType);
+
+ if (FAILED(hr))
+ {
+ _ASSERTE(!"unexpected failure!");
+ delete pCordbBaseType;
+ // @dbgtodo Microsoft leaks: Release the previously created types if this fails later in the loop
+ return hr;
+ }
+
+ pCordbBaseType->m_inst.m_cInst = i + 1;
+ pCordbBaseType->m_inst.m_cClassTyPars = i + 1;
+ pCordbBaseType->m_inst.m_ppInst = new (nothrow) CordbType *[i+1];
+
+ if (pCordbBaseType->m_inst.m_ppInst == NULL)
+ {
+ delete pCordbBaseType;
+ // @dbgtodo Microsoft leaks: Doesn't release the previously created types if this fails later in the loop
+ return E_OUTOFMEMORY;
+ }
+
+ for (unsigned int j = 0; j < (i + 1); j++)
+ {
+ // Constructed types include pointers across to other types - increase
+ // the reference counts on these....
+ pInst->m_ppInst[j]->AddRef();
+
+ pCordbBaseType->m_inst.m_ppInst[j] = pInst->m_ppInst[j];
+ }
+ }
+ pCordbType = pCordbBaseType;
+ }
+
+ *ppResultType = pCordbType;
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Creates a CordbType instantation around a cordbClass and TypeParameter list.
+// In other words, this does:
+// CordbClass(List<T>) + Instantiation({T=int}) --> CordbType(List<int>)
+//
+// This really just converts CordbClass(List<T>) --> CordbType(List<T>), and then calls CordbType::MkTyAppType
+//
+// Arguments:
+// pAppDomain - the AD that the class lives in.
+// elementType - element type of the class. Either ELEMENT_TYPE_CLASS or ELEMENT_TYPE_VALUETYPE
+// pClass - the uninstantiated class (eg, List<T>). This function will fill out the tycon->m_type field
+// to an uninstantiated CordbType (eg CordbType(List<T>))
+// pInst - the list of type parameters to instantiate with.
+// ppResultType - OUT: the CordbType instantiated with the type parameters (eg, CordbType(List<int>))
+//
+// Returns:
+// S_OK on success.
+//
+HRESULT CordbType::MkType(CordbAppDomain * pAppDomain,
+ CorElementType elementType,
+ CordbClass * pClass,
+ const Instantiation * pInst,
+ CordbType ** ppResultType)
+{
+ _ASSERTE(pAppDomain != NULL);
+ _ASSERTE(ppResultType != NULL);
+
+ switch (elementType)
+ {
+ // Normalize E_T_VALUETYPE away, so types do not record whether they are VCs or not, but CorDebugClass does.
+ // Update our view of whether a class is a VC based on the evidence we have here.
+ case ELEMENT_TYPE_VALUETYPE:
+
+ _ASSERTE(((pClass != NULL) && (!pClass->IsValueClassKnown() || pClass->IsValueClassNoInit())) ||
+ !"A non-value class is being used with ELEMENT_TYPE_VALUETYPE");
+
+ pClass->SetIsValueClass(true);
+ pClass->SetIsValueClassKnown(true);
+ // drop through
+
+ case ELEMENT_TYPE_CLASS:
+ {
+ // This probably isn't needed...
+ if (pClass == NULL)
+ {
+ elementType = ELEMENT_TYPE_OBJECT;
+ goto LReallyObject;
+ }
+
+ CordbType * pType = NULL;
+
+ pType = pClass->GetType();
+
+ if (pType == NULL)
+ {
+ pType = new (nothrow) CordbType(pAppDomain, ELEMENT_TYPE_CLASS, pClass);
+
+ if (pType == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ pClass->SetType(pType);
+ }
+
+ _ASSERTE(pClass->GetType() != NULL);
+
+ return CordbType::MkTyAppType(pAppDomain, pType, pInst, ppResultType);
+ }
+
+ default:
+LReallyObject:
+
+ _ASSERTE(pInst->m_cInst == 0);
+ return MkType(pAppDomain, elementType, ppResultType);
+
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Make a CordbType for a function pointer type (ELEMENT_TYPE_FNPTR).
+//
+// Arguments:
+// pAppDomain - the Appdomian the type lives in.
+// elementType - must be ELEMENT_TYPE_FNPTR.
+// pInst - instantiation information.
+// ppResultType - OUT: out-parameter to hold resulting CordbType
+//
+// Return:
+// S_OK on success.
+//
+HRESULT CordbType::MkType(CordbAppDomain * pAppDomain,
+ CorElementType elementType,
+ const Instantiation * pInst,
+ CordbType ** ppResultType)
+{
+ CordbType * pType;
+
+ _ASSERTE(elementType == ELEMENT_TYPE_FNPTR);
+
+ HRESULT hr = MkType(pAppDomain, elementType, &pType);
+
+ if (!SUCCEEDED(hr))
+ {
+ return hr;
+ }
+ return CordbType::MkTyAppType(pAppDomain, pType, pInst, ppResultType);
+}
+
+
+//-----------------------------------------------------------------------------
+// Public API to get the CorElementType of the type.
+//
+// Parameters:
+// pType - OUT: on return, gets the CorElementType
+//
+// Returns:
+// S_OK on success. CORDBG_E_CLASS_NOT_LOADED or synchronization errors on failure
+//-----------------------------------------------------------------------------
+HRESULT CordbType::GetType(CorElementType *pType)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ // See if this E_T_CLASS is really a value type?
+ if (m_elementType == ELEMENT_TYPE_CLASS)
+ {
+ _ASSERTE(m_pClass);
+ bool isVC = false;
+ // Determining if something is a VC or not can involve asking the EE.
+ // We could do it ourselves based on the metadata but it's non-trivial
+ // determining if a class has System.ValueType as a parent (we have
+ // to find and OpenScope the mscorlib.dll which we don't currently do
+ // on the right-side). But the IsValueClass call can fail if the
+ // class is not yet loaded on the right side. In that case we
+ // ignore the failure and return ELEMENT_TYPE_CLASS
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ isVC = m_pClass->IsValueClass();
+ }
+ EX_CATCH_HRESULT(hr);
+ if (!FAILED(hr) && isVC)
+ {
+ *pType = ELEMENT_TYPE_VALUETYPE;
+ return S_OK;
+ }
+ }
+ *pType = m_elementType;
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Public method to get the ICorDebugClass that matches this type.
+// ICorDebugType has instantiated type-params (eg, List<int>), whereas
+// ICorDebugClass is open (eg, List<T>).
+//
+// Parameters:
+// pClass - OUT: gets class on return.
+// Returns:
+// S_OK on success. CORDBG_E_CLASS_NOT_LOADED if the class is not loaded.
+// Else some other error.
+//-----------------------------------------------------------------------------
+HRESULT CordbType::GetClass(ICorDebugClass **pClass)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ if ((m_pClass == NULL) && (m_elementType == ELEMENT_TYPE_STRING ||
+ m_elementType == ELEMENT_TYPE_OBJECT))
+ {
+ Init(FALSE);
+ }
+ if (m_pClass == NULL)
+ {
+ *pClass = NULL;
+ return CORDBG_E_CLASS_NOT_LOADED;
+ }
+ *pClass = m_pClass;
+ m_pClass->ExternalAddRef();
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Public method to get array rank. This is only valid for arrays.
+//
+// Parameters:
+// pnRank - OUT: *pnRank is set to rank on return
+//
+// Return:
+// S_OK if success. E_INVALIDARG is this Type doesn't have a rank.
+//-----------------------------------------------------------------------------
+HRESULT CordbType::GetRank(ULONG32 *pnRank)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(pnRank, ULONG32 *);
+
+ if (m_elementType != ELEMENT_TYPE_SZARRAY &&
+ m_elementType != ELEMENT_TYPE_ARRAY)
+ return E_INVALIDARG;
+
+ *pnRank = (ULONG32) m_rank;
+
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Public convenience method to get the first type parameter.
+// This is purely to avoid needing to call EnumerateTypeParameters for
+// the set of types that only have 1 type-parameter.
+//
+// Parameters:
+// pType - OUT: get the ICorDebugType for the first type-parameter.
+// Returns:
+// S_OK on success.
+//-----------------------------------------------------------------------------
+HRESULT CordbType::GetFirstTypeParameter(ICorDebugType **pType)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(pType, ICorDebugType **);
+
+ // Since this is a public API, make sure there actually is at least 1 type-parameter.
+ if (m_inst.m_cInst == 0)
+ {
+ return E_INVALIDARG;
+ }
+
+ _ASSERTE(m_inst.m_ppInst != NULL);
+ _ASSERTE(m_inst.m_ppInst[0] != NULL);
+
+ *pType = m_inst.m_ppInst[0];
+ if (*pType)
+ (*pType)->AddRef();
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Internal worker to create a CordbType around a CordbClass.
+// Parameters:
+// appdomain - AD that the type lives in.
+// et - CorElementType of the incoming CordbClass
+// cl - CordbClass representing the type to build a CordbType for
+// pRes - OUT: out parameter to return the newly created CordbType object.
+//
+// Return:
+// S_OK on success.
+//-----------------------------------------------------------------------------
+HRESULT CordbType::MkUnparameterizedType(CordbAppDomain *appdomain, CorElementType et, CordbClass *cl,CordbType **pRes)
+{
+ // Pass in empty instantiation since CordbClass has no generic info.
+ // We should make some assert between et and cl->GetType().
+ Instantiation emptyInstantiation;
+
+ return CordbType::MkType(appdomain, et, cl, &emptyInstantiation, pRes);
+}
+
+
+//-----------------------------------------------------------------------------
+// Internal helper to get the First type parameter.
+// This is an internal convenience function for the public GetFirstTypeParameter.
+//
+// Parameters:
+// pRes - OUT: out-param to get the unary type-parameter.
+//-----------------------------------------------------------------------------
+void
+CordbType::DestUnaryType(CordbType **pRes)
+{
+ _ASSERTE(m_elementType == ELEMENT_TYPE_PTR
+ || m_elementType == ELEMENT_TYPE_BYREF
+ || m_elementType == ELEMENT_TYPE_ARRAY
+ || m_elementType == ELEMENT_TYPE_SZARRAY);
+ _ASSERTE(m_inst.m_cInst == 1);
+ _ASSERTE(m_inst.m_ppInst != NULL);
+ *pRes = m_inst.m_ppInst[0];
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Internal method to get the Class and type-parameters from a CordbType.
+//-----------------------------------------------------------------------------
+void
+CordbType::DestConstructedType(CordbClass **cls, Instantiation *inst)
+{
+ ASSERT(m_elementType == ELEMENT_TYPE_CLASS);
+ *cls = m_pClass;
+ *inst = m_inst;
+}
+
+//-----------------------------------------------------------------------------
+// Internal method to get all the type-parameters for a FnPtr
+//-----------------------------------------------------------------------------
+void
+CordbType::DestNaryType(Instantiation *inst)
+{
+ ASSERT(m_elementType == ELEMENT_TYPE_FNPTR);
+ *inst = m_inst;
+}
+
+
+//-----------------------------------------------------------------------------
+// CordbType::SigToType
+// Internal helper to create a CordbType from a Metadata signature (SigParser)
+//
+// This parses a metadata signature in the context of a module to return a CordbType.
+// This heavily relies on the metadata and signature format. See ECMA Partition II for details.
+// Since signatures may be recursive, this function can be called recursively.
+// Since metadata signatures exist all over, this can be called in many different scenarios, including
+// resolving a TypeSpec, looking up a field.
+//
+// pModule - module that the signature lives in.
+// pSigParse - Signature, positioned at the point to read the Type from.
+// This will not change the SigParser's current position.
+// inst - instantiation containing Type Params for the context of the SigParser.
+// For a local var or argument lookup, this would be the type-params from the Frame.
+// For a field lookup, this would be the type-params for the containing type.
+// pRes - OUT: yields the CordbType for this signature.
+//
+// Returns:
+// S_OK on success
+//-----------------------------------------------------------------------------
+HRESULT
+CordbType::SigToType(CordbModule * pModule,
+ SigParser * pSigParser,
+ const Instantiation * pInst,
+ CordbType ** ppResultType)
+{
+ FAIL_IF_NEUTERED(pModule);
+ INTERNAL_SYNC_API_ENTRY(pModule->GetProcess());
+
+ _ASSERTE(pSigParser != NULL);
+
+
+ //
+ // Make a local copy of the SigParser since we are going to mutate it.
+ //
+ SigParser sigParser = *pSigParser;
+
+ CorElementType elementType;
+ HRESULT hr;
+
+ IfFailRet(sigParser.GetElemType(&elementType));
+
+ switch (elementType)
+ {
+ case ELEMENT_TYPE_VAR:
+ case ELEMENT_TYPE_MVAR:
+ {
+ ULONG tyvar_num;
+
+ IfFailRet(sigParser.GetData(&tyvar_num));
+
+
+ if (elementType == ELEMENT_TYPE_VAR)
+ {
+ // ELEMENT_TYPE_VAR refers to an indexed type-parameter in the containing Type.
+ // Eg, we may be doing a field lookup on 'List<T> { T m_head}', and the field's return type 'T' is Type-parameter #0.
+ // Or this maybe part of a base class's TypeSpec.
+ _ASSERTE (tyvar_num < (pInst->m_cClassTyPars));
+ if (tyvar_num >= (pInst->m_cClassTyPars))
+ return E_FAIL;
+
+ _ASSERTE (pInst->m_ppInst != NULL);
+ *ppResultType = pInst->m_ppInst[tyvar_num];
+ }
+ else
+ {
+ //ELEMENT_TYPE_MVAR refers to an indexed type-parameter in the containing Method.
+ // Eg, we may be in Class::Func<T> and refering to T.
+ // The Instantiation array has Type type-parameters first, and then any Method Type-parameters.
+ // The m_cClassTyPars field indicats where the split is between Type and Method type-parameters. Type type-params
+ // come first.
+ _ASSERTE(elementType == ELEMENT_TYPE_MVAR);
+
+
+ _ASSERTE (tyvar_num < (pInst->m_cInst - pInst->m_cClassTyPars));
+ if (tyvar_num >= (pInst->m_cInst - pInst->m_cClassTyPars))
+ return E_FAIL;
+
+ _ASSERTE (pInst->m_ppInst != NULL);
+ *ppResultType = pInst->m_ppInst[tyvar_num + pInst->m_cClassTyPars];
+ }
+
+ return S_OK;
+ }
+ case ELEMENT_TYPE_GENERICINST:
+ {
+ //ELEMENT_TYPE_GENERICINST is that start of a instantiated generic type.
+ //Format for the signature blob is:
+ // 1) CorElementType, Token - this is the uninstantiated type (eg, for Pair<int, string>, it would be token for Pair<T,U>)
+ // 2) int - Count of generic args - eg, for Pair<T,U>, it would be "2".
+ // 3) type1,type2, ... - meteadata representation for generic args. For example above, it would be Type(int), Type(string).
+
+
+ // ignore "WITH", look at next ELEMENT_TYPE to get CLASS or VALUE
+
+ IfFailRet(sigParser.GetElemType(&elementType));
+
+ mdToken token;
+
+ IfFailRet(sigParser.GetToken(&token));
+
+ CordbClass * pClass;
+
+ IfFailRet( pModule->ResolveTypeRefOrDef(token, &pClass));
+
+ // The use of a class in a signature provides definite evidence as to whether it is a VC or not.
+ _ASSERTE(!pClass->IsValueClassKnown() ||
+ (pClass->IsValueClassNoInit() == (elementType == ELEMENT_TYPE_VALUETYPE)) ||
+ !"A value class is being used with ELEMENT_TYPE_GENERICINST");
+
+ pClass->SetIsValueClass(elementType == ELEMENT_TYPE_VALUETYPE);
+ pClass->SetIsValueClassKnown(true);
+
+ // Build up the array of generic arguments.
+ ULONG cArgs; // number of generic arguments in the type.
+
+ IfFailRet(sigParser.GetData(&cArgs));
+
+ S_UINT32 allocSize = S_UINT32( cArgs ) * S_UINT32( sizeof(CordbType *) );
+
+ if (allocSize.IsOverflow())
+ {
+ IfFailRet(E_OUTOFMEMORY);
+ }
+
+ CordbType ** ppTypeInstantiations = reinterpret_cast<CordbType **>(_alloca( allocSize.Value()));
+
+ for (unsigned int i = 0; i < cArgs;i++)
+ {
+ IfFailRet(CordbType::SigToType(pModule, &sigParser, pInst, &ppTypeInstantiations[i]));
+
+ IfFailRet(sigParser.SkipExactlyOne());
+ }
+
+ // Now we have the Open type (eg, Pair<T,U>) and the instantiation list, so create the Closed CordbType..
+ Instantiation typeInstantiation(cArgs, ppTypeInstantiations);
+
+ return CordbType::MkType(pModule->GetAppDomain(), elementType, pClass, &typeInstantiation, ppResultType);
+ }
+ case ELEMENT_TYPE_CLASS:
+ case ELEMENT_TYPE_VALUETYPE: // OK: this E_T_VALUETYPE comes from signature
+ {
+ // Path for non-generic types
+
+ mdToken token;
+
+ IfFailRet(sigParser.GetToken(&token));
+
+ CordbClass * pClass;
+
+ IfFailRet(pModule->ResolveTypeRefOrDef(token, &pClass));
+
+ // The use of a class in a signature provides definite evidence as to whether it is a VC or not.
+
+ _ASSERTE(!pClass->IsValueClassKnown() ||
+ (pClass->IsValueClassNoInit() == (elementType == ELEMENT_TYPE_VALUETYPE)) ||
+ !"A non-value class is being used with ELEMENT_TYPE_VALUETYPE");
+
+ pClass->SetIsValueClass(elementType == ELEMENT_TYPE_VALUETYPE);
+ pClass->SetIsValueClassKnown(true);
+
+ return CordbType::MkUnparameterizedType(pModule->GetAppDomain(), elementType, pClass, ppResultType);
+ }
+ case ELEMENT_TYPE_SENTINEL:
+ case ELEMENT_TYPE_MODIFIER:
+ case ELEMENT_TYPE_PINNED:
+ {
+ IfFailRet(CordbType::SigToType(pModule, &sigParser, pInst, ppResultType));
+ // Throw away SENTINELS on all CordbTypes...
+ return S_OK;
+ }
+ case ELEMENT_TYPE_CMOD_REQD:
+ case ELEMENT_TYPE_CMOD_OPT:
+ {
+ mdToken token;
+
+ IfFailRet(sigParser.GetToken(&token));
+
+ IfFailRet(CordbType::SigToType(pModule, &sigParser, pInst, ppResultType));
+ // Throw away CMOD on all CordbTypes...
+ return S_OK;
+ }
+
+ case ELEMENT_TYPE_ARRAY:
+ {
+ CordbType * pType;
+
+ IfFailRet(CordbType::SigToType(pModule, &sigParser, pInst, &pType));
+
+ IfFailRet(sigParser.SkipExactlyOne());
+
+ ULONG rank;
+
+ IfFailRet(sigParser.GetData(&rank));
+
+ return CordbType::MkType(pModule->GetAppDomain(), elementType, rank, pType, ppResultType);
+ }
+ case ELEMENT_TYPE_SZARRAY:
+ {
+ CordbType * pType;
+
+ IfFailRet(CordbType::SigToType(pModule, &sigParser, pInst, &pType));
+
+ return CordbType::MkType(pModule->GetAppDomain(), elementType, 1, pType, ppResultType);
+ }
+
+ case ELEMENT_TYPE_PTR:
+ case ELEMENT_TYPE_BYREF:
+ {
+ CordbType * pType;
+
+ IfFailRet(CordbType::SigToType(pModule, &sigParser, pInst, &pType));
+
+ return CordbType::MkType(pModule->GetAppDomain(),elementType, 0, pType, ppResultType);
+ }
+
+ case ELEMENT_TYPE_FNPTR:
+ {
+ ULONG cArgs;
+
+ IfFailRet(sigParser.GetData(&cArgs)); // Skip callingConv
+
+ IfFailRet(sigParser.GetData(&cArgs)); // Get number of parameters
+
+ S_UINT32 allocSize = ( S_UINT32(cArgs) + S_UINT32(1) ) * S_UINT32( sizeof(CordbType *) );
+
+ if (allocSize.IsOverflow())
+ {
+ IfFailRet(E_OUTOFMEMORY);
+ }
+
+ CordbType ** ppTypeInstantiations = (CordbType **) _alloca( allocSize.Value() );
+
+ for (unsigned int i = 0; i <= cArgs; i++)
+ {
+ IfFailRet(CordbType::SigToType(pModule, &sigParser, pInst, &ppTypeInstantiations[i]));
+
+ IfFailRet(sigParser.SkipExactlyOne());
+ }
+
+ Instantiation typeInstantiation(cArgs + 1, ppTypeInstantiations);
+
+ return CordbType::MkType(pModule->GetAppDomain(), elementType, &typeInstantiation, ppResultType);
+ }
+
+ case ELEMENT_TYPE_VOID:
+ case ELEMENT_TYPE_BOOLEAN:
+ case ELEMENT_TYPE_CHAR:
+ case ELEMENT_TYPE_I1:
+ case ELEMENT_TYPE_U1:
+ case ELEMENT_TYPE_I2:
+ case ELEMENT_TYPE_U2:
+ case ELEMENT_TYPE_I4:
+ case ELEMENT_TYPE_U4:
+ case ELEMENT_TYPE_I8:
+ case ELEMENT_TYPE_U8:
+ case ELEMENT_TYPE_R4:
+ case ELEMENT_TYPE_R8:
+ case ELEMENT_TYPE_STRING:
+ case ELEMENT_TYPE_TYPEDBYREF:
+ case ELEMENT_TYPE_OBJECT:
+ case ELEMENT_TYPE_I:
+ case ELEMENT_TYPE_U:
+ return CordbType::MkType(pModule->GetAppDomain(), elementType, ppResultType);
+
+ default:
+ _ASSERTE(!"unexpected element type!");
+ return E_FAIL;
+ }
+} // CordbType::SigToType
+
+//-----------------------------------------------------------------------------
+// Marshal a DebuggerIPCE_BasicTypeData --> CordbType.
+//
+// This will build up a DebuggerIPCE_ExpandedTypeData and convert that into
+// a CordbType. This may send additional IPC events if needed to
+// go from Basic --> Expanded data. Note that this is designed to handle generics.
+//
+// Parameters:
+// pAppDomain - the AppDomain the type lives in.
+// data - DebuggerIPCE_BasicTypeData from Left-Side containing type description.
+// pRes - OUT: out-parameter to hold built type.
+//
+// Returns:
+// S_OK on success.
+//-----------------------------------------------------------------------------
+HRESULT CordbType::TypeDataToType(CordbAppDomain *pAppDomain, DebuggerIPCE_BasicTypeData *data, CordbType **pRes)
+{
+ FAIL_IF_NEUTERED(pAppDomain);
+ INTERNAL_SYNC_API_ENTRY(pAppDomain->GetProcess()); //
+
+
+
+ HRESULT hr = S_OK;
+ CorElementType et = data->elementType;
+ switch (et)
+ {
+ case ELEMENT_TYPE_ARRAY:
+ case ELEMENT_TYPE_SZARRAY:
+ case ELEMENT_TYPE_PTR:
+ case ELEMENT_TYPE_BYREF:
+ // For these element types the "Basic" type data only contains the type handle.
+ // So we fetch some more data, and the go onto the "Expanded" case...
+ {
+ EX_TRY
+ {
+ DebuggerIPCE_ExpandedTypeData typeInfo;
+ CordbProcess * pProcess = pAppDomain->GetProcess();
+
+ {
+ RSLockHolder lockHolder(pProcess->GetProcessLock());
+ pProcess->GetDAC()->TypeHandleToExpandedTypeInfo(NoValueTypeBoxing, // could be generics
+ // which are never boxed
+ pAppDomain->GetADToken(),
+ data->vmTypeHandle,
+ &typeInfo);
+ }
+
+ IfFailThrow(CordbType::TypeDataToType(pAppDomain,&typeInfo, pRes));
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+
+ }
+
+ case ELEMENT_TYPE_FNPTR:
+ {
+ DebuggerIPCE_ExpandedTypeData e;
+ e.elementType = et;
+ e.NaryTypeData.typeHandle = data->vmTypeHandle;
+ return CordbType::TypeDataToType(pAppDomain, &e, pRes);
+ }
+ default:
+ // For all other element types the "Basic" view of a type
+ // contains the same information as the "expanded"
+ // view, so just reuse the code for the Expanded view...
+ DebuggerIPCE_ExpandedTypeData e;
+ e.elementType = et;
+ e.ClassTypeData.metadataToken = data->metadataToken;
+ e.ClassTypeData.vmDomainFile = data->vmDomainFile;
+ e.ClassTypeData.vmModule = data->vmModule;
+ e.ClassTypeData.typeHandle = data->vmTypeHandle;
+ return CordbType::TypeDataToType(pAppDomain, &e, pRes);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Marshal DebuggerIPCE_ExpandedTypeData --> CordbType
+// The ExpandedTypeData just contains top level generic info, and so
+// the RS may need to send more IPC events to fill out details.
+//
+// Parameters:
+// pAppDomain - the appdomain that all the types live in.
+// data - data used to build up CordbType
+// pRes - OUT: out param to get back CordbType on return.
+//
+// Returns:
+// S_OK on success.
+//-----------------------------------------------------------------------------
+HRESULT CordbType::TypeDataToType(CordbAppDomain *pAppDomain, DebuggerIPCE_ExpandedTypeData *data, CordbType **pRes)
+{
+ INTERNAL_SYNC_API_ENTRY(pAppDomain->GetProcess()); //
+
+ CorElementType et = data->elementType;
+ HRESULT hr = S_OK;
+ switch (et)
+ {
+
+ case ELEMENT_TYPE_OBJECT:
+ case ELEMENT_TYPE_VOID:
+ case ELEMENT_TYPE_BOOLEAN:
+ case ELEMENT_TYPE_CHAR:
+ case ELEMENT_TYPE_I1:
+ case ELEMENT_TYPE_U1:
+ case ELEMENT_TYPE_I2:
+ case ELEMENT_TYPE_U2:
+ case ELEMENT_TYPE_I4:
+ case ELEMENT_TYPE_U4:
+ case ELEMENT_TYPE_I8:
+ case ELEMENT_TYPE_U8:
+ case ELEMENT_TYPE_R4:
+ case ELEMENT_TYPE_R8:
+ case ELEMENT_TYPE_STRING:
+ case ELEMENT_TYPE_TYPEDBYREF:
+ case ELEMENT_TYPE_I:
+ case ELEMENT_TYPE_U:
+ETObject:
+ // It's a primitive (therefore non-generic) type, so we can just create it immediately.
+ IfFailRet (CordbType::MkType(pAppDomain, et, pRes));
+ break;
+
+ case ELEMENT_TYPE_CLASS:
+ case ELEMENT_TYPE_VALUETYPE: // OK: this E_T_VALUETYPE comes from the EE
+ {
+ //
+ if (data->ClassTypeData.metadataToken == mdTokenNil) {
+ et = ELEMENT_TYPE_OBJECT;
+ goto ETObject;
+ }
+ CordbModule * pClassModule = NULL;
+ EX_TRY
+ {
+ pClassModule = pAppDomain->LookupOrCreateModule(data->ClassTypeData.vmModule, data->ClassTypeData.vmDomainFile);
+ }
+ EX_CATCH_HRESULT(hr);
+ if( pClassModule == NULL )
+ {
+ // We don't know anything about this module - shouldn't happen.
+ // <TODO>This can be hit by the issue described in VSWhidbey 465120</TODO>
+ _ASSERTE(!"Unrecognized module");
+ return CORDBG_E_MODULE_NOT_LOADED;
+ }
+
+ CordbClass *tycon;
+ IfFailRet (pClassModule->LookupOrCreateClass(data->ClassTypeData.metadataToken,&tycon));
+ if (!(data->ClassTypeData.typeHandle.IsNull()))
+ {
+ // It's a generic type. We have the typehandle, use that to query for the rest of
+ // the tyeparameters and build up the instantiation for the CordbType.
+
+ IfFailRet (CordbType::InstantiateFromTypeHandle(pAppDomain, data->ClassTypeData.typeHandle, et, tycon, pRes));
+ // Set the type handle regardless of how we found
+ // the type. For example if type was already
+ // constructed without the type handle still set
+ // it here.
+ if (*pRes)
+ {
+ (*pRes)->m_typeHandleExact = data->ClassTypeData.typeHandle;
+ }
+ break;
+ }
+ else
+ {
+ // Non generic type. Since we already have the CordbClass for it, we can trivially create the CordbType
+ IfFailRet (CordbType::MkUnparameterizedType(pAppDomain, et,tycon,pRes));
+ break;
+ }
+
+ }
+ case ELEMENT_TYPE_ARRAY:
+ case ELEMENT_TYPE_SZARRAY:
+ {
+ CordbType *argty;
+ IfFailRet (CordbType::TypeDataToType(pAppDomain, &(data->ArrayTypeData.arrayTypeArg), &argty));
+ IfFailRet (CordbType::MkType(pAppDomain, et, data->ArrayTypeData.arrayRank, argty, pRes));
+ break;
+ }
+
+ case ELEMENT_TYPE_PTR:
+ case ELEMENT_TYPE_BYREF:
+ {
+ CordbType *argty;
+ IfFailRet (CordbType::TypeDataToType(pAppDomain, &(data->UnaryTypeData.unaryTypeArg), &argty));
+ IfFailRet (CordbType::MkType(pAppDomain, et, 0, argty, pRes));
+ break;
+ }
+ case ELEMENT_TYPE_FNPTR:
+ {
+ IfFailRet (CordbType::InstantiateFromTypeHandle(pAppDomain, data->NaryTypeData.typeHandle, et, NULL, pRes));
+ if (*pRes)
+ {
+ (*pRes)->m_typeHandleExact = data->NaryTypeData.typeHandle;
+ }
+ break;
+ }
+ case ELEMENT_TYPE_END:
+ *pRes = NULL;
+ return E_FAIL;
+
+ default:
+ _ASSERTE(!"unexpected element type!");
+ return E_FAIL;
+
+ }
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// CordbType::InstantiateFromTypeHandle
+// Internal helper method.
+// Builds (Left-Side) TypeHandle --> (Right-Side) CordbType
+// This is very useful when we get a typehandle from the LeftSide. A common
+// scenario is when we get an Object back from the LS, which happens when
+// we build the CordbType corresponding to a Cordb*Value.
+//
+// Parameters:
+// pAppdomain - the appdomain the type lives in.
+// vmTypeHandle - a Left-Side typehandle describing the type.
+// elementType - convenient way to indicate whether we've got ELEMENT_TYPE_FNPTR or
+// something else. We should be able to retrieve this from the TypeHandle,
+// but our caller already has it available.
+// typeConstructor - CordbClass corresponding to the typeHandle. This could be built
+// up from typehandle, but our caller already has it.
+// Will be NULL for ELEMENT_TYPE_FNPTR
+// pResultType - OUT: out parameter to yield CordbType for the TypeHandle.
+//
+// Returns:
+// S_OK on success.
+//-----------------------------------------------------------------------------
+HRESULT CordbType::InstantiateFromTypeHandle(CordbAppDomain * pAppDomain,
+ VMPTR_TypeHandle vmTypeHandle,
+ CorElementType elementType,
+ CordbClass * typeConstructor,
+ CordbType ** pResultType)
+{
+ HRESULT hr = S_OK;
+
+ // Should already by synced by caller.
+ INTERNAL_SYNC_API_ENTRY(pAppDomain->GetProcess()); //
+ _ASSERTE((pAppDomain->GetProcess()->GetShim() == NULL) || (pAppDomain->GetProcess()->GetSynchronized()));
+
+ EX_TRY
+ {
+ CordbProcess * pProcess = pAppDomain->GetProcess();
+ //
+ // Step 1) Ask DacDbi interface for a list of type-parameters given a TypeHandle.
+ //
+
+ TypeParamsList params;
+ {
+ RSLockHolder lockHolder(pProcess->GetProcessLock());
+ pProcess->GetDAC()->GetTypeHandleParams(pAppDomain->GetADToken(), vmTypeHandle, &params);
+ }
+
+ // convert the parameter type information to a list of CordbTypeInstances (one for each parameter)
+ // note: typeList will be destroyed on exit, running destructors for each element. In this case, that
+ // means it will simply assert IsNeutered.
+ DacDbiArrayList<CordbType *> typeList;
+ typeList.Alloc(params.Count());
+ for (int i = 0; i < params.Count(); ++i)
+ {
+ IfFailThrow(TypeDataToType(pAppDomain, &(params[i]), &(typeList[i])));
+ }
+
+ // now make an instance of CordbType from an instantiation
+ Instantiation instantiation(params.Count(), &(typeList[0]));
+ if (elementType == ELEMENT_TYPE_FNPTR)
+ {
+ IfFailThrow(CordbType::MkType(pAppDomain, elementType, &instantiation, pResultType));
+ }
+ else
+ {
+ IfFailThrow(CordbType::MkType(pAppDomain, elementType, typeConstructor, &instantiation, pResultType));
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+} // CordbType::InstantiateFromTypeHandle
+
+//-----------------------------------------------------------------------------
+// Initialize the CordbType.
+// This will involve a lot of queries to the Left-side.
+// This means finding the type-handle, getting / creating associated CordbClass,
+// filling out the instantiation, getting field info, etc.
+//
+// Parameters:
+// fForceInit - if false, may skip initialization if TypeHandle already known.
+//
+// Returns:
+// S_OK if success, CORDBG_E_CLASS_NOT_LOADED, E_INVALIDARG, OOM on failure
+//-----------------------------------------------------------------------------
+HRESULT CordbType::Init(BOOL fForceInit)
+{
+ INTERNAL_SYNC_API_ENTRY(GetProcess()); //
+
+ HRESULT hr = S_OK;
+
+ if (m_pClass && m_pClass->GetLoadLevel() != CordbClass::FullInfo)
+ fForceInit = TRUE;
+
+ // Step 1. initialize the type constructor (if one exists)
+ // and the (class) type parameters....
+ if (m_elementType == ELEMENT_TYPE_CLASS)
+ {
+
+ // start by initing only enough so that we can determine whether
+ // or not this is a generic class. When dealing with generic
+ // type instantiations there is no guarantee the open generic
+ // type is fully restored. If we load too eagerly it might fail
+ // and we wouldn't actually need that extra data anyways.
+ _ASSERTE(m_pClass != NULL);
+ EX_TRY
+ {
+ m_pClass->Init(CordbClass::BasicInfo);
+ }
+ EX_CATCH_HRESULT(hr);
+ IfFailRet(hr);
+
+ // non-generic classes need the class object to be fully inited
+ // in the generic case we won't ever use that data
+ if (!m_pClass->HasTypeParams())
+ {
+ EX_TRY
+ {
+ m_pClass->Init(CordbClass::FullInfo);
+ }
+ EX_CATCH_HRESULT(hr);
+ IfFailRet(hr);
+
+ return S_OK; // Non-generic, that's all - no clean-up required
+ }
+ }
+
+ _ASSERTE(m_elementType != ELEMENT_TYPE_CLASS || m_pClass->HasTypeParams());
+
+ for (unsigned int i = 0; i<m_inst.m_cClassTyPars; i++)
+ {
+ _ASSERTE(m_inst.m_ppInst != NULL);
+ _ASSERTE(m_inst.m_ppInst[i] != NULL);
+ IfFailRet( m_inst.m_ppInst[i]->Init(fForceInit) );
+ }
+
+ // Step 2. Try to fetch the type handle if necessary (only
+ // for instantiated class types, pointer types etc.)
+ // We do this by preparing an event specifying the type and
+ // then fetching the type handle from the left-side. This
+ // will not always succeed, as forcing the load of the type handle would be the
+ // equivalent of doing a FuncEval, i.e. the instantiation may
+ // not have been created. But we try anyway to reduce the number of
+ // failures.
+ //
+ // Note that in the normal case we will have the type handle from the EE
+ // anyway, e.g. if the CordbType was created when reporting the type
+ // of an actual object.
+
+ // Initialize m_typeHandleExact if it needs it
+ if (m_elementType == ELEMENT_TYPE_ARRAY ||
+ m_elementType == ELEMENT_TYPE_SZARRAY ||
+ m_elementType == ELEMENT_TYPE_BYREF ||
+ m_elementType == ELEMENT_TYPE_PTR ||
+ m_elementType == ELEMENT_TYPE_FNPTR ||
+ (m_elementType == ELEMENT_TYPE_CLASS && m_pClass->HasTypeParams()))
+ {
+ // It is OK if getting an exact type handle
+ // fails with CORDBG_E_CLASS_NOT_LOADED. In that case we leave
+ // the type information incomplete and subsequent operations
+ // will try to call Init() again. The immediate operation will fail later if
+ // TypeToBasicTypeData requests the exact type information for this type.
+ hr = InitInstantiationTypeHandle(fForceInit);
+ if (hr != CORDBG_E_CLASS_NOT_LOADED)
+ IfFailRet(hr);
+ }
+
+
+ // For OBJECT and STRING we may not have a value for m_class
+ // object. Go try and get it.
+ if (m_elementType == ELEMENT_TYPE_STRING ||
+ m_elementType == ELEMENT_TYPE_OBJECT)
+ {
+ IfFailRet(InitStringOrObjectClass(fForceInit));
+ }
+
+ // Step 3. Fetch the information that is specific to the type where necessary...
+ // Now we have the type handle for the constructed type, we can ask for the size of
+ // the object. Only do this for constructed value types.
+ //
+ // Note that the exact and/or approximate type handles may not be available.
+ if ((m_elementType == ELEMENT_TYPE_CLASS) && m_pClass->HasTypeParams())
+ {
+ IfFailRet(InitInstantiationFieldInfo(fForceInit));
+ }
+
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Internal function to communicate with Left-Side to get an exact TypeHandle
+// (runtime type representation) for this CordbType.
+//
+// Parameters:
+// fForceInit - if false, may skip initialization if TypeHandle already known.
+//
+// Returns:
+// S_OK on success or failure HR E_INVALIDARG, OOM, CORDBG_E_CLASS_NOT_LOADED
+// on failure
+//-----------------------------------------------------------------------------
+HRESULT CordbType::InitInstantiationTypeHandle(BOOL fForceInit)
+{
+
+ // Check if we've already done this Init
+ if (!fForceInit && !m_typeHandleExact.IsNull())
+ return S_OK;
+
+ HRESULT hr = S_OK;
+
+ // Create an array of DebuggerIPCE_BasicTypeData structures from the array of type parameters.
+ // First, get a buffer to hold the information
+ CordbProcess *pProcess = GetProcess();
+ S_UINT32 bufferSize = S_UINT32(sizeof(DebuggerIPCE_BasicTypeData)) *
+ S_UINT32(m_inst.m_cClassTyPars);
+ EX_TRY
+ {
+ if( bufferSize.IsOverflow() )
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+ NewHolder<DebuggerIPCE_BasicTypeData> pArgTypeData(new DebuggerIPCE_BasicTypeData[bufferSize.Value()]);
+
+ // We will have already called Init on each of the type parameters further above. Now we build a
+ // list of type information for each type parameter.
+ for (unsigned int i = 0; i < m_inst.m_cClassTyPars; i++)
+ {
+ _ASSERTE(m_inst.m_ppInst != NULL);
+ _ASSERTE(m_inst.m_ppInst[i] != NULL);
+ IfFailThrow(m_inst.m_ppInst[i]->TypeToBasicTypeData(&pArgTypeData[i]));
+ }
+
+ DebuggerIPCE_ExpandedTypeData typeData;
+
+ // get the top-level type information
+ TypeToExpandedTypeData(&typeData);
+
+ ArgInfoList argInfo(pArgTypeData, m_inst.m_cClassTyPars);
+
+ {
+ // Get the TypeHandle based on the type data
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+ hr = pProcess->GetDAC()->GetExactTypeHandle(&typeData, &argInfo, m_typeHandleExact);
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+} // CordbType::InitInstantiationTypeHandle
+
+//-----------------------------------------------------------------------------
+// Internal helper for CordbType::Init to finish initialize types for
+// System.String or System.Object.
+// This just needs to set the m_class field.
+//
+// Parameters:
+// fForceInit - force re-initialization if already initialized.
+//
+// Returns:
+// S_OK on success or CORDBG_E_CLASS_NOT_LOADED on failure.
+//
+// Note: verification with IPC result may assert
+//-----------------------------------------------------------------------------
+
+HRESULT CordbType::InitStringOrObjectClass(BOOL fForceInit)
+{
+ // This CordbType is a non-generic class, either System.String or System.Object.
+ // Need to find the CordbClass instance (in the proper AppDomain) that matches that type.
+
+ // Check if we've already done this Init
+ if (!fForceInit && m_pClass != NULL)
+ {
+ return S_OK;
+ }
+
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+ //
+ // Step 1a) Send a request to the DAC to map: CorElementType --> {token, Module}
+ //
+ CordbProcess *pProcess = GetProcess();
+ mdTypeDef metadataToken;
+ VMPTR_DomainFile vmDomainFile = VMPTR_DomainFile::NullPtr();
+ VMPTR_Module vmModule = VMPTR_Module::NullPtr();
+
+ {
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+ pProcess->GetDAC()->GetSimpleType(m_appdomain->GetADToken(),
+ m_elementType,
+ &metadataToken,
+ &vmModule,
+ &vmDomainFile);
+ }
+
+ //
+ // Step 2) Lookup CordbClass based off token + Module.
+ //
+ CordbModule * pTypeModule = m_appdomain->LookupOrCreateModule(vmModule, vmDomainFile);
+
+ _ASSERTE(pTypeModule != NULL);
+ IfFailThrow(pTypeModule->LookupOrCreateClass(metadataToken, &m_pClass));
+
+ _ASSERTE(m_pClass != NULL);
+
+ _ASSERTE(SUCCEEDED(hr));
+ m_pClass->AddRef();
+
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+} // CordbType::InitStringOrObjectClass
+
+//-----------------------------------------------------------------------------
+// Internal helper for CordbType::Init to get FieldInfos for a generic Type.
+// Non-generic types can use the FieldInfos off their associated CordbClass.
+//
+// Parameters:
+// fForceInit - force re-initialization if already initialized?
+//
+// Returns:
+// S_OK on success.
+//-----------------------------------------------------------------------------
+HRESULT CordbType::InitInstantiationFieldInfo(BOOL fForceInit)
+{
+ HRESULT hr = S_OK;
+
+ // Check if we've already done this Init
+ if (!m_fieldInfoNeedsInit && !fForceInit)
+ {
+ return hr;
+ }
+
+ _ASSERTE(m_elementType == ELEMENT_TYPE_CLASS);
+ _ASSERTE(m_pClass->HasTypeParams());
+
+ VMPTR_TypeHandle typeHandleApprox = m_typeHandleExact;
+
+ // If the exact type handle is not available then get the approximate type handle.
+ if (typeHandleApprox.IsNull())
+ {
+ // set up a buffer to hold type parameter information for the type. (See
+ // code:CordbType::GatherTypeData for more information). First, compute its size.
+ unsigned int typeDataNodeCount = 0;
+ this->CountTypeDataNodes(&typeDataNodeCount);
+
+ EX_TRY
+ {
+ // allocate a buffer to hold the parameter data
+ TypeInfoList typeData;
+
+ typeData.Alloc(typeDataNodeCount);
+
+ // fill the buffer
+ DebuggerIPCE_TypeArgData * pCurrent = &(typeData[0]);
+ GatherTypeData(this, &pCurrent);
+
+ // request the type handle from the DAC
+ CordbProcess *pProcess = GetProcess();
+ {
+ RSLockHolder lockHolder(pProcess->GetProcessLock());
+ typeHandleApprox = pProcess->GetDAC()->GetApproxTypeHandle(&typeData);
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ if(FAILED(hr)) return hr;
+ }
+
+ // OK, now get the field info if we can.
+ CordbProcess *pProcess = GetProcess();
+ EX_TRY
+ {
+ {
+ // this may be called multiple times. Each call will discard previous values in m_fieldList and reinitialize
+ // the list with updated information
+ RSLockHolder lockHolder(pProcess->GetProcessLock());
+ pProcess->GetDAC()->GetInstantiationFieldInfo(m_pClass->GetModule()->GetRuntimeDomainFile(),
+ m_typeHandleExact,
+ typeHandleApprox,
+ &m_fieldList,
+ &m_objectSize);
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+HRESULT CordbType::ReturnedByValue()
+{
+ HRESULT hr = S_OK;
+
+ if (!IsValueType())
+ return S_OK;
+
+
+ ULONG32 unboxedSize = 0;
+ IfFailRet(GetUnboxedObjectSize(&unboxedSize));
+
+ if (unboxedSize > sizeof(SIZE_T))
+ return S_FALSE;
+
+ mdToken mdClass = m_pClass->GetToken();
+
+ int fieldCount = 0;
+ bool unsupported = false;
+
+ HCORENUM fields = 0;
+ ULONG fetched = 0;
+ mdToken mdField;
+ IMetaDataImport *pImport = m_pClass->GetModule()->GetMetaDataImporter();
+ IfFailRet(pImport->EnumFields(&fields, mdClass, &mdField, 1, &fetched));
+
+ while (hr == S_OK && fetched == 1)
+ {
+ DWORD attr = 0;
+ PCCOR_SIGNATURE sigBlob = 0;
+ ULONG sigLen = 0;
+ hr = pImport->GetFieldProps(mdField, NULL, NULL, 0, NULL, &attr, &sigBlob, &sigLen, NULL, NULL, NULL);
+
+ if (SUCCEEDED(hr))
+ {
+ // !static
+ if ((attr & 0x10) == 0)
+ {
+ if (fieldCount++)
+ break;
+
+ CorElementType et;
+ SigParser parser(sigBlob, sigLen);
+ parser.GetByte(NULL); // 0x6, field signature
+ parser.SkipCustomModifiers();
+ hr = parser.GetElemType(&et);
+ if (SUCCEEDED(hr))
+ {
+ switch (et)
+ {
+ case ELEMENT_TYPE_R4:
+ case ELEMENT_TYPE_R8:
+ unsupported = true;
+ break;
+
+ case ELEMENT_TYPE_CLASS:
+ case ELEMENT_TYPE_STRING:
+ case ELEMENT_TYPE_PTR:
+ // OK
+ break;
+
+ default:
+ if (!CorIsPrimitiveType(et))
+ unsupported = true;
+ break;
+ }
+
+ if (unsupported)
+ break;
+ }
+ }
+
+ hr = pImport->EnumFields(&fields, mdClass, &mdField, 1, &fetched);
+ }
+
+ if (FAILED(hr))
+ {
+ pImport->CloseEnum(fields);
+ return hr;
+ }
+ }
+
+ pImport->CloseEnum(fields);
+
+ if (unsupported)
+ return S_FALSE;
+
+ return fieldCount <= 1 ? S_OK : S_FALSE;
+}
+
+
+//-----------------------------------------------------------------------------
+// Internal helper to get the size (in bytes) of the unboxed object.
+// For a generic type, the size of the type depends on the size of the
+// type-parameters.
+// This is commonly used by Cordb*Value in their Initialization when they
+// need to cache the size of the Target object they refer to.
+//
+// This should only be called on Value-types and Primitives (eg, i4, FnPtr).
+// It should not be called on Reference types.
+//
+// Parameters:
+// pObjectSize - OUT: out-parameter to get the size in bytes.
+//
+// Returns:
+// S_OK on success.
+//-----------------------------------------------------------------------------
+HRESULT
+CordbType::GetUnboxedObjectSize(ULONG32 *pObjectSize)
+{
+ INTERNAL_SYNC_API_ENTRY(GetProcess()); //
+
+ HRESULT hr = S_OK;
+ bool isVC = false;
+
+ EX_TRY
+ {
+ isVC = IsValueType();
+ }
+ EX_CATCH_HRESULT(hr);
+
+ IfFailRet(hr);
+
+ if (isVC)
+ {
+ *pObjectSize = 0;
+
+ hr = Init(FALSE);
+
+ if (!SUCCEEDED(hr))
+ return hr;
+
+ *pObjectSize = (ULONG) ((!m_pClass->HasTypeParams()) ? m_pClass->ObjectSize() : this->m_objectSize);
+
+ return hr;
+ }
+ else
+ {
+ // Caller gaurantees that we're not a class. And the check above guarantees we're not a value-type.
+ // So we're some sort of primitive, and thus we can determine size from the signature.
+ //
+ // @dbgtodo inspection - We didn't have this assert in Whidbey, and it's firing in vararg
+ // scenarios even though it's returning the right value for reference types (i.e. 4 on x86 and 8 on
+ // 64-bit). Commenting it out for now.
+ //_ASSERTE(m_elementType != ELEMENT_TYPE_CLASS);
+
+ // We need to use a temporary variable here -- attempting to cast among pointer types
+ // (i.e., (PCCOR_SIGNATURE) &m_elementType) yields incorrect results on big-endian machines
+ COR_SIGNATURE corSig = (COR_SIGNATURE) m_elementType;
+
+ SigParser sigParser(&corSig, sizeof(corSig));
+
+ ULONG size;
+
+ IfFailRet(sigParser.PeekElemTypeSize(&size));
+
+ *pObjectSize = size;
+ return hr;
+ }
+}
+
+VMPTR_DomainFile CordbType::GetDomainFile()
+{
+ if (m_pClass != NULL)
+ {
+ CordbModule * pModule = m_pClass->GetModule();
+ if (pModule)
+ {
+ return pModule->m_vmDomainFile;
+ }
+ else
+ {
+ return VMPTR_DomainFile::NullPtr();
+ }
+ }
+ else
+ {
+ return VMPTR_DomainFile::NullPtr();
+ }
+}
+
+
+VMPTR_Module CordbType::GetModule()
+{
+ if (m_pClass != NULL)
+ {
+ CordbModule * pModule = m_pClass->GetModule();
+ if (pModule)
+ {
+ return pModule->GetRuntimeModule();
+ }
+ else
+ {
+ return VMPTR_Module::NullPtr();
+ }
+ }
+ else
+ {
+ return VMPTR_Module::NullPtr();
+ }
+}
+//-----------------------------------------------------------------------------
+// Internal method to Marshal: CordbType --> DebuggerIPCE_BasicTypeData
+// Nb. CordbType::Init will call this. The operation
+// fails if the exact type information has been requested but was not available
+//
+// Parameters:
+// data - OUT: BasicTypeData instance to fill out.
+//
+// Returns:
+// S_OK on success, CORDBG_E_CLASS_NOT_LOADED on failure
+//-----------------------------------------------------------------------------
+HRESULT CordbType::TypeToBasicTypeData(DebuggerIPCE_BasicTypeData *data)
+{
+ switch (m_elementType)
+ {
+ case ELEMENT_TYPE_ARRAY:
+ case ELEMENT_TYPE_SZARRAY:
+ case ELEMENT_TYPE_BYREF:
+ case ELEMENT_TYPE_PTR:
+ data->elementType = m_elementType;
+ data->metadataToken = mdTokenNil;
+ data->vmDomainFile = VMPTR_DomainFile::NullPtr();
+ data->vmTypeHandle = m_typeHandleExact;
+ if (data->vmTypeHandle.IsNull())
+ {
+ return CORDBG_E_CLASS_NOT_LOADED;
+ }
+ _ASSERTE(!data->vmTypeHandle.IsNull());
+ break;
+
+ case ELEMENT_TYPE_CLASS:
+ _ASSERTE(m_pClass != NULL);
+ data->elementType = m_pClass->IsValueClassNoInit() ? ELEMENT_TYPE_VALUETYPE : ELEMENT_TYPE_CLASS;
+ data->metadataToken = m_pClass->MDToken();
+ data->vmDomainFile = GetDomainFile();
+ data->vmTypeHandle = m_typeHandleExact;
+ if (m_pClass->HasTypeParams() && data->vmTypeHandle.IsNull())
+ {
+ return CORDBG_E_CLASS_NOT_LOADED;
+ }
+ break;
+ default:
+ // This includes all the "primitive" types, in which CorElementType is a sufficient description.
+ data->elementType = m_elementType;
+ data->metadataToken = mdTokenNil;
+ data->vmDomainFile = VMPTR_DomainFile::NullPtr();
+ data->vmTypeHandle = VMPTR_TypeHandle::NullPtr();
+ break;
+ }
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Internal method to marshal: CordbType --> ExpandedTypeData
+//
+// Nb. CordbType::Init need NOT have been called before this...
+// Also, this does not write the type arguments. How this is done depends
+// depends on where this is called from.
+//
+// Parameters:
+// data - OUT: outgoing ExpandedTypeData to fill in with stats about CordbType.
+//-----------------------------------------------------------------------------
+void CordbType::TypeToExpandedTypeData(DebuggerIPCE_ExpandedTypeData *data)
+{
+
+ switch (m_elementType)
+ {
+ case ELEMENT_TYPE_ARRAY:
+ case ELEMENT_TYPE_SZARRAY:
+
+ data->ArrayTypeData.arrayRank = m_rank;
+ data->elementType = m_elementType;
+ break;
+
+ case ELEMENT_TYPE_BYREF:
+ case ELEMENT_TYPE_PTR:
+ case ELEMENT_TYPE_FNPTR:
+
+ data->elementType = m_elementType;
+ break;
+
+ case ELEMENT_TYPE_CLASS:
+ {
+ data->elementType = m_pClass->IsValueClassNoInit() ? ELEMENT_TYPE_VALUETYPE : ELEMENT_TYPE_CLASS;
+ data->ClassTypeData.metadataToken = m_pClass->GetToken();
+ data->ClassTypeData.vmDomainFile = GetDomainFile();
+ data->ClassTypeData.vmModule = GetModule();
+ data->ClassTypeData.typeHandle = VMPTR_TypeHandle::NullPtr();
+
+ break;
+ }
+ case ELEMENT_TYPE_END:
+ _ASSERTE(!"bad element type!");
+
+ default:
+ data->elementType = m_elementType;
+ break;
+ }
+}
+
+
+void CordbType::TypeToTypeArgData(DebuggerIPCE_TypeArgData *data)
+{
+ TypeToExpandedTypeData(&(data->data));
+ data->numTypeArgs = m_inst.m_cClassTyPars;
+}
+
+
+//-----------------------------------------------------------------------------
+// Query if this CordbType represents a ValueType (Does not include primitives).
+// Since CordbType doesn't record ValueType status, this may involve querying
+// the CordbClass or even asking the Left-Side (if the CordbClass is not init)
+//
+// Return Value:
+// indicates whether this is a value type
+// Note:
+// Throws.
+//-----------------------------------------------------------------------------
+bool CordbType::IsValueType()
+{
+ if (m_elementType == ELEMENT_TYPE_CLASS)
+ {
+ return m_pClass->IsValueClass();
+ }
+ else
+ return false;
+}
+
+//------------------------------------------------------------------------
+// If this is a ptr type, get the CordbType that it points to.
+// Eg, for CordbType("Int*") or CordbType("Int&"), returns CordbType("Int").
+// If not a ptr type, returns null.
+// Since it's all internal, no reference counting.
+// This is effectively a specialized version of DestUnaryType.
+//------------------------------------------------------------------------
+CordbType * CordbType::GetPointerElementType()
+{
+ if ((m_elementType != ELEMENT_TYPE_PTR) && (m_elementType != ELEMENT_TYPE_BYREF))
+ {
+ return NULL;
+ }
+
+ CordbType * pOut;
+ DestUnaryType(&pOut);
+
+ _ASSERTE(pOut != NULL);
+ return pOut;
+}
+//------------------------------------------------------------------------
+// Helper for IsGcRoot.
+// Determine if the element type is a non GC-root candidate.
+// Updating GC-roots requires coordinating with the GC's write-barrier.
+// Whereas non-GC roots can be updated more freely.
+//
+// Parameters:
+// et - An element type.
+// Returns:
+// True if variables of et can be used as a GC root.
+//------------------------------------------------------------------------
+static inline bool IsElementTypeNonGcRoot(CorElementType et)
+{
+ // Functon ptrs are raw data, not GC-roots.
+ if (et == ELEMENT_TYPE_FNPTR)
+ {
+ return true;
+ }
+
+ // This is almost exactly if we're a primitive, but
+ // primitives include some things that could be GC-roots, so we strip those out,
+ return CorIsPrimitiveType(et)
+ && (et != ELEMENT_TYPE_STRING) && (et != ELEMENT_TYPE_VOID); // exlcude these from primitives
+
+}
+//------------------------------------------------------------------------
+// Helper for IsGcRoot
+// Non-gc roots include Value types + non-gc elemement types (like E_T_I4, E_T_FNPTR)
+//
+// Parameters:
+// pType - type to check whether it's a GC-root.
+// Returns:
+// true if we know we're not a GC-root
+// false if we still might be (so caller must do further checkin)
+//------------------------------------------------------------------------
+static inline bool _IsNonGCRootHelper(CordbType * pType)
+{
+ _ASSERTE(pType != NULL);
+
+ CorElementType et = pType->GetElementType();
+ if (IsElementTypeNonGcRoot(et))
+ {
+ return true;
+ }
+
+ HRESULT hr = S_OK;
+ bool fValueClass = false;
+
+ // If we are a value-type, then we can't be a Gc-root.
+ EX_TRY
+ {
+ fValueClass = pType->IsValueType();
+ }
+ EX_CATCH_HRESULT(hr);
+ if (FAILED(hr) || fValueClass)
+ {
+ return true;
+ }
+
+ // Don't know
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Is this type a GC-root. (Not to be confused w/ "does this contain embedded GC roots")
+// All object references are GC-roots. E_T_PTR are actually not GC-roots.
+//
+// Returns:
+// True - if this is a GC-root.
+// False - not a GC root.
+//-----------------------------------------------------------------------------
+bool CordbType::IsGCRoot()
+{
+ // If it's a E_T_PTR type, then look at what it's a a pointer of.
+ CordbType * pPtr = this->GetPointerElementType();
+ if (pPtr == NULL)
+ {
+ // If non pointer, than we can just look at our current type.
+ return !_IsNonGCRootHelper(this);
+ }
+
+ return !_IsNonGCRootHelper(pPtr);
+}
+
+
+//------------------------------------------------------------------------
+// Public function to enumerate type-parameters.
+// Parameters:
+// ppTypeParameterEnum - OUT: on return, get an enumerator.
+// Returns:
+// S_OK on success.
+//------------------------------------------------------------------------
+HRESULT CordbType::EnumerateTypeParameters(ICorDebugTypeEnum **ppTypeParameterEnum)
+{
+ PUBLIC_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(ppTypeParameterEnum, ICorDebugTypeEnum **);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+
+ CordbTypeEnum *icdTPE = CordbTypeEnum::Build(m_appdomain, m_appdomain->GetLongExitNeuterList(), this->m_inst.m_cInst, this->m_inst.m_ppInst);
+ if ( icdTPE == NULL )
+ {
+ (*ppTypeParameterEnum) = NULL;
+ return E_OUTOFMEMORY;
+ }
+
+ (*ppTypeParameterEnum) = static_cast<ICorDebugTypeEnum*> (icdTPE);
+ icdTPE->ExternalAddRef();
+ return S_OK;
+}
+
+
+//-----------------------------------------------------------------------------
+// CordbType::GetBase
+// Public convenience method to get the instantiated base type.
+//
+// Parameters:
+// ppType - OUT: yields the base type for the current type.
+//
+// Returns:
+// S_OK if succeeded.
+//
+HRESULT CordbType::GetBase(ICorDebugType ** ppType)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ ATT_ALLOW_LIVE_DO_STOPGO(this->GetProcess()); // @todo - can this by RequiredStopped?
+
+ HRESULT hr = S_OK;
+
+ LOG((LF_CORDB, LL_EVERYTHING, "CordbType::GetBase called\n"));
+
+ VALIDATE_POINTER_TO_OBJECT(ppType, ICorDebugType **);
+
+ if (m_elementType != ELEMENT_TYPE_CLASS)
+ {
+ return E_INVALIDARG;
+ }
+
+ EX_TRY
+ {
+ CordbType * pType = NULL;
+
+ _ASSERTE(m_pClass != NULL);
+
+ // Get the supertype from metadata for m_class
+ mdToken extendsToken;
+
+ IMetaDataImport * pImport = m_pClass->GetModule()->GetMetaDataImporter(); // throws
+
+ hr = pImport->GetTypeDefProps(m_pClass->MDToken(), NULL, 0, NULL, NULL, &extendsToken);
+ IfFailThrow(hr);
+
+ // Now create a CordbType instance for the base type that has the same type parameters as the derived type.
+ if ((extendsToken == mdTypeDefNil) || (extendsToken == mdTypeRefNil) || (extendsToken == mdTokenNil))
+ {
+ // No base class.
+ pType = NULL;
+ }
+ else if (TypeFromToken(extendsToken) == mdtTypeSpec)
+ {
+ // TypeSpec has a signature. So get the sig and convert it to a CordbType.
+ // generic base class of a generic type is a TypeSpec.
+ // If we have:
+ // class Triple<T,U,V> derives from Pair<T,V>,
+ // then the base class for Triple would be a TypeSpec:
+ // Class(Pair<T,V>), 2 args, ELEMENT_TYPE_VAR #0, ELEMENT_TYPE_VAR#2.
+ // m_inst provides the type-parameters to resolve the ELEMENT_TYPE_VAR types.
+
+ PCCOR_SIGNATURE pSig;
+ ULONG sigSize;
+
+ // Get the signature for the constructed supertype...
+ hr = pImport->GetTypeSpecFromToken(extendsToken, &pSig, &sigSize);
+ IfFailThrow(hr);
+
+ _ASSERTE(pSig != NULL);
+
+ SigParser sigParser(pSig, sigSize);
+
+ // Instantiate the signature of the supertype using the type instantiation for
+ // the current type....
+ hr = SigToType(m_pClass->GetModule(), &sigParser, &m_inst, &pType);
+ IfFailThrow(hr);
+ }
+ else if ((TypeFromToken(extendsToken) == mdtTypeRef) || (TypeFromToken(extendsToken) == mdtTypeDef))
+ {
+ // TypeDef/TypeRef for non-generic base-class class.
+ CordbClass * pSuperClass;
+
+ hr = m_pClass->GetModule()->ResolveTypeRefOrDef(extendsToken, &pSuperClass);
+ IfFailThrow(hr);
+
+ _ASSERTE(pSuperClass != NULL);
+
+ hr = MkUnparameterizedType(m_appdomain, ELEMENT_TYPE_CLASS, pSuperClass, &pType);
+ IfFailThrow(hr);
+ }
+ else
+ {
+ pType = NULL;
+ _ASSERTE(!"unexpected token!");
+ }
+
+ // At this point, we've succeeded
+ _ASSERTE(SUCCEEDED(hr));
+
+ (*ppType) = pType;
+
+ if (*ppType)
+ {
+ pType->AddRef();
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+
+//-----------------------------------------------------------------------------
+// Get rich field information given a token.
+//
+// Parameters:
+// fldToken - metadata field token specifying a field on this Type.
+// ppFieldData - OUT: get the rich field information for the given field
+//
+// Returns:
+// S_OK on success. CORDBG_E_ENC_HANGING_FIELD for EnC fields (common case)
+// Other errors on failure case.
+//-----------------------------------------------------------------------------
+HRESULT CordbType::GetFieldInfo(mdFieldDef fldToken, FieldData ** ppFieldData)
+{
+ INTERNAL_SYNC_API_ENTRY(GetProcess()); //
+ HRESULT hr = S_OK;
+
+ *ppFieldData = NULL;
+
+ EX_TRY
+ {
+ if (m_elementType != ELEMENT_TYPE_CLASS)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ // Initialize so that the field information is up-to-date.
+ hr = Init(FALSE);
+ IfFailThrow(hr);
+
+ if (m_pClass->HasTypeParams())
+ {
+ if (m_fieldList.IsEmpty())
+ {
+ ThrowHR(CORDBG_E_FIELD_NOT_AVAILABLE);
+ }
+ else
+ {
+ // Use a static helper function in CordbClass, though we're really
+ // searching through this->m_fields
+ hr = CordbClass::SearchFieldInfo(m_pClass->GetModule(),
+ &m_fieldList,
+ m_pClass->MDToken(),
+ fldToken,
+ ppFieldData);
+ // fall through and return.
+ // Let possible CORDBG_E_ENC_HANGING_FIELD errors propogate
+ }
+ }
+ else
+ {
+ hr = m_pClass->GetFieldInfo(fldToken, ppFieldData); // this is for non-generic types....
+ // Let possible CORDBG_E_ENC_HANGING_FIELD errors propogate
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ _ASSERTE(SUCCEEDED(hr) == (*ppFieldData != NULL));
+ return hr;
+}
+
+
+//-----------------------------------------------------------------------------
+// Class is a class somewhere on the hierarchy for m_type. Search for
+// a CordbType corresponding to the CordbClass, but which has the type-parameters
+// from the current CordbType.
+// In other words, instantiate a CordbType from baseClass, using the type-params
+// in the current Type.
+//
+// For example, given:
+// class C<T>
+// class D : C<int>
+// then if the CordbObjectValue is of type D and pClass is the class
+// for "C", then searching will set relevantType to C<int>. This
+// type is then used to fetch fields from the object.
+//
+// Adds a reference to the resulting type. Since this is for internal
+// use only we probably don't need todo this...
+//
+// Parameters:
+// baseClass - open Type that needs to be instantiated with this CordbType's params.
+// ppRes - OUT: out-parameter to get CordbType. ppRes->GetClass() should equal baseClass.
+//
+// Returns:
+// S_OK on success. CORDBG_E_OBJECT_NEUTERED, CORDBG_E_CLASS_NOT_LOADED, E_INVALIDARG, OOM
+//-----------------------------------------------------------------------------
+HRESULT CordbType::GetParentType(CordbClass *baseClass, CordbType **ppRes)
+{
+ INTERNAL_SYNC_API_ENTRY(GetProcess()); //
+
+ // Ensure that we're not trying to match up against a neutered class.
+ if (baseClass->IsNeutered())
+ {
+ return CORDBG_E_OBJECT_NEUTERED;
+ }
+
+ HRESULT hr = S_OK;
+ _ASSERTE(ppRes);
+ *ppRes = NULL;
+ CordbType *res = this;
+ res->AddRef();
+ int safety = 20000; // no inheritance hierarchy is 20000 deep... we include this just in case there's a issue below and we don't terminate
+ while (safety--)
+ {
+ if (res->m_pClass == NULL)
+ {
+ if (FAILED(hr = res->Init(FALSE)))
+ {
+ res->Release();
+ return hr;
+ }
+ }
+ _ASSERTE(res->m_pClass);
+ if (res->m_pClass == baseClass)
+ {
+ // Found it!
+ break;
+ }
+
+ // Another way to determine if we're talking about the
+ // same class... Compare tokens and module.
+ mdTypeDef tok;
+ mdTypeDef targetTok;
+ if (FAILED(hr = res->m_pClass->GetToken(&tok))
+ || FAILED(hr = baseClass->GetToken(&targetTok)))
+ {
+ res->Release();
+ return hr;
+ }
+ if (tok == targetTok && res->m_pClass->GetModule() == baseClass->GetModule())
+ {
+ // Found it!
+ break;
+ }
+
+ // OK, this is not the right class so look up the inheritance chain
+ ICorDebugType *nextType = NULL;
+ if (FAILED(hr = res->GetBase(&nextType)))
+ {
+ res->Release();
+ return hr;
+ }
+
+ res->Release(); // matches the AddRef above and/or the one implicit in GetBase, for all but last time around the loop
+ res = static_cast<CordbType *> (nextType);
+ if (!res || res->m_elementType == ELEMENT_TYPE_OBJECT)
+ {
+ // Did not find it...
+ break;
+ }
+ }
+ // We exit the loop above owning one reference to res.
+ // Upon exit res will either be the appropriate type for the
+ // class we're looking for or will be the CordbType for System.Object
+ // or will be NULL
+
+ // If it's System.Object then assume something's gone wrong with
+ // the way we did the search and bail out to an old fashioned
+ // MkUnparameterizedType on the class given originally
+ if (!res || res->m_elementType == ELEMENT_TYPE_OBJECT)
+ {
+ if (res)
+ res->Release(); // matches the one left over from the loop
+ IfFailRet(CordbType::MkUnparameterizedType(baseClass->GetAppDomain(), ELEMENT_TYPE_CLASS, baseClass, &res));
+ res->AddRef();
+ }
+
+
+ *ppRes = res;
+ return hr;
+}
+
+
+//-----------------------------------------------------------------------------
+// Walk a type tree, writing the number of type args including internal nodes.
+//
+// Parameters:
+// count - IN/OUT: counter to update.
+//-----------------------------------------------------------------------------
+void CordbType::CountTypeDataNodes(unsigned int *count)
+{
+ (*count)++;
+ for (unsigned int i = 0; i < this->m_inst.m_cClassTyPars; i++)
+ {
+ this->m_inst.m_ppInst[i]->CountTypeDataNodes(count);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Internal helper method.
+// Counts the total generic args (including sub-args) for an Instantiation.
+// Eg, for List<int, Pair<string, float>>, it would return 3.
+//
+// Parameters:
+// genericArgsCount - size of the genericArgs array in elements.
+// genericArgs - array of type parameters.
+// count - IN/OUT - will increment with total number of generic args.
+// caller must intialize this (likely to 0).
+//-----------------------------------------------------------------------------
+void CordbType::CountTypeDataNodesForInstantiation(unsigned int genericArgsCount, ICorDebugType *genericArgs[], unsigned int *count)
+{
+ for (unsigned int i = 0; i < genericArgsCount; i++)
+ {
+ (static_cast<CordbType *>(genericArgs[i]))->CountTypeDataNodes(count);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Recursively walk a type tree, writing the type args into a linear.
+// Eg, for List<A, Pair<B, C>>, this will write the TypeArgData buffer
+// for { A, B, C }.
+//
+// Parameters:
+// curr_tyargData - IN/OUT: Pointer into buffer of TypeArgData structures.
+// Caller must ensure this buffer is large enough (probably by calling
+// CountTypeDataNodes).
+// On output, set to the next element in the buffer.
+//-----------------------------------------------------------------------------
+void CordbType::GatherTypeData(CordbType *type, DebuggerIPCE_TypeArgData **curr_tyargData)
+{
+ type->TypeToTypeArgData(*curr_tyargData);
+ (*curr_tyargData)++;
+ for (unsigned int i = 0; i < type->m_inst.m_cClassTyPars; i++)
+ {
+ GatherTypeData(type->m_inst.m_ppInst[i], curr_tyargData);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Flatten Instantiation into a linear buffer of TypeArgData
+// Use CountTypeDataNodesForInstantiation on the instantiation to get a large
+// enough buffer.
+//
+// Parameters:
+// genericArgsCount - size of genericArgs array in elements.
+// genericArgs - incoming array to walk
+// curr_tyargData - IN/OUT: Pointer into buffer of TypeArgData structures.
+// Caller must ensure this buffer is large enough (probably by calling
+// CountTypeDataNodes).
+// On output, set to the next element in the buffer.
+//
+//-----------------------------------------------------------------------------
+void CordbType::GatherTypeDataForInstantiation(unsigned int genericArgsCount, ICorDebugType *genericArgs[], DebuggerIPCE_TypeArgData **curr_tyargData)
+{
+ for (unsigned int i = 0; i < genericArgsCount; i++)
+ {
+ GatherTypeData(static_cast<CordbType *> (genericArgs[i]), curr_tyargData);
+ }
+}
+
+#ifdef FEATURE_64BIT_ALIGNMENT
+// checks if the type requires 8-byte alignment. the algorithm used here
+// was adapted from AdjustArgPtrForAlignment() in bcltype/VarArgsNative.cpp
+HRESULT CordbType::RequiresAlign8(BOOL* isRequired)
+{
+ if (isRequired == NULL)
+ return E_INVALIDARG;
+
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+ *isRequired = FALSE;
+
+ ULONG32 size = 0;
+ GetUnboxedObjectSize(&size);
+
+ if (size >= 8)
+ {
+ CorElementType type;
+ GetType(&type);
+
+ if (type != ELEMENT_TYPE_TYPEDBYREF)
+ {
+ if (type == ELEMENT_TYPE_VALUETYPE)
+ {
+ if (m_typeHandleExact.IsNull())
+ InitInstantiationTypeHandle(FALSE);
+
+ *isRequired = GetProcess()->GetDAC()->RequiresAlign8(m_typeHandleExact);
+ }
+ else
+ {
+ *isRequired = TRUE;
+ }
+ }
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+#endif
+
+/* ------------------------------------------------------------------------- *
+ * TypeParameter Enumerator class
+ * ------------------------------------------------------------------------- */
+
+// Factory methods
+CordbTypeEnum* CordbTypeEnum::Build(CordbAppDomain * pAppDomain, NeuterList * pNeuterList, unsigned int cTypars, CordbType **ppTypars)
+{
+ return BuildImpl( pAppDomain, pNeuterList, cTypars, ppTypars );
+}
+
+CordbTypeEnum* CordbTypeEnum::Build(CordbAppDomain * pAppDomain, NeuterList * pNeuterList, unsigned int cTypars, RSSmartPtr<CordbType> *ppTypars)
+{
+ return BuildImpl( pAppDomain, pNeuterList, cTypars, ppTypars );
+}
+
+//-----------------------------------------------------------------------------
+// We need to support taking both an array of CordbType* and an array of RSSmartPtr<CordbType>,
+// but the code is identical in both cases. Rather than duplicate any code explicity, it's better to
+// have the compiler do it for us using this template method.
+// Another option would be to create an IList<T> interface and implementations for both arrays
+// of T* and arrays of RSSmartPtr<T>. This would be more generally useful, but much more code.
+//-----------------------------------------------------------------------------
+template<class T> CordbTypeEnum* CordbTypeEnum::BuildImpl(CordbAppDomain * pAppDomain, NeuterList * pNeuterList, unsigned int cTypars, T* ppTypars)
+{
+ CordbTypeEnum* newEnum = new (nothrow) CordbTypeEnum( pAppDomain, pNeuterList );
+ if( NULL == newEnum )
+ {
+ return NULL;
+ }
+
+ _ASSERTE( newEnum->m_ppTypars == NULL );
+ newEnum->m_ppTypars = new (nothrow) RSSmartPtr<CordbType> [cTypars];
+ if( newEnum->m_ppTypars == NULL )
+ {
+ delete newEnum;
+ return NULL;
+ }
+
+ newEnum->m_iMax = cTypars;
+ for (unsigned int i = 0; i < cTypars; i++)
+ {
+ newEnum->m_ppTypars[i].Assign(ppTypars[i]);
+ }
+
+ return newEnum;
+}
+
+// Private, called only by Build above
+CordbTypeEnum::CordbTypeEnum(CordbAppDomain * pAppDomain, NeuterList * pNeuterList) :
+ CordbBase(pAppDomain->GetProcess(), 0),
+ m_ppTypars(NULL),
+ m_iCurrent(0),
+ m_iMax(0)
+{
+ _ASSERTE(pAppDomain != NULL);
+ _ASSERTE(pNeuterList != NULL);
+
+ m_pAppDomain = pAppDomain;
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ pNeuterList->Add(GetProcess(), this);
+ }
+ EX_CATCH_HRESULT(hr);
+ SetUnrecoverableIfFailed(GetProcess(), hr);
+}
+
+CordbTypeEnum::~CordbTypeEnum()
+{
+ _ASSERTE(this->IsNeutered());
+}
+
+void CordbTypeEnum::Neuter()
+{
+ delete [] m_ppTypars;
+ m_ppTypars = NULL;
+ m_pAppDomain = NULL;
+
+ CordbBase::Neuter();
+}
+
+
+HRESULT CordbTypeEnum::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugEnum)
+ *pInterface = static_cast<ICorDebugEnum*>(this);
+ else if (id == IID_ICorDebugTypeEnum)
+ *pInterface = static_cast<ICorDebugTypeEnum*>(this);
+ else if (id == IID_IUnknown)
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugTypeEnum*>(this));
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+HRESULT CordbTypeEnum::Skip(ULONG celt)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = E_FAIL;
+ if ( (m_iCurrent+celt) < m_iMax ||
+ celt == 0)
+ {
+ m_iCurrent += celt;
+ hr = S_OK;
+ }
+
+ return hr;
+}
+
+HRESULT CordbTypeEnum::Reset(void)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ m_iCurrent = 0;
+ return S_OK;
+}
+
+HRESULT CordbTypeEnum::Clone(ICorDebugEnum **ppEnum)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+
+ VALIDATE_POINTER_TO_OBJECT(ppEnum, ICorDebugEnum **);
+
+ HRESULT hr = S_OK;
+
+ CordbTypeEnum *pCVE = CordbTypeEnum::Build( m_pAppDomain, m_pAppDomain->GetLongExitNeuterList(), m_iMax, m_ppTypars );
+ if ( pCVE == NULL )
+ {
+ (*ppEnum) = NULL;
+ hr = E_OUTOFMEMORY;
+ goto LExit;
+ }
+
+ pCVE->AddRef();
+ (*ppEnum) = (ICorDebugEnum*)pCVE;
+
+LExit:
+ return hr;
+}
+
+HRESULT CordbTypeEnum::GetCount(ULONG *pcelt)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ VALIDATE_POINTER_TO_OBJECT(pcelt, ULONG *);
+
+ if( pcelt == NULL)
+ return E_INVALIDARG;
+
+ (*pcelt) = m_iMax;
+ return S_OK;
+}
+
+//
+// In the event of failure, the current pointer will be left at
+// one element past the troublesome element. Thus, if one were
+// to repeatedly ask for one element to iterate through the
+// array, you would iterate exactly m_iMax times, regardless
+// of individual failures.
+HRESULT CordbTypeEnum::Next(ULONG celt, ICorDebugType *values[], ULONG *pceltFetched)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(values, ICorDebugClass *,
+ celt, true, true);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pceltFetched, ULONG *);
+
+ if ((pceltFetched == NULL) && (celt != 1))
+ {
+ return E_INVALIDARG;
+ }
+
+ if (celt == 0)
+ {
+ if (pceltFetched != NULL)
+ {
+ *pceltFetched = 0;
+ }
+ return S_OK;
+ }
+
+ HRESULT hr = S_OK;
+
+ int iMax = min( m_iMax, m_iCurrent+celt);
+ int i;
+
+ for (i = m_iCurrent; i < iMax; i++)
+ {
+ //printf("CordbTypeEnum::Next, returning = 0x%08x.\n", m_ppTypars[i]);
+ values[i-m_iCurrent] = m_ppTypars[i];
+ values[i-m_iCurrent]->AddRef();
+ }
+
+ int count = (i - m_iCurrent);
+
+ if ( FAILED( hr ) )
+ { //we failed: +1 pushes us past troublesome element
+ m_iCurrent += 1 + count;
+ }
+ else
+ {
+ m_iCurrent += count;
+ }
+
+ if (pceltFetched != NULL)
+ {
+ *pceltFetched = count;
+ }
+
+ //
+ // If we reached the end of the enumeration, but not the end
+ // of the number of requested items, we return S_FALSE.
+ //
+ if (((ULONG)count) < celt)
+ {
+ return S_FALSE;
+ }
+
+ return hr;
+}
+
diff --git a/src/debug/di/shared.cpp b/src/debug/di/shared.cpp
new file mode 100644
index 0000000000..7d1e858316
--- /dev/null
+++ b/src/debug/di/shared.cpp
@@ -0,0 +1,16 @@
+// 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.
+
+//
+
+/*
+ *
+ * Common source file for all files in ..\shared for compiling into the right-side
+ *
+ */
+#include "stdafx.h"
+
+#include "../shared/utils.cpp"
+#include "../shared/dbgtransportsession.cpp"
+#include "../shared/stringcopyholder.cpp"
diff --git a/src/debug/di/shimcallback.cpp b/src/debug/di/shimcallback.cpp
new file mode 100644
index 0000000000..f134df703a
--- /dev/null
+++ b/src/debug/di/shimcallback.cpp
@@ -0,0 +1,1317 @@
+// 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.
+//*****************************************************************************
+// File: ShimCallback.cpp
+//
+
+//
+// The V3 ICD debugging APIs have a lower abstraction level than V2.
+// This provides V2 ICD debugging functionality on top of the V3 debugger object.
+//*****************************************************************************
+
+#include "stdafx.h"
+
+#include "safewrap.h"
+#include "check.h"
+
+#include <limits.h>
+#include "shimpriv.h"
+
+
+//
+// Callback that shim provides, which then queues up the events.
+//
+ShimProxyCallback::ShimProxyCallback(ShimProcess * pShim)
+ : m_cRef(0)
+{
+ m_pShim = pShim;
+}
+
+// Implement IUnknown
+ULONG ShimProxyCallback::AddRef()
+{
+ InterlockedIncrement(&m_cRef);
+ return m_cRef;
+}
+ULONG ShimProxyCallback::Release()
+{
+ LONG ref = InterlockedDecrement(&m_cRef);
+ if (ref == 0)
+ {
+ delete this;
+ return 0;
+ }
+ return ref;
+
+}
+HRESULT ShimProxyCallback::QueryInterface(REFIID riid, void **ppInterface)
+{
+ if (riid == IID_ICorDebugManagedCallback)
+ {
+ *ppInterface = static_cast<ICorDebugManagedCallback*>(this);
+ }
+ else if (riid == IID_ICorDebugManagedCallback2)
+ {
+ *ppInterface = static_cast<ICorDebugManagedCallback2*>(this);
+ }
+ else if (riid == IID_ICorDebugManagedCallback3)
+ {
+ *ppInterface = static_cast<ICorDebugManagedCallback3*>(this);
+ }
+ else if (riid == IID_IUnknown)
+ {
+ *ppInterface = static_cast<IUnknown*>(static_cast<ICorDebugManagedCallback*>(this));
+ }
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ this->AddRef();
+ return S_OK;
+}
+
+//
+// Map from an old frame to a new one.
+//
+// Arguments:
+// pThread - thread that frame is on
+// pOldFrame - old frame before the continue, may have gotten neutered.
+//
+// Returns:
+// a new, non-neutered frame that matches the old frame.
+//
+// Notes:
+// Called by event handlers below (which are considered Outside the RS).
+// No adjust of reference, Thread already has reference.
+// @dbgtodo shim-stackwalks: this is used for exception callbacks, which may change for V3.
+ICorDebugFrame * UpdateFrame(ICorDebugThread * pThread, ICorDebugFrame * pOldFrame)
+{
+ PUBLIC_API_ENTRY_FOR_SHIM(NULL);
+
+ RSExtSmartPtr<ICorDebugFrame> pNewFrame;
+
+ EX_TRY
+ {
+ CordbFrame * pFrame = static_cast<CordbFrame *> (pOldFrame);
+ if (pFrame != NULL)
+ {
+ FramePointer fp = pFrame->GetFramePointer();
+
+ CordbThread * pThread2 = static_cast<CordbThread *> (pThread);
+ pThread2->FindFrame(&pNewFrame, fp);
+
+ //
+ }
+ }
+ EX_CATCH
+ {
+ // Do not throw out of this function. Doing so means that the debugger never gets a chance to
+ // continue the debuggee process. This will lead to a hang. Instead, try to make a best effort to
+ // continue with a NULL ICDFrame. VS is able to handle this gracefully.
+ pNewFrame.Assign(NULL);
+ }
+ EX_END_CATCH(SwallowAllExceptions);
+
+ return pNewFrame;
+}
+
+
+
+//
+// Below this was autogenerated
+//
+
+// Implementation of ICorDebugManagedCallback::Breakpoint
+HRESULT ShimProxyCallback::Breakpoint(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, ICorDebugBreakpoint * pBreakpoint)
+{
+ m_pShim->PreDispatchEvent();
+ class BreakpointEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugThread > m_pThread;
+ RSExtSmartPtr<ICorDebugBreakpoint > m_pBreakpoint;
+
+ public:
+ // Ctor
+ BreakpointEvent(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, ICorDebugBreakpoint * pBreakpoint) :
+ ManagedEvent(pThread)
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pThread.Assign(pThread);
+ this->m_pBreakpoint.Assign(pBreakpoint);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->Breakpoint(m_pAppDomain, m_pThread, m_pBreakpoint);
+ }
+ }; // end class BreakpointEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new BreakpointEvent(pAppDomain, pThread, pBreakpoint));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::Breakpoint
+
+
+// Implementation of ICorDebugManagedCallback::StepComplete
+HRESULT ShimProxyCallback::StepComplete(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, ICorDebugStepper * pStepper, CorDebugStepReason reason)
+{
+ m_pShim->PreDispatchEvent();
+ class StepCompleteEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugThread > m_pThread;
+ RSExtSmartPtr<ICorDebugStepper > m_pStepper;
+ CorDebugStepReason m_reason;
+
+ public:
+ // Ctor
+ StepCompleteEvent(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, ICorDebugStepper * pStepper, CorDebugStepReason reason) :
+ ManagedEvent(pThread)
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pThread.Assign(pThread);
+ this->m_pStepper.Assign(pStepper);
+ this->m_reason = reason;
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->StepComplete(m_pAppDomain, m_pThread, m_pStepper, m_reason);
+ }
+ }; // end class StepCompleteEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new StepCompleteEvent(pAppDomain, pThread, pStepper, reason));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::StepComplete
+
+
+// Implementation of ICorDebugManagedCallback::Break
+HRESULT ShimProxyCallback::Break(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread)
+{
+ m_pShim->PreDispatchEvent();
+ class BreakEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugThread > m_pThread;
+
+ public:
+ // Ctor
+ BreakEvent(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread) :
+ ManagedEvent(pThread)
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pThread.Assign(pThread);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->Break(m_pAppDomain, m_pThread);
+ }
+ }; // end class BreakEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new BreakEvent(pAppDomain, pThread));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::Break
+
+
+// Implementation of ICorDebugManagedCallback::Exception
+HRESULT ShimProxyCallback::Exception(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, BOOL fUnhandled)
+{
+ m_pShim->PreDispatchEvent();
+ class ExceptionEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugThread > m_pThread;
+ BOOL m_fUnhandled;
+
+ public:
+ // Ctor
+ ExceptionEvent(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, BOOL fUnhandled) :
+ ManagedEvent(pThread)
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pThread.Assign(pThread);
+ this->m_fUnhandled = fUnhandled;
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->Exception(m_pAppDomain, m_pThread, m_fUnhandled);
+ }
+ }; // end class ExceptionEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new ExceptionEvent(pAppDomain, pThread, fUnhandled));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::Exception
+
+
+// Implementation of ICorDebugManagedCallback::EvalComplete
+HRESULT ShimProxyCallback::EvalComplete(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, ICorDebugEval * pEval)
+{
+ m_pShim->PreDispatchEvent();
+ class EvalCompleteEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugThread > m_pThread;
+ RSExtSmartPtr<ICorDebugEval > m_pEval;
+
+ public:
+ // Ctor
+ EvalCompleteEvent(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, ICorDebugEval * pEval) :
+ ManagedEvent(pThread)
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pThread.Assign(pThread);
+ this->m_pEval.Assign(pEval);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->EvalComplete(m_pAppDomain, m_pThread, m_pEval);
+ }
+ }; // end class EvalCompleteEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new EvalCompleteEvent(pAppDomain, pThread, pEval));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::EvalComplete
+
+
+// Implementation of ICorDebugManagedCallback::EvalException
+HRESULT ShimProxyCallback::EvalException(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, ICorDebugEval * pEval)
+{
+ m_pShim->PreDispatchEvent();
+ class EvalExceptionEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugThread > m_pThread;
+ RSExtSmartPtr<ICorDebugEval > m_pEval;
+
+ public:
+ // Ctor
+ EvalExceptionEvent(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, ICorDebugEval * pEval) :
+ ManagedEvent(pThread)
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pThread.Assign(pThread);
+ this->m_pEval.Assign(pEval);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->EvalException(m_pAppDomain, m_pThread, m_pEval);
+ }
+ }; // end class EvalExceptionEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new EvalExceptionEvent(pAppDomain, pThread, pEval));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::EvalException
+
+
+// Implementation of ICorDebugManagedCallback::CreateProcess
+// This will only be called for a Real create-process event.
+HRESULT ShimProxyCallback::CreateProcess(ICorDebugProcess * pProcess)
+{
+ m_pShim->PreDispatchEvent(true);
+ QueueCreateProcess(pProcess);
+ return S_OK;
+}
+
+void ShimProxyCallback::QueueCreateProcess(ICorDebugProcess * pProcess)
+{
+ class CreateProcessEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugProcess > m_pProcess;
+
+ public:
+ // Ctor
+ CreateProcessEvent(ICorDebugProcess * pProcess, ShimProcess * pShim) :
+ ManagedEvent(),
+ m_pShim(pShim)
+ {
+ this->m_pProcess.Assign(pProcess);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ // signal that we are in the callback--this will be cleared in code:CordbProcess::ContinueInternal
+ m_pShim->SetInCreateProcess(true);
+ return args.GetCallback1()->CreateProcess(m_pProcess);
+ }
+
+ // we need access to the shim in Dispatch so we can set the InCreateProcess flag to keep track of
+ // when we are actually in the callback. We need this information to be able to emulate
+ // the hresult logic in v2.0.
+ ShimProcess * m_pShim;
+ }; // end class CreateProcessEvent
+
+ if (!m_pShim->RemoveDuplicateCreationEventIfPresent(pProcess))
+ {
+ m_pShim->GetManagedEventQueue()->QueueEvent(new CreateProcessEvent(pProcess, m_pShim));
+ }
+} // end of methodICorDebugManagedCallback::CreateProcess
+
+
+// Implementation of ICorDebugManagedCallback::ExitProcess
+HRESULT ShimProxyCallback::ExitProcess(ICorDebugProcess * pProcess)
+{
+ m_pShim->PreDispatchEvent();
+ class ExitProcessEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugProcess > m_pProcess;
+
+ public:
+ // Ctor
+ ExitProcessEvent(ICorDebugProcess * pProcess) :
+ ManagedEvent()
+ {
+ this->m_pProcess.Assign(pProcess);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->ExitProcess(m_pProcess);
+ }
+ }; // end class ExitProcessEvent
+
+ m_pShim->RemoveDuplicateCreationEventIfPresent(pProcess);
+ m_pShim->GetManagedEventQueue()->QueueEvent(new ExitProcessEvent(pProcess));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::ExitProcess
+
+
+// Implementation of ICorDebugManagedCallback::CreateThread
+HRESULT ShimProxyCallback::CreateThread(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread)
+{
+ m_pShim->PreDispatchEvent();
+ class CreateThreadEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugThread > m_pThread;
+
+ public:
+ // Ctor
+ CreateThreadEvent(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread) :
+ ManagedEvent(pThread)
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pThread.Assign(pThread);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->CreateThread(m_pAppDomain, m_pThread);
+ }
+ }; // end class CreateThreadEvent
+
+ if (!m_pShim->RemoveDuplicateCreationEventIfPresent(pThread))
+ {
+ m_pShim->GetManagedEventQueue()->QueueEvent(new CreateThreadEvent(pAppDomain, pThread));
+ }
+ return S_OK;
+} // end of methodICorDebugManagedCallback::CreateThread
+
+
+// Implementation of ICorDebugManagedCallback::ExitThread
+HRESULT ShimProxyCallback::ExitThread(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread)
+{
+ m_pShim->PreDispatchEvent();
+ class ExitThreadEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugThread > m_pThread;
+
+ public:
+ // Ctor
+ ExitThreadEvent(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread) :
+ ManagedEvent(pThread)
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pThread.Assign(pThread);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->ExitThread(m_pAppDomain, m_pThread);
+ }
+ }; // end class ExitThreadEvent
+
+ m_pShim->RemoveDuplicateCreationEventIfPresent(pThread);
+ m_pShim->GetManagedEventQueue()->QueueEvent(new ExitThreadEvent(pAppDomain, pThread));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::ExitThread
+
+
+// Called from fake attach events.
+//
+// Arguments:
+// pAppDomain - appdomain for the LoadModule debug event
+// pModule - module being loaded.
+//
+// Notes:
+// See code:ShimProcess::QueueFakeAttachEvents
+// This is the fake version of code:ShimProxyCallback::LoadModule.
+// It sends an IPC event to go in process to collect information that we can't yet get via
+// DAC from out-of-proc.
+void ShimProxyCallback::FakeLoadModule(ICorDebugAppDomain *pAppDomain, ICorDebugModule *pModule)
+{
+ class FakeLoadModuleEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugModule > m_pModule;
+
+ public:
+ // Ctor
+ FakeLoadModuleEvent(ICorDebugAppDomain * pAppDomain, ICorDebugModule * pModule, ShimProcess * pShim) :
+ ManagedEvent(),
+ m_pShim(pShim)
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pModule.Assign(pModule);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ // signal that we are in the callback--this will be cleared in code:CordbProcess::ContinueInternal
+ m_pShim->SetInLoadModule(true);
+ return args.GetCallback1()->LoadModule(m_pAppDomain, m_pModule);
+ }
+
+ // we need access to the shim in Dispatch so we can set the InLoadModule flag to keep track
+ // when we are actually in the callback. We need this information to be able to emulate
+ // the hresult logic in v2.0.
+ ShimProcess * m_pShim;
+ }; // end class LoadModuleEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new FakeLoadModuleEvent(pAppDomain, pModule, m_pShim));
+} // end of methodICorDebugManagedCallback::LoadModule
+
+
+// Implementation of ICorDebugManagedCallback::LoadModule
+HRESULT ShimProxyCallback::LoadModule(ICorDebugAppDomain * pAppDomain, ICorDebugModule * pModule)
+{
+ m_pShim->PreDispatchEvent();
+ class LoadModuleEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugModule > m_pModule;
+
+ public:
+ // Ctor
+ LoadModuleEvent(ICorDebugAppDomain * pAppDomain, ICorDebugModule * pModule) :
+ ManagedEvent()
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pModule.Assign(pModule);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->LoadModule(m_pAppDomain, m_pModule);
+ }
+ }; // end class LoadModuleEvent
+
+ if (!m_pShim->RemoveDuplicateCreationEventIfPresent(pModule))
+ {
+ m_pShim->GetManagedEventQueue()->QueueEvent(new LoadModuleEvent(pAppDomain, pModule));
+ }
+ return S_OK;
+} // end of methodICorDebugManagedCallback::LoadModule
+
+
+// Implementation of ICorDebugManagedCallback::UnloadModule
+HRESULT ShimProxyCallback::UnloadModule(ICorDebugAppDomain * pAppDomain, ICorDebugModule * pModule)
+{
+ m_pShim->PreDispatchEvent();
+ class UnloadModuleEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugModule > m_pModule;
+
+ public:
+ // Ctor
+ UnloadModuleEvent(ICorDebugAppDomain * pAppDomain, ICorDebugModule * pModule) :
+ ManagedEvent()
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pModule.Assign(pModule);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->UnloadModule(m_pAppDomain, m_pModule);
+ }
+ }; // end class UnloadModuleEvent
+
+ m_pShim->RemoveDuplicateCreationEventIfPresent(pModule);
+ m_pShim->GetManagedEventQueue()->QueueEvent(new UnloadModuleEvent(pAppDomain, pModule));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::UnloadModule
+
+
+// Implementation of ICorDebugManagedCallback::LoadClass
+HRESULT ShimProxyCallback::LoadClass(ICorDebugAppDomain * pAppDomain, ICorDebugClass * pClass)
+{
+ m_pShim->PreDispatchEvent();
+ class LoadClassEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugClass > m_pClass;
+
+ public:
+ // Ctor
+ LoadClassEvent(ICorDebugAppDomain * pAppDomain, ICorDebugClass * pClass) :
+ ManagedEvent()
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pClass.Assign(pClass);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->LoadClass(m_pAppDomain, m_pClass);
+ }
+ }; // end class LoadClassEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new LoadClassEvent(pAppDomain, pClass));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::LoadClass
+
+
+// Implementation of ICorDebugManagedCallback::UnloadClass
+HRESULT ShimProxyCallback::UnloadClass(ICorDebugAppDomain * pAppDomain, ICorDebugClass * pClass)
+{
+ m_pShim->PreDispatchEvent();
+ class UnloadClassEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugClass > m_pClass;
+
+ public:
+ // Ctor
+ UnloadClassEvent(ICorDebugAppDomain * pAppDomain, ICorDebugClass * pClass) :
+ ManagedEvent()
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pClass.Assign(pClass);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->UnloadClass(m_pAppDomain, m_pClass);
+ }
+ }; // end class UnloadClassEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new UnloadClassEvent(pAppDomain, pClass));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::UnloadClass
+
+
+// Implementation of ICorDebugManagedCallback::DebuggerError
+HRESULT ShimProxyCallback::DebuggerError(ICorDebugProcess * pProcess, HRESULT errorHR, DWORD errorCode)
+{
+ m_pShim->PreDispatchEvent();
+ class DebuggerErrorEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugProcess > m_pProcess;
+ HRESULT m_errorHR;
+ DWORD m_errorCode;
+
+ public:
+ // Ctor
+ DebuggerErrorEvent(ICorDebugProcess * pProcess, HRESULT errorHR, DWORD errorCode) :
+ ManagedEvent()
+ {
+ this->m_pProcess.Assign(pProcess);
+ this->m_errorHR = errorHR;
+ this->m_errorCode = errorCode;
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->DebuggerError(m_pProcess, m_errorHR, m_errorCode);
+ }
+ }; // end class DebuggerErrorEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new DebuggerErrorEvent(pProcess, errorHR, errorCode));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::DebuggerError
+
+
+// Implementation of ICorDebugManagedCallback::LogMessage
+HRESULT ShimProxyCallback::LogMessage(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, LONG lLevel, __in LPWSTR pLogSwitchName, __in LPWSTR pMessage)
+{
+ m_pShim->PreDispatchEvent();
+ class LogMessageEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugThread > m_pThread;
+ LONG m_lLevel;
+ StringCopyHolder m_pLogSwitchName;
+ StringCopyHolder m_pMessage;
+
+ public:
+ // Ctor
+ LogMessageEvent(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, LONG lLevel, LPCWSTR pLogSwitchName, LPCWSTR pMessage) :
+ ManagedEvent(pThread)
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pThread.Assign(pThread);
+ this->m_lLevel = lLevel;
+ this->m_pLogSwitchName.AssignCopy(pLogSwitchName);
+ this->m_pMessage.AssignCopy(pMessage);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->LogMessage(m_pAppDomain, m_pThread, m_lLevel, const_cast<WCHAR*>((const WCHAR*)m_pLogSwitchName), const_cast<WCHAR*>((const WCHAR*)m_pMessage));
+ }
+ }; // end class LogMessageEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new LogMessageEvent(pAppDomain, pThread, lLevel, pLogSwitchName, pMessage));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::LogMessage
+
+
+// Implementation of ICorDebugManagedCallback::LogSwitch
+HRESULT ShimProxyCallback::LogSwitch(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, LONG lLevel, ULONG ulReason, __in LPWSTR pLogSwitchName, __in LPWSTR pParentName)
+{
+ m_pShim->PreDispatchEvent();
+ class LogSwitchEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugThread > m_pThread;
+ LONG m_lLevel;
+ ULONG m_ulReason;
+ StringCopyHolder m_pLogSwitchName;
+ StringCopyHolder m_pParentName;
+
+ public:
+ // Ctor
+ LogSwitchEvent(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, LONG lLevel, ULONG ulReason, LPCWSTR pLogSwitchName, LPCWSTR pParentName) :
+ ManagedEvent(pThread)
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pThread.Assign(pThread);
+ this->m_lLevel = lLevel;
+ this->m_ulReason = ulReason;
+ this->m_pLogSwitchName.AssignCopy(pLogSwitchName);
+ this->m_pParentName.AssignCopy(pParentName);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->LogSwitch(m_pAppDomain, m_pThread, m_lLevel, m_ulReason, const_cast<WCHAR*>((const WCHAR*)m_pLogSwitchName), const_cast<WCHAR*>((const WCHAR*)m_pParentName));
+ }
+ }; // end class LogSwitchEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new LogSwitchEvent(pAppDomain, pThread, lLevel, ulReason, pLogSwitchName, pParentName));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::LogSwitch
+
+
+// Implementation of ICorDebugManagedCallback::CreateAppDomain
+HRESULT ShimProxyCallback::CreateAppDomain(ICorDebugProcess * pProcess, ICorDebugAppDomain * pAppDomain)
+{
+ m_pShim->PreDispatchEvent();
+ class CreateAppDomainEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugProcess > m_pProcess;
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+
+ public:
+ // Ctor
+ CreateAppDomainEvent(ICorDebugProcess * pProcess, ICorDebugAppDomain * pAppDomain) :
+ ManagedEvent()
+ {
+ this->m_pProcess.Assign(pProcess);
+ this->m_pAppDomain.Assign(pAppDomain);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->CreateAppDomain(m_pProcess, m_pAppDomain);
+ }
+ }; // end class CreateAppDomainEvent
+
+ if (!m_pShim->RemoveDuplicateCreationEventIfPresent(pAppDomain))
+ {
+ m_pShim->GetManagedEventQueue()->QueueEvent(new CreateAppDomainEvent(pProcess, pAppDomain));
+ }
+ return S_OK;
+} // end of methodICorDebugManagedCallback::CreateAppDomain
+
+
+// Implementation of ICorDebugManagedCallback::ExitAppDomain
+HRESULT ShimProxyCallback::ExitAppDomain(ICorDebugProcess * pProcess, ICorDebugAppDomain * pAppDomain)
+{
+ m_pShim->PreDispatchEvent();
+ class ExitAppDomainEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugProcess > m_pProcess;
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+
+ public:
+ // Ctor
+ ExitAppDomainEvent(ICorDebugProcess * pProcess, ICorDebugAppDomain * pAppDomain) :
+ ManagedEvent()
+ {
+ this->m_pProcess.Assign(pProcess);
+ this->m_pAppDomain.Assign(pAppDomain);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->ExitAppDomain(m_pProcess, m_pAppDomain);
+ }
+ }; // end class ExitAppDomainEvent
+
+ m_pShim->RemoveDuplicateCreationEventIfPresent(pAppDomain);
+ m_pShim->GetManagedEventQueue()->QueueEvent(new ExitAppDomainEvent(pProcess, pAppDomain));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::ExitAppDomain
+
+
+// Implementation of ICorDebugManagedCallback::LoadAssembly
+HRESULT ShimProxyCallback::LoadAssembly(ICorDebugAppDomain * pAppDomain, ICorDebugAssembly * pAssembly)
+{
+ m_pShim->PreDispatchEvent();
+ class LoadAssemblyEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugAssembly > m_pAssembly;
+
+ public:
+ // Ctor
+ LoadAssemblyEvent(ICorDebugAppDomain * pAppDomain, ICorDebugAssembly * pAssembly) :
+ ManagedEvent()
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pAssembly.Assign(pAssembly);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->LoadAssembly(m_pAppDomain, m_pAssembly);
+ }
+ }; // end class LoadAssemblyEvent
+
+ if (!m_pShim->RemoveDuplicateCreationEventIfPresent(pAssembly))
+ {
+ m_pShim->GetManagedEventQueue()->QueueEvent(new LoadAssemblyEvent(pAppDomain, pAssembly));
+ }
+ return S_OK;
+} // end of methodICorDebugManagedCallback::LoadAssembly
+
+
+// Implementation of ICorDebugManagedCallback::UnloadAssembly
+HRESULT ShimProxyCallback::UnloadAssembly(ICorDebugAppDomain * pAppDomain, ICorDebugAssembly * pAssembly)
+{
+ m_pShim->PreDispatchEvent();
+ class UnloadAssemblyEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugAssembly > m_pAssembly;
+
+ public:
+ // Ctor
+ UnloadAssemblyEvent(ICorDebugAppDomain * pAppDomain, ICorDebugAssembly * pAssembly) :
+ ManagedEvent()
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pAssembly.Assign(pAssembly);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->UnloadAssembly(m_pAppDomain, m_pAssembly);
+ }
+ }; // end class UnloadAssemblyEvent
+
+ m_pShim->RemoveDuplicateCreationEventIfPresent(pAssembly);
+ m_pShim->GetManagedEventQueue()->QueueEvent(new UnloadAssemblyEvent(pAppDomain, pAssembly));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::UnloadAssembly
+
+
+// Implementation of ICorDebugManagedCallback::ControlCTrap
+HRESULT ShimProxyCallback::ControlCTrap(ICorDebugProcess * pProcess)
+{
+ m_pShim->PreDispatchEvent();
+ class ControlCTrapEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugProcess > m_pProcess;
+
+ public:
+ // Ctor
+ ControlCTrapEvent(ICorDebugProcess * pProcess) :
+ ManagedEvent()
+ {
+ this->m_pProcess.Assign(pProcess);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->ControlCTrap(m_pProcess);
+ }
+ }; // end class ControlCTrapEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new ControlCTrapEvent(pProcess));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::ControlCTrap
+
+
+// Implementation of ICorDebugManagedCallback::NameChange
+HRESULT ShimProxyCallback::NameChange(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread)
+{
+ m_pShim->PreDispatchEvent();
+ class NameChangeEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugThread > m_pThread;
+
+ public:
+ // Ctor
+ NameChangeEvent(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread) :
+ ManagedEvent(pThread)
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pThread.Assign(pThread);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->NameChange(m_pAppDomain, m_pThread);
+ }
+ }; // end class NameChangeEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new NameChangeEvent(pAppDomain, pThread));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::NameChange
+
+
+// Implementation of ICorDebugManagedCallback::UpdateModuleSymbols
+HRESULT ShimProxyCallback::UpdateModuleSymbols(ICorDebugAppDomain * pAppDomain, ICorDebugModule * pModule, IStream * pSymbolStream)
+{
+ m_pShim->PreDispatchEvent();
+ class UpdateModuleSymbolsEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugModule > m_pModule;
+ RSExtSmartPtr<IStream > m_pSymbolStream;
+
+ public:
+ // Ctor
+ UpdateModuleSymbolsEvent(ICorDebugAppDomain * pAppDomain, ICorDebugModule * pModule, IStream * pSymbolStream) :
+ ManagedEvent()
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pModule.Assign(pModule);
+ this->m_pSymbolStream.Assign(pSymbolStream);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->UpdateModuleSymbols(m_pAppDomain, m_pModule, m_pSymbolStream);
+ }
+ }; // end class UpdateModuleSymbolsEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new UpdateModuleSymbolsEvent(pAppDomain, pModule, pSymbolStream));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::UpdateModuleSymbols
+
+
+// Implementation of ICorDebugManagedCallback::EditAndContinueRemap
+HRESULT ShimProxyCallback::EditAndContinueRemap(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, ICorDebugFunction * pFunction, BOOL fAccurate)
+{
+ m_pShim->PreDispatchEvent();
+ class EditAndContinueRemapEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugThread > m_pThread;
+ RSExtSmartPtr<ICorDebugFunction > m_pFunction;
+ BOOL m_fAccurate;
+
+ public:
+ // Ctor
+ EditAndContinueRemapEvent(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, ICorDebugFunction * pFunction, BOOL fAccurate) :
+ ManagedEvent(pThread)
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pThread.Assign(pThread);
+ this->m_pFunction.Assign(pFunction);
+ this->m_fAccurate = fAccurate;
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->EditAndContinueRemap(m_pAppDomain, m_pThread, m_pFunction, m_fAccurate);
+ }
+ }; // end class EditAndContinueRemapEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new EditAndContinueRemapEvent(pAppDomain, pThread, pFunction, fAccurate));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::EditAndContinueRemap
+
+
+// Implementation of ICorDebugManagedCallback::BreakpointSetError
+HRESULT ShimProxyCallback::BreakpointSetError(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, ICorDebugBreakpoint * pBreakpoint, DWORD dwError)
+{
+ m_pShim->PreDispatchEvent();
+ class BreakpointSetErrorEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugThread > m_pThread;
+ RSExtSmartPtr<ICorDebugBreakpoint > m_pBreakpoint;
+ DWORD m_dwError;
+
+ public:
+ // Ctor
+ BreakpointSetErrorEvent(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, ICorDebugBreakpoint * pBreakpoint, DWORD dwError) :
+ ManagedEvent(pThread)
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pThread.Assign(pThread);
+ this->m_pBreakpoint.Assign(pBreakpoint);
+ this->m_dwError = dwError;
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback1()->BreakpointSetError(m_pAppDomain, m_pThread, m_pBreakpoint, m_dwError);
+ }
+ }; // end class BreakpointSetErrorEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new BreakpointSetErrorEvent(pAppDomain, pThread, pBreakpoint, dwError));
+ return S_OK;
+} // end of methodICorDebugManagedCallback::BreakpointSetError
+
+
+// Implementation of ICorDebugManagedCallback2::FunctionRemapOpportunity
+HRESULT ShimProxyCallback::FunctionRemapOpportunity(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, ICorDebugFunction * pOldFunction, ICorDebugFunction * pNewFunction, ULONG32 oldILOffset)
+{
+ m_pShim->PreDispatchEvent();
+ class FunctionRemapOpportunityEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugThread > m_pThread;
+ RSExtSmartPtr<ICorDebugFunction > m_pOldFunction;
+ RSExtSmartPtr<ICorDebugFunction > m_pNewFunction;
+ ULONG32 m_oldILOffset;
+
+ public:
+ // Ctor
+ FunctionRemapOpportunityEvent(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, ICorDebugFunction * pOldFunction, ICorDebugFunction * pNewFunction, ULONG32 oldILOffset) :
+ ManagedEvent(pThread)
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pThread.Assign(pThread);
+ this->m_pOldFunction.Assign(pOldFunction);
+ this->m_pNewFunction.Assign(pNewFunction);
+ this->m_oldILOffset = oldILOffset;
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback2()->FunctionRemapOpportunity(m_pAppDomain, m_pThread, m_pOldFunction, m_pNewFunction, m_oldILOffset);
+ }
+ }; // end class FunctionRemapOpportunityEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new FunctionRemapOpportunityEvent(pAppDomain, pThread, pOldFunction, pNewFunction, oldILOffset));
+ return S_OK;
+} // end of methodICorDebugManagedCallback2::FunctionRemapOpportunity
+
+
+// Implementation of ICorDebugManagedCallback2::CreateConnection
+HRESULT ShimProxyCallback::CreateConnection(ICorDebugProcess * pProcess, CONNID dwConnectionId, __in LPWSTR pConnectionName)
+{
+ m_pShim->PreDispatchEvent();
+ class CreateConnectionEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugProcess > m_pProcess;
+ CONNID m_dwConnectionId;
+ StringCopyHolder m_pConnectionName;
+
+ public:
+ // Ctor
+ CreateConnectionEvent(ICorDebugProcess * pProcess, CONNID dwConnectionId, LPCWSTR pConnectionName) :
+ ManagedEvent()
+ {
+ this->m_pProcess.Assign(pProcess);
+ this->m_dwConnectionId = dwConnectionId;
+ this->m_pConnectionName.AssignCopy(pConnectionName);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback2()->CreateConnection(m_pProcess, m_dwConnectionId, const_cast<WCHAR*>((const WCHAR*)m_pConnectionName));
+ }
+ }; // end class CreateConnectionEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new CreateConnectionEvent(pProcess, dwConnectionId, pConnectionName));
+ return S_OK;
+} // end of methodICorDebugManagedCallback2::CreateConnection
+
+
+// Implementation of ICorDebugManagedCallback2::ChangeConnection
+HRESULT ShimProxyCallback::ChangeConnection(ICorDebugProcess * pProcess, CONNID dwConnectionId)
+{
+ m_pShim->PreDispatchEvent();
+ class ChangeConnectionEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugProcess > m_pProcess;
+ CONNID m_dwConnectionId;
+
+ public:
+ // Ctor
+ ChangeConnectionEvent(ICorDebugProcess * pProcess, CONNID dwConnectionId) :
+ ManagedEvent()
+ {
+ this->m_pProcess.Assign(pProcess);
+ this->m_dwConnectionId = dwConnectionId;
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback2()->ChangeConnection(m_pProcess, m_dwConnectionId);
+ }
+ }; // end class ChangeConnectionEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new ChangeConnectionEvent(pProcess, dwConnectionId));
+ return S_OK;
+} // end of methodICorDebugManagedCallback2::ChangeConnection
+
+
+// Implementation of ICorDebugManagedCallback2::DestroyConnection
+HRESULT ShimProxyCallback::DestroyConnection(ICorDebugProcess * pProcess, CONNID dwConnectionId)
+{
+ m_pShim->PreDispatchEvent();
+ class DestroyConnectionEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugProcess > m_pProcess;
+ CONNID m_dwConnectionId;
+
+ public:
+ // Ctor
+ DestroyConnectionEvent(ICorDebugProcess * pProcess, CONNID dwConnectionId) :
+ ManagedEvent()
+ {
+ this->m_pProcess.Assign(pProcess);
+ this->m_dwConnectionId = dwConnectionId;
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback2()->DestroyConnection(m_pProcess, m_dwConnectionId);
+ }
+ }; // end class DestroyConnectionEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new DestroyConnectionEvent(pProcess, dwConnectionId));
+ return S_OK;
+} // end of methodICorDebugManagedCallback2::DestroyConnection
+
+
+
+// Implementation of ICorDebugManagedCallback2::Exception
+HRESULT ShimProxyCallback::Exception(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, ICorDebugFrame * pFrame, ULONG32 nOffset, CorDebugExceptionCallbackType dwEventType, DWORD dwFlags)
+{
+ m_pShim->PreDispatchEvent();
+ class ExceptionEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugThread > m_pThread;
+ RSExtSmartPtr<ICorDebugFrame > m_pFrame;
+ ULONG32 m_nOffset;
+ CorDebugExceptionCallbackType m_dwEventType;
+ DWORD m_dwFlags;
+
+ public:
+ // Ctor
+ ExceptionEvent(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, ICorDebugFrame * pFrame, ULONG32 nOffset, CorDebugExceptionCallbackType dwEventType, DWORD dwFlags) :
+ ManagedEvent(pThread)
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pThread.Assign(pThread);
+ this->m_pFrame.Assign(pFrame);
+ this->m_nOffset = nOffset;
+ this->m_dwEventType = dwEventType;
+ this->m_dwFlags = dwFlags;
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback2()->Exception(m_pAppDomain, m_pThread, UpdateFrame(m_pThread, m_pFrame), m_nOffset, m_dwEventType, m_dwFlags);
+ }
+ }; // end class ExceptionEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new ExceptionEvent(pAppDomain, pThread, pFrame, nOffset, dwEventType, dwFlags));
+ return S_OK;
+} // end of methodICorDebugManagedCallback2::Exception
+
+
+// Implementation of ICorDebugManagedCallback2::ExceptionUnwind
+HRESULT ShimProxyCallback::ExceptionUnwind(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, CorDebugExceptionUnwindCallbackType dwEventType, DWORD dwFlags)
+{
+ m_pShim->PreDispatchEvent();
+ class ExceptionUnwindEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugThread > m_pThread;
+ CorDebugExceptionUnwindCallbackType m_dwEventType;
+ DWORD m_dwFlags;
+
+ public:
+ // Ctor
+ ExceptionUnwindEvent(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, CorDebugExceptionUnwindCallbackType dwEventType, DWORD dwFlags) :
+ ManagedEvent(pThread)
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pThread.Assign(pThread);
+ this->m_dwEventType = dwEventType;
+ this->m_dwFlags = dwFlags;
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback2()->ExceptionUnwind(m_pAppDomain, m_pThread, m_dwEventType, m_dwFlags);
+ }
+ }; // end class ExceptionUnwindEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new ExceptionUnwindEvent(pAppDomain, pThread, dwEventType, dwFlags));
+ return S_OK;
+} // end of methodICorDebugManagedCallback2::ExceptionUnwind
+
+
+// Implementation of ICorDebugManagedCallback2::FunctionRemapComplete
+HRESULT ShimProxyCallback::FunctionRemapComplete(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, ICorDebugFunction * pFunction)
+{
+ m_pShim->PreDispatchEvent();
+ class FunctionRemapCompleteEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugThread > m_pThread;
+ RSExtSmartPtr<ICorDebugFunction > m_pFunction;
+
+ public:
+ // Ctor
+ FunctionRemapCompleteEvent(ICorDebugAppDomain * pAppDomain, ICorDebugThread * pThread, ICorDebugFunction * pFunction) :
+ ManagedEvent(pThread)
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pThread.Assign(pThread);
+ this->m_pFunction.Assign(pFunction);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback2()->FunctionRemapComplete(m_pAppDomain, m_pThread, m_pFunction);
+ }
+ }; // end class FunctionRemapCompleteEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new FunctionRemapCompleteEvent(pAppDomain, pThread, pFunction));
+ return S_OK;
+} // end of methodICorDebugManagedCallback2::FunctionRemapComplete
+
+
+// Implementation of ICorDebugManagedCallback2::MDANotification
+HRESULT ShimProxyCallback::MDANotification(ICorDebugController * pController, ICorDebugThread * pThread, ICorDebugMDA * pMDA)
+{
+ m_pShim->PreDispatchEvent();
+ class MDANotificationEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugController > m_pController;
+ RSExtSmartPtr<ICorDebugThread > m_pThread;
+ RSExtSmartPtr<ICorDebugMDA > m_pMDA;
+
+ public:
+ // Ctor
+ MDANotificationEvent(ICorDebugController * pController, ICorDebugThread * pThread, ICorDebugMDA * pMDA) :
+ ManagedEvent(pThread)
+ {
+ this->m_pController.Assign(pController);
+ this->m_pThread.Assign(pThread);
+ this->m_pMDA.Assign(pMDA);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback2()->MDANotification(m_pController, m_pThread, m_pMDA);
+ }
+ }; // end class MDANotificationEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new MDANotificationEvent(pController, pThread, pMDA));
+ return S_OK;
+} // end of methodICorDebugManagedCallback2::MDANotification
+
+// Implementation of ICorDebugManagedCallback3::CustomNotification
+// Arguments:
+// input:
+// pThread - thread on which the notification occurred
+// pAppDomain - appDomain in which the notification occurred
+// Return value: S_OK
+HRESULT ShimProxyCallback::CustomNotification(ICorDebugThread * pThread, ICorDebugAppDomain * pAppDomain)
+{
+ m_pShim->PreDispatchEvent();
+ class CustomNotificationEvent : public ManagedEvent
+ {
+ // callbacks parameters. These are strong references
+ RSExtSmartPtr<ICorDebugAppDomain > m_pAppDomain;
+ RSExtSmartPtr<ICorDebugThread > m_pThread;
+
+ public:
+ // Ctor
+ CustomNotificationEvent(ICorDebugThread * pThread, ICorDebugAppDomain * pAppDomain) :
+ ManagedEvent(pThread)
+ {
+ this->m_pAppDomain.Assign(pAppDomain);
+ this->m_pThread.Assign(pThread);
+ }
+
+ HRESULT Dispatch(DispatchArgs args)
+ {
+ return args.GetCallback3()->CustomNotification(m_pThread, m_pAppDomain);
+ }
+ }; // end class CustomNotificationEvent
+
+ m_pShim->GetManagedEventQueue()->QueueEvent(new CustomNotificationEvent(pThread, pAppDomain));
+ return S_OK;
+}
+
+
diff --git a/src/debug/di/shimdatatarget.cpp b/src/debug/di/shimdatatarget.cpp
new file mode 100644
index 0000000000..f3a53b8870
--- /dev/null
+++ b/src/debug/di/shimdatatarget.cpp
@@ -0,0 +1,92 @@
+// 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.
+//*****************************************************************************
+
+//
+// File: ShimDataTarget.cpp
+//
+//*****************************************************************************
+#include "stdafx.h"
+#include "safewrap.h"
+
+#include "check.h"
+
+#include <limits.h>
+
+#include "shimpriv.h"
+
+
+// Standard impl of IUnknown::QueryInterface
+HRESULT STDMETHODCALLTYPE ShimDataTarget::QueryInterface(
+ REFIID InterfaceId,
+ PVOID* pInterface
+ )
+{
+ if (InterfaceId == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown *>(static_cast<ICorDebugDataTarget *>(this));
+ }
+ else if (InterfaceId == IID_ICorDebugDataTarget)
+ {
+ *pInterface = static_cast<ICorDebugDataTarget *>(this);
+ }
+ else if (InterfaceId == IID_ICorDebugMutableDataTarget)
+ {
+ *pInterface = static_cast<ICorDebugMutableDataTarget *>(this);
+ }
+ else if (InterfaceId == IID_ICorDebugDataTarget4)
+ {
+ *pInterface = static_cast<ICorDebugDataTarget4 *>(this);
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+// Standard impl of IUnknown::AddRef
+ULONG STDMETHODCALLTYPE ShimDataTarget::AddRef()
+{
+ LONG ref = InterlockedIncrement(&m_ref);
+ return ref;
+}
+
+// Standard impl of IUnknown::Release
+ULONG STDMETHODCALLTYPE ShimDataTarget::Release()
+{
+ LONG ref = InterlockedDecrement(&m_ref);
+ if (ref == 0)
+ {
+ delete this;
+ }
+ return ref;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Get the OS Process ID that this DataTarget is for.
+//
+// Return Value:
+// The OS PID of the process this data target is representing.
+DWORD ShimDataTarget::GetPid()
+{
+ return m_processId;
+}
+
+//---------------------------------------------------------------------------------------
+// Hook a custom function to handle ICorDebugMutableDataTarget::ContinueStatusChanged
+//
+// Arguments:
+// fpContinueStatusChanged - callback function to invoke.
+// pUserData - user data to pass to callback
+//
+void ShimDataTarget::HookContinueStatusChanged(FPContinueStatusChanged fpContinueStatusChanged, void * pUserData)
+{
+ m_fpContinueStatusChanged = fpContinueStatusChanged;
+ m_pContinueStatusChangedUserData = pUserData;
+}
diff --git a/src/debug/di/shimdatatarget.h b/src/debug/di/shimdatatarget.h
new file mode 100644
index 0000000000..adcbae8056
--- /dev/null
+++ b/src/debug/di/shimdatatarget.h
@@ -0,0 +1,133 @@
+// 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.
+//*****************************************************************************
+// ShimDataTarget.h
+//
+
+//
+// header for liveproc data targets
+//*****************************************************************************
+
+#ifndef SHIMDATATARGET_H_
+#define SHIMDATATARGET_H_
+
+
+// Function to invoke for
+typedef HRESULT (*FPContinueStatusChanged)(void * pUserData, DWORD dwThreadId, CORDB_CONTINUE_STATUS dwContinueStatus);
+
+
+//---------------------------------------------------------------------------------------
+// Data target for a live process. This is used by Shim.
+//
+class ShimDataTarget : public ICorDebugMutableDataTarget, ICorDebugDataTarget4
+{
+public:
+ virtual ~ShimDataTarget() {}
+
+ // Allow hooking an implementation for ContinueStatusChanged.
+ void HookContinueStatusChanged(FPContinueStatusChanged fpContinueStatusChanged, void * pUserData);
+
+ // Release any resources. Also called by destructor.
+ virtual void Dispose() = 0;
+
+ // Set data-target into an error mode. This can be used to mark that the process
+ // is unavailable because it's running
+ void SetError(HRESULT hr);
+
+ // Get the OS Process ID that this DataTarget is for.
+ DWORD GetPid();
+
+ //
+ // IUnknown.
+ //
+ virtual HRESULT STDMETHODCALLTYPE QueryInterface(
+ REFIID InterfaceId,
+ PVOID* Interface);
+
+ virtual ULONG STDMETHODCALLTYPE AddRef();
+
+ virtual ULONG STDMETHODCALLTYPE Release();
+
+ //
+ // ICorDebugMutableDataTarget.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE GetPlatform(
+ CorDebugPlatform * pPlatform) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE ReadVirtual(
+ CORDB_ADDRESS address,
+ BYTE * pBuffer,
+ ULONG32 request,
+ ULONG32 * pcbRead) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE WriteVirtual(
+ CORDB_ADDRESS address,
+ const BYTE * pBuffer,
+ ULONG32 request) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE GetThreadContext(
+ DWORD dwThreadID,
+ ULONG32 contextFlags,
+ ULONG32 contextSize,
+ BYTE * context) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE SetThreadContext(
+ DWORD dwThreadID,
+ ULONG32 contextSize,
+ const BYTE * context) = 0;
+
+ virtual HRESULT STDMETHODCALLTYPE ContinueStatusChanged(
+ DWORD dwThreadId,
+ CORDB_CONTINUE_STATUS dwContinueStatus) = 0;
+
+ // @dbgtodo - add Native Patch Table support
+
+ //
+ // ICorDebugDataTarget4
+ //
+
+ // Unwind to the next stack frame
+ virtual HRESULT STDMETHODCALLTYPE VirtualUnwind(
+ DWORD threadId, ULONG32 contextSize, PBYTE context) = 0;
+
+protected:
+ // Pid of the target process.
+ DWORD m_processId;
+
+ // If this HRESULT != S_OK, then all interface methods will return this.
+ // This provides a way to mark the debugggee as stopped / dead.
+ HRESULT m_hr;
+
+ FPContinueStatusChanged m_fpContinueStatusChanged;
+ void * m_pContinueStatusChangedUserData;
+
+ // Reference count.
+ LONG m_ref;
+};
+
+//---------------------------------------------------------------------------------------
+//
+// Construction method for data-target
+//
+// Arguments:
+// machineInfo - used for Mac debugging; uniquely identifies the debugger proxy on the remote machine
+// processId - (input) live OS process ID to build a data-target for.
+// ppDataTarget - (output) new data-target instance. This gets addreffed.
+//
+// Return Value:
+// S_OK on success.
+//
+// Assumptions:
+// pid must be for local, same architecture, process.
+// Caller must have security permissions for OpenProcess()
+// Caller must release *ppDataTarget.
+//
+
+HRESULT BuildPlatformSpecificDataTarget(MachineInfo machineInfo,
+ DWORD processId,
+ ShimDataTarget ** ppDataTarget);
+
+#endif // SHIMDATATARGET_H_
+
diff --git a/src/debug/di/shimevents.cpp b/src/debug/di/shimevents.cpp
new file mode 100644
index 0000000000..e54b1bd7f2
--- /dev/null
+++ b/src/debug/di/shimevents.cpp
@@ -0,0 +1,292 @@
+// 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.
+//*****************************************************************************
+// File: ShimEvents.cpp
+//
+
+//
+// The V3 ICD debugging APIs have a lower abstraction level than V2.
+// This provides V2 ICD debugging functionality on top of the V3 debugger object.
+//*****************************************************************************
+
+#include "stdafx.h"
+
+#include "safewrap.h"
+#include "check.h"
+
+#include <limits.h>
+#include "shimpriv.h"
+
+//---------------------------------------------------------------------------------------
+// Need virtual dtor since this is a base class.
+// Derived classes will do real work
+//---------------------------------------------------------------------------------------
+ManagedEvent::~ManagedEvent()
+{
+}
+
+#ifdef _DEBUG
+//---------------------------------------------------------------------------------------
+// For debugging, get a pointer value that can identify the type of this event.
+//
+// Returns:
+// persistent pointer value that can be used as cookie to identify this event type.
+//---------------------------------------------------------------------------------------
+void * ManagedEvent::GetDebugCookie()
+{
+ // Return vtable, first void* in the structure.
+ return *(reinterpret_cast<void**> (this));
+}
+#endif
+
+//---------------------------------------------------------------------------------------
+// Ctor for DispatchArgs
+//
+// Arguments:
+// pCallback1 - 1st callback, for debug events in V1.0, V1.1
+// pCallback2 - 2nd callback, for debug events added in V2
+//
+// Notes:
+// We'll have a lot of derived classes of ManagedEvent, and so encapsulating the arguments
+// for the Dispatch() function lets us juggle them around easily without hitting every signature.
+//---------------------------------------------------------------------------------------
+ManagedEvent::DispatchArgs::DispatchArgs(ICorDebugManagedCallback * pCallback1, ICorDebugManagedCallback2 * pCallback2, ICorDebugManagedCallback3 * pCallback3)
+{
+ m_pCallback1 = pCallback1;
+ m_pCallback2 = pCallback2;
+ m_pCallback3 = pCallback3;
+}
+
+
+// trivial accessor to get Callback 1
+ICorDebugManagedCallback * ManagedEvent::DispatchArgs::GetCallback1()
+{
+ return m_pCallback1;
+}
+
+// trivial accessor to get callback 2
+ICorDebugManagedCallback2 * ManagedEvent::DispatchArgs::GetCallback2()
+{
+ return m_pCallback2;
+}
+
+// trivial accessor to get callback 3
+ICorDebugManagedCallback3 * ManagedEvent::DispatchArgs::GetCallback3()
+{
+ return m_pCallback3;
+}
+
+// Returns OS Thread Id that this event occurred on, 0 if no thread affinity.
+DWORD ManagedEvent::GetOSTid()
+{
+ return m_dwThreadId;
+}
+
+//---------------------------------------------------------------------------------------
+// Constructore for events with thread affinity
+//
+// Arguments:
+// pThread - thread that this event is associated with.
+//
+// Notes:
+// Thread affinity is used with code:ManagedEventQueue::HasQueuedCallbacks
+// This includes event callbacks that have a thread parameter
+//---------------------------------------------------------------------------------------
+ManagedEvent::ManagedEvent(ICorDebugThread * pThread)
+{
+ m_dwThreadId = 0;
+ if (pThread != NULL)
+ {
+ pThread->GetID(&m_dwThreadId);
+ }
+
+ m_pNext = NULL;
+}
+
+//---------------------------------------------------------------------------------------
+// Constructor for events with no thread affinity
+//---------------------------------------------------------------------------------------
+ManagedEvent::ManagedEvent()
+{
+ m_dwThreadId = 0;
+ m_pNext = NULL;
+}
+
+
+
+
+
+
+// Ctor
+ManagedEventQueue::ManagedEventQueue()
+{
+ m_pFirstEvent = NULL;
+ m_pLastEvent = NULL;
+ m_pLock = NULL;
+}
+
+//---------------------------------------------------------------------------------------
+// Initialize
+//
+// Arguments:
+// pLock - lock that protects this event queue. This takes a weak ref to the lock,
+// so caller ensures lock stays alive for lifespan of this object
+//
+// Notes:
+// Event queue locks itself using this lock.
+// Only call this once.
+//---------------------------------------------------------------------------------------
+void ManagedEventQueue::Init(RSLock * pLock)
+{
+ _ASSERTE(m_pLock == NULL);
+ m_pLock = pLock;
+}
+
+//---------------------------------------------------------------------------------------
+// Remove event from the top.
+//
+// Returns:
+// Event that was just dequeued.
+//
+// Notes:
+// Caller then takes ownership of Event and will call Delete on it.
+// If IsEmpty() function returns NULL.
+//
+// It is an error to call Dequeue when the only elements in the queue are suspended.
+// Suspending the queue implies there are going to be new events added which should come before
+// the elements that are suspended. Trying to deqeue when there are only suspended elements
+// left is error-prone - if it were allowed, the order may be non-deterministic.
+// In practice we could probably ban calling Dequeue at all when any elements are suspended,
+// but this seems overly restrictive - there is nothing wrong with allowing these "new"
+// events to be dequeued since we know they come first (you can't nest suspensions).
+//---------------------------------------------------------------------------------------
+ManagedEvent * ManagedEventQueue::Dequeue()
+{
+ RSLockHolder lockHolder(m_pLock);
+ if (m_pFirstEvent == NULL)
+ {
+ return NULL;
+ }
+
+ ManagedEvent * pEvent = m_pFirstEvent;
+ m_pFirstEvent = m_pFirstEvent->m_pNext;
+ if (m_pFirstEvent == NULL)
+ {
+ m_pLastEvent = NULL;
+ }
+
+ pEvent->m_pNext = NULL;
+ return pEvent;
+}
+
+//---------------------------------------------------------------------------------------
+// Append the event to the end of the queue.
+// Queue owns the event and will delete it (unless it's dequeued first).
+//
+// Note that this can be called when a suspended queue is active. Events are pushed onto
+// the currently active queue (ahead of the suspended queue).
+//
+// Arguments:
+// pEvent - event to queue.
+//
+//---------------------------------------------------------------------------------------
+void ManagedEventQueue::QueueEvent(ManagedEvent * pEvent)
+{
+ RSLockHolder lockHolder(m_pLock);
+ _ASSERTE(pEvent != NULL);
+ _ASSERTE(pEvent->m_pNext == NULL);
+
+ if (m_pLastEvent == NULL)
+ {
+ _ASSERTE(m_pFirstEvent == NULL);
+ m_pFirstEvent = m_pLastEvent = pEvent;
+ }
+ else
+ {
+ m_pLastEvent->m_pNext = pEvent;
+ m_pLastEvent = pEvent;
+ }
+}
+
+
+//---------------------------------------------------------------------------------------
+// Returns true iff the event queue is empty (including any suspended queue elements)
+//---------------------------------------------------------------------------------------
+bool ManagedEventQueue::IsEmpty()
+{
+ RSLockHolder lockHolder(m_pLock);
+ if (m_pFirstEvent != NULL)
+ {
+ _ASSERTE(m_pLastEvent != NULL);
+ return false;
+ }
+
+ _ASSERTE(m_pLastEvent == NULL);
+ return true;
+}
+
+
+//---------------------------------------------------------------------------------------
+// Delete all events and empty the queue (including any suspended queue elements)
+//
+// Notes:
+// This is like calling { while(!IsEmpty()) delete Dequeue(); }
+//---------------------------------------------------------------------------------------
+void ManagedEventQueue::DeleteAll()
+{
+ RSLockHolder lockHolder(m_pLock);
+
+ while (m_pFirstEvent != NULL)
+ {
+ // verify that the last event in the queue is actually the one stored as the last event
+ _ASSERTE( m_pFirstEvent->m_pNext != NULL || m_pFirstEvent == m_pLastEvent );
+
+ ManagedEvent * pNext = m_pFirstEvent->m_pNext;
+ delete m_pFirstEvent;
+ m_pFirstEvent = pNext;
+ }
+ m_pLastEvent = NULL;
+
+ _ASSERTE(IsEmpty());
+};
+
+//---------------------------------------------------------------------------------------
+// Worker to implement ICorDebugProcess::HasQueuedCallbacks for shim
+//---------------------------------------------------------------------------------------
+BOOL ManagedEventQueue::HasQueuedCallbacks(ICorDebugThread * pThread)
+{
+ // This is from the public paths of ICorDebugProcess::HasQueuedCallbacks.
+ // In V2, this would fail in cases, notably including if the process is not synchronized.
+ // In arrowhead, it always succeeds.
+
+ // No thread - look process wide.
+ if (pThread == NULL)
+ {
+ return !IsEmpty();
+ }
+
+ // If we have a thread, look for events with thread affinity.
+ DWORD dwThreadID = 0;
+ HRESULT hr = pThread->GetID(&dwThreadID);
+ (void)hr; //prevent "unused variable" error from GCC
+ SIMPLIFYING_ASSUMPTION(SUCCEEDED(hr));
+
+ // Don't take lock until after we don't call any ICorDebug APIs.
+ RSLockHolder lockHolder(m_pLock);
+
+ ManagedEvent * pCurrent = m_pFirstEvent;
+ while (pCurrent != NULL)
+ {
+ if (pCurrent->GetOSTid() == dwThreadID)
+ {
+ return true;
+ }
+ pCurrent = pCurrent->m_pNext;
+ }
+ return false;
+}
+
+
+
+
diff --git a/src/debug/di/shimlocaldatatarget.cpp b/src/debug/di/shimlocaldatatarget.cpp
new file mode 100644
index 0000000000..c4a5263810
--- /dev/null
+++ b/src/debug/di/shimlocaldatatarget.cpp
@@ -0,0 +1,471 @@
+// 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.
+//*****************************************************************************
+
+//
+// File: ShimLocalDataTarget.cpp
+//
+//*****************************************************************************
+#include "stdafx.h"
+#include "safewrap.h"
+
+#include "check.h"
+
+#include <limits.h>
+
+#include "shimpriv.h"
+#include "shimdatatarget.h"
+
+
+// The Shim's Live data-target is allowed to call OS APIs directly.
+// see code:RSDebuggingInfo#UseDataTarget.
+#undef ReadProcessMemory
+#undef WriteProcessMemory
+
+
+class ShimLocalDataTarget : public ShimDataTarget
+{
+public:
+ ShimLocalDataTarget(DWORD processId, HANDLE hProcess);
+
+ ~ShimLocalDataTarget();
+
+ virtual void Dispose();
+
+ //
+ // ICorDebugMutableDataTarget.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE GetPlatform(
+ CorDebugPlatform *pPlatform);
+
+ virtual HRESULT STDMETHODCALLTYPE ReadVirtual(
+ CORDB_ADDRESS address,
+ BYTE * pBuffer,
+ ULONG32 request,
+ ULONG32 *pcbRead);
+
+ virtual HRESULT STDMETHODCALLTYPE WriteVirtual(
+ CORDB_ADDRESS address,
+ const BYTE * pBuffer,
+ ULONG32 request);
+
+ virtual HRESULT STDMETHODCALLTYPE GetThreadContext(
+ DWORD dwThreadID,
+ ULONG32 contextFlags,
+ ULONG32 contextSize,
+ BYTE * context);
+
+ virtual HRESULT STDMETHODCALLTYPE SetThreadContext(
+ DWORD dwThreadID,
+ ULONG32 contextSize,
+ const BYTE * context);
+
+ virtual HRESULT STDMETHODCALLTYPE ContinueStatusChanged(
+ DWORD dwThreadId,
+ CORDB_CONTINUE_STATUS dwContinueStatus);
+
+ virtual HRESULT STDMETHODCALLTYPE VirtualUnwind(
+ DWORD threadId, ULONG32 contextSize, PBYTE context);
+
+private:
+ // Handle to the process. We own this.
+ HANDLE m_hProcess;
+};
+
+
+// Determines whether the target and host are running on compatible platforms.
+// Arguments:
+// input: hTargetProcess - handle for the target process
+// Return Value: TRUE iff both target and host are both Wow64 or neither is.
+// Note: throws
+BOOL CompatibleHostAndTargetPlatforms(HANDLE hTargetProcess)
+{
+#if defined(FEATURE_PAL)
+ return TRUE;
+#else
+ // get the platform for the host process
+ BOOL fHostProcessIsWow64 = FALSE;
+ BOOL fSuccess = FALSE;
+ HANDLE hHostProcess = GetCurrentProcess();
+
+ fSuccess = IsWow64Process(hHostProcess, &fHostProcessIsWow64);
+ CloseHandle(hHostProcess);
+ hHostProcess = NULL;
+
+ if (!fSuccess)
+ {
+ ThrowHR(HRESULT_FROM_GetLastError());
+ }
+
+ // get the platform for the target process
+ if (hTargetProcess == NULL)
+ {
+ ThrowHR(HRESULT_FROM_GetLastError());
+ }
+
+ BOOL fTargetProcessIsWow64 = FALSE;
+ fSuccess = IsWow64Process(hTargetProcess, &fTargetProcessIsWow64);
+
+ if (!fSuccess)
+ {
+ ThrowHR(HRESULT_FROM_GetLastError());
+ }
+
+ // We don't want to expose the IPC block if one process is x86 and
+ // the other is ia64 or amd64
+ if (fTargetProcessIsWow64 != fHostProcessIsWow64)
+ {
+ return FALSE;
+ }
+ else
+ {
+ return TRUE;
+ }
+#endif
+} // CompatibleHostAndTargetPlatforms
+
+// Helper macro to check for failure conditions at the start of data-target methods.
+#define ReturnFailureIfStateNotOk() \
+ if (m_hr != S_OK) \
+ { \
+ return m_hr; \
+ }
+
+//---------------------------------------------------------------------------------------
+//
+// ctor for ShimLocalDataTarget.
+//
+// Arguments:
+// processId - pid of live process.
+// hProcess - handle to kernel process object.
+//
+// Assumptions:
+// Shim takes ownership of handle hProcess.
+//
+
+ShimLocalDataTarget::ShimLocalDataTarget(DWORD processId, HANDLE hProcess)
+{
+ m_ref = 0;
+
+ m_processId = processId;
+ m_hProcess = hProcess;
+
+ m_hr = S_OK;
+
+ m_fpContinueStatusChanged = NULL;
+ m_pContinueStatusChangedUserData = NULL;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// dctor for ShimLocalDataTarget.
+//
+ShimLocalDataTarget::~ShimLocalDataTarget()
+{
+ Dispose();
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Dispose all resources and neuter the object.
+//
+//
+//
+// Notes:
+// Release all resources (such as the handle to the process we got in the ctor).
+// May be called multiple times.
+// All other non-trivial APIs (eg, not IUnknown) will fail after this.
+//
+
+void ShimLocalDataTarget::Dispose()
+{
+ if (m_hProcess != NULL)
+ {
+ CloseHandle(m_hProcess);
+ m_hProcess = NULL;
+ }
+ m_hr = CORDBG_E_OBJECT_NEUTERED;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Construction method for data-target
+//
+// Arguments:
+// processId - (input) live OS process ID to build a data-target for.
+// ppDataTarget - (output) new data-target instance. This gets addreffed.
+//
+// Return Value:
+// S_OK on success.
+//
+// Assumptions:
+// pid must be for local, same architecture, process.
+// Caller must have security permissions for OpenProcess()
+// Caller must release *ppDataTarget.
+//
+
+HRESULT BuildPlatformSpecificDataTarget(MachineInfo machineInfo,
+ DWORD processId,
+ ShimDataTarget ** ppDataTarget)
+{
+ HRESULT hr = S_OK;
+ HANDLE hProcess = NULL;
+ ShimLocalDataTarget * pLocalDataTarget = NULL;
+
+ *ppDataTarget = NULL;
+
+ hProcess = OpenProcess(
+ PROCESS_DUP_HANDLE |
+ PROCESS_QUERY_INFORMATION |
+ PROCESS_TERMINATE |
+ PROCESS_VM_OPERATION |
+ PROCESS_VM_READ |
+ PROCESS_VM_WRITE |
+ SYNCHRONIZE,
+ FALSE,
+ processId);
+
+ if (hProcess == NULL)
+ {
+ hr = HRESULT_FROM_GetLastError();
+ goto Label_Exit;
+ }
+
+ EX_TRY
+ {
+ if (!CompatibleHostAndTargetPlatforms(hProcess))
+ {
+ hr = CORDBG_E_UNCOMPATIBLE_PLATFORMS;
+ goto Label_Exit;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ if (FAILED(hr))
+ {
+ goto Label_Exit;
+ }
+ pLocalDataTarget = new (nothrow) ShimLocalDataTarget(processId, hProcess);
+ if (pLocalDataTarget == NULL)
+ {
+ hr = E_OUTOFMEMORY;
+ goto Label_Exit;
+ }
+
+ // ShimLocalDataTarget now has ownership of Handle.
+ hProcess = NULL;
+
+ _ASSERTE(SUCCEEDED(hr));
+ *ppDataTarget = pLocalDataTarget;
+ pLocalDataTarget->AddRef(); // must addref out-parameters
+
+Label_Exit:
+ if (FAILED(hr))
+ {
+ if (hProcess != NULL)
+ {
+ CloseHandle(hProcess);
+ }
+ delete pLocalDataTarget;
+ }
+
+ return hr;
+}
+
+// impl of interface method ICorDebugDataTarget::GetPlatform
+HRESULT STDMETHODCALLTYPE
+ShimLocalDataTarget::GetPlatform(
+ CorDebugPlatform *pPlatform)
+{
+#ifdef FEATURE_PAL
+#error ShimLocalDataTarget is not implemented on PAL systems yet
+#endif
+ // Assume that we're running on Windows for now.
+#if defined(DBG_TARGET_X86)
+ *pPlatform = CORDB_PLATFORM_WINDOWS_X86;
+#elif defined(DBG_TARGET_AMD64)
+ *pPlatform = CORDB_PLATFORM_WINDOWS_AMD64;
+#elif defined(DBG_TARGET_ARM)
+ *pPlatform = CORDB_PLATFORM_WINDOWS_ARM;
+#elif defined(DBG_TARGET_ARM64)
+ *pPlatform = CORDB_PLATFORM_WINDOWS_ARM64;
+#else
+#error Unknown Processor.
+#endif
+ return S_OK;
+}
+
+// impl of interface method ICorDebugDataTarget::ReadVirtual
+HRESULT STDMETHODCALLTYPE
+ShimLocalDataTarget::ReadVirtual(
+ CORDB_ADDRESS address,
+ PBYTE pBuffer,
+ ULONG32 cbRequestSize,
+ ULONG32 *pcbRead)
+{
+ ReturnFailureIfStateNotOk();
+
+
+ // ReadProcessMemory will fail if any part of the
+ // region to read does not have read access. This
+ // routine attempts to read the largest valid prefix
+ // so it has to break up reads on page boundaries.
+
+ HRESULT hrStatus = S_OK;
+ ULONG32 totalDone = 0;
+ SIZE_T read;
+ ULONG32 readSize;
+
+ while (cbRequestSize > 0)
+ {
+ // Calculate bytes to read and don't let read cross
+ // a page boundary.
+ readSize = OS_PAGE_SIZE - (ULONG32)(address & (OS_PAGE_SIZE - 1));
+ readSize = min(cbRequestSize, readSize);
+
+ if (!ReadProcessMemory(m_hProcess, (PVOID)(ULONG_PTR)address,
+ pBuffer, readSize, &read))
+ {
+ if (totalDone == 0)
+ {
+ // If we haven't read anything indicate failure.
+ hrStatus = HRESULT_FROM_GetLastError();
+ }
+ break;
+ }
+
+ totalDone += (ULONG32)read;
+ address += read;
+ pBuffer += read;
+ cbRequestSize -= (ULONG32)read;
+ }
+
+ *pcbRead = totalDone;
+ return hrStatus;
+}
+
+// impl of interface method ICorDebugMutableDataTarget::WriteVirtual
+HRESULT STDMETHODCALLTYPE
+ShimLocalDataTarget::WriteVirtual(
+ CORDB_ADDRESS pAddress,
+ const BYTE * pBuffer,
+ ULONG32 cbRequestSize)
+{
+ ReturnFailureIfStateNotOk();
+
+ SIZE_T cbWritten;
+ BOOL fWriteOk = WriteProcessMemory(m_hProcess, CORDB_ADDRESS_TO_PTR(pAddress), pBuffer, cbRequestSize, &cbWritten);
+ if (fWriteOk)
+ {
+ _ASSERTE(cbWritten == cbRequestSize); // MSDN docs say this must always be true
+ return S_OK;
+ }
+ else
+ {
+ return HRESULT_FROM_GetLastError();
+ }
+}
+
+HRESULT STDMETHODCALLTYPE
+ShimLocalDataTarget::GetThreadContext(
+ DWORD dwThreadID,
+ ULONG32 contextFlags,
+ ULONG32 contextSize,
+ BYTE * pContext)
+{
+ ReturnFailureIfStateNotOk();
+ // @dbgtodo - Ideally we should cache the thread handles so that we don't need to
+ // open and close the thread handles every time.
+
+ HRESULT hr = E_FAIL;
+
+ if (!CheckContextSizeForBuffer(contextSize, pContext))
+ {
+ return E_INVALIDARG;
+ }
+
+ HandleHolder hThread = OpenThread(
+ THREAD_GET_CONTEXT | THREAD_SET_CONTEXT | THREAD_QUERY_INFORMATION ,
+ FALSE, // thread handle is not inheritable.
+ dwThreadID);
+
+ if (hThread != NULL)
+ {
+ DT_CONTEXT * pCtx = reinterpret_cast<DT_CONTEXT *>(pContext);
+ pCtx->ContextFlags = contextFlags;
+
+ if (DbiGetThreadContext(hThread, pCtx))
+ {
+ hr = S_OK;
+ }
+ }
+
+ // hThread destructed automatically
+ return hr;
+}
+
+// impl of interface method ICorDebugMutableDataTarget::SetThreadContext
+HRESULT STDMETHODCALLTYPE
+ShimLocalDataTarget::SetThreadContext(
+ DWORD dwThreadID,
+ ULONG32 contextSize,
+ const BYTE * pContext)
+{
+ ReturnFailureIfStateNotOk();
+ HRESULT hr = E_FAIL;
+
+ if (!CheckContextSizeForBuffer(contextSize, pContext))
+ {
+ return E_INVALIDARG;
+ }
+
+
+ HandleHolder hThread = OpenThread(
+ THREAD_GET_CONTEXT | THREAD_SET_CONTEXT | THREAD_QUERY_INFORMATION,
+ FALSE, // thread handle is not inheritable.
+ dwThreadID);
+
+ if (hThread != NULL)
+ {
+ if (DbiSetThreadContext(hThread, reinterpret_cast<const DT_CONTEXT *>(pContext)))
+ {
+ hr = S_OK;
+ }
+ }
+
+ // hThread destructed automatically
+ return hr;
+}
+
+// Public implementation of ICorDebugMutableDataTarget::ContinueStatusChanged
+HRESULT STDMETHODCALLTYPE
+ShimLocalDataTarget::ContinueStatusChanged(
+ DWORD dwThreadId,
+ CORDB_CONTINUE_STATUS dwContinueStatus)
+{
+ ReturnFailureIfStateNotOk();
+ if (m_fpContinueStatusChanged != NULL)
+ {
+ return m_fpContinueStatusChanged(m_pContinueStatusChangedUserData, dwThreadId, dwContinueStatus);
+ }
+ return E_NOTIMPL;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Unwind the stack to the next frame.
+//
+// Return Value:
+// context filled in with the next frame
+//
+HRESULT STDMETHODCALLTYPE
+ShimLocalDataTarget::VirtualUnwind(DWORD threadId, ULONG32 contextSize, PBYTE context)
+{
+#ifndef FEATURE_PAL
+ _ASSERTE(!"ShimLocalDataTarget::VirtualUnwind NOT IMPLEMENTED");
+#endif
+ return E_NOTIMPL;
+}
+
diff --git a/src/debug/di/shimpriv.h b/src/debug/di/shimpriv.h
new file mode 100644
index 0000000000..9c83301009
--- /dev/null
+++ b/src/debug/di/shimpriv.h
@@ -0,0 +1,1056 @@
+// 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.
+//*****************************************************************************
+// shimprivate.h
+//
+
+//
+// private header for RS shim which bridges from V2 to V3.
+//*****************************************************************************
+
+#ifndef SHIMPRIV_H
+#define SHIMPRIV_H
+
+#include "helpers.h"
+
+#include "shimdatatarget.h"
+
+#include <shash.h>
+
+// Forward declarations
+class CordbWin32EventThread;
+class Cordb;
+
+class ShimStackWalk;
+class ShimChain;
+class ShimChainEnum;
+class ShimFrameEnum;
+
+// This struct specifies that it's a hash table of ShimStackWalk * using ICorDebugThread as the key.
+struct ShimStackWalkHashTableTraits : public PtrSHashTraits<ShimStackWalk, ICorDebugThread *> {};
+typedef SHash<ShimStackWalkHashTableTraits> ShimStackWalkHashTable;
+
+
+//---------------------------------------------------------------------------------------
+//
+// Simple struct for storing a void *. This is to be used with a SHash hash table.
+//
+
+struct DuplicateCreationEventEntry
+{
+public:
+ DuplicateCreationEventEntry(void * pKey) : m_pKey(pKey) {};
+
+ // These functions must be defined for DuplicateCreationEventsHashTableTraits.
+ void * GetKey() {return m_pKey;};
+ static UINT32 Hash(void * pKey) {return (UINT32)(size_t)pKey;};
+
+private:
+ void * m_pKey;
+};
+
+// This struct specifies that it's a hash table of DuplicateCreationEventEntry * using a void * as the key.
+// The void * is expected to be an ICDProcess/ICDAppDomain/ICDThread/ICDAssembly/ICDThread interface pointer.
+struct DuplicateCreationEventsHashTableTraits : public PtrSHashTraits<DuplicateCreationEventEntry, void *> {};
+typedef SHash<DuplicateCreationEventsHashTableTraits> DuplicateCreationEventsHashTable;
+
+//
+// Callback that shim provides, which then queues up the events.
+//
+class ShimProxyCallback :
+ public ICorDebugManagedCallback,
+ public ICorDebugManagedCallback2,
+ public ICorDebugManagedCallback3
+{
+ ShimProcess * m_pShim; // weak reference
+ LONG m_cRef;
+
+public:
+ ShimProxyCallback(ShimProcess * pShim);
+ virtual ~ShimProxyCallback() {}
+
+ // Implement IUnknown
+ ULONG STDMETHODCALLTYPE AddRef();
+ ULONG STDMETHODCALLTYPE Release();
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //
+ // Implementation of ICorDebugManagedCallback
+ //
+
+ COM_METHOD Breakpoint( ICorDebugAppDomain *pAppDomain,
+ ICorDebugThread *pThread,
+ ICorDebugBreakpoint *pBreakpoint);
+
+ COM_METHOD StepComplete( ICorDebugAppDomain *pAppDomain,
+ ICorDebugThread *pThread,
+ ICorDebugStepper *pStepper,
+ CorDebugStepReason reason);
+
+ COM_METHOD Break( ICorDebugAppDomain *pAppDomain,
+ ICorDebugThread *thread);
+
+ COM_METHOD Exception( ICorDebugAppDomain *pAppDomain,
+ ICorDebugThread *pThread,
+ BOOL unhandled);
+
+ COM_METHOD EvalComplete( ICorDebugAppDomain *pAppDomain,
+ ICorDebugThread *pThread,
+ ICorDebugEval *pEval);
+
+ COM_METHOD EvalException( ICorDebugAppDomain *pAppDomain,
+ ICorDebugThread *pThread,
+ ICorDebugEval *pEval);
+
+ COM_METHOD CreateProcess( ICorDebugProcess *pProcess);
+ void QueueCreateProcess( ICorDebugProcess *pProcess);
+
+ COM_METHOD ExitProcess( ICorDebugProcess *pProcess);
+
+ COM_METHOD CreateThread( ICorDebugAppDomain *pAppDomain, ICorDebugThread *thread);
+
+
+ COM_METHOD ExitThread( ICorDebugAppDomain *pAppDomain, ICorDebugThread *thread);
+
+ COM_METHOD LoadModule( ICorDebugAppDomain *pAppDomain, ICorDebugModule *pModule);
+
+ void FakeLoadModule(ICorDebugAppDomain *pAppDomain, ICorDebugModule *pModule);
+
+ COM_METHOD UnloadModule( ICorDebugAppDomain *pAppDomain, ICorDebugModule *pModule);
+
+ COM_METHOD LoadClass( ICorDebugAppDomain *pAppDomain, ICorDebugClass *c);
+
+ COM_METHOD UnloadClass( ICorDebugAppDomain *pAppDomain, ICorDebugClass *c);
+
+ COM_METHOD DebuggerError( ICorDebugProcess *pProcess, HRESULT errorHR, DWORD errorCode);
+
+ COM_METHOD LogMessage( ICorDebugAppDomain *pAppDomain,
+ ICorDebugThread *pThread,
+ LONG lLevel,
+ __in LPWSTR pLogSwitchName,
+ __in LPWSTR pMessage);
+
+ COM_METHOD LogSwitch( ICorDebugAppDomain *pAppDomain,
+ ICorDebugThread *pThread,
+ LONG lLevel,
+ ULONG ulReason,
+ __in LPWSTR pLogSwitchName,
+ __in LPWSTR pParentName);
+
+ COM_METHOD CreateAppDomain(ICorDebugProcess *pProcess,
+ ICorDebugAppDomain *pAppDomain);
+
+ COM_METHOD ExitAppDomain(ICorDebugProcess *pProcess,
+ ICorDebugAppDomain *pAppDomain);
+
+ COM_METHOD LoadAssembly(ICorDebugAppDomain *pAppDomain,
+ ICorDebugAssembly *pAssembly);
+
+ COM_METHOD UnloadAssembly(ICorDebugAppDomain *pAppDomain,
+ ICorDebugAssembly *pAssembly);
+
+ COM_METHOD ControlCTrap(ICorDebugProcess *pProcess);
+
+ COM_METHOD NameChange(ICorDebugAppDomain *pAppDomain, ICorDebugThread *pThread);
+
+
+ COM_METHOD UpdateModuleSymbols( ICorDebugAppDomain *pAppDomain,
+ ICorDebugModule *pModule,
+ IStream *pSymbolStream);
+
+ COM_METHOD EditAndContinueRemap( ICorDebugAppDomain *pAppDomain,
+ ICorDebugThread *pThread,
+ ICorDebugFunction *pFunction,
+ BOOL fAccurate);
+
+ COM_METHOD BreakpointSetError( ICorDebugAppDomain *pAppDomain,
+ ICorDebugThread *pThread,
+ ICorDebugBreakpoint *pBreakpoint,
+ DWORD dwError);
+
+ ///
+ /// Implementation of ICorDebugManagedCallback2
+ ///
+ COM_METHOD FunctionRemapOpportunity( ICorDebugAppDomain *pAppDomain,
+ ICorDebugThread *pThread,
+ ICorDebugFunction *pOldFunction,
+ ICorDebugFunction *pNewFunction,
+ ULONG32 oldILOffset);
+
+ COM_METHOD CreateConnection(ICorDebugProcess *pProcess, CONNID dwConnectionId, __in LPWSTR pConnName);
+
+ COM_METHOD ChangeConnection(ICorDebugProcess *pProcess, CONNID dwConnectionId );
+
+
+ COM_METHOD DestroyConnection(ICorDebugProcess *pProcess, CONNID dwConnectionId);
+
+ COM_METHOD Exception(ICorDebugAppDomain *pAppDomain,
+ ICorDebugThread *pThread,
+ ICorDebugFrame *pFrame,
+ ULONG32 nOffset,
+ CorDebugExceptionCallbackType dwEventType,
+ DWORD dwFlags );
+
+ COM_METHOD ExceptionUnwind(ICorDebugAppDomain *pAppDomain,
+ ICorDebugThread *pThread,
+ CorDebugExceptionUnwindCallbackType dwEventType,
+ DWORD dwFlags);
+
+ COM_METHOD FunctionRemapComplete( ICorDebugAppDomain *pAppDomain,
+ ICorDebugThread *pThread,
+ ICorDebugFunction *pFunction);
+
+ COM_METHOD MDANotification(ICorDebugController * pController, ICorDebugThread *pThread, ICorDebugMDA * pMDA);
+
+ ///
+ /// Implementation of ICorDebugManagedCallback3
+ ///
+
+ // Implementation of ICorDebugManagedCallback3::CustomNotification
+ COM_METHOD CustomNotification(ICorDebugThread * pThread, ICorDebugAppDomain * pAppDomain);
+
+};
+
+
+//
+// Base class for event queue. These are nested into a singly linked list.
+// Shim maintains event queue
+//
+class ManagedEvent
+{
+public:
+ // Need virtual dtor since this is a base class.
+ virtual ~ManagedEvent();
+
+#ifdef _DEBUG
+ // For debugging, get a pointer value that can identify the type of this event.
+ void * GetDebugCookie();
+#endif
+
+ // We'll have a lot of derived classes of ManagedEvent, and so encapsulating the arguments
+ // for the Dispatch() function lets us juggle them around easily without hitting every signature.
+ class DispatchArgs
+ {
+ public:
+ DispatchArgs(ICorDebugManagedCallback * pCallback1, ICorDebugManagedCallback2 * pCallback2, ICorDebugManagedCallback3 * pCallback3);
+
+ ICorDebugManagedCallback * GetCallback1();
+ ICorDebugManagedCallback2 * GetCallback2();
+ ICorDebugManagedCallback3 * GetCallback3();
+
+
+ protected:
+ ICorDebugManagedCallback * m_pCallback1;
+ ICorDebugManagedCallback2 * m_pCallback2;
+ ICorDebugManagedCallback3 * m_pCallback3;
+ };
+
+ // Returns: value of callback from end-user
+ virtual HRESULT Dispatch(DispatchArgs args) = 0;
+
+
+ // Returns 0 if none.
+ DWORD GetOSTid();
+
+protected:
+ // Ctor for events with thread-affinity
+ ManagedEvent(ICorDebugThread * pThread);
+
+ // Ctor for events without thread affinity.
+ ManagedEvent();
+
+ friend class ManagedEventQueue;
+ ManagedEvent * m_pNext;
+
+ DWORD m_dwThreadId;
+};
+
+//
+// Queue of managed events.
+// Shim can use this to collect managed debug events, queue them, and then drain the event
+// queue when a sync-complete occurs.
+// Event queue gets initialized with a lock and will lock internally.
+class ManagedEventQueue
+{
+public:
+ ManagedEventQueue();
+
+
+ void Init(RSLock * pLock);
+
+ // Remove event from the top. Caller then takes ownership of Event and will call Delete on it.
+ // Caller checks IsEmpty() first.
+ ManagedEvent * Dequeue();
+
+ // Queue owns the event and will delete it (unless it's dequeued first).
+ void QueueEvent(ManagedEvent * pEvent);
+
+ // Test if event queue is empty
+ bool IsEmpty();
+
+ // Empty event queue and delete all objects
+ void DeleteAll();
+
+ // Nothrows
+ BOOL HasQueuedCallbacks(ICorDebugThread * pThread);
+
+ // Save the current queue and start with a new empty queue
+ void SuspendQueue();
+
+ // Restore the saved queue onto the end of the current queue
+ void RestoreSuspendedQueue();
+
+protected:
+ // The lock to be used for synchronizing all access to the queue
+ RSLock * m_pLock;
+
+ // If empty, First + Last are both NULL.
+ // Else first points to the head of the queue; and Last points to the end of the queue.
+ ManagedEvent * m_pFirstEvent;
+ ManagedEvent * m_pLastEvent;
+
+};
+
+
+//---------------------------------------------------------------------------------------
+//
+// Shim's layer on top of a process.
+//
+// Notes:
+// This contains a V3 ICorDebugProcess, and provides V2 ICDProcess functionality.
+//
+class ShimProcess
+{
+ // Delete via Ref count semantics.
+ ~ShimProcess();
+public:
+ // Initialize ref count is 0.
+ ShimProcess();
+
+ // Lifetime semantics handled by reference counting.
+ void AddRef();
+ void Release();
+
+ // Release all resources. Can be called multiple times.
+ void Dispose();
+
+ // Initialization phases.
+ // 1. allocate new ShimProcess(). This lets us spin up a Win32 EventThread, which can then
+ // be used to
+ // 2. Call ShimProcess::CreateProcess/DebugActiveProcess. This will call CreateAndStartWin32ET to
+ // craete the w32et.
+ // 3. Create OS-debugging pipeline. This establishes the physical OS process and gets us a pid/handle
+ // 4. pShim->InitializeDataTarget - this creates a reader/writer abstraction around the OS process.
+ // 5. pShim->SetProcess() - this connects the Shim to the ICDProcess object.
+ HRESULT InitializeDataTarget(DWORD processId);
+ void SetProcess(ICorDebugProcess * pProcess);
+
+ //-----------------------------------------------------------
+ // Creation
+ //-----------------------------------------------------------
+
+ static HRESULT CreateProcess(
+ Cordb * pCordb,
+ ICorDebugRemoteTarget * pRemoteTarget,
+ LPCWSTR programName,
+ __in_z LPWSTR programArgs,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ PVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation,
+ CorDebugCreateProcessFlags corDebugFlags
+ );
+
+ static HRESULT DebugActiveProcess(
+ Cordb * pCordb,
+ ICorDebugRemoteTarget * pRemoteTarget,
+ DWORD pid,
+ BOOL win32Attach
+
+ );
+
+ // Locates the DAC module adjacent to DBI
+ static HMODULE GetDacModule();
+
+ //
+ // Functions used by CordbProcess
+ //
+
+ // Determine if the calling thread is the win32 event thread.
+ bool IsWin32EventThread();
+
+
+ // Expose the W32ET thread to the CordbProcess so that it can emulate V2 behavior
+ CordbWin32EventThread * GetWin32EventThread();
+
+ // Accessor wrapper to mark whether we're interop-debugging.
+ void SetIsInteropDebugging(bool fIsInteropDebugging);
+
+ // Handle a debug event.
+ HRESULT HandleWin32DebugEvent(const DEBUG_EVENT * pEvent);
+
+ ManagedEventQueue * GetManagedEventQueue();
+
+ ManagedEvent * DequeueManagedEvent();
+
+ ShimProxyCallback * GetShimCallback();
+
+ // Begin Queing the fake attach events.
+ void BeginQueueFakeAttachEvents();
+
+ // Queue fake attach events if needed
+ void QueueFakeAttachEventsIfNeeded(bool fRealCreateProcessEvent);
+
+ // Actually do the work to queue the fake attach events.
+ void QueueFakeAttachEvents();
+
+ // Helper to queue fake assembly and mdule events
+ void QueueFakeAssemblyAndModuleEvent(ICorDebugAssembly * pAssembly);
+
+ // Queue fake thread-create events on attach. Order via native threads.
+ HRESULT QueueFakeThreadAttachEventsNativeOrder();
+
+ // Queue fake thread-create events on attach. No ordering.
+ HRESULT QueueFakeThreadAttachEventsNoOrder();
+
+ bool IsThreadSuspendedOrHijacked(ICorDebugThread * pThread);
+
+ // Expose m_attached to CordbProcess.
+ bool GetAttached();
+
+ // We need to know whether we are in the CreateProcess callback to be able to
+ // return the v2.0 hresults from code:CordbProcess::SetDesiredNGENCompilerFlags
+ // when we are using the shim.
+ //
+ // Expose m_fInCreateProcess
+ bool GetInCreateProcess();
+ void SetInCreateProcess(bool value);
+
+ // We need to know whether we are in the FakeLoadModule callback to be able to
+ // return the v2.0 hresults from code:CordbModule::SetJITCompilerFlags when
+ // we are using the shim.
+ //
+ // Expose m_fInLoadModule
+ bool GetInLoadModule();
+ void SetInLoadModule(bool value);
+
+ // When we get a continue, we need to clear the flags indicating we're still in a callback
+ void NotifyOnContinue ();
+
+ // The RS calls this function when the stack is about to be changed in any way, e.g. continue, SetIP,
+ // etc.
+ void NotifyOnStackInvalidate();
+
+ // Helpers to filter HRs to emulate V2 error codes.
+ HRESULT FilterSetNgenHresult(HRESULT hr);
+ HRESULT FilterSetJitFlagsHresult(HRESULT hr);
+
+ //.............................................................
+
+
+ // Lookup or create a ShimStackWalk for the specified thread. ShimStackWalk and ICorDebugThread has
+ // a 1:1 relationship.
+ ShimStackWalk * LookupOrCreateShimStackWalk(ICorDebugThread * pThread);
+
+ // Clear all ShimStackWalks and flush all the caches.
+ void ClearAllShimStackWalk();
+
+ // Get the corresponding ICDProcess object.
+ ICorDebugProcess * GetProcess();
+
+ // Get the data target to access the debuggee.
+ ICorDebugMutableDataTarget * GetDataTarget();
+
+ // Get the native event pipeline
+ INativeEventPipeline * GetNativePipeline();
+
+ // Are we interop-debugging?
+ bool IsInteropDebugging();
+
+
+ // Finish all the necessary initialization work and queue up any necessary fake attach events before
+ // dispatching an event.
+ void PreDispatchEvent(bool fRealCreateProcessEvent = false);
+
+ // Look for a CLR in the process and if found, return it's instance ID
+ HRESULT FindLoadedCLR(CORDB_ADDRESS * pClrInstanceId);
+
+ // Retrieve the IP address and the port number of the debugger proxy.
+ MachineInfo GetMachineInfo();
+
+ // Add an entry in the duplicate creation event hash table for the specified key.
+ void AddDuplicateCreationEvent(void * pKey);
+
+ // Check if a duplicate creation event entry exists for the specified key. If so, remove it.
+ bool RemoveDuplicateCreationEventIfPresent(void * pKey);
+
+ void SetMarkAttachPendingEvent();
+
+ void SetTerminatingEvent();
+
+ RSLock * GetShimLock();
+
+protected:
+
+ // Reference count.
+ LONG m_ref;
+
+ //
+ // Helper functions
+ //
+ HRESULT CreateAndStartWin32ET(Cordb * pCordb);
+
+ //
+ // Synchronization events to ensure that AttachPending bit is marked before DebugActiveProcess
+ // returns or debugger is detaching
+ //
+ HANDLE m_markAttachPendingEvent;
+ HANDLE m_terminatingEvent;
+
+ // Finds the base address of [core]clr.dll
+ CORDB_ADDRESS GetCLRInstanceBaseAddress();
+
+ //
+ // Event Queues
+ //
+
+ // Shim maintains event queue to emulate V2 semantics.
+ // In V2, IcorDebug internally queued debug events and dispatched them
+ // once the debuggee was synchronized. In V3, ICorDebug dispatches events immediately.
+ // The event queue is moved into the shim to build V2 semantics of V3 behavior.
+ ManagedEventQueue m_eventQueue;
+
+ // Lock to protect Shim data structures. This is currently a small lock that
+ // protects leaf-level structures, but it may grow to protect larger things.
+ RSLock m_ShimLock;
+
+ // Serializes ShimProcess:Dispose() with other ShimProcess functions. For now, this
+ // cannot be the same as m_ShimLock. See LL_SHIM_PROCESS_DISPOSE_LOCK for more
+ // information
+ RSLock m_ShimProcessDisposeLock;
+
+ // Sticky bit to do lazy-initialization on the first managed event.
+ bool m_fFirstManagedEvent;
+
+ RSExtSmartPtr<ShimProxyCallback> m_pShimCallback;
+
+
+ // This is for emulating V2 Attach. Initialized to false, and then set to true if we ened to send fake attach events.
+ // Reset to false once the events are sent. See code:ShimProcess::QueueFakeAttachEventsIfNeeded
+ bool m_fNeedFakeAttachEvents;
+
+ // True if the process was created from an attach (DebugActiveProcess); False if it was launched (CreateProcess)
+ // This is used to send an Attach IPC event, and also used to provide more specific error codes.
+ bool m_attached;
+
+ // True iff we are in the shim's CreateProcess callback. This is used to determine which hresult to
+ // return from code:CordbProcess::SetDesiredNGENCompilerFlags so we correctly emulate the behavior of v2.0.
+ // This is set at the beginning of the callback and cleared in code:CordbProcess::ContinueInternal.
+ bool m_fInCreateProcess;
+
+ // True iff we are in the shim's FakeLoadModule callback. This is used to determine which hresult to
+ // return from code:CordbModule::SetJITCompilerFlags so we correctly emulate the behavior of v2.0.
+ // This is set at the beginning of the callback and cleared in code:CordbProcess::ContinueInternal.
+ bool m_fInLoadModule;
+ //
+ // Data
+ //
+
+ // Pointer to CordbProcess.
+ // @dbgtodo shim: We'd like this to eventually go through public interfaces (ICorDebugProcess)
+ IProcessShimHooks * m_pProcess; // Reference is kept by m_pIProcess;
+ RSExtSmartPtr<ICorDebugProcess> m_pIProcess;
+
+ // Win32EvenThread, which is the thread that uses the native debug API.
+ CordbWin32EventThread * m_pWin32EventThread;
+
+ // Actual data-target. Since we're shimming V2 scenarios, and V3 is always
+ // live-debugging, this is always a live data-target.
+ RSExtSmartPtr<ShimDataTarget> m_pLiveDataTarget;
+
+
+ // If true, the shim is emulating interop-debugging
+ // If false, the shim is emulating managed-only debugging.
+ // Both managed and native debugging have the same underlying pipeline (built
+ // on native-debug events). So the only difference is how they handle those events.
+ bool m_fIsInteropDebugging;
+
+ // true iff Dispose() was called. Consult this and do your work under m_ShimProcessDisposeLock
+ // to serialize yourself against a call to Dispose(). This protects your work
+ // from the user doing a Debugger Detach in the middle.
+ bool m_fIsDisposed;
+
+ //.............................................................................
+ //
+ // Members used for handling native events when managed-only debugging.
+ //
+ //.............................................................................
+
+ // Default handler for native events when managed-only debugging.
+ void DefaultEventHandler(const DEBUG_EVENT * pEvent, DWORD * pdwContinueStatus);
+
+ // Given a debug event, track the file handles.
+ void TrackFileHandleForDebugEvent(const DEBUG_EVENT * pEvent);
+
+ // Have we gotten the loader breakpoint yet?
+ // A Debugger needs to do special work to skip the loader breakpoint,
+ // and that's also when it should dispatch the faked managed attach events.
+ bool m_loaderBPReceived;
+
+ // Raw callback for ContinueStatusChanged from Data-target.
+ static HRESULT ContinueStatusChanged(void * pUserData, DWORD dwThreadId, CORDB_CONTINUE_STATUS dwContinueStatus);
+
+ // Real worker to update ContinueStatusChangedData
+ HRESULT ContinueStatusChangedWorker(DWORD dwThreadId, CORDB_CONTINUE_STATUS dwContinueStatus);
+
+ struct ContinueStatusChangedData
+ {
+ void Clear();
+ bool IsSet();
+ // Tid of Thread changed
+ DWORD m_dwThreadId;
+
+ // New continue status.
+ CORDB_CONTINUE_STATUS m_status;
+ } m_ContinueStatusChangedData;
+
+ // the hash table of ShimStackWalks
+ ShimStackWalkHashTable * m_pShimStackWalkHashTable;
+
+ // the hash table of duplicate creation events
+ DuplicateCreationEventsHashTable * m_pDupeEventsHashTable;
+
+ MachineInfo m_machineInfo;
+};
+
+
+//---------------------------------------------------------------------------------------
+//
+// This is the container class of ShimChains, ICorDebugFrames, ShimChainEnums, and ShimFrameEnums.
+// It has a 1:1 relationship with ICorDebugThreads. Upon creation, this class walks the entire stack and
+// caches all the stack frames and chains. The enumerators are created on demand.
+//
+
+class ShimStackWalk
+{
+public:
+ ShimStackWalk(ShimProcess * pProcess, ICorDebugThread * pThread);
+ ~ShimStackWalk();
+
+ // These functions do not adjust the reference count.
+ ICorDebugThread * GetThread();
+ ShimChain * GetChain(UINT32 index);
+ ICorDebugFrame * GetFrame(UINT32 index);
+
+ // Get the number of frames and chains.
+ ULONG GetChainCount();
+ ULONG GetFrameCount();
+
+ RSLock * GetShimLock();
+
+ // Add ICDChainEnum and ICDFrameEnum.
+ void AddChainEnum(ShimChainEnum * pChainEnum);
+ void AddFrameEnum(ShimFrameEnum * pFrameEnum);
+
+ // The next two functions are for ShimStackWalkHashTableTraits.
+ ICorDebugThread * GetKey();
+ static UINT32 Hash(ICorDebugThread * pThread);
+
+ // Check if the specified frame is the leaf frame according to the V2 definition.
+ BOOL IsLeafFrame(ICorDebugFrame * pFrame);
+
+ // Check if the two specified frames are the same. This function checks the SPs, frame address, etc.
+ // instead of just checking for pointer equality.
+ BOOL IsSameFrame(ICorDebugFrame * pLeft, ICorDebugFrame * pRight);
+
+ // The following functions are entry point into the ShimStackWalk. They are called by the RS.
+ void EnumerateChains(ICorDebugChainEnum ** ppChainEnum);
+
+ void GetActiveChain(ICorDebugChain ** ppChain);
+ void GetActiveFrame(ICorDebugFrame ** ppFrame);
+ void GetActiveRegisterSet(ICorDebugRegisterSet ** ppRegisterSet);
+
+ void GetChainForFrame(ICorDebugFrame * pFrame, ICorDebugChain ** ppChain);
+ void GetCallerForFrame(ICorDebugFrame * pFrame, ICorDebugFrame ** ppCallerFrame);
+ void GetCalleeForFrame(ICorDebugFrame * pFrame, ICorDebugFrame ** ppCalleeFrame);
+
+private:
+ //---------------------------------------------------------------------------------------
+ //
+ // This is a helper class used to store the information of a chain during a stackwalk. A chain is marked
+ // by the CONTEXT on the leaf boundary and a FramePointer on the root boundary. Also, notice that we
+ // are keeping two CONTEXTs. This is because some chain types may cancel a previous unmanaged chain.
+ // For example, a CHAIN_FUNC_EVAL chain cancels any CHAIN_ENTER_UNMANAGED chain immediately preceding
+ // it. In this case, the leaf boundary of the CHAIN_FUNC_EVAL chain is marked by the CONTEXT of the
+ // previous CHAIN_ENTER_MANAGED, not the previous CHAIN_ENTER_UNMANAGED.
+ //
+
+ struct ChainInfo
+ {
+ public:
+ ChainInfo() : m_rootFP(LEAF_MOST_FRAME), m_reason(CHAIN_NONE), m_fNeedEnterManagedChain(FALSE), m_fLeafNativeContextIsValid(FALSE) {}
+
+ void CancelUMChain() { m_reason = CHAIN_NONE; }
+ BOOL IsTrackingUMChain() { return (m_reason == CHAIN_ENTER_UNMANAGED); }
+
+ DT_CONTEXT m_leafNativeContext;
+ DT_CONTEXT m_leafManagedContext;
+ FramePointer m_rootFP;
+ CorDebugChainReason m_reason;
+ bool m_fNeedEnterManagedChain;
+ bool m_fLeafNativeContextIsValid;
+ };
+
+ //---------------------------------------------------------------------------------------
+ //
+ // This is a helper class used to store information during a stackwalk. Conceptually it is a simplified
+ // version of FrameInfo used on the LS in V2.
+ //
+
+ struct StackWalkInfo
+ {
+ public:
+ StackWalkInfo();
+ ~StackWalkInfo();
+
+ // Reset all the per-frame information.
+ void ResetForNextFrame();
+
+ // During the stackwalk, we need to find out whether we should process the next stack frame or the
+ // next internal frame. These functions help us determine whether we have exhausted one or both
+ // types of frames. The stackwalk is finished when both types are exhausted.
+ bool ExhaustedAllFrames();
+ bool ExhaustedAllStackFrames();
+ bool ExhaustedAllInternalFrames();
+
+ // Simple helper function to get the current internal frame.
+ ICorDebugInternalFrame2 * GetCurrentInternalFrame();
+
+ // Check whether we are processing the first frame.
+ BOOL IsLeafFrame();
+
+ // Check whether we are skipping frames because of a child frame.
+ BOOL IsSkippingFrame();
+
+ // Indicates whether we are dealing with a converted frame.
+ // See code:CordbThread::ConvertFrameForILMethodWithoutMetadata.
+ BOOL HasConvertedFrame();
+
+ // Store the child frame we are currently trying to find the parent frame for.
+ // If this is NULL, then we are not skipping frames.
+ RSExtSmartPtr<ICorDebugNativeFrame2> m_pChildFrame;
+
+ // Store the converted frame, if any.
+ RSExtSmartPtr<ICorDebugInternalFrame2> m_pConvertedInternalFrame2;
+
+ // Store the array of internal frames. This is an array of RSExtSmartPtrs, and so each element
+ // is protected, and we only need to call Clear() to release each element and free all the memory.
+ RSExtPtrArray<ICorDebugInternalFrame2> m_ppInternalFrame2;
+
+ UINT32 m_cChain; // number of chains
+ UINT32 m_cFrame; // number of frames
+ UINT32 m_firstFrameInChain; // the index of the first frame in the current chain
+ UINT32 m_cInternalFrames; // number of internal frames
+ UINT32 m_curInternalFrame; // the index of the current internal frame being processed
+
+ CorDebugInternalFrameType m_internalFrameType;
+
+ bool m_fExhaustedAllStackFrames;
+
+ // Indicate whether we are processing an internal frame or a stack frame.
+ bool m_fProcessingInternalFrame;
+
+ // Indicate whether we should skip the current chain because it's a chain derived from a leaf frame
+ // of type TYPE_INTERNAL. This is the behaviour in V2.
+ // See code:DebuggerWalkStackProc.
+ bool m_fSkipChain;
+
+ // Indicate whether the current frame is the first frame we process.
+ bool m_fLeafFrame;
+
+ // Indicate whether we are processing a converted frame.
+ bool m_fHasConvertedFrame;
+ };
+
+ // A ShimStackWalk is deleted when a process is continued, or when the stack is changed in any way
+ // (e.g. SetIP, EnC, etc.).
+ void Populate();
+ void Clear();
+
+ // Get a FramePointer to mark the root boundary of a chain.
+ FramePointer GetFramePointerForChain(DT_CONTEXT * pContext);
+ FramePointer GetFramePointerForChain(ICorDebugInternalFrame2 * pInternalFrame2);
+
+ CorDebugInternalFrameType GetInternalFrameType(ICorDebugInternalFrame2 * pFrame2);
+
+ // Append a frame to the array.
+ void AppendFrame(ICorDebugFrame * pFrame, StackWalkInfo * pStackWalkInfo);
+ void AppendFrame(ICorDebugInternalFrame2 * pInternalFrame2, StackWalkInfo * pStackWalkInfo);
+
+ // Append a chain to the array.
+ void AppendChainWorker(StackWalkInfo * pStackWalkInfo,
+ DT_CONTEXT * pLeafContext,
+ FramePointer fpRoot,
+ CorDebugChainReason chainReason,
+ BOOL fIsManagedChain);
+ void AppendChain(ChainInfo * pChainInfo, StackWalkInfo * pStackWalkInfo);
+
+ // Save information on the ChainInfo regarding the current chain.
+ void SaveChainContext(ICorDebugStackWalk * pSW, ChainInfo * pChainInfo, DT_CONTEXT * pContext);
+
+ // Check what we are process next, a internal frame or a stack frame.
+ BOOL CheckInternalFrame(ICorDebugFrame * pNextStackFrame,
+ StackWalkInfo * pStackWalkInfo,
+ ICorDebugThread3 * pThread3,
+ ICorDebugStackWalk * pSW);
+
+ // Convert an ICDInternalFrame to another ICDInternalFrame due to IL methods without metadata.
+ // See code:CordbThread::ConvertFrameForILMethodWithoutMetadata.
+ BOOL ConvertInternalFrameToDynamicMethod(StackWalkInfo * pStackWalkInfo);
+
+ // Convert an ICDNativeFrame to an ICDInternalFrame due to IL methods without metadata.
+ // See code:CordbThread::ConvertFrameForILMethodWithoutMetadata.
+ BOOL ConvertStackFrameToDynamicMethod(ICorDebugFrame * pFrame, StackWalkInfo * pStackWalkInfo);
+
+ // Process an unmanaged chain.
+ BOOL ShouldTrackUMChain(StackWalkInfo * pswInfo);
+ void TrackUMChain(ChainInfo * pChainInfo, StackWalkInfo * pStackWalkInfo);
+
+ // Check whether the internal frame is a newly exposed type in Arrowhead. If so, then the shim should
+ // not expose it.
+ BOOL IsV3FrameType(CorDebugInternalFrameType type);
+
+ // Check whether the specified frame represents a dynamic method.
+ BOOL IsILFrameWithoutMetadata(ICorDebugFrame * pFrame);
+
+ CDynArray<ShimChain *> m_stackChains; // growable ordered array of chains and frames
+ CDynArray<ICorDebugFrame *> m_stackFrames;
+
+ ShimChainEnum * m_pChainEnumList; // linked list of ShimChainEnum and ShimFrameEnum
+ ShimFrameEnum * m_pFrameEnumList;
+
+ // the thread on which we are doing a stackwalk, i.e. the "owning" thread
+ RSExtSmartPtr<ShimProcess> m_pProcess;
+ RSExtSmartPtr<ICorDebugThread> m_pThread;
+};
+
+
+//---------------------------------------------------------------------------------------
+//
+// This class implements the deprecated ICDChain interface.
+//
+
+class ShimChain : public ICorDebugChain
+{
+public:
+ ShimChain(ShimStackWalk * pSW,
+ DT_CONTEXT * pContext,
+ FramePointer fpRoot,
+ UINT32 chainIndex,
+ UINT32 frameStartIndex,
+ UINT32 frameEndIndex,
+ CorDebugChainReason chainReason,
+ BOOL fIsManaged,
+ RSLock * pShimLock);
+ virtual ~ShimChain();
+
+ void Neuter();
+ BOOL IsNeutered();
+
+ //
+ // IUnknown
+ //
+
+ ULONG STDMETHODCALLTYPE AddRef();
+ ULONG STDMETHODCALLTYPE Release();
+ COM_METHOD QueryInterface(REFIID riid, void ** ppInterface);
+
+ //
+ // ICorDebugChain
+ //
+
+ COM_METHOD GetThread(ICorDebugThread ** ppThread);
+ COM_METHOD GetStackRange(CORDB_ADDRESS * pStart, CORDB_ADDRESS * pEnd);
+ COM_METHOD GetContext(ICorDebugContext ** ppContext);
+ COM_METHOD GetCaller(ICorDebugChain ** ppChain);
+ COM_METHOD GetCallee(ICorDebugChain ** ppChain);
+ COM_METHOD GetPrevious(ICorDebugChain ** ppChain);
+ COM_METHOD GetNext(ICorDebugChain ** ppChain);
+ COM_METHOD IsManaged(BOOL * pManaged);
+ COM_METHOD EnumerateFrames(ICorDebugFrameEnum ** ppFrames);
+ COM_METHOD GetActiveFrame(ICorDebugFrame ** ppFrame);
+ COM_METHOD GetRegisterSet(ICorDebugRegisterSet ** ppRegisters);
+ COM_METHOD GetReason(CorDebugChainReason * pReason);
+
+ //
+ // accessors
+ //
+
+ // Get the owning ShimStackWalk.
+ ShimStackWalk * GetShimStackWalk();
+
+ // Get the first and last index of the frame owned by this chain. This class itself doesn't store the
+ // frames. Rather, the frames are stored on the ShimStackWalk. This class just stores the indices.
+ // Note that the indices are [firstIndex, lastIndex), i.e. the last index is exclusive.
+ UINT32 GetFirstFrameIndex();
+ UINT32 GetLastFrameIndex();
+
+private:
+ // A chain describes a stack range within the stack. This includes a CONTEXT at the start (leafmost)
+ // end of the chain, and a frame pointer where the chain ends (rootmost). This stack range is exposed
+ // publicly via ICDChain::GetStackRange(), and can be used to stitch managed and native stack frames
+ // together into a unified stack.
+ DT_CONTEXT m_context; // the leaf end of the chain
+ FramePointer m_fpRoot; // the root end of the chain
+
+ ShimStackWalk * m_pStackWalk; // the owning ShimStackWalk
+ Volatile<ULONG> m_refCount;
+
+ // The 0-based index of this chain in the ShimStackWalk's chain array (m_pStackWalk->m_stackChains).
+ UINT32 m_chainIndex;
+
+ // The 0-based index of the first frame owned by this chain in the ShimStackWalk's frame array
+ // (m_pStackWalk->m_stackFrames). See code::ShimChain::GetFirstFrameIndex().
+ UINT32 m_frameStartIndex;
+
+ // The 0-based index of the last frame owned by this chain in the ShimStackWalk's frame array
+ // (m_pStackWalk->m_stackFrames). This index is exlusive. See code::ShimChain::GetLastFrameIndex().
+ UINT32 m_frameEndIndex;
+
+ CorDebugChainReason m_chainReason;
+ BOOL m_fIsManaged; // indicates whether this chain contains managed frames
+ BOOL m_fIsNeutered;
+
+ RSLock * m_pShimLock; // shim lock from ShimProcess to protect neuteredness checks
+};
+
+
+//---------------------------------------------------------------------------------------
+//
+// This class implements the deprecated ICDChainEnum interface.
+//
+
+class ShimChainEnum : public ICorDebugChainEnum
+{
+public:
+ ShimChainEnum(ShimStackWalk * pSW, RSLock * pShimLock);
+ virtual ~ShimChainEnum();
+
+ void Neuter();
+ BOOL IsNeutered();
+
+ //
+ // IUnknown
+ //
+
+ ULONG STDMETHODCALLTYPE AddRef();
+ ULONG STDMETHODCALLTYPE Release();
+ COM_METHOD QueryInterface(REFIID riid, void ** ppInterface);
+
+ //
+ // ICorDebugEnum
+ //
+
+ COM_METHOD Skip(ULONG celt);
+ COM_METHOD Reset();
+ COM_METHOD Clone(ICorDebugEnum ** ppEnum);
+ COM_METHOD GetCount(ULONG * pcChains);
+
+ //
+ // ICorDebugChainEnum
+ //
+
+ COM_METHOD Next(ULONG cChains, ICorDebugChain * rgpChains[], ULONG * pcChainsFetched);
+
+ //
+ // accessors
+ //
+
+ // used to link ShimChainEnums in a list
+ ShimChainEnum * GetNext();
+ void SetNext(ShimChainEnum * pNext);
+
+private:
+ ShimStackWalk * m_pStackWalk; // the owning ShimStackWalk
+
+ // This points to the next ShimChainEnum in the linked list of ShimChainEnums to be cleaned up.
+ // The head of the list is on the ShimStackWalk (m_pStackWalk->m_pChainEnumList).
+ ShimChainEnum * m_pNext;
+
+ UINT32 m_currentChainIndex; // the index of the current ShimChain being enumerated
+ Volatile<ULONG> m_refCount;
+ BOOL m_fIsNeutered;
+
+ RSLock * m_pShimLock; // shim lock from ShimProcess to protect neuteredness checks
+};
+
+
+//---------------------------------------------------------------------------------------
+//
+// This class implements the deprecated ICDFrameEnum interface.
+//
+
+class ShimFrameEnum : public ICorDebugFrameEnum
+{
+public:
+ ShimFrameEnum(ShimStackWalk * pSW, ShimChain * pChain, UINT32 frameStartIndex, UINT32 frameEndIndex, RSLock * pShimLock);
+ virtual ~ShimFrameEnum();
+
+ void Neuter();
+ BOOL IsNeutered();
+
+ //
+ // IUnknown
+ //
+
+ ULONG STDMETHODCALLTYPE AddRef();
+ ULONG STDMETHODCALLTYPE Release();
+ COM_METHOD QueryInterface(REFIID riid, void ** ppInterface);
+
+ //
+ // ICorDebugEnum
+ //
+
+ COM_METHOD Skip(ULONG celt);
+ COM_METHOD Reset();
+ COM_METHOD Clone(ICorDebugEnum ** ppEnum);
+ COM_METHOD GetCount(ULONG * pcFrames);
+
+ //
+ // ICorDebugFrameEnum
+ //
+
+ COM_METHOD Next(ULONG cFrames, ICorDebugFrame * rgpFrames[], ULONG * pcFramesFetched);
+
+ //
+ // accessors
+ //
+
+ // used to link ShimChainEnums in a list
+ ShimFrameEnum * GetNext();
+ void SetNext(ShimFrameEnum * pNext);
+
+private:
+ ShimStackWalk * m_pStackWalk; // the owning ShimStackWalk
+ ShimChain * m_pChain; // the owning ShimChain
+ RSLock * m_pShimLock; // shim lock from ShimProcess to protect neuteredness checks
+
+ // This points to the next ShimFrameEnum in the linked list of ShimFrameEnums to be cleaned up.
+ // The head of the list is on the ShimStackWalk (m_pStackWalk->m_pFrameEnumList).
+ ShimFrameEnum * m_pNext;
+
+ UINT32 m_currentFrameIndex; // the current ICDFrame being enumerated
+ UINT32 m_endFrameIndex; // the last index (exclusive) of the frame owned by the chain;
+ // see code:ShimChain::GetLastFrameIndex
+ Volatile<ULONG> m_refCount;
+ BOOL m_fIsNeutered;
+};
+
+
+#endif // SHIMPRIV_H
+
diff --git a/src/debug/di/shimprocess.cpp b/src/debug/di/shimprocess.cpp
new file mode 100644
index 0000000000..a6fc15407e
--- /dev/null
+++ b/src/debug/di/shimprocess.cpp
@@ -0,0 +1,1904 @@
+// 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.
+//*****************************************************************************
+// File: ShimProcess.cpp
+//
+
+//
+// The V3 ICD debugging APIs have a lower abstraction level than V2.
+// This provides V2 ICD debugging functionality on top of the V3 debugger object.
+//*****************************************************************************
+
+#include "stdafx.h"
+
+#include "safewrap.h"
+#include "check.h"
+
+#include <limits.h>
+#include "shimpriv.h"
+
+#if !defined(FEATURE_CORESYSTEM)
+#include <tlhelp32.h>
+#endif
+
+//---------------------------------------------------------------------------------------
+//
+// Ctor for a ShimProcess
+//
+// Notes:
+// See InitializeDataTarget in header for details of how to instantiate a ShimProcess and hook it up.
+// Initial ref count is 0. This is the convention used int the RS, and it plays well with semantics
+// like immediately assigning to a smart pointer (which will bump the count up to 1).
+
+ShimProcess::ShimProcess() :
+ m_ref(0),
+ m_fFirstManagedEvent(false),
+ m_fInCreateProcess(false),
+ m_fInLoadModule(false),
+ m_fIsInteropDebugging(false),
+ m_fIsDisposed(false),
+ m_loaderBPReceived(false)
+{
+ m_ShimLock.Init("ShimLock", RSLock::cLockReentrant, RSLock::LL_SHIM_LOCK);
+ m_ShimProcessDisposeLock.Init(
+ "ShimProcessDisposeLock",
+ RSLock::cLockReentrant | RSLock::cLockNonDbgApi,
+ RSLock::LL_SHIM_PROCESS_DISPOSE_LOCK);
+ m_eventQueue.Init(&m_ShimLock);
+ m_pShimCallback.Assign(new ShimProxyCallback(this)); // Throws
+
+ m_fNeedFakeAttachEvents = false;
+ m_ContinueStatusChangedData.Clear();
+
+ m_pShimStackWalkHashTable = new ShimStackWalkHashTable();
+
+ m_pDupeEventsHashTable = new DuplicateCreationEventsHashTable();
+
+ m_machineInfo.Clear();
+
+ m_markAttachPendingEvent = WszCreateEvent(NULL, TRUE, FALSE, NULL);
+ if (m_markAttachPendingEvent == NULL)
+ {
+ ThrowLastError();
+ }
+
+ m_terminatingEvent = WszCreateEvent(NULL, TRUE, FALSE, NULL);
+ if (m_terminatingEvent == NULL)
+ {
+ ThrowLastError();
+ }
+}
+
+//---------------------------------------------------------------------------------------
+//
+// ShimProcess dtor. Invoked when reference count goes to 0.
+//
+// Assumptions:
+// Dtors should not do any interesting work. If this object has been initialized,
+// then call Dispose() first.
+//
+//
+ShimProcess::~ShimProcess()
+{
+ // Expected that this was either already disposed first, or not initialized.
+ _ASSERTE(m_pWin32EventThread == NULL);
+
+ _ASSERTE(m_ShimProcessDisposeLock.IsInit());
+ m_ShimProcessDisposeLock.Destroy();
+
+ if (m_markAttachPendingEvent != NULL)
+ {
+ CloseHandle(m_markAttachPendingEvent);
+ m_markAttachPendingEvent = NULL;
+ }
+
+ if (m_terminatingEvent != NULL)
+ {
+ CloseHandle(m_terminatingEvent);
+ m_terminatingEvent = NULL;
+ }
+
+ // Dtor will release m_pLiveDataTarget
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Part of initialization to hook up to process.
+//
+// Arguments:
+// pProcess - debuggee object to connect to. Maybe null if part of shutdown.
+//
+// Notes:
+// This will take a strong reference to the process object.
+// This is part of the initialization phase.
+// This should only be called once.
+//
+//
+void ShimProcess::SetProcess(ICorDebugProcess * pProcess)
+{
+ PRIVATE_SHIM_CALLBACK_IN_THIS_SCOPE0(NULL);
+
+ // Data-target should already be setup before we try to connect to a process.
+ _ASSERTE(m_pLiveDataTarget != NULL);
+
+ // Reference is kept by m_pProcess;
+ m_pIProcess.Assign(pProcess);
+
+ // Get the private shim hooks. This just exists to access private functionality that has not
+ // yet been promoted to the ICorDebug interfaces.
+ m_pProcess = static_cast<CordbProcess *>(pProcess);
+
+ if (pProcess != NULL)
+ {
+ // Verify that DataTarget + new process have the same pid?
+ _ASSERTE(m_pProcess->GetPid() == m_pLiveDataTarget->GetPid());
+ }
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Create a Data-Target around the live process.
+//
+// Arguments:
+// processId - OS process ID to connect to. Must be a local, same platform, process.
+//
+// Return Value:
+// S_OK on success.
+//
+// Assumptions:
+// This is part of the initialization dance.
+//
+// Notes:
+// Only call this once, during the initialization dance.
+//
+HRESULT ShimProcess::InitializeDataTarget(DWORD processId)
+{
+ _ASSERTE(m_pLiveDataTarget == NULL);
+
+
+ HRESULT hr = BuildPlatformSpecificDataTarget(GetMachineInfo(), processId, &m_pLiveDataTarget);
+ if (FAILED(hr))
+ {
+ _ASSERTE(m_pLiveDataTarget == NULL);
+ return hr;
+ }
+ m_pLiveDataTarget->HookContinueStatusChanged(ShimProcess::ContinueStatusChanged, this);
+
+ // Ref on pDataTarget is now 1.
+ _ASSERTE(m_pLiveDataTarget != NULL);
+
+ return S_OK;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Determines if current thread is the Win32 Event Thread
+//
+// Return Value:
+// True iff current thread is win32 event thread, else false.
+//
+// Notes:
+// The win32 event thread is created by code:ShimProcess::CreateAndStartWin32ET
+//
+bool ShimProcess::IsWin32EventThread()
+{
+ return (m_pWin32EventThread != NULL) && m_pWin32EventThread->IsWin32EventThread();
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Add a reference
+//
+void ShimProcess::AddRef()
+{
+ InterlockedIncrement(&m_ref);
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Release a reference.
+//
+// Notes:
+// When ref goes to 0, object is deleted.
+//
+void ShimProcess::Release()
+{
+ LONG ref = InterlockedDecrement(&m_ref);
+ if (ref == 0)
+ {
+ delete this;
+ }
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Dispose (Neuter) the object.
+//
+//
+// Assumptions:
+// This is called to gracefully shutdown the ShimProcess object.
+// This must be called before destruction if the object was initialized.
+//
+// Notes:
+// This will release all external resources, including getting the win32 event thread to exit.
+// This can safely be called multiple times.
+//
+void ShimProcess::Dispose()
+{
+ // Serialize Dispose with any other locked access to the shim. This helps
+ // protect against the debugger detaching while we're in the middle of
+ // doing stuff on the ShimProcess
+ RSLockHolder lockHolder(&m_ShimProcessDisposeLock);
+
+ m_fIsDisposed = true;
+
+ // Can't shut down the W32ET if we're on it.
+ _ASSERTE(!IsWin32EventThread());
+
+ m_eventQueue.DeleteAll();
+
+ if (m_pWin32EventThread != NULL)
+ {
+ // This will block waiting for the thread to exit gracefully.
+ m_pWin32EventThread->Stop();
+
+ delete m_pWin32EventThread;
+ m_pWin32EventThread = NULL;
+ }
+
+ if (m_pLiveDataTarget != NULL)
+ {
+ m_pLiveDataTarget->Dispose();
+ m_pLiveDataTarget.Clear();
+ }
+
+ m_pIProcess.Clear();
+ m_pProcess = NULL;
+
+ _ASSERTE(m_ShimLock.IsInit());
+ m_ShimLock.Destroy();
+
+ if (m_pShimStackWalkHashTable != NULL)
+ {
+ // The hash table should be empty by now. ClearAllShimStackWalk() should have been called.
+ _ASSERTE(m_pShimStackWalkHashTable->GetCount() == 0);
+
+ delete m_pShimStackWalkHashTable;
+ m_pShimStackWalkHashTable = NULL;
+ }
+
+ if (m_pDupeEventsHashTable != NULL)
+ {
+ if (m_pDupeEventsHashTable->GetCount() > 0)
+ {
+ // loop through all the entries in the hash table, remove them, and delete them
+ for (DuplicateCreationEventsHashTable::Iterator pCurElem = m_pDupeEventsHashTable->Begin(),
+ pEndElem = m_pDupeEventsHashTable->End();
+ pCurElem != pEndElem;
+ pCurElem++)
+ {
+ DuplicateCreationEventEntry * pEntry = *pCurElem;
+ delete pEntry;
+ }
+ m_pDupeEventsHashTable->RemoveAll();
+ }
+
+ delete m_pDupeEventsHashTable;
+ m_pDupeEventsHashTable = NULL;
+ }
+}
+
+
+
+//---------------------------------------------------------------------------------------
+// Track (and close) file handles from debug events.
+//
+// Arguments:
+// pEvent - debug event
+//
+// Notes:
+// Some debug events introduce file handles that the debugger needs to track and
+// close on other debug events. For example, the LoadDll,CreateProcess debug
+// events both give back a file handle that the debugger must close. This is generally
+// done on the corresponding UnloadDll/ExitProcess debug events.
+//
+// Since we won't use the file handles, we'll just close them as soon as we get them.
+// That way, we don't need to remember any state.
+void ShimProcess::TrackFileHandleForDebugEvent(const DEBUG_EVENT * pEvent)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ HANDLE hFile = NULL;
+
+ switch(pEvent->dwDebugEventCode)
+ {
+ //
+ // Events that add a file handle
+ //
+ case CREATE_PROCESS_DEBUG_EVENT:
+ hFile = pEvent->u.CreateProcessInfo.hFile;
+ CloseHandle(hFile);
+ break;
+
+ case LOAD_DLL_DEBUG_EVENT:
+ hFile = pEvent->u.LoadDll.hFile;
+ CloseHandle(hFile);
+ break;
+
+ }
+}
+
+//---------------------------------------------------------------------------------------
+// ThreadProc helper to drain event queue.
+//
+// Arguments:
+// parameter - thread proc parameter, an ICorDebugProcess*
+//
+// Returns
+// 0.
+//
+// Notes:
+// This is useful when the shim queued a fake managed event (such as Control+C)
+// and needs to get the debuggee to synchronize in order to start dispatching events.
+// @dbgtodo sync: this will likely change as we iron out the Synchronization feature crew.
+//
+// We do this in a new thread proc to avoid thread restrictions:
+// Can't call this on win32 event thread because that can't send the IPC event to
+// make the aysnc-break request.
+// Can't call this on the RCET because that can't send an async-break (see SendIPCEvent for details)
+// So we just spin up a new thread to do the work.
+//---------------------------------------------------------------------------------------
+DWORD WINAPI CallStopGoThreadProc(LPVOID parameter)
+{
+ ICorDebugProcess* pProc = reinterpret_cast<ICorDebugProcess *>(parameter);
+
+ // We expect these operations to succeed; but if they do fail, there's nothing we can really do about it.
+ // If it fails on process exit/neuter/detach, then it would be ignorable.
+ HRESULT hr;
+
+
+ // Calling Stop + Continue will synchronize the process and force any queued events to be called.
+ // Stop is synchronous and will block until debuggee is synchronized.
+ hr = pProc->Stop(INFINITE);
+ SIMPLIFYING_ASSUMPTION(SUCCEEDED(hr));
+
+ // Continue will resume the debuggee. If there are queued events (which we expect in this case)
+ // then continue will drain the event queue instead of actually resuming the process.
+ hr = pProc->Continue(FALSE);
+ SIMPLIFYING_ASSUMPTION(SUCCEEDED(hr));
+
+ // This thread just needs to trigger an event dispatch. Now that it's done that, it can exit.
+ return 0;
+}
+
+
+//---------------------------------------------------------------------------------------
+// Does default event handling for native debug events.
+//
+// Arguments:
+// pEvent - IN event ot handle
+// pdwContinueStatus - IN /OUT - continuation status for event.
+//
+// Assumptions:
+// Called when target is stopped. Caller still needs to Continue the debug event.
+// This is called on the win32 event thread.
+//
+// Notes:
+// Some native events require extra work before continuing. Eg, skip loader
+// breakpoint, close certain handles, etc.
+// This is only called in the manage-only case. In the interop-case, the
+// debugger will get and handle these native debug events.
+void ShimProcess::DefaultEventHandler(
+ const DEBUG_EVENT * pEvent,
+ DWORD * pdwContinueStatus)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+
+ //
+ // Loader breakpoint
+ //
+
+ BOOL fFirstChance;
+ const EXCEPTION_RECORD * pRecord = NULL;
+
+ if (IsExceptionEvent(pEvent, &fFirstChance, &pRecord))
+ {
+ DWORD dwThreadId = GetThreadId(pEvent);
+
+ switch(pRecord->ExceptionCode)
+ {
+ case STATUS_BREAKPOINT:
+ {
+ if (!m_loaderBPReceived)
+ {
+ m_loaderBPReceived = true;
+
+ // Clear the loader breakpoint
+ *pdwContinueStatus = DBG_CONTINUE;
+
+ // After loader-breakpoint, notify that managed attach can begin.
+ // This is done to trigger a synchronization. The shim
+ // can then send the fake attach events once the target
+ // is synced.
+ // @dbgtodo sync: not needed once shim can
+ // work on sync APIs.
+ m_pProcess->QueueManagedAttachIfNeeded(); // throws
+ }
+ }
+ break;
+
+ /*
+ // If we handle the Ctlr-C event here and send the notification to the debugger, then we may break pre-V4
+ // behaviour because the debugger may handle the event and intercept the handlers registered in the debuggee
+ // process. So don't handle the event here and let the debuggee process handle it instead. See Dev10 issue
+ // 846455 for more info.
+ //
+ // However, when the re-arch is completed, we will need to work with VS to define what the right behaviour
+ // should be. We don't want to rely on in-process code to handle the Ctrl-C event.
+ case DBG_CONTROL_C:
+ {
+ // Queue a fake managed Ctrl+C event.
+ m_pShimCallback->ControlCTrap(GetProcess());
+
+ // Request an Async Break
+ // This is on Win32 Event Thread, so we can't call Stop / Continue.
+ // Instead, spawn a new threead, and have that call Stop/Continue, which
+ // will get the RCET to drain the event queue and dispatch the ControlCTrap we just queued.
+ {
+ DWORD dwDummyId;
+ CreateThread(NULL,
+ 0,
+ CallStopGoThreadProc,
+ (LPVOID) GetProcess(),
+ 0,
+ &dwDummyId);
+ }
+
+ // We don't worry about suspending the Control-C thread right now. The event is
+ // coming asynchronously, and so it's ok if the debuggee slips forward while
+ // we try to do a managed async break.
+
+
+ // Clear the control-C event.
+ *pdwContinueStatus = DBG_CONTINUE;
+ }
+ break;
+
+*/
+ }
+
+
+ }
+
+
+ // Native debugging APIs have an undocumented expectation that you clear for OutputDebugString.
+ if (pEvent->dwDebugEventCode == OUTPUT_DEBUG_STRING_EVENT)
+ {
+ *pdwContinueStatus = DBG_CONTINUE;
+ }
+
+ //
+ // File handles.
+ //
+ TrackFileHandleForDebugEvent(pEvent);
+}
+
+//---------------------------------------------------------------------------------------
+// Determine if we need to change the continue status
+//
+// Returns:
+// True if the continue status was changed. Else false.
+//
+// Assumptions:
+// This is single-threaded, which is enforced by it only be called on the win32et.
+// The shim guarnatees only 1 outstanding debug-event at a time.
+//
+// Notes:
+// See code:ShimProcess::ContinueStatusChangedWorker for big picture.
+// Continue status is changed from a data-target callback which invokes
+// code:ShimProcess::ContinueStatusChangedWorker.
+// Call code:ShimProcess::ContinueStatusChangedData::Clear to clear the 'IsSet' bit.
+//
+bool ShimProcess::ContinueStatusChangedData::IsSet()
+{
+
+ return m_dwThreadId != 0;
+}
+
+//---------------------------------------------------------------------------------------
+// Clears the bit marking
+//
+// Assumptions:
+// This is single-threaded, which is enforced by it only be called on the win32et.
+// The shim guarantees only 1 outstanding debug-event at a time.
+//
+// Notes:
+// See code:ShimProcess::ContinueStatusChangedWorker for big picture.
+// This makes code:ShimProcess::ContinueStatusChangedData::IsSet return false.
+// This can safely be called multiple times in a row.
+//
+void ShimProcess::ContinueStatusChangedData::Clear()
+{
+ m_dwThreadId = 0;
+}
+
+//---------------------------------------------------------------------------------------
+// Callback invoked from data-target when continue status is changed.
+//
+// Arguments:
+// pUserData - data we supplied to the callback. a 'this' pointer.
+// dwThreadId - the tid whose continue status is changing
+// dwContinueStatus - the new continue status.
+//
+// Notes:
+//
+
+// Static
+HRESULT ShimProcess::ContinueStatusChanged(void * pUserData, DWORD dwThreadId, CORDB_CONTINUE_STATUS dwContinueStatus)
+{
+ ShimProcess * pThis = reinterpret_cast<ShimProcess *>(pUserData);
+ return pThis->ContinueStatusChangedWorker(dwThreadId, dwContinueStatus);
+}
+
+//---------------------------------------------------------------------------------------
+// Real worker callback invoked from data-target when continue status is changed.
+//
+// Arguments:
+// dwThreadId - the tid whose continue status is changing
+// dwContinueStatus - the new continue status.
+//
+// Notes:
+// ICorDebugProcess4::Filter returns an initial continue status (heavily biased to 'gn').
+// Some ICorDebug operations may need to change the continue status that filter returned.
+// For example, on windows, hijacking a thread at an unhandled exception would need to
+// change the status to 'gh' (since continuing 2nd chance exception 'gn' will tear down the
+// process and the hijack would never execute).
+//
+// Such operations will invoke into the data-target (code:ICorDebugMutableDataTarget::ContinueStatusChanged)
+// to notify the debugger that the continue status was changed.
+//
+// The shim only executes such operations on the win32-event thread in a small window between
+// WaitForDebugEvent and Continue. Therefore, we know:
+// * the callback must come on the Win32EventThread (which means our handling the callback is
+// single-threaded.
+// * We only have 1 outstanding debug event to worry about at a time. This simplifies our tracking.
+//
+// The shim tracks the outstanding change request in m_ContinueStatusChangedData.
+
+HRESULT ShimProcess::ContinueStatusChangedWorker(DWORD dwThreadId, CORDB_CONTINUE_STATUS dwContinueStatus)
+{
+ // Should only be set once. This is only called on the win32 event thread, which protects against races.
+ _ASSERTE(IsWin32EventThread());
+ _ASSERTE(!m_ContinueStatusChangedData.IsSet());
+
+ m_ContinueStatusChangedData.m_dwThreadId = dwThreadId;
+ m_ContinueStatusChangedData.m_status = dwContinueStatus;
+
+ // Setting dwThreadId to non-zero should now mark this as set.
+ _ASSERTE(m_ContinueStatusChangedData.IsSet());
+ return S_OK;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Add a duplicate creation event entry for the specified key.
+//
+// Arguments:
+// pKey - the key of the entry to be added; this is expected to be an
+// ICDProcess/ICDAppDomain/ICDThread/ICDAssembly/ICDModule
+//
+// Assumptions:
+// pKey is really an interface pointer of one of the types mentioned above
+//
+// Notes:
+// We have to keep track of which creation events we have sent already because some runtime data structures
+// are discoverable through enumeration before they send their creation events. As a result, we may have
+// faked up a creation event for a data structure during attach, and then later on get another creation
+// event for the same data structure. VS is not resilient in the face of multiple creation events for
+// the same data structure.
+//
+// Needless to say this is a problem in attach scenarios only. However, keep in mind that for CoreCLR,
+// launch really is early attach. For early attach, we get three creation events up front: a create
+// process, a create appdomain, and a create thread.
+//
+
+void ShimProcess::AddDuplicateCreationEvent(void * pKey)
+{
+ NewHolder<DuplicateCreationEventEntry> pEntry(new DuplicateCreationEventEntry(pKey));
+ m_pDupeEventsHashTable->Add(pEntry);
+ pEntry.SuppressRelease();
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Check whether the specified key exists in the hash table. If so, remove it.
+//
+// Arguments:
+// pKey - the key of the entry to check; this is expected to be an
+// ICDProcess/ICDAppDomain/ICDThread/ICDAssembly/ICDModule
+//
+// Return Value:
+// Returns true if the entry exists. The entry will have been removed because we can't have more than two
+// duplicates for any given event.
+//
+// Assumptions:
+// pKey is really an interface pointer of one of the types mentioned above
+//
+// Notes:
+// See code:ShimProcess::AddDuplicateCreationEvent.
+//
+
+bool ShimProcess::RemoveDuplicateCreationEventIfPresent(void * pKey)
+{
+ // We only worry about duplicate events in attach scenarios.
+ if (GetAttached())
+ {
+ // Only do the check if the hash table actually contains entries.
+ if (m_pDupeEventsHashTable->GetCount() > 0)
+ {
+ // Check if this is a dupe.
+ DuplicateCreationEventEntry * pResult = m_pDupeEventsHashTable->Lookup(pKey);
+ if (pResult != NULL)
+ {
+ // This is a dupe. We can't have a dupe twice, so remove it.
+ // This will help as a bit of optimization, since we will no longer check the hash table if
+ // its count reaches 0.
+ m_pDupeEventsHashTable->Remove(pKey);
+ delete pResult;
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+
+//---------------------------------------------------------------------------------------
+// Gets the exception record format of the host
+//
+// Returns:
+// The CorDebugRecordFormat for the host architecture.
+//
+// Notes:
+// This corresponds to the definition EXCEPTION_RECORD on the host-architecture.
+// It can be passed into ICorDebugProcess4::Filter.
+CorDebugRecordFormat GetHostExceptionRecordFormat()
+{
+#if defined(_WIN64)
+ return FORMAT_WINDOWS_EXCEPTIONRECORD64;
+#else
+ return FORMAT_WINDOWS_EXCEPTIONRECORD32;
+#endif
+}
+
+//---------------------------------------------------------------------------------------
+// Main event handler for native debug events. Must also ensure Continue is called.
+//
+// Arguments:
+// pEvent - debug event to handle
+//
+// Assumptions:
+// Caller did a Flush() if needed.
+//
+// Notes:
+// The main Handle native debug events.
+// This must call back into ICD to let ICD filter the debug event (in case it's a managed notification).
+//
+// If we're interop-debugging (V2), then the debugger is expecting the debug events. In that case,
+// we go through the V2 interop-debugging logic to queue / dispatch the events.
+// If we're managed-only debugging, then the shim provides a default handler for the native debug.
+// This includes some basic work (skipping the loader breakpoint, close certain handles, etc).
+//---------------------------------------------------------------------------------------
+HRESULT ShimProcess::HandleWin32DebugEvent(const DEBUG_EVENT * pEvent)
+{
+ _ASSERTE(IsWin32EventThread());
+
+ //
+ // If this is an exception event, then we need to feed it into the CLR.
+ //
+ BOOL dwFirstChance = FALSE;
+ const EXCEPTION_RECORD * pRecord = NULL;
+ const DWORD dwThreadId = GetThreadId(pEvent);
+
+ bool fContinueNow = true;
+
+ // If true, we're continuing (unhandled) a 2nd-chance exception
+ bool fExceptionGoingUnhandled = false;
+
+ //
+ const DWORD kDONTCARE = 0;
+ DWORD dwContinueStatus = kDONTCARE;
+
+ if (IsExceptionEvent(pEvent, &dwFirstChance, &pRecord))
+ {
+ // As a diagnostic aid we can configure the debugger to assert when the debuggee does DebugBreak()
+#ifdef DEBUG
+ static ConfigDWORD config;
+ DWORD fAssert = config.val(CLRConfig::INTERNAL_DbgAssertOnDebuggeeDebugBreak);
+ if (fAssert)
+ {
+ // If we got a 2nd-chance breakpoint, then it's extremely likely that it's from an
+ // _ASSERTE in the target and we really want to know about it now before we kill the
+ // target. The debuggee will exit once we continue (unless we are mixed-mode debugging), so alert now.
+ // This assert could be our only warning of various catastrophic failures in the left-side.
+ if (!dwFirstChance && (pRecord->ExceptionCode == STATUS_BREAKPOINT) && !m_fIsInteropDebugging)
+ {
+ DWORD pid = (m_pLiveDataTarget == NULL) ? 0 : m_pLiveDataTarget->GetPid();
+
+ CONSISTENCY_CHECK_MSGF(false,
+ ("Unhandled breakpoint exception in debuggee (pid=%d (0x%x)) on thread %d(0x%x)\n"
+ "This may mean there was an assert in the debuggee on that thread.\n"
+ "\n"
+ "You should attach to that process (non-invasively) and get a callstack of that thread.\n"
+ "(This assert only occurs when CLRConfig::INTERNAL_DebuggerAssertOnDebuggeeDebugBreak is set)\n",
+ pid, pid, dwThreadId,dwThreadId));
+ }
+ }
+#endif
+
+ // We pass the Shim's proxy callback object, which will just take the callbacks and queue them
+ // to an event-queue in the shim. When we get the sync-complete event, the shim
+ // will then drain the event queue and dispatch the events to the user's callback object.
+ const DWORD dwFlags = dwFirstChance ? 1 : 0;
+
+ m_ContinueStatusChangedData.Clear();
+
+ // If ICorDebug doesn't care about this exception, it will leave dwContinueStatus unchanged.
+ RSExtSmartPtr<ICorDebugProcess4> pProcess4;
+ GetProcess()->QueryInterface(IID_ICorDebugProcess4, (void**) &pProcess4);
+
+ HRESULT hrFilter = pProcess4->Filter(
+ (const BYTE*) pRecord,
+ sizeof(EXCEPTION_RECORD),
+ GetHostExceptionRecordFormat(),
+ dwFlags,
+ dwThreadId,
+ m_pShimCallback,
+ &dwContinueStatus);
+ if (FAILED(hrFilter))
+ {
+ // Filter failed (eg. DAC couldn't be loaded), return the
+ // error so it can become an unrecoverable error.
+ return hrFilter;
+ }
+
+ // For unhandled exceptions, hijacking if needed.
+ if (!dwFirstChance)
+ {
+ // May invoke data-target callback (which may call code:ShimProcess::ContinueStatusChanged) to change continue status.
+ if (!m_pProcess->HijackThreadForUnhandledExceptionIfNeeded(dwThreadId))
+ {
+ // We decided not to hijack, so this exception is going to go unhandled
+ fExceptionGoingUnhandled = true;
+ }
+
+ if (m_ContinueStatusChangedData.IsSet())
+ {
+ _ASSERTE(m_ContinueStatusChangedData.m_dwThreadId == dwThreadId);
+
+ // Claiming this now means we won't do any other processing on the exception event.
+ // This means the interop-debugging logic will never see 2nd-chance managed exceptions.
+ dwContinueStatus = m_ContinueStatusChangedData.m_status;
+ }
+ }
+ }
+
+ // Do standard event handling, including Handling loader-breakpoint,
+ // and callback into CordbProcess for Attach if needed.
+ HRESULT hrIgnore = S_OK;
+ EX_TRY
+ {
+ // For NonClr notifications, allow extra processing.
+ // This includes both non-exception events, and exception events that aren't
+ // specific CLR debugging services notifications.
+ if (dwContinueStatus == kDONTCARE)
+ {
+ if (m_fIsInteropDebugging)
+ {
+ // Interop-debugging logic will handle the continue.
+ fContinueNow = false;
+#if defined(FEATURE_INTEROP_DEBUGGING)
+ // @dbgtodo interop: All the interop-debugging logic is still in CordbProcess.
+ // Call back into that. This will handle Continuing the debug event.
+ m_pProcess->HandleDebugEventForInteropDebugging(pEvent);
+#else
+ _ASSERTE(!"Interop debugging not supported on Rotor");
+#endif
+ }
+ else
+ {
+ dwContinueStatus = DBG_EXCEPTION_NOT_HANDLED;
+
+ // For managed-only debugging, there's no user handler for native debug events,
+ // and so we still need to do some basic work on certain debug events.
+ DefaultEventHandler(pEvent, &dwContinueStatus);
+
+ // This is the managed-only case. No reason to keep the target win32 frozen, so continue it immediately.
+ _ASSERTE(fContinueNow);
+ }
+ }
+ }
+ EX_CATCH_HRESULT(hrIgnore);
+ // Dont' expect errors here (but could probably return it up to become an
+ // unrecoverable error if necessary). We still want to call Continue thought.
+ SIMPLIFYING_ASSUMPTION(SUCCEEDED(hrIgnore));
+
+ //
+ // Continue the debuggee if needed.
+ //
+ if (fContinueNow)
+ {
+ BOOL fContinueOk = GetNativePipeline()->ContinueDebugEvent(
+ GetProcessId(pEvent),
+ dwThreadId,
+ dwContinueStatus);
+ (void)fContinueOk; //prevent "unused variable" error from GCC
+ SIMPLIFYING_ASSUMPTION(fContinueOk);
+
+ if (fExceptionGoingUnhandled)
+ {
+ _ASSERTE(dwContinueStatus == DBG_EXCEPTION_NOT_HANDLED);
+ // We just passed a 2nd-chance exception back to the OS which may have now invoked
+ // Windows error-reporting logic which suspended all threads in the target. Since we're
+ // still debugging and may want to break, inspect state and even detach (eg. to attach
+ // a different sort of debugger that can handle the exception) we need to let our threads run.
+ // Note that when WER auto-invokes a debugger it doesn't suspend threads, so it doesn't really
+ // make sense for them to be suspended now when a debugger is already attached.
+ // A better solution may be to suspend this faulting thread before continuing the event, do an
+ // async-break and give the debugger a notification of an unhandled exception. But this will require
+ // an ICorDebug API change, and also makes it harder to reliably get the WER dialog box once we're
+ // ready for it.
+ // Unfortunately we have to wait for WerFault.exe to start and actually suspend the threads, and
+ // there doesn't appear to be any better way than to just sleep for a little here. In practice 200ms
+ // seems like more than enough, but this is so uncommon of a scenario that a half-second delay
+ // (just to be safe) isn't a problem.
+ // Provide an undocumented knob to turn this behavior off in the very rare case it's not what we want
+ // (eg. we're trying to debug something that races with crashing / terminating the process on multiple
+ // threads)
+ static ConfigDWORD config;
+ DWORD fSkipResume = config.val(CLRConfig::UNSUPPORTED_DbgDontResumeThreadsOnUnhandledException);
+ if (!fSkipResume)
+ {
+ ::Sleep(500);
+ hrIgnore = GetNativePipeline()->EnsureThreadsRunning();
+ SIMPLIFYING_ASSUMPTION(SUCCEEDED(hrIgnore));
+ }
+ }
+ }
+
+ return S_OK;
+}
+
+// Trivial accessor to get the event queue.
+ManagedEventQueue * ShimProcess::GetManagedEventQueue()
+{
+ return &m_eventQueue;
+}
+
+// Combines GetManagedEventQueue() and Dequeue() into a single function
+// that holds m_ShimProcessDisposeLock for the duration
+ManagedEvent * ShimProcess::DequeueManagedEvent()
+{
+ // Serialize this function with Dispoe()
+ RSLockHolder lockHolder(&m_ShimProcessDisposeLock);
+ if (m_fIsDisposed)
+ return NULL;
+
+ return m_eventQueue.Dequeue();
+}
+
+// Trivial accessor to get Shim's proxy callback object.
+ShimProxyCallback * ShimProcess::GetShimCallback()
+{
+ return m_pShimCallback;
+}
+
+// Trivial accessor to get the ICDProcess for the debuggee.
+// A ShimProcess object can then provide V2 functionality by building it on V3 functionality
+// exposed by the ICDProcess object.
+ICorDebugProcess * ShimProcess::GetProcess()
+{
+ return m_pIProcess;
+}
+
+// Trivial accessor to get the data-target for the debuggee.
+// The data-target lets us access the debuggee, especially reading debuggee memory.
+ICorDebugMutableDataTarget * ShimProcess::GetDataTarget()
+{
+ return m_pLiveDataTarget;
+};
+
+
+// Trivial accessor to get the raw native event pipeline.
+// In V3, ICorDebug no longer owns the event thread and it does not own the event pipeline either.
+INativeEventPipeline * ShimProcess::GetNativePipeline()
+{
+ return m_pWin32EventThread->GetNativePipeline();
+}
+
+// Trivial accessor to expose the W32ET thread to the CordbProcess so that it can emulate V2 behavior.
+// In V3, ICorDebug no longer owns the event thread and it does not own the event pipeline either.
+// The Win32 Event Thread is the only thread that can use the native pipeline
+// see code:ShimProcess::GetNativePipeline.
+CordbWin32EventThread * ShimProcess::GetWin32EventThread()
+{
+ return m_pWin32EventThread;
+}
+
+
+// Trivial accessor to mark whether we're interop-debugging.
+// Retreived via code:ShimProcess::IsInteropDebugging
+void ShimProcess::SetIsInteropDebugging(bool fIsInteropDebugging)
+{
+ m_fIsInteropDebugging = fIsInteropDebugging;
+}
+
+// Trivial accessor to check if we're interop-debugging.
+// This affects how we handle native debug events.
+// The significant usage of this is in code:ShimProcess::HandleWin32DebugEvent
+bool ShimProcess::IsInteropDebugging()
+{
+ return m_fIsInteropDebugging;
+}
+
+
+//---------------------------------------------------------------------------------------
+// Begin queueing the fake attach events.
+//
+// Notes:
+// See code:ShimProcess::QueueFakeAttachEvents for more about "fake attach events".
+//
+// This marks that we need to send fake attach events, and queus a CreateProcess.
+// Caller calls code:ShimProcess::QueueFakeAttachEventsIfNeeded to finish queuing
+// the rest of the fake attach events.
+void ShimProcess::BeginQueueFakeAttachEvents()
+{
+ m_fNeedFakeAttachEvents = true;
+
+ // Put a fake CreateProcess event in the queue.
+ // This will not be drained until we get a Sync-Complete from the Left-side.
+ GetShimCallback()->QueueCreateProcess(GetProcess());
+ AddDuplicateCreationEvent(GetProcess());
+}
+
+//---------------------------------------------------------------------------------------
+// potentially Queue fake attach events like we did in V2.
+//
+// Arguments:
+// fRealCreateProcessEvent - true if the shim is about to dispatch a real create process event (as opposed
+// to one faked up by the shim itself)
+//
+// Notes:
+// See code:ShimProcess::QueueFakeAttachEvents for details.
+void ShimProcess::QueueFakeAttachEventsIfNeeded(bool fRealCreateProcessEvent)
+{
+ // This was set high in code:ShimProcess::BeginQueueFakeAttachEvents
+ if (!m_fNeedFakeAttachEvents)
+ {
+ return;
+ }
+ m_fNeedFakeAttachEvents = false;
+
+ // If the first event we get after attaching is a create process event, then this is an early attach
+ // scenario and we don't need to queue any fake attach events.
+ if (!fRealCreateProcessEvent)
+ {
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ QueueFakeAttachEvents();
+ }
+ EX_CATCH_HRESULT(hr);
+ }
+}
+
+//---------------------------------------------------------------------------------------
+// Send fake Thread-create events for attach, using an arbitrary order.
+//
+// Returns:
+// S_OK on success, else error.
+//
+// Notes:
+// This sends fake thread-create events, ala V2 attach.
+// See code:ShimProcess::QueueFakeAttachEvents for details
+//
+// The order of thread creates is random and at the mercy of ICorDebugProcess::EnumerateThreads.
+// Whidbey would send thread creates in the order of the OS's native thread
+// list. Since Arrowhead no longer sends fake attach events, the shim simulates
+// the fake attach events. But ICorDebug doesn't provide a way to get the
+// same order that V2 used. So without using platform-specific thread-enumeration,
+// we can't get the V2 ordering.
+//
+// Compare to code:ShimProcess::QueueFakeThreadAttachEventsNativeOrder,
+// which sends threads in the OS native thread create order.
+//
+HRESULT ShimProcess::QueueFakeThreadAttachEventsNoOrder()
+{
+ ICorDebugProcess * pProcess = GetProcess();
+
+ RSExtSmartPtr<ICorDebugThreadEnum> pThreadEnum;
+ RSExtSmartPtr<ICorDebugThread> pThread;
+
+ // V2 would only send create threads after a thread had run managed code.
+ // V3 has a discovery model where Enumeration can find threads before they've run managed code.
+ // So the emulation here may send some additional create-thread events that v2 didn't send.
+ HRESULT hr = pProcess->EnumerateThreads(&pThreadEnum);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ ULONG cDummy;
+
+ while(SUCCEEDED(pThreadEnum->Next(1, &pThread, &cDummy)) && (pThread != NULL))
+ {
+ RSExtSmartPtr<ICorDebugAppDomain> pAppDomain;
+ hr = pThread->GetAppDomain(&pAppDomain);
+
+ // Getting the appdomain shouldn't fail. If it does, we can't dispatch
+ // this callback, but we can still dispatch the other thread creates.
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+ if (pAppDomain != NULL)
+ {
+ GetShimCallback()->CreateThread(pAppDomain, pThread);
+ AddDuplicateCreationEvent(pThread);
+ }
+ pThread.Clear();
+ }
+
+ return S_OK;
+}
+
+//---------------------------------------------------------------------------------------
+// Send fake Thread-create events for attach, using the order of the OS native
+// thread list.
+//
+// Returns:
+// S_OK on success, else error.
+//
+// Notes:
+// This sends fake thread-create events, ala V2 attach.
+// See code:ShimProcess::QueueFakeAttachEvents for details
+// The order of the thread creates matches the OS's native thread list.
+// This is important because the debugger can use the order of thread-create
+// callbacks to associate logical thread-ids (0,1,2...) with threads. Users
+// may rely on thread 0 always being the main thread.
+// In contrast, the order from ICorDebugProcess::EnumerateThreads is random.
+//
+// Compare to code:ShimProcess::QueueFakeThreadAttachEventsNoOrder, which
+// sends the threads in an arbitrary order.
+HRESULT ShimProcess::QueueFakeThreadAttachEventsNativeOrder()
+{
+#ifdef FEATURE_CORESYSTEM
+ _ASSERTE("NYI");
+ return E_FAIL;
+#else
+ ICorDebugProcess * pProcess = GetProcess();
+
+ DWORD dwProcessId;
+ HRESULT hr = pProcess->GetID(&dwProcessId);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ HANDLE hThreadSnap = INVALID_HANDLE_VALUE;
+ THREADENTRY32 te32;
+
+ // Take a snapshot of all running threads
+ hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
+ if (hThreadSnap == INVALID_HANDLE_VALUE)
+ {
+ hr = HRESULT_FROM_GetLastError();
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+ return hr;
+ }
+ // HandleHolder doesn't deal with INVALID_HANDLE_VALUE, so we only assign if we have a legal value.
+ HandleHolder hSnapshotHolder(hThreadSnap);
+
+ // Fill in the size of the structure before using it.
+ te32.dwSize = sizeof(THREADENTRY32);
+
+ // Retrieve information about the first thread, and exit if unsuccessful
+ if (!Thread32First(hThreadSnap, &te32))
+ {
+ hr = HRESULT_FROM_GetLastError();
+ return hr;
+ }
+
+ // Now walk the thread list of the system,
+ // and display information about each thread
+ // associated with the specified process
+ do
+ {
+ if (te32.th32OwnerProcessID == dwProcessId)
+ {
+ RSExtSmartPtr<ICorDebugThread> pThread;
+ pProcess->GetThread(te32.th32ThreadID, &pThread);
+ if (pThread != NULL)
+ {
+ // If we fail to get the appdomain for some reason, then then
+ // we can't dispatch this thread callback. But we can still
+ // finish enumerating.
+ RSExtSmartPtr<ICorDebugAppDomain> pAppDomain;
+ HRESULT hrGetAppDomain = pThread->GetAppDomain(&pAppDomain);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hrGetAppDomain);
+ if (pAppDomain != NULL)
+ {
+ GetShimCallback()->CreateThread(pAppDomain, pThread);
+ AddDuplicateCreationEvent(pThread);
+
+ //fix for issue DevDiv2\DevDiv 77523 - threads are switched out in SQL don't get thread create notifications
+ // mark that this thread has queued a create event
+ CordbThread* pThreadInternal = static_cast<CordbThread*>(pThread.GetValue());
+ pThreadInternal->SetCreateEventQueued();
+ }
+ }
+ }
+ } while(Thread32Next(hThreadSnap, &te32));
+
+
+ //fix for issue DevDiv2\DevDiv 77523 - threads are switched out in SQL don't get thread create notifications
+ //
+
+
+ // Threads which were switched out won't be present in the native thread order enumeration above.
+ // In order to not miss them we will enumerate all the managed thread objects and for any that we haven't
+ // already queued a notification for, we will queue a notification now.
+ RSExtSmartPtr<ICorDebugThreadEnum> pThreadEnum;
+ RSExtSmartPtr<ICorDebugThread> pThread;
+ hr = pProcess->EnumerateThreads(&pThreadEnum);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ ULONG cDummy;
+
+ while(SUCCEEDED(pThreadEnum->Next(1, &pThread, &cDummy)) && (pThread != NULL))
+ {
+ RSExtSmartPtr<ICorDebugAppDomain> pAppDomain;
+ hr = pThread->GetAppDomain(&pAppDomain);
+ CordbThread* pThreadInternal = static_cast<CordbThread*>(pThread.GetValue());
+
+ // Getting the appdomain shouldn't fail. If it does, we can't dispatch
+ // this callback, but we can still dispatch the other thread creates.
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+ if (pAppDomain != NULL && !pThreadInternal->CreateEventWasQueued())
+ {
+ GetShimCallback()->CreateThread(pAppDomain, pThread);
+ AddDuplicateCreationEvent(pThread);
+ pThreadInternal->SetCreateEventQueued();
+ }
+ pThread.Clear();
+ }
+
+
+ return S_OK;
+#endif
+}
+
+//---------------------------------------------------------------------------------------
+// Queues the fake Assembly and Module load events
+//
+// Arguments:
+// pAssembly - non-null, the assembly to queue.
+//
+// Notes:
+// Helper for code:ShimProcess::QueueFakeAttachEvents
+// Queues create events for the assembly and for all modules within the
+// assembly. Most assemblies only have 1 module.
+void ShimProcess::QueueFakeAssemblyAndModuleEvent(ICorDebugAssembly * pAssembly)
+{
+ RSExtSmartPtr<ICorDebugAppDomain> pAppDomain;
+
+ HRESULT hr = pAssembly->GetAppDomain(&pAppDomain);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+
+ //
+ // Send the fake Load Assembly event.
+ //
+ GetShimCallback()->LoadAssembly(pAppDomain, pAssembly);
+ AddDuplicateCreationEvent(pAssembly);
+
+ //
+ // Send Modules - must be in load order
+ //
+ RSExtSmartPtr<ICorDebugModuleEnum> pModuleEnum;
+ hr = pAssembly->EnumerateModules(&pModuleEnum);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+
+ ULONG countModules;
+ hr = pModuleEnum->GetCount(&countModules);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+
+ // ISSUE WORKAROUND 835869
+ // The CordbEnumFilter used as the implementation of CordbAssembly::EnumerateModules has
+ // a ref counting bug in it. It adds one ref to each item when it is constructed and never
+ // removes that ref. Expected behavior would be that it adds a ref at construction, another on
+ // every call to next, and releases the construction ref when the enumerator is destroyed. The
+ // user is expected to release the reference they receive from Next. Thus enumerating exactly
+ // one time and calling Release() does the correct thing regardless of whether this bug is present
+ // or not. Note that with the bug the enumerator holds 0 references at the end of this loop,
+ // however the assembly also holds references so the modules will not be prematurely released.
+ for(ULONG i = 0; i < countModules; i++)
+ {
+ ICorDebugModule* pModule = NULL;
+ ULONG countFetched = 0;
+ pModuleEnum->Next(1, &pModule, &countFetched);
+ _ASSERTE(pModule != NULL);
+ if(pModule != NULL)
+ {
+ pModule->Release();
+ }
+ }
+
+ RSExtSmartPtr<ICorDebugModule> * pModules = new RSExtSmartPtr<ICorDebugModule> [countModules];
+ m_pProcess->GetModulesInLoadOrder(pAssembly, pModules, countModules);
+ for(ULONG iModule = 0; iModule < countModules; iModule++)
+ {
+ ICorDebugModule * pModule = pModules[iModule];
+
+ GetShimCallback()->FakeLoadModule(pAppDomain, pModule);
+ AddDuplicateCreationEvent(pModule);
+
+ // V2 may send UpdatePdbStreams for certain modules (like dynamic or in-memory modules).
+ // We don't yet have this support for out-of-proc.
+ // When the LoadModule event that we just queued is actually dispatched, it will
+ // send an IPC event in-process that will collect the information and queue the event
+ // at that time.
+ // @dbgtodo : I don't think the above is true anymore - clean it up?
+
+ RSExtSmartPtr<IStream> pSymbolStream;
+
+ // ICorDebug has no public way to request raw symbols. This is by-design because we
+ // don't want people taking a dependency on a specific format (to give us the ability
+ // to innovate for the RefEmit case). So we must use a private hook here to get the
+ // symbol data.
+ CordbModule * pCordbModule = static_cast<CordbModule *>(pModule);
+ IDacDbiInterface::SymbolFormat symFormat = IDacDbiInterface::kSymbolFormatNone;
+ EX_TRY
+ {
+ symFormat = pCordbModule->GetInMemorySymbolStream(&pSymbolStream);
+ }
+ EX_CATCH_HRESULT(hr);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr); // Shouldn't be any errors trying to read symbols
+
+ // Only pass the raw symbols onto the debugger if they're in PDB format (all that was supported
+ // in V2). Note that we could have avoided creating a stream for the non-PDB case, but we'd have
+ // to refactor GetInMemorySymbolStream and the perf impact should be negligable.
+ if (symFormat == IDacDbiInterface::kSymbolFormatPDB)
+ {
+ _ASSERTE(pSymbolStream != NULL); // symFormat should have been kSymbolFormatNone if null stream
+ GetShimCallback()->UpdateModuleSymbols(pAppDomain, pModule, pSymbolStream);
+ }
+
+ }
+ delete [] pModules;
+}
+
+//---------------------------------------------------------------------------------------
+// Get an array of appdomains, sorted by increasing AppDomain ID
+//
+// Arguments:
+// pProcess - process containing the appdomains
+// ppAppDomains - array that this function will allocate to hold appdomains
+// pCount - size of ppAppDomains array
+//
+// Assumptions:
+// Caller must delete [] ppAppDomains
+//
+// Notes
+// This is used as part of code:ShimProcess::QueueFakeAttachEvents.
+// The fake attach events want appdomains in creation order. ICorDebug doesn't provide
+// this ordering in the enumerators.
+//
+// This returns the appdomains sorted in order of increasing AppDomain ID, since that's the best
+// approximation of creation order that we have.
+// @dbgtodo - determine if ICD will provide
+// ordered enumerators
+//
+HRESULT GetSortedAppDomains(ICorDebugProcess * pProcess, RSExtSmartPtr<ICorDebugAppDomain> **ppAppDomains, ULONG * pCount)
+{
+ _ASSERTE(ppAppDomains != NULL);
+
+ HRESULT hr = S_OK;
+ RSExtSmartPtr<ICorDebugAppDomainEnum> pAppEnum;
+
+ //
+ // Find the size of the array to hold all the appdomains
+ //
+ hr = pProcess->EnumerateAppDomains(&pAppEnum);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+ ULONG countAppDomains = 0;
+
+ hr = pAppEnum->GetCount(&countAppDomains);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+
+ //
+ // Allocate the array
+ //
+ RSExtSmartPtr<ICorDebugAppDomain> * pAppDomains = new RSExtSmartPtr<ICorDebugAppDomain>[countAppDomains];
+ *ppAppDomains = pAppDomains;
+ *pCount = countAppDomains;
+
+ //
+ // Load all the appdomains into the array
+ //
+ ULONG countDummy;
+ hr = pAppEnum->Next(countAppDomains, (ICorDebugAppDomain**) pAppDomains, &countDummy);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+ SIMPLIFYING_ASSUMPTION(countDummy == countAppDomains);
+
+ //
+ // Now sort them based on appdomain ID.
+ // We generally expect a very low number of appdomains (usually 1). So a n^2 sort shouldn't be a perf
+ // problem here.
+ //
+ for(ULONG i = 0; i < countAppDomains; i++)
+ {
+ ULONG32 id1;
+ hr = pAppDomains[i]->GetID(&id1);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+
+ for(ULONG j = i + 1; j < countAppDomains; j++)
+ {
+ ULONG32 id2;
+ hr = pAppDomains[j]->GetID(&id2);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+
+ if (id1 > id2)
+ {
+ // swap values
+ ICorDebugAppDomain * pTemp = pAppDomains[i];
+ pAppDomains[i].Assign(pAppDomains[j]);
+ pAppDomains[j].Assign(pTemp);
+
+ // update id1 key since it's in the outer-loop.
+ id1 = id2;
+ }
+ }
+ }
+
+
+
+ return S_OK;
+
+}
+
+//---------------------------------------------------------------------------------------
+// To emulate the V2 attach-handshake, give the shim a chance to inject fake attach events.
+//
+// Notes:
+// Do this before the queue is empty so that HasQueuedCallbacks() doesn't toggle from false to true.
+// This is called once the process is synchronized, which emulates V2 semantics on attach.
+// This may be called on the Win32Event Thread from inside of Filter, or on another thread.
+void ShimProcess::QueueFakeAttachEvents()
+{
+ // Serialize this function with Dispose()
+ RSLockHolder lockHolder(&m_ShimProcessDisposeLock);
+ if (m_fIsDisposed)
+ return;
+
+ // The fake CreateProcess is already queued. Start queuing the rest of the events.
+ // The target is stopped (synchronized) this whole time.
+ // This will use the inspection API to look at the process and queue up the fake
+ // events that V2 would have sent in a similar situation. All of the callbacks to GetShimCallback()
+ // just queue up the events. The event queue is then drained as the V2 debugger calls continue.
+
+ HRESULT hr = S_OK;
+ ICorDebugProcess * pProcess = GetProcess();
+
+ //
+ // First, Queue all the Fake AppDomains
+ //
+ RSExtSmartPtr<ICorDebugAppDomain> * pAppDomains = NULL;
+ ULONG countAppDomains = 0;
+
+ hr = GetSortedAppDomains(pProcess, &pAppDomains, &countAppDomains);
+ if (FAILED(hr))
+ return;
+
+ for(ULONG i = 0; i < countAppDomains; i++)
+ {
+ // V2 expects that the debugger then attaches to each AppDomain during the Create-appdomain callback.
+ // This was done to allow for potential per-appdomain debugging. However, only-process
+ // wide debugging support was allowed in V2. The caller had to attach to all Appdomains.
+
+ GetShimCallback()->CreateAppDomain(pProcess, pAppDomains[i]);
+ AddDuplicateCreationEvent(pAppDomains[i]);
+ }
+
+ // V2 had a break in the callback queue at this point.
+
+ //
+ // Second, queue all Assembly and Modules events.
+ //
+
+ for(ULONG iAppDomain = 0; iAppDomain < countAppDomains; iAppDomain++)
+ {
+ ICorDebugAppDomain * pAppDomain = pAppDomains[iAppDomain];
+ //
+ // Send Assemblies. Must be in load order.
+ //
+
+ RSExtSmartPtr<ICorDebugAssemblyEnum> pAssemblyEnum;
+ hr = pAppDomain->EnumerateAssemblies(&pAssemblyEnum);
+ if (FAILED(hr))
+ break;
+
+ ULONG countAssemblies;
+ hr = pAssemblyEnum->GetCount(&countAssemblies);
+ if (FAILED(hr))
+ break;
+
+ RSExtSmartPtr<ICorDebugAssembly> * pAssemblies = new RSExtSmartPtr<ICorDebugAssembly> [countAssemblies];
+ m_pProcess->GetAssembliesInLoadOrder(pAppDomain, pAssemblies, countAssemblies);
+ for(ULONG iAssembly = 0; iAssembly < countAssemblies; iAssembly++)
+ {
+ QueueFakeAssemblyAndModuleEvent(pAssemblies[iAssembly]);
+ }
+ delete [] pAssemblies;
+
+ }
+
+ delete [] pAppDomains;
+
+
+ // V2 would have a break in the callback queue at this point.
+
+ // V2 would send all relevant ClassLoad events now.
+ //
+ // That includes class loads for all modules that:
+ // - are dynamic
+ // - subscribed to class load events via ICorDebugModule::EnableClassLoadCallbacks.
+ // We don't provide Class-loads in our emulation because:
+ // 1. "ClassLoad" doesn't actually mean anything here.
+ // 2. We have no way of enumerating "loaded" classes in the CLR. We could use the metadata to enumerate
+ // all classes, but that's offers no value.
+ // 3. ClassLoad is useful for dynamic modules to notify a debugger that the module changed and
+ // to update symbols; but the LoadModule/UpdateModule syms already do that.
+
+
+ //
+ // Third, Queue all Threads
+ //
+#if !defined(FEATURE_DBGIPC_TRANSPORT_DI) && !defined(FEATURE_CORESYSTEM)
+ // Use OS thread enumeration facilities to ensure that the managed thread
+ // thread order is the same as the corresponding native thread order.
+ QueueFakeThreadAttachEventsNativeOrder();
+#else
+ // Use ICorDebug to enumerate threads. The order of managed threads may
+ // not match the order the threads were created in.
+ QueueFakeThreadAttachEventsNoOrder();
+#endif
+
+ // Forth, Queue all Connections.
+ // Enumerate connections is not exposed through ICorDebug, so we need to go use a private hook on CordbProcess.
+ m_pProcess->QueueFakeConnectionEvents();
+
+ // For V2 jit-attach, the callback queue would also include the jit-attach event (Exception, UserBreak, MDA, etc).
+ // This was explicitly in the same callback queue so that a debugger would drain it as part of draining the attach
+ // events.
+
+ // In V3, on normal attach, the VM just sends a Sync-complete event.
+ // On jit-attach, the VM sends the jit-attach event and then the sync-complete.
+ // The shim just queues the fake attach events at the first event it gets from the left-side.
+ // In jit-attach, the shim will queue the fake events right before it queues the jit-attach event,
+ // thus keeping them in the same callback queue as V2 did.
+
+}
+
+// Accessor for m_attached.
+bool ShimProcess::GetAttached()
+{
+ return m_attached;
+}
+// We need to know whether we are in the CreateProcess callback to be able to
+// return the v2.0 hresults from code:CordbProcess::SetDesiredNGENCompilerFlags
+// when we are using the shim.
+//
+// Expose m_fInCreateProcess
+bool ShimProcess::GetInCreateProcess()
+{
+ return m_fInCreateProcess;
+}
+
+void ShimProcess::SetInCreateProcess(bool value)
+{
+ m_fInCreateProcess = value;
+}
+
+// We need to know whether we are in the FakeLoadModule callback to be able to
+// return the v2.0 hresults from code:CordbModule::SetJITCompilerFlags when
+// we are using the shim.
+//
+// Expose m_fInLoadModule
+bool ShimProcess::GetInLoadModule()
+{
+ return m_fInLoadModule;
+
+}
+
+void ShimProcess::SetInLoadModule(bool value)
+{
+ m_fInLoadModule = value;
+}
+
+// When we get a continue, we need to clear the flags indicating we're still in a callback
+void ShimProcess::NotifyOnContinue ()
+{
+ m_fInCreateProcess = false;
+ m_fInLoadModule = false;
+}
+
+// The RS calls this function when the stack is about to be changed in any way, e.g. continue, SetIP, etc.
+void ShimProcess::NotifyOnStackInvalidate()
+{
+ ClearAllShimStackWalk();
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Filter HResults for ICorDebugProcess2::SetDesiredNGENCompilerFlags to emualte V2 error semantics.
+// Arguments:
+// hr - V3 hresult
+//
+// Returns:
+// hresult V2 would have returned in same situation.
+HRESULT ShimProcess::FilterSetNgenHresult(HRESULT hr)
+{
+ if ((hr == CORDBG_E_MUST_BE_IN_CREATE_PROCESS) && !m_fInCreateProcess)
+ {
+ return hr;
+ }
+ if (m_attached)
+ {
+ return CORDBG_E_CANNOT_BE_ON_ATTACH;
+ }
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+// Filter HRs for ICorDebugModule::EnableJITDebugging, ICorDebugModule2::SetJITCompilerFlags
+// to emulate V2 error semantics
+//
+// Arguments:
+// hr - V3 hresult
+//
+// Returns:
+// hresult V2 would have returned in same situation.
+HRESULT ShimProcess::FilterSetJitFlagsHresult(HRESULT hr)
+{
+ if ((hr == CORDBG_E_MUST_BE_IN_LOAD_MODULE) && !m_fInLoadModule)
+ {
+ return hr;
+ }
+ if (m_attached && (hr == CORDBG_E_MUST_BE_IN_LOAD_MODULE))
+ {
+ return CORDBG_E_CANNOT_BE_ON_ATTACH;
+ }
+ return hr;
+}
+
+// ----------------------------------------------------------------------------
+// ShimProcess::LookupOrCreateShimStackWalk
+//
+// Description:
+// Find the ShimStackWalk associated with the specified ICDThread. Create one if it's not found.
+//
+// Arguments:
+// * pThread - the specified thread
+//
+// Return Value:
+// Return the ShimStackWalk associated with the specified thread.
+//
+// Notes:
+// The ShimStackWalks handed back by this function is only valid until the next time the stack is changed
+// in any way. In other words, the ShimStackWalks are valid until the next time
+// code:CordbThread::CleanupStack or code:CordbThread::MarkStackFramesDirty is called.
+//
+// ShimStackWalk and ICDThread have a 1:1 relationship. Only one ShimStackWalk will be created for any
+// given ICDThread. So if two threads in the debugger are walking the same thread in the debuggee, they
+// operate on the same ShimStackWalk. This is ok because ShimStackWalks walk the stack at creation time,
+// cache all the frames, and become read-only after creation.
+//
+// Refer to code:ShimProcess::ClearAllShimStackWalk to see how ShimStackWalks are cleared.
+//
+
+ShimStackWalk * ShimProcess::LookupOrCreateShimStackWalk(ICorDebugThread * pThread)
+{
+ ShimStackWalk * pSW = NULL;
+
+ {
+ // do the lookup under the Shim lock
+ RSLockHolder lockHolder(&m_ShimLock);
+ pSW = m_pShimStackWalkHashTable->Lookup(pThread);
+ }
+
+ if (pSW == NULL)
+ {
+ // create one if it's not found and add it to the hash table
+ NewHolder<ShimStackWalk> pNewSW(new ShimStackWalk(this, pThread));
+
+ {
+ // Do the lookup again under the Shim lock, and only add the new ShimStackWalk if no other thread
+ // has beaten us to it.
+ RSLockHolder lockHolder(&m_ShimLock);
+ pSW = m_pShimStackWalkHashTable->Lookup(pThread);
+ if (pSW == NULL)
+ {
+ m_pShimStackWalkHashTable->Add(pNewSW);
+ pSW = pNewSW;
+
+ // don't release the memory if all goes well
+ pNewSW.SuppressRelease();
+ }
+ else
+ {
+ // The NewHolder will automatically delete the ShimStackWalk when it goes out of scope.
+ }
+ }
+ }
+
+ return pSW;
+}
+
+// ----------------------------------------------------------------------------
+// ShimProcess::ClearAllShimStackWalk
+//
+// Description:
+// Remove and delete all the entries in the hash table of ShimStackWalks.
+//
+// Notes:
+// Refer to code:ShimProcess::LookupOrCreateShimStackWalk to see how ShimStackWalks are created.
+//
+
+void ShimProcess::ClearAllShimStackWalk()
+{
+ RSLockHolder lockHolder(&m_ShimLock);
+
+ // loop through all the entries in the hash table, remove them, and delete them
+ for (ShimStackWalkHashTable::Iterator pCurElem = m_pShimStackWalkHashTable->Begin(),
+ pEndElem = m_pShimStackWalkHashTable->End();
+ pCurElem != pEndElem;
+ pCurElem++)
+ {
+ ShimStackWalk * pSW = *pCurElem;
+ m_pShimStackWalkHashTable->Remove(pSW->GetThread());
+ delete pSW;
+ }
+}
+
+//---------------------------------------------------------------------------------------
+// Called before shim dispatches an event.
+//
+// Arguments:
+// fRealCreateProcessEvent - true if the shim is about to dispatch a real create process event (as opposed
+// to one faked up by the shim itself)
+// Notes:
+// This may be called from within Filter, which means we may be on the win32-event-thread.
+// This is called on all callbacks from the VM.
+// This gives us a chance to queue fake-attach events. So call it before the Jit-attach
+// event has been queued.
+void ShimProcess::PreDispatchEvent(bool fRealCreateProcessEvent /*= false*/)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ // For emulating the V2 case, we need to do additional initialization before dispatching the callback to the user.
+ if (!m_fFirstManagedEvent)
+ {
+ // Remember that we're processing the first managed event so that we only call HandleFirstRCEvent() once
+ m_fFirstManagedEvent = true;
+
+ // This can fail with the incompatable version HR. The process has already been terminated if this
+ // is the case. This will dispatch an Error callback
+ // If this fails, the process is in an undefined state.
+ // @dbgtodo ipc-block: this will go away once we get rid
+ // of the IPC block.
+ m_pProcess->FinishInitializeIPCChannel(); // throws on error
+ }
+
+ {
+ // In jit-attach cases, the first event the shim gets is the event that triggered the jit-attach.
+ // Queue up the fake events now, and then once we return, our caller will queue the jit-attach event.
+ // In the jit-attach case, this is before a sync-complete has been sent (since the sync doesn't get sent
+ // until after the jit-attach event is sent).
+ QueueFakeAttachEventsIfNeeded(fRealCreateProcessEvent);
+ }
+
+ // Always request an sync (emulates V2 behavior). If LS is not sync-ready, it will ignore the request.
+ m_pProcess->RequestSyncAtEvent();
+
+
+}
+
+// ----------------------------------------------------------------------------
+// ShimProcess::GetCLRInstanceBaseAddress
+// Finds the base address of [core]clr.dll
+// Arguments: none
+// Return value: returns the base address of [core]clr.dll if possible or NULL otherwise
+//
+CORDB_ADDRESS ShimProcess::GetCLRInstanceBaseAddress()
+{
+ CORDB_ADDRESS baseAddress = CORDB_ADDRESS(NULL);
+ DWORD dwPid = m_pLiveDataTarget->GetPid();
+
+#if defined(FEATURE_CORESYSTEM)
+ // Debugger attaching to CoreCLR via CoreCLRCreateCordbObject should have already specified CLR module address.
+ // Code that help to find it now lives in dbgshim.
+#else
+ // get a "snapshot" of all modules in the target
+ HandleHolder hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPid);
+ MODULEENTRY32 moduleEntry = { 0 };
+
+ if (hSnapshot == INVALID_HANDLE_VALUE)
+ {
+ // we haven't got a loaded CLR yet
+ baseAddress = CORDB_ADDRESS(NULL);
+ }
+ else
+ {
+ // we need to loop through the modules until we find mscorwks.dll
+ moduleEntry.dwSize = sizeof(MODULEENTRY32);
+
+ if (!Module32First(hSnapshot, &moduleEntry))
+ {
+ baseAddress = CORDB_ADDRESS(NULL);
+ }
+ else
+ {
+
+ do
+ {
+ if (!_wcsicmp(moduleEntry.szModule, MAKEDLLNAME_W(MAIN_CLR_MODULE_NAME_W)))
+ {
+ // we found it, so save the base address
+ baseAddress = PTR_TO_CORDB_ADDRESS(moduleEntry.modBaseAddr);
+ }
+ } while (Module32Next(hSnapshot, &moduleEntry));
+ }
+ }
+#endif
+ return baseAddress;
+} // ShimProcess::GetCLRInstanceBaseAddress
+
+// ----------------------------------------------------------------------------
+// ShimProcess::FindLoadedCLR
+//
+// Description:
+// Look for any CLR loaded into the process. If found, return the instance ID for it.
+//
+// Arguments:
+// * pClrInstanceId - out parameter for the instance ID of the CLR
+//
+// Return Value:
+// Returns S_OK if a CLR was found, and stores its instance ID in pClrInstanceId.
+// Otherwise returns an error code.
+//
+// Notes:
+// If there are multiple CLRs loaded in the process, the one chosen for the returned
+// instance ID is unspecified.
+//
+HRESULT ShimProcess::FindLoadedCLR(CORDB_ADDRESS * pClrInstanceId)
+{
+ *pClrInstanceId = GetCLRInstanceBaseAddress();
+
+ if (*pClrInstanceId == 0)
+ {
+ return E_UNEXPECTED;
+ }
+
+ return S_OK;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Locates DAC by finding mscordac{wks|core} next to DBI
+//
+// Return Value:
+// Returns the module handle for DAC
+// Throws on errors.
+//
+
+HMODULE ShimProcess::GetDacModule()
+{
+ HModuleHolder hDacDll;
+ PathString wszAccessDllPath;
+
+#ifdef FEATURE_PAL
+ if (!PAL_GetPALDirectoryWrapper(wszAccessDllPath))
+ {
+ ThrowLastError();
+ }
+ PCWSTR eeFlavor = MAKEDLLNAME_W(W("mscordaccore"));
+#else
+ //
+ // Load the access DLL from the same directory as the the current CLR Debugging Services DLL.
+ //
+
+ if (!WszGetModuleFileName(GetModuleInst(), wszAccessDllPath))
+ {
+ ThrowLastError();
+ }
+
+ if (!SUCCEEDED(CopySystemDirectory(wszAccessDllPath, wszAccessDllPath)))
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ // Dac Dll is named:
+ // mscordaccore.dll <-- coreclr
+ // mscordacwks.dll <-- desktop
+ PCWSTR eeFlavor =
+#if defined(FEATURE_MAIN_CLR_MODULE_USES_CORE_NAME)
+ W("mscordaccore.dll");
+#else
+ W("mscordacwks.dll");
+#endif
+
+#endif // FEATURE_PAL
+ wszAccessDllPath.Append(eeFlavor);
+
+ hDacDll.Assign(WszLoadLibrary(wszAccessDllPath));
+ if (!hDacDll)
+ {
+ DWORD dwLastError = GetLastError();
+ if (dwLastError == ERROR_MOD_NOT_FOUND)
+ {
+ // Give a more specific error in the case where we can't find the DAC dll.
+ ThrowHR(CORDBG_E_DEBUG_COMPONENT_MISSING);
+ }
+ else
+ {
+ ThrowWin32(dwLastError);
+ }
+ }
+ hDacDll.SuppressRelease();
+ return (HMODULE) hDacDll;
+}
+
+MachineInfo ShimProcess::GetMachineInfo()
+{
+ return m_machineInfo;
+}
+
+void ShimProcess::SetMarkAttachPendingEvent()
+{
+ SetEvent(m_markAttachPendingEvent);
+}
+
+void ShimProcess::SetTerminatingEvent()
+{
+ SetEvent(m_terminatingEvent);
+}
+
+RSLock * ShimProcess::GetShimLock()
+{
+ return &m_ShimLock;
+}
+
+
+bool ShimProcess::IsThreadSuspendedOrHijacked(ICorDebugThread * pThread)
+{
+ return m_pProcess->IsThreadSuspendedOrHijacked(pThread);
+}
diff --git a/src/debug/di/shimremotedatatarget.cpp b/src/debug/di/shimremotedatatarget.cpp
new file mode 100644
index 0000000000..f971b0d65a
--- /dev/null
+++ b/src/debug/di/shimremotedatatarget.cpp
@@ -0,0 +1,349 @@
+// 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.
+//*****************************************************************************
+
+//
+// File: ShimRemoteDataTarget.cpp
+//
+//*****************************************************************************
+#include "stdafx.h"
+#include "safewrap.h"
+
+#include "check.h"
+
+#include <limits.h>
+
+#include "shimpriv.h"
+#include "shimdatatarget.h"
+
+#include "dbgtransportsession.h"
+#include "dbgtransportmanager.h"
+
+
+class ShimRemoteDataTarget : public ShimDataTarget
+{
+public:
+ ShimRemoteDataTarget(DWORD processId, DbgTransportTarget * pProxy, DbgTransportSession * pTransport);
+
+ virtual ~ShimRemoteDataTarget();
+
+ virtual void Dispose();
+
+ //
+ // ICorDebugMutableDataTarget.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE GetPlatform(
+ CorDebugPlatform *pPlatform);
+
+ virtual HRESULT STDMETHODCALLTYPE ReadVirtual(
+ CORDB_ADDRESS address,
+ BYTE * pBuffer,
+ ULONG32 request,
+ ULONG32 *pcbRead);
+
+ virtual HRESULT STDMETHODCALLTYPE WriteVirtual(
+ CORDB_ADDRESS address,
+ const BYTE * pBuffer,
+ ULONG32 request);
+
+ virtual HRESULT STDMETHODCALLTYPE GetThreadContext(
+ DWORD dwThreadID,
+ ULONG32 contextFlags,
+ ULONG32 contextSize,
+ BYTE * context);
+
+ virtual HRESULT STDMETHODCALLTYPE SetThreadContext(
+ DWORD dwThreadID,
+ ULONG32 contextSize,
+ const BYTE * context);
+
+ virtual HRESULT STDMETHODCALLTYPE ContinueStatusChanged(
+ DWORD dwThreadId,
+ CORDB_CONTINUE_STATUS dwContinueStatus);
+
+ virtual HRESULT STDMETHODCALLTYPE VirtualUnwind(
+ DWORD threadId, ULONG32 contextSize, PBYTE context);
+
+private:
+ DbgTransportTarget * m_pProxy;
+ DbgTransportSession * m_pTransport;
+};
+
+
+// Helper macro to check for failure conditions at the start of data-target methods.
+#define ReturnFailureIfStateNotOk() \
+ if (m_hr != S_OK) \
+ { \
+ return m_hr; \
+ }
+
+//---------------------------------------------------------------------------------------
+//
+// This is the ctor for ShimRemoteDataTarget.
+//
+// Arguments:
+// processId - pid of live process on the remote machine
+// pProxy - connection to the debugger proxy
+// pTransport - connection to the debuggee process
+//
+
+ShimRemoteDataTarget::ShimRemoteDataTarget(DWORD processId,
+ DbgTransportTarget * pProxy,
+ DbgTransportSession * pTransport)
+{
+ m_ref = 0;
+
+ m_processId = processId;
+ m_pProxy = pProxy;
+ m_pTransport = pTransport;
+
+ m_hr = S_OK;
+
+ m_fpContinueStatusChanged = NULL;
+ m_pContinueStatusChangedUserData = NULL;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// dtor for ShimRemoteDataTarget
+//
+
+ShimRemoteDataTarget::~ShimRemoteDataTarget()
+{
+ Dispose();
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Dispose all resources and neuter the object.
+//
+// Notes:
+// Release all resources (such as the connections to the debugger proxy and the debuggee process).
+// May be called multiple times.
+// All other non-trivial APIs (eg, not IUnknown) will fail after this.
+//
+
+void ShimRemoteDataTarget::Dispose()
+{
+ if (m_pTransport != NULL)
+ {
+ m_pProxy->ReleaseTransport(m_pTransport);
+ }
+
+ m_pTransport = NULL;
+ m_hr = CORDBG_E_OBJECT_NEUTERED;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Construction method for data-target
+//
+// Arguments:
+// machineInfo - (input) the IP address of the remote machine and the port number of the debugger proxy
+// processId - (input) live OS process ID to build a data-target for.
+// ppDataTarget - (output) new data-target instance. This gets addreffed.
+//
+// Return Value:
+// S_OK on success.
+//
+// Assumptions:
+// pid is for a process on the remote machine specified by the IP address in machineInfo
+// Caller must release *ppDataTarget.
+//
+
+HRESULT BuildPlatformSpecificDataTarget(MachineInfo machineInfo,
+ DWORD processId,
+ ShimDataTarget ** ppDataTarget)
+{
+ HandleHolder hDummy;
+ HRESULT hr = E_FAIL;
+
+ ShimRemoteDataTarget * pRemoteDataTarget = NULL;
+ DbgTransportTarget * pProxy = g_pDbgTransportTarget;
+ DbgTransportSession * pTransport = NULL;
+
+ hr = pProxy->GetTransportForProcess(processId, &pTransport, &hDummy);
+ if (FAILED(hr))
+ {
+ goto Label_Exit;
+ }
+
+ if (!pTransport->WaitForSessionToOpen(10000))
+ {
+ hr = CORDBG_E_TIMEOUT;
+ goto Label_Exit;
+ }
+
+ pRemoteDataTarget = new (nothrow) ShimRemoteDataTarget(processId, pProxy, pTransport);
+ if (pRemoteDataTarget == NULL)
+ {
+ hr = E_OUTOFMEMORY;
+ goto Label_Exit;
+ }
+
+ _ASSERTE(SUCCEEDED(hr));
+ *ppDataTarget = pRemoteDataTarget;
+ pRemoteDataTarget->AddRef(); // must addref out-parameters
+
+Label_Exit:
+ if (FAILED(hr))
+ {
+ if (pRemoteDataTarget != NULL)
+ {
+ // The ShimRemoteDataTarget has ownership of the proxy and the transport,
+ // so we don't need to clean them up here.
+ delete pRemoteDataTarget;
+ }
+ else
+ {
+ if (pTransport != NULL)
+ {
+ pProxy->ReleaseTransport(pTransport);
+ }
+ }
+ }
+
+ return hr;
+}
+
+// impl of interface method ICorDebugDataTarget::GetPlatform
+HRESULT STDMETHODCALLTYPE
+ShimRemoteDataTarget::GetPlatform(
+ CorDebugPlatform *pPlatform)
+{
+#ifdef FEATURE_PAL
+ #if defined(DBG_TARGET_X86)
+ *pPlatform = CORDB_PLATFORM_POSIX_X86;
+ #elif defined(DBG_TARGET_AMD64)
+ *pPlatform = CORDB_PLATFORM_POSIX_AMD64;
+ #elif defined(DBG_TARGET_ARM)
+ *pPlatform = CORDB_PLATFORM_POSIX_ARM;
+ #elif defined(DBG_TARGET_ARM64)
+ *pPlatform = CORDB_PLATFORM_POSIX_ARM64;
+ #else
+ #error Unknown Processor.
+ #endif
+#else
+ #if defined(DBG_TARGET_X86)
+ *pPlatform = CORDB_PLATFORM_WINDOWS_X86;
+ #elif defined(DBG_TARGET_AMD64)
+ *pPlatform = CORDB_PLATFORM_WINDOWS_AMD64;
+ #elif defined(DBG_TARGET_ARM)
+ *pPlatform = CORDB_PLATFORM_WINDOWS_ARM;
+ #elif defined(DBG_TARGET_ARM64)
+ *pPlatform = CORDB_PLATFORM_WINDOWS_ARM64;
+ #else
+ #error Unknown Processor.
+ #endif
+#endif
+
+ return S_OK;
+}
+
+// impl of interface method ICorDebugDataTarget::ReadVirtual
+HRESULT STDMETHODCALLTYPE
+ShimRemoteDataTarget::ReadVirtual(
+ CORDB_ADDRESS address,
+ PBYTE pBuffer,
+ ULONG32 cbRequestSize,
+ ULONG32 *pcbRead)
+{
+ ReturnFailureIfStateNotOk();
+
+ HRESULT hr = E_FAIL;
+ hr = m_pTransport->ReadMemory(reinterpret_cast<BYTE *>(CORDB_ADDRESS_TO_PTR(address)),
+ pBuffer,
+ cbRequestSize);
+ if (pcbRead != NULL)
+ {
+ *pcbRead = (SUCCEEDED(hr) ? cbRequestSize : 0);
+ }
+ return hr;
+}
+
+// impl of interface method ICorDebugMutableDataTarget::WriteVirtual
+HRESULT STDMETHODCALLTYPE
+ShimRemoteDataTarget::WriteVirtual(
+ CORDB_ADDRESS pAddress,
+ const BYTE * pBuffer,
+ ULONG32 cbRequestSize)
+{
+ ReturnFailureIfStateNotOk();
+
+ HRESULT hr = E_FAIL;
+ hr = m_pTransport->WriteMemory(reinterpret_cast<BYTE *>(CORDB_ADDRESS_TO_PTR(pAddress)),
+ const_cast<BYTE *>(pBuffer),
+ cbRequestSize);
+ return hr;
+}
+
+// impl of interface method ICorDebugMutableDataTarget::GetThreadContext
+HRESULT STDMETHODCALLTYPE
+ShimRemoteDataTarget::GetThreadContext(
+ DWORD dwThreadID,
+ ULONG32 contextFlags,
+ ULONG32 contextSize,
+ BYTE * pContext)
+{
+ ReturnFailureIfStateNotOk();
+
+ // GetThreadContext() is currently not implemented in ShimRemoteDataTarget, which is used with our pipe transport
+ // (FEATURE_DBGIPC_TRANSPORT_DI). Pipe transport is used on POSIX system, but occasionally we can turn it on for Windows for testing,
+ // and then we'd like to have same behavior as on POSIX system (zero context).
+ //
+ // We don't have a good way to implement GetThreadContext() in ShimRemoteDataTarget yet, because we have no way to convert a thread ID to a
+ // thread handle. The function to do the conversion is OpenThread(), which is not implemented in PAL. Even if we had a handle, PAL implementation
+ // of GetThreadContext() is very limited and doesn't work when we're not attached with ptrace.
+ // Instead, we just zero out the seed CONTEXT for the stackwalk. This tells the stackwalker to
+ // start the stackwalk with the first explicit frame. This won't work when we do native debugging,
+ // but that won't happen on the POSIX systems since they don't support native debugging.
+ ZeroMemory(pContext, contextSize);
+ return E_NOTIMPL;
+}
+
+// impl of interface method ICorDebugMutableDataTarget::SetThreadContext
+HRESULT STDMETHODCALLTYPE
+ShimRemoteDataTarget::SetThreadContext(
+ DWORD dwThreadID,
+ ULONG32 contextSize,
+ const BYTE * pContext)
+{
+ ReturnFailureIfStateNotOk();
+
+ // ICorDebugDataTarget::GetThreadContext() and ICorDebugDataTarget::SetThreadContext() are currently only
+ // required for interop-debugging and inspection of floating point registers, both of which are not
+ // implemented on Mac.
+ _ASSERTE(!"The remote data target doesn't know how to set a thread's CONTEXT.");
+ return E_NOTIMPL;
+}
+
+// Public implementation of ICorDebugMutableDataTarget::ContinueStatusChanged
+HRESULT STDMETHODCALLTYPE
+ShimRemoteDataTarget::ContinueStatusChanged(
+ DWORD dwThreadId,
+ CORDB_CONTINUE_STATUS dwContinueStatus)
+{
+ ReturnFailureIfStateNotOk();
+
+ _ASSERTE(!"ShimRemoteDataTarget::ContinueStatusChanged() is called unexpectedly");
+ if (m_fpContinueStatusChanged != NULL)
+ {
+ return m_fpContinueStatusChanged(m_pContinueStatusChangedUserData, dwThreadId, dwContinueStatus);
+ }
+ return E_NOTIMPL;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Unwind the stack to the next frame.
+//
+// Return Value:
+// context filled in with the next frame
+//
+HRESULT STDMETHODCALLTYPE
+ShimRemoteDataTarget::VirtualUnwind(DWORD threadId, ULONG32 contextSize, PBYTE context)
+{
+ return m_pTransport->VirtualUnwind(threadId, contextSize, context);
+}
diff --git a/src/debug/di/shimstackwalk.cpp b/src/debug/di/shimstackwalk.cpp
new file mode 100644
index 0000000000..9be1ea1a78
--- /dev/null
+++ b/src/debug/di/shimstackwalk.cpp
@@ -0,0 +1,2264 @@
+// 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.
+//
+// ShimStackWalk.cpp
+//
+
+//
+// This file contains the implementation of the Arrowhead stackwalking shim. This shim builds on top of
+// the public Arrowhead ICD stackwalking API, and it is intended to be backward-compatible with the existing
+// debuggers using the V2.0 ICD API.
+//
+// ======================================================================================
+
+#include "stdafx.h"
+#include "primitives.h"
+
+#if defined(_TARGET_X86_)
+static const ULONG32 REGISTER_X86_MAX = REGISTER_X86_FPSTACK_7 + 1;
+static const ULONG32 MAX_MASK_COUNT = (REGISTER_X86_MAX + 7) >> 3;
+#elif defined(_TARGET_AMD64_)
+static const ULONG32 REGISTER_AMD64_MAX = REGISTER_AMD64_XMM15 + 1;
+static const ULONG32 MAX_MASK_COUNT = (REGISTER_AMD64_MAX + 7) >> 3;
+#endif
+
+ShimStackWalk::ShimStackWalk(ShimProcess * pProcess, ICorDebugThread * pThread)
+ : m_pChainEnumList(NULL),
+ m_pFrameEnumList(NULL)
+{
+ // The following assignments increment the ref count.
+ m_pProcess.Assign(pProcess);
+ m_pThread.Assign(pThread);
+
+ Populate();
+}
+
+ShimStackWalk::~ShimStackWalk()
+{
+ Clear();
+}
+
+// ----------------------------------------------------------------------------
+// ShimStackWalk::Clear
+//
+// Description:
+// Clear all the memory used by this ShimStackWalk, including the array of frames, the array of chains,
+// the linked list of ShimChainEnums, and the linked list of ShimFrameEnums.
+//
+
+void ShimStackWalk::Clear()
+{
+ // call Release() on each of the ShimChains
+ for (int i = 0; i < m_stackChains.Count(); i++)
+ {
+ (*m_stackChains.Get(i))->Neuter();
+ (*m_stackChains.Get(i))->Release();
+ }
+ m_stackChains.Clear();
+
+ // call Release() on each of the ICDFrames
+ for (int i = 0; i < m_stackFrames.Count(); i++)
+ {
+ (*m_stackFrames.Get(i))->Release();
+ }
+ m_stackFrames.Clear();
+
+ // call Release() on each of the ShimChainEnums
+ while (m_pChainEnumList != NULL)
+ {
+ ShimChainEnum * pCur = m_pChainEnumList;
+ m_pChainEnumList = m_pChainEnumList->GetNext();
+ pCur->Neuter();
+ pCur->Release();
+ }
+
+ // call Release() on each of the ShimFrameEnums
+ while (m_pFrameEnumList != NULL)
+ {
+ ShimFrameEnum * pCur = m_pFrameEnumList;
+ m_pFrameEnumList = m_pFrameEnumList->GetNext();
+ pCur->Neuter();
+ pCur->Release();
+ }
+
+ // release the references
+ m_pProcess.Clear();
+ m_pThread.Clear();
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Helper used by the stackwalker to determine whether a given UM chain should be tracked
+// during the stackwalk for eventual transmission to the debugger. This function is the
+// V4 equivalent of Whidbey's code:ShouldSendUMLeafChain (which ran on the LS, from
+// Debug\EE\frameinfo.cpp).
+//
+// Note that code:ShouldSendUMLeafChain still exists today (to facilitate some in-process
+// debugging stackwalks that are still necessary). So consult the comments in
+// code:ShouldSendUMLeafChain for a more thorough discussion of why we do the checks we
+// do to decide whether to track the chain.
+//
+// Arguments:
+// pswInfo - StackWalkInfo representing the frame in question
+//
+// Return Value:
+// nonzero iff the chain should be tracked
+//
+
+BOOL ShimStackWalk::ShouldTrackUMChain(StackWalkInfo * pswInfo)
+{
+ _ASSERTE (pswInfo != NULL);
+
+ // Always track chains for non-leaf UM frames
+ if (!pswInfo->IsLeafFrame())
+ return TRUE;
+
+ // Sometimes we want to track leaf UM chains, and sometimes we don't. Check all the
+ // reasons not to track the chain, and return FALSE if any of them are hit.
+
+ CorDebugUserState threadUserState;
+ HRESULT hr = m_pThread->GetUserState(&threadUserState);
+ IfFailThrow(hr);
+
+ // ShouldSendUMLeafChain checked IsInWaitSleepJoin which is just USER_WAIT_SLEEP_JOIN
+ if ((threadUserState & USER_WAIT_SLEEP_JOIN) != 0)
+ return FALSE;
+
+ // This check is the same as Thread::IsUnstarted() from ShouldSendUMLeafChain
+ if ((threadUserState & USER_UNSTARTED) != 0)
+ return FALSE;
+
+ // This check is the same as Thread::IsDead() from ShouldSendUMLeafChain
+ if ((threadUserState & USER_STOPPED) != 0)
+ return FALSE;
+
+ // #DacShimSwWorkAround
+ //
+ // This part cannot be determined using DBI alone. We must call through to the DAC
+ // because we have no other way to get at TS_Hijacked & TS_SyncSuspended. When the
+ // rearchitecture is complete, this DAC call should be able to go away, and we
+ // should be able to use DBI for all the info we need.
+ //
+ // One might think one could avoid the DAC for TS_SyncSuspended by just checking
+ // USER_SUSPENDED, but that won't work. Although USER_SUSPENDED will be returned some
+ // of the time when TS_SyncSuspended is set, that will not be the case when the
+ // debugger must suspend the thread. Example: if the given thread is in the middle of
+ // throwing a managed exception when the debugger breaks in, then TS_SyncSuspended
+ // will be set due to the debugger's breaking, but USER_SUSPENDED will not be set, so
+ // we'll think the UM chain should be tracked, resulting in a stack that differs from
+ // Whidbey.
+ if (m_pProcess->IsThreadSuspendedOrHijacked(m_pThread))
+ return FALSE;
+
+ return TRUE;
+}
+
+// ----------------------------------------------------------------------------
+// ShimStackWalk::Populate
+//
+// Description:
+// Walk the entire stack and populate the arrays of stack frames and stack chains.
+//
+
+void ShimStackWalk::Populate()
+{
+ HRESULT hr = S_OK;
+
+ // query for the ICDThread3 interface
+ RSExtSmartPtr<ICorDebugThread3> pThread3;
+ hr = m_pThread->QueryInterface(IID_ICorDebugThread3, reinterpret_cast<void **>(&pThread3));
+ IfFailThrow(hr);
+
+ // create the ICDStackWalk
+ RSExtSmartPtr<ICorDebugStackWalk> pSW;
+ hr = pThread3->CreateStackWalk(&pSW);
+ IfFailThrow(hr);
+
+ // structs used to store information during the stackwalk
+ ChainInfo chainInfo;
+ StackWalkInfo swInfo;
+
+ // use the ICDStackWalk to retrieve the internal frames
+ hr = pThread3->GetActiveInternalFrames(0, &(swInfo.m_cInternalFrames), NULL);
+ IfFailThrow(hr);
+
+ // allocate memory for the internal frames
+ if (swInfo.m_cInternalFrames > 0)
+ {
+ // allocate memory for the array of RSExtSmartPtrs
+ swInfo.m_ppInternalFrame2.AllocOrThrow(swInfo.m_cInternalFrames);
+
+ // create a temporary buffer of raw ICDInternalFrame2 to pass to the ICD API
+ NewArrayHolder<ICorDebugInternalFrame2 *> pTmpArray(new ICorDebugInternalFrame2* [swInfo.m_cInternalFrames]);
+ hr = pThread3->GetActiveInternalFrames(swInfo.m_cInternalFrames,
+ &(swInfo.m_cInternalFrames),
+ pTmpArray);
+ IfFailThrow(hr);
+
+ // transfer the raw array to the RSExtSmartPtr array
+ for (UINT32 i = 0; i < swInfo.m_cInternalFrames; i++)
+ {
+ // Assign() increments the ref count
+ swInfo.m_ppInternalFrame2.Assign(i, pTmpArray[i]);
+ pTmpArray[i]->Release();
+ }
+ pTmpArray.Clear();
+ }
+
+ //
+ // This is basically how the loop works:
+ // 1) Determine whether we should process the next internal frame or the next stack frame.
+ // 2) If we are skipping frames, the only thing we need to do is to check whether we have reached
+ // the parent frame.
+ // 3) Process CHAIN_ENTER_MANAGED/CHAIN_ENTER_UNMANAGED chains
+ // 4) Append the frame to the cache.
+ // 5) Handle other types of chains.
+ // 6) Advance to the next frame.
+ // 7) Check if we should exit the loop.
+ //
+ while (true)
+ {
+ // reset variables used in the loop
+ swInfo.ResetForNextFrame();
+
+ // retrieve the next stack frame if it's available
+ RSExtSmartPtr<ICorDebugFrame> pFrame;
+ if (!swInfo.ExhaustedAllStackFrames())
+ {
+ hr = pSW->GetFrame(&pFrame);
+ IfFailThrow(hr);
+ }
+
+ // This next clause processes the current frame, regardless of whether it's an internal frame or a
+ // stack frame. Normally, "pFrame != NULL" is a good enough check, except for the case where we
+ // have exhausted all the stack frames but still have internal frames to process.
+ if ((pFrame != NULL) || swInfo.ExhaustedAllStackFrames())
+ {
+ // prefetch the internal frame type
+ if (!swInfo.ExhaustedAllInternalFrames())
+ {
+ swInfo.m_internalFrameType = GetInternalFrameType(swInfo.GetCurrentInternalFrame());
+ }
+
+ // We cannot have exhausted both the stack frames and the internal frames when we get to here.
+ // We should have exited the loop if we have exhausted both types of frames.
+ if (swInfo.ExhaustedAllStackFrames())
+ {
+ swInfo.m_fProcessingInternalFrame = true;
+ }
+ else if (swInfo.ExhaustedAllInternalFrames())
+ {
+ swInfo.m_fProcessingInternalFrame = false;
+ }
+ else
+ {
+ // check whether we should process the next internal frame or the next stack frame
+ swInfo.m_fProcessingInternalFrame = (CheckInternalFrame(pFrame, &swInfo, pThread3, pSW) == TRUE);
+ }
+
+ // The only thing we do while we are skipping frames is to check whether we have reached the
+ // parent frame, and we only need to check if we are processing a stack frame.
+ if (swInfo.IsSkippingFrame())
+ {
+ if (!swInfo.m_fProcessingInternalFrame)
+ {
+ // Check whether we have reached the parent frame yet.
+ RSExtSmartPtr<ICorDebugNativeFrame2> pNFrame2;
+ hr = pFrame->QueryInterface(IID_ICorDebugNativeFrame2, reinterpret_cast<void **>(&pNFrame2));
+ IfFailThrow(hr);
+
+ BOOL fIsParent = FALSE;
+ hr = swInfo.m_pChildFrame->IsMatchingParentFrame(pNFrame2, &fIsParent);
+ IfFailThrow(hr);
+
+ if (fIsParent)
+ {
+ swInfo.m_pChildFrame.Clear();
+ }
+ }
+ }
+ else if(swInfo.m_fProcessingInternalFrame && !chainInfo.m_fLeafNativeContextIsValid &&
+ swInfo.m_internalFrameType == STUBFRAME_M2U)
+ {
+ // Filter this frame out entirely
+ // This occurs because InlinedCallFrames get placed inside leaf managed methods.
+ // The frame gets erected before the native call is made and destroyed afterwards
+ // but there is a window in which the debugger could stop where the internal frame
+ // is live but we are executing jitted code. See Dev10 issue 743230
+ // It is quite possible other frames have this same pattern if the debugger were
+ // stopped right at the spot where they are being constructed. And that is
+ // just a facet of the general data structure consistency problems the debugger
+ // will always face
+ }
+ else
+ {
+ // Don't add any frame just yet. We need to deal with any unmanaged chain
+ // we are tracking first.
+
+ // track the current enter-unmanaged chain and/or enter-managed chain
+ TrackUMChain(&chainInfo, &swInfo);
+
+ if (swInfo.m_fProcessingInternalFrame)
+ {
+ // Check if this is a leaf internal frame. If so, check its frame type.
+ // In V2, code:DebuggerWalkStackProc doesn't expose chains derived from leaf internal
+ // frames of type TYPE_INTERNAL. However, V2 still exposes leaf M2U and U2M internal
+ // frames.
+ if (swInfo.IsLeafFrame())
+ {
+ if (swInfo.m_internalFrameType == STUBFRAME_EXCEPTION)
+ {
+ // We need to make sure we don't accidentally send an enter-unmanaged chain
+ // because of the leaf STUBFRAME_EXCEPTION.
+ chainInfo.CancelUMChain();
+ swInfo.m_fSkipChain = true;
+ }
+ }
+
+ _ASSERTE(!swInfo.IsSkippingFrame());
+ if (ConvertInternalFrameToDynamicMethod(&swInfo))
+ {
+ // We have just converted a STUBFRAME_JIT_COMPILATION to a
+ // STUBFRAME_LIGHTWEIGHT_FUNCTION (or to NULL). Since the latter frame type doesn't
+ // map to any chain in V2, let's skip the chain handling.
+ swInfo.m_fSkipChain = true;
+
+ // We may have converted to NULL, which means that we are dealing with an IL stub
+ // and we shouldn't expose it.
+ if (swInfo.GetCurrentInternalFrame() != NULL)
+ {
+ AppendFrame(swInfo.GetCurrentInternalFrame(), &swInfo);
+ }
+ }
+ else
+ {
+ // One more check before we append the internal frame: make sure the frame type is a
+ // V2 frame type first.
+ if (!IsV3FrameType(swInfo.m_internalFrameType))
+ {
+ AppendFrame(swInfo.GetCurrentInternalFrame(), &swInfo);
+ }
+ }
+ }
+ else
+ {
+ if (!chainInfo.m_fNeedEnterManagedChain)
+ {
+ // If we have hit any managed stack frame, then we may need to send
+ // an enter-managed chain later. Save the CONTEXT now.
+ SaveChainContext(pSW, &chainInfo, &(chainInfo.m_leafManagedContext));
+ chainInfo.m_fNeedEnterManagedChain = true;
+ }
+
+ // We are processing a stack frame.
+ // Only append the frame if it's NOT a dynamic method.
+ _ASSERTE(!swInfo.IsSkippingFrame());
+ if (ConvertStackFrameToDynamicMethod(pFrame, &swInfo))
+ {
+ // We have converted a ICDNativeFrame for an IL method without metadata to an
+ // ICDInternalFrame of type STUBFRAME_LIGHTWEIGHT_FUNCTION (or to NULL).
+ // Fortunately, we don't have to update any state here
+ // (e.g. m_fProcessingInternalFrame) because the rest of the loop doesn't care.
+ if (swInfo.GetCurrentInternalFrame() != NULL)
+ {
+ AppendFrame(swInfo.GetCurrentInternalFrame(), &swInfo);
+ }
+ }
+ else
+ {
+ AppendFrame(pFrame, &swInfo);
+ }
+
+ // If we have just processed a child frame, we should start skipping.
+ // Get the ICDNativeFrame2 pointer to check.
+ RSExtSmartPtr<ICorDebugNativeFrame2> pNFrame2;
+ hr = pFrame->QueryInterface(IID_ICorDebugNativeFrame2, reinterpret_cast<void **>(&pNFrame2));
+ IfFailThrow(hr);
+
+ if (pNFrame2 != NULL)
+ {
+ BOOL fIsChild = FALSE;
+ hr = pNFrame2->IsChild(&fIsChild);
+ IfFailThrow(hr);
+
+ if (fIsChild)
+ {
+ swInfo.m_pChildFrame.Assign(pNFrame2);
+ }
+ }
+ }
+ }
+ } // process the current frame (managed stack frame or internal frame)
+
+ // We can take care of other types of chains here, but only do so if we are not currently skipping
+ // child frames.
+ if (!swInfo.IsSkippingFrame())
+ {
+ if ((pFrame == NULL) &&
+ !swInfo.ExhaustedAllStackFrames())
+ {
+ // We are here because we are processing a native marker stack frame, not because
+ // we have exhausted all the stack frames.
+
+ // We need to save the CONTEXT to start tracking an unmanaged chain.
+ SaveChainContext(pSW, &chainInfo, &(chainInfo.m_leafNativeContext));
+ chainInfo.m_fLeafNativeContextIsValid = true;
+
+ // begin tracking UM chain if we're supposed to
+ if (ShouldTrackUMChain(&swInfo))
+ {
+ chainInfo.m_reason = CHAIN_ENTER_UNMANAGED;
+ }
+ }
+ else
+ {
+ // handle other types of chains
+ if (swInfo.m_fProcessingInternalFrame)
+ {
+ if (!swInfo.m_fSkipChain)
+ {
+ BOOL fNewChain = FALSE;
+
+ switch (swInfo.m_internalFrameType)
+ {
+ case STUBFRAME_M2U: // fall through
+ case STUBFRAME_U2M: // fall through
+ // These frame types are tracked specially.
+ break;
+
+ case STUBFRAME_APPDOMAIN_TRANSITION: // fall through
+ case STUBFRAME_LIGHTWEIGHT_FUNCTION: // fall through
+ case STUBFRAME_INTERNALCALL:
+ // These frame types don't correspond to chains.
+ break;
+
+ case STUBFRAME_FUNC_EVAL:
+ chainInfo.m_reason = CHAIN_FUNC_EVAL;
+ fNewChain = TRUE;
+ break;
+
+ case STUBFRAME_CLASS_INIT: // fall through
+ case STUBFRAME_JIT_COMPILATION:
+ // In Whidbey, these two frame types are the same.
+ chainInfo.m_reason = CHAIN_CLASS_INIT;
+ fNewChain = TRUE;
+ break;
+
+ case STUBFRAME_EXCEPTION:
+ chainInfo.m_reason = CHAIN_EXCEPTION_FILTER;
+ fNewChain = TRUE;
+ break;
+
+ case STUBFRAME_SECURITY:
+ chainInfo.m_reason = CHAIN_SECURITY;
+ fNewChain = TRUE;
+ break;
+
+ default:
+ // We can only reach this case if we have converted an IL stub to NULL.
+ _ASSERTE(swInfo.HasConvertedFrame());
+ break;
+ }
+
+ if (fNewChain)
+ {
+ chainInfo.m_rootFP = GetFramePointerForChain(swInfo.GetCurrentInternalFrame());
+ AppendChain(&chainInfo, &swInfo);
+ }
+ }
+ } // chain handling for an internl frame
+ } // chain handling for a managed stack frame or an internal frame
+ } // chain handling
+
+ // Reset the flag for leaf frame if we have processed any frame. The only case where we should
+ // not reset this flag is if the ICDStackWalk is stopped at a native stack frame on creation.
+ if (swInfo.IsLeafFrame())
+ {
+ if (swInfo.m_fProcessingInternalFrame || (pFrame != NULL))
+ {
+ swInfo.m_fLeafFrame = false;
+ }
+ }
+
+ // advance to the next frame
+ if (swInfo.m_fProcessingInternalFrame)
+ {
+ swInfo.m_curInternalFrame += 1;
+ }
+ else
+ {
+ hr = pSW->Next();
+ IfFailThrow(hr);
+
+ // check for the end of stack condition
+ if (hr == CORDBG_S_AT_END_OF_STACK)
+ {
+ // By the time we finish the stackwalk, all child frames should have been matched with their
+ // respective parent frames.
+ _ASSERTE(!swInfo.IsSkippingFrame());
+
+ swInfo.m_fExhaustedAllStackFrames = true;
+ }
+ }
+
+ // Break out of the loop if we have exhausted all the frames.
+ if (swInfo.ExhaustedAllFrames())
+ {
+ break;
+ }
+ }
+
+ // top off the stackwalk with a thread start chain
+ chainInfo.m_reason = CHAIN_THREAD_START;
+ chainInfo.m_rootFP = ROOT_MOST_FRAME; // In Whidbey, we actually use the cached stack base value.
+ AppendChain(&chainInfo, &swInfo);
+}
+
+// the caller is responsible for addref and release
+ICorDebugThread * ShimStackWalk::GetThread()
+{
+ return m_pThread;
+}
+
+// the caller is responsible for addref and release
+ShimChain * ShimStackWalk::GetChain(UINT32 index)
+{
+ if (index >= (UINT32)(m_stackChains.Count()))
+ {
+ return NULL;
+ }
+ else
+ {
+ return *(m_stackChains.Get((int)index));
+ }
+}
+
+// the caller is responsible for addref and release
+ICorDebugFrame * ShimStackWalk::GetFrame(UINT32 index)
+{
+ if (index >= (UINT32)(m_stackFrames.Count()))
+ {
+ return NULL;
+ }
+ else
+ {
+ return *(m_stackFrames.Get((int)index));
+ }
+}
+
+ULONG ShimStackWalk::GetChainCount()
+{
+ return m_stackChains.Count();
+}
+
+ULONG ShimStackWalk::GetFrameCount()
+{
+ return m_stackFrames.Count();
+}
+
+RSLock * ShimStackWalk::GetShimLock()
+{
+ return m_pProcess->GetShimLock();
+}
+
+
+// ----------------------------------------------------------------------------
+// ShimStackWalk::AddChainEnum
+//
+// Description:
+// Add the specified ShimChainEnum to the head of the linked list of ShimChainEnums on the ShimStackWalk.
+//
+// Arguments:
+// * pChainEnum - the ShimChainEnum to be added
+//
+
+void ShimStackWalk::AddChainEnum(ShimChainEnum * pChainEnum)
+{
+ pChainEnum->SetNext(m_pChainEnumList);
+ if (m_pChainEnumList != NULL)
+ {
+ m_pChainEnumList->Release();
+ }
+
+ m_pChainEnumList = pChainEnum;
+ if (m_pChainEnumList != NULL)
+ {
+ m_pChainEnumList->AddRef();
+ }
+}
+
+// ----------------------------------------------------------------------------
+// ShimStackWalk::AddFrameEnum
+//
+// Description:
+// Add the specified ShimFrameEnum to the head of the linked list of ShimFrameEnums on the ShimStackWalk.
+//
+// Arguments:
+// * pFrameEnum - the ShimFrameEnum to be added
+//
+
+void ShimStackWalk::AddFrameEnum(ShimFrameEnum * pFrameEnum)
+{
+ pFrameEnum->SetNext(m_pFrameEnumList);
+ if (m_pFrameEnumList != NULL)
+ {
+ m_pFrameEnumList->Release();
+ }
+
+ m_pFrameEnumList = pFrameEnum;
+ if (m_pFrameEnumList != NULL)
+ {
+ m_pFrameEnumList->AddRef();
+ }
+}
+
+// Return the ICDThread associated with the current ShimStackWalk as a key for ShimStackWalkHashTableTraits.
+ICorDebugThread * ShimStackWalk::GetKey()
+{
+ return m_pThread;
+}
+
+// Hash a given ICDThread, which is used as the key for ShimStackWalkHashTableTraits.
+//static
+UINT32 ShimStackWalk::Hash(ICorDebugThread * pThread)
+{
+ // just return the pointer value
+ return (UINT32)(size_t)pThread;
+}
+
+// ----------------------------------------------------------------------------
+// ShimStackWalk::IsLeafFrame
+//
+// Description:
+// Check whether the specified frame is the leaf frame.
+//
+// Arguments:
+// * pFrame - frame to be checked
+//
+// Return Value:
+// Return TRUE if the specified frame is the leaf frame.
+// Return FALSE otherwise.
+//
+// Notes:
+// * The definition of the leaf frame in V2 is the frame at the leaf of the leaf chain.
+//
+
+BOOL ShimStackWalk::IsLeafFrame(ICorDebugFrame * pFrame)
+{
+ // check if we have any chain
+ if (GetChainCount() > 0)
+ {
+ // check if the leaf chain has any frame
+ if (GetChain(0)->GetLastFrameIndex() > 0)
+ {
+ return IsSameFrame(pFrame, GetFrame(0));
+ }
+ }
+ return FALSE;
+}
+
+// ----------------------------------------------------------------------------
+// ShimStackWalk::IsSameFrame
+//
+// Description:
+// Given two ICDFrames, check if they refer to the same frame.
+// This is much more than a pointer comparison. This function actually checks the frame address,
+// the stack pointer, etc. to make sure if the frames are the same.
+//
+// Arguments:
+// * pLeft - frame to be compared
+// * pRight - frame to be compared
+//
+// Return Value:
+// Return TRUE if the two ICDFrames represent the same frame.
+//
+
+BOOL ShimStackWalk::IsSameFrame(ICorDebugFrame * pLeft, ICorDebugFrame * pRight)
+{
+ HRESULT hr = E_FAIL;
+
+ // Quick check #1: If the pointers are the same then the two frames are the same (duh!).
+ if (pLeft == pRight)
+ {
+ return TRUE;
+ }
+
+ RSExtSmartPtr<ICorDebugNativeFrame> pLeftNativeFrame;
+ hr = pLeft->QueryInterface(IID_ICorDebugNativeFrame, reinterpret_cast<void **>(&pLeftNativeFrame));
+
+ if (SUCCEEDED(hr))
+ {
+ // The left frame is a stack frame.
+ RSExtSmartPtr<ICorDebugNativeFrame> pRightNativeFrame;
+ hr = pRight->QueryInterface(IID_ICorDebugNativeFrame, reinterpret_cast<void **>(&pRightNativeFrame));
+
+ if (FAILED(hr))
+ {
+ // The right frame is NOT a stack frame.
+ return FALSE;
+ }
+ else
+ {
+ // Quick check #2: If the IPs are different then the two frames are not the same (duh!).
+ ULONG32 leftOffset;
+ ULONG32 rightOffset;
+
+ hr = pLeftNativeFrame->GetIP(&leftOffset);
+ IfFailThrow(hr);
+
+ hr = pRightNativeFrame->GetIP(&rightOffset);
+ IfFailThrow(hr);
+
+ if (leftOffset != rightOffset)
+ {
+ return FALSE;
+ }
+
+ // real check
+ CORDB_ADDRESS leftStart;
+ CORDB_ADDRESS leftEnd;
+ CORDB_ADDRESS rightStart;
+ CORDB_ADDRESS rightEnd;
+
+ hr = pLeftNativeFrame->GetStackRange(&leftStart, &leftEnd);
+ IfFailThrow(hr);
+
+ hr = pRightNativeFrame->GetStackRange(&rightStart, &rightEnd);
+ IfFailThrow(hr);
+
+ return ((leftStart == rightStart) && (leftEnd == rightEnd));
+ }
+ }
+ else
+ {
+ RSExtSmartPtr<ICorDebugInternalFrame2> pLeftInternalFrame2;
+ hr = pLeft->QueryInterface(IID_ICorDebugInternalFrame2,
+ reinterpret_cast<void **>(&pLeftInternalFrame2));
+
+ if (SUCCEEDED(hr))
+ {
+ // The left frame is an internal frame.
+ RSExtSmartPtr<ICorDebugInternalFrame2> pRightInternalFrame2;
+ hr = pRight->QueryInterface(IID_ICorDebugInternalFrame2,
+ reinterpret_cast<void **>(&pRightInternalFrame2));
+
+ if (FAILED(hr))
+ {
+ return FALSE;
+ }
+ else
+ {
+ // The right frame is also an internal frame.
+
+ // Check the frame address.
+ CORDB_ADDRESS leftFrameAddr;
+ CORDB_ADDRESS rightFrameAddr;
+
+ hr = pLeftInternalFrame2->GetAddress(&leftFrameAddr);
+ IfFailThrow(hr);
+
+ hr = pRightInternalFrame2->GetAddress(&rightFrameAddr);
+ IfFailThrow(hr);
+
+ return (leftFrameAddr == rightFrameAddr);
+ }
+ }
+
+ return FALSE;
+ }
+}
+
+// This is the shim implementation of ICDThread::EnumerateChains().
+void ShimStackWalk::EnumerateChains(ICorDebugChainEnum ** ppChainEnum)
+{
+ NewHolder<ShimChainEnum> pChainEnum(new ShimChainEnum(this, GetShimLock()));
+
+ *ppChainEnum = pChainEnum;
+ (*ppChainEnum)->AddRef();
+ AddChainEnum(pChainEnum);
+
+ pChainEnum.SuppressRelease();
+}
+
+// This is the shim implementation of ICDThread::GetActiveChain().
+void ShimStackWalk::GetActiveChain(ICorDebugChain ** ppChain)
+{
+ if (GetChainCount() == 0)
+ {
+ *ppChain = NULL;
+ }
+ else
+ {
+ *ppChain = static_cast<ICorDebugChain *>(GetChain(0));
+ (*ppChain)->AddRef();
+ }
+}
+
+// This is the shim implementation of ICDThread::GetActiveFrame().
+void ShimStackWalk::GetActiveFrame(ICorDebugFrame ** ppFrame)
+{
+ //
+ // Make sure two things:
+ // 1) We have at least one frame.
+ // 2) The leaf frame is in the leaf chain, i.e. the leaf chain is not empty.
+ //
+ if ((GetFrameCount() == 0) ||
+ (GetChain(0)->GetLastFrameIndex() == 0))
+ {
+ *ppFrame = NULL;
+ }
+ else
+ {
+ *ppFrame = GetFrame(0);
+ (*ppFrame)->AddRef();
+ }
+}
+
+// This is the shim implementation of ICDThread::GetRegisterSet().
+void ShimStackWalk::GetActiveRegisterSet(ICorDebugRegisterSet ** ppRegisterSet)
+{
+ _ASSERTE(GetChainCount() != 0);
+ _ASSERTE(GetChain(0) != NULL);
+
+ // Return the register set of the leaf chain.
+ HRESULT hr = GetChain(0)->GetRegisterSet(ppRegisterSet);
+ IfFailThrow(hr);
+}
+
+// This is the shim implementation of ICDFrame::GetChain().
+void ShimStackWalk::GetChainForFrame(ICorDebugFrame * pFrame, ICorDebugChain ** ppChain)
+{
+ CORDB_ADDRESS frameStart;
+ CORDB_ADDRESS frameEnd;
+ IfFailThrow(pFrame->GetStackRange(&frameStart, &frameEnd));
+
+ for (UINT32 i = 0; i < GetChainCount(); i++)
+ {
+ ShimChain * pCurChain = GetChain(i);
+
+ CORDB_ADDRESS chainStart;
+ CORDB_ADDRESS chainEnd;
+ IfFailThrow(pCurChain->GetStackRange(&chainStart, &chainEnd));
+
+ if ((chainStart <= frameStart) && (frameEnd <= chainEnd))
+ {
+ // We need to check the next chain as well since some chains overlap at the boundary.
+ // If the current chain is the last one, no additional checking is required.
+ if (i < (GetChainCount() - 1))
+ {
+ ShimChain * pNextChain = GetChain(i + 1);
+
+ CORDB_ADDRESS nextChainStart;
+ CORDB_ADDRESS nextChainEnd;
+ IfFailThrow(pNextChain->GetStackRange(&nextChainStart, &nextChainEnd));
+
+ if ((nextChainStart <= frameStart) && (frameEnd <= nextChainEnd))
+ {
+ // The frame lies in the stack ranges of two chains. This can only happn at the boundary.
+ if (pCurChain->GetFirstFrameIndex() == pCurChain->GetLastFrameIndex())
+ {
+ // Make sure the next chain is not empty.
+ _ASSERTE(pNextChain->GetFirstFrameIndex() != pNextChain->GetLastFrameIndex());
+
+ // The current chain is empty, so the chain we want is the next one.
+ pCurChain = pNextChain;
+ }
+ // If the next chain is empty, then we'll just return the current chain and no additional
+ // work is needed. If the next chain is not empty, then we have more checking to do.
+ else if (pNextChain->GetFirstFrameIndex() != pNextChain->GetLastFrameIndex())
+ {
+ // Both chains are non-empty.
+ if (IsSameFrame(GetFrame(pNextChain->GetFirstFrameIndex()), pFrame))
+ {
+ // The same frame cannot be in both chains.
+ _ASSERTE(!IsSameFrame(GetFrame(pCurChain->GetLastFrameIndex() - 1), pFrame));
+ pCurChain = pNextChain;
+ }
+ else
+ {
+ _ASSERTE(IsSameFrame(GetFrame(pCurChain->GetLastFrameIndex() - 1), pFrame));
+ }
+ }
+ }
+ }
+
+ *ppChain = static_cast<ICorDebugChain *>(pCurChain);
+ (*ppChain)->AddRef();
+ return;
+ }
+ }
+}
+
+// This is the shim implementation of ICDFrame::GetCaller().
+void ShimStackWalk::GetCallerForFrame(ICorDebugFrame * pFrame, ICorDebugFrame ** ppCallerFrame)
+{
+ for (UINT32 i = 0; i < GetChainCount(); i++)
+ {
+ ShimChain * pCurChain = GetChain(i);
+
+ for (UINT32 j = pCurChain->GetFirstFrameIndex(); j < pCurChain->GetLastFrameIndex(); j++)
+ {
+ if (IsSameFrame(GetFrame(j), pFrame))
+ {
+ // Check whether this is the last frame in the chain.
+ UINT32 callerFrameIndex = j + 1;
+ if (callerFrameIndex < pCurChain->GetLastFrameIndex())
+ {
+ *ppCallerFrame = static_cast<ICorDebugFrame *>(GetFrame(callerFrameIndex));
+ (*ppCallerFrame)->AddRef();
+ }
+ else
+ {
+ *ppCallerFrame = NULL;
+ }
+ return;
+ }
+ }
+ }
+}
+
+// This is the shim implementation of ICDFrame::GetCallee().
+void ShimStackWalk::GetCalleeForFrame(ICorDebugFrame * pFrame, ICorDebugFrame ** ppCalleeFrame)
+{
+ for (UINT32 i = 0; i < GetChainCount(); i++)
+ {
+ ShimChain * pCurChain = GetChain(i);
+
+ for (UINT32 j = pCurChain->GetFirstFrameIndex(); j < pCurChain->GetLastFrameIndex(); j++)
+ {
+ if (IsSameFrame(GetFrame(j), pFrame))
+ {
+ // Check whether this is the first frame in the chain.
+ if (j > pCurChain->GetFirstFrameIndex())
+ {
+ UINT32 calleeFrameIndex = j - 1;
+ *ppCalleeFrame = static_cast<ICorDebugFrame *>(GetFrame(calleeFrameIndex));
+ (*ppCalleeFrame)->AddRef();
+ }
+ else
+ {
+ *ppCalleeFrame = NULL;
+ }
+ return;
+ }
+ }
+ }
+}
+
+FramePointer ShimStackWalk::GetFramePointerForChain(DT_CONTEXT * pContext)
+{
+ return FramePointer::MakeFramePointer(CORDbgGetSP(pContext));
+}
+
+FramePointer ShimStackWalk::GetFramePointerForChain(ICorDebugInternalFrame2 * pInternalFrame2)
+{
+ CORDB_ADDRESS frameAddr;
+ HRESULT hr = pInternalFrame2->GetAddress(&frameAddr);
+ IfFailThrow(hr);
+
+ return FramePointer::MakeFramePointer(reinterpret_cast<void *>(frameAddr));
+}
+
+CorDebugInternalFrameType ShimStackWalk::GetInternalFrameType(ICorDebugInternalFrame2 * pFrame2)
+{
+ HRESULT hr = E_FAIL;
+
+ // Retrieve the frame type of the internal frame.
+ RSExtSmartPtr<ICorDebugInternalFrame> pFrame;
+ hr = pFrame2->QueryInterface(IID_ICorDebugInternalFrame, reinterpret_cast<void **>(&pFrame));
+ IfFailThrow(hr);
+
+ CorDebugInternalFrameType type;
+ hr = pFrame->GetFrameType(&type);
+ IfFailThrow(hr);
+
+ return type;
+}
+
+// ----------------------------------------------------------------------------
+// ShimStackWalk::AppendFrame
+//
+// Description:
+// Append the specified frame to the array and increment the counter.
+//
+// Arguments:
+// * pFrame - the frame to be added
+// * pStackWalkInfo - contains information of the stackwalk
+//
+
+void ShimStackWalk::AppendFrame(ICorDebugFrame * pFrame, StackWalkInfo * pStackWalkInfo)
+{
+ // grow the
+ ICorDebugFrame ** ppFrame = m_stackFrames.AppendThrowing();
+
+ // Be careful of the AddRef() below. Once we do the addref, we need to save the pointer and
+ // explicitly release it.
+ *ppFrame = pFrame;
+ (*ppFrame)->AddRef();
+
+ pStackWalkInfo->m_cFrame += 1;
+}
+
+// ----------------------------------------------------------------------------
+// Refer to comment of the overloaded function.
+//
+
+void ShimStackWalk::AppendFrame(ICorDebugInternalFrame2 * pInternalFrame2, StackWalkInfo * pStackWalkInfo)
+{
+ RSExtSmartPtr<ICorDebugFrame> pFrame;
+ HRESULT hr = pInternalFrame2->QueryInterface(IID_ICorDebugFrame, reinterpret_cast<void **>(&pFrame));
+ IfFailThrow(hr);
+
+ AppendFrame(pFrame, pStackWalkInfo);
+}
+
+// ----------------------------------------------------------------------------
+// ShimStackWalk::AppendChainWorker
+//
+// Description:
+// Append the specified chain to the array.
+//
+// Arguments:
+// * pStackWalkInfo - contains information regarding the stackwalk
+// * pLeafContext - the leaf CONTEXT of the chain to be added
+// * fpRoot - the root boundary of the chain to be added
+// * chainReason - the chain reason of the chain to be added
+// * fIsManagedChain - whether the chain to be added is managed
+//
+
+void ShimStackWalk::AppendChainWorker(StackWalkInfo * pStackWalkInfo,
+ DT_CONTEXT * pLeafContext,
+ FramePointer fpRoot,
+ CorDebugChainReason chainReason,
+ BOOL fIsManagedChain)
+{
+ // first, create the chain
+ NewHolder<ShimChain> pChain(new ShimChain(this,
+ pLeafContext,
+ fpRoot,
+ pStackWalkInfo->m_cChain,
+ pStackWalkInfo->m_firstFrameInChain,
+ pStackWalkInfo->m_cFrame,
+ chainReason,
+ fIsManagedChain,
+ GetShimLock()));
+
+ // Grow the array and add the newly created chain.
+ // Once we call AddRef() we own the ShimChain and need to release it.
+ ShimChain ** ppChain = m_stackChains.AppendThrowing();
+ *ppChain = pChain;
+ (*ppChain)->AddRef();
+
+ // update the counters on the StackWalkInfo
+ pStackWalkInfo->m_cChain += 1;
+ pStackWalkInfo->m_firstFrameInChain = pStackWalkInfo->m_cFrame;
+
+ // If all goes well, suppress the release so that the ShimChain won't go away.
+ pChain.SuppressRelease();
+}
+
+// ----------------------------------------------------------------------------
+// ShimStackWalk::AppendChain
+//
+// Description:
+// Append the chain to the array. This function is also smart enough to send an enter-managed chain
+// if necessary. In other words, this function may append two chains at the same time.
+//
+// Arguments:
+// * pChainInfo - information on the chain to be added
+// * pStackWalkInfo - information regarding the current stackwalk
+//
+
+void ShimStackWalk::AppendChain(ChainInfo * pChainInfo, StackWalkInfo * pStackWalkInfo)
+{
+ // Check if the chain to be added is managed or not.
+ BOOL fManagedChain = FALSE;
+ if ((pChainInfo->m_reason == CHAIN_ENTER_MANAGED) ||
+ (pChainInfo->m_reason == CHAIN_CLASS_INIT) ||
+ (pChainInfo->m_reason == CHAIN_SECURITY) ||
+ (pChainInfo->m_reason == CHAIN_FUNC_EVAL))
+ {
+ fManagedChain = TRUE;
+ }
+
+ DT_CONTEXT * pChainContext = NULL;
+ if (fManagedChain)
+ {
+ // The chain to be added is managed itself. So we don't need to send an enter-managed chain.
+ pChainInfo->m_fNeedEnterManagedChain = false;
+ pChainContext = &(pChainInfo->m_leafManagedContext);
+ }
+ else
+ {
+ // The chain to be added is unmanaged. Check if we need to send an enter-managed chain.
+ if (pChainInfo->m_fNeedEnterManagedChain)
+ {
+ // We need to send an extra enter-managed chain.
+ _ASSERTE(pChainInfo->m_fLeafNativeContextIsValid);
+ BYTE * sp = reinterpret_cast<BYTE *>(CORDbgGetSP(&(pChainInfo->m_leafNativeContext)));
+#if !defined(_TARGET_ARM_) && !defined(_TARGET_ARM64_)
+ // Dev11 324806: on ARM we use the caller's SP for a frame's ending delimiter so we cannot
+ // subtract 4 bytes from the chain's ending delimiter else the frame might never be in range.
+ // TODO: revisit overlapping ranges on ARM, it would be nice to make it consistent with the other architectures.
+ sp -= sizeof(LPVOID);
+#endif
+ FramePointer fp = FramePointer::MakeFramePointer(sp);
+
+ AppendChainWorker(pStackWalkInfo,
+ &(pChainInfo->m_leafManagedContext),
+ fp,
+ CHAIN_ENTER_MANAGED,
+ TRUE);
+
+ pChainInfo->m_fNeedEnterManagedChain = false;
+ }
+ _ASSERTE(pChainInfo->m_fLeafNativeContextIsValid);
+ pChainContext = &(pChainInfo->m_leafNativeContext);
+ }
+
+ // Add the actual chain.
+ AppendChainWorker(pStackWalkInfo,
+ pChainContext,
+ pChainInfo->m_rootFP,
+ pChainInfo->m_reason,
+ fManagedChain);
+}
+
+// ----------------------------------------------------------------------------
+// ShimStackWalk::SaveChainContext
+//
+// Description:
+// Save the current CONTEXT on the ICDStackWalk into the specified CONTEXT. Also update the root end
+// of the chain on the ChainInfo.
+//
+// Arguments:
+// * pSW - the ICDStackWalk for the current stackwalk
+// * pChainInfo - the ChainInfo keeping track of the current chain
+// * pContext - the destination CONTEXT
+//
+
+void ShimStackWalk::SaveChainContext(ICorDebugStackWalk * pSW, ChainInfo * pChainInfo, DT_CONTEXT * pContext)
+{
+ HRESULT hr = pSW->GetContext(CONTEXT_FULL,
+ sizeof(*pContext),
+ NULL,
+ reinterpret_cast<BYTE *>(pContext));
+ IfFailThrow(hr);
+
+ pChainInfo->m_rootFP = GetFramePointerForChain(pContext);
+}
+
+// ----------------------------------------------------------------------------
+// ShimStackWalk::CheckInternalFrame
+//
+// Description:
+// Check whether the next frame to be processed should be the next internal frame or the next stack frame.
+//
+// Arguments:
+// * pNextStackFrame - the next stack frame
+// * pStackWalkInfo - information regarding the current stackwalk; also contains the next internal frame
+// * pThread3 - the thread we are walking
+// * pSW - the current stackwalk
+//
+// Return Value:
+// Return TRUE if we should process an internal frame next.
+//
+
+BOOL ShimStackWalk::CheckInternalFrame(ICorDebugFrame * pNextStackFrame,
+ StackWalkInfo * pStackWalkInfo,
+ ICorDebugThread3 * pThread3,
+ ICorDebugStackWalk * pSW)
+{
+ _ASSERTE(pNextStackFrame != NULL);
+ _ASSERTE(!pStackWalkInfo->ExhaustedAllInternalFrames());
+
+ HRESULT hr = E_FAIL;
+ BOOL fIsInternalFrameFirst = FALSE;
+
+ // Special handling for the case where a managed method contains a M2U internal frame.
+ // Normally only IL stubs contain M2U internal frames, but we may have inlined pinvoke calls in
+ // optimized code. In that case, we would have an InlinedCallFrame in a normal managed method on x86.
+ // On WIN64, we would have a normal NDirectMethodFrame* in a normal managed method.
+ if (pStackWalkInfo->m_internalFrameType == STUBFRAME_M2U)
+ {
+ // create a temporary ICDStackWalk
+ RSExtSmartPtr<ICorDebugStackWalk> pTmpSW;
+ hr = pThread3->CreateStackWalk(&pTmpSW);
+ IfFailThrow(hr);
+
+ // retrieve the current CONTEXT
+ DT_CONTEXT ctx;
+ ctx.ContextFlags = DT_CONTEXT_FULL;
+ hr = pSW->GetContext(ctx.ContextFlags, sizeof(ctx), NULL, reinterpret_cast<BYTE *>(&ctx));
+ IfFailThrow(hr);
+
+ // set the CONTEXT on the temporary ICDStackWalk
+ hr = pTmpSW->SetContext(SET_CONTEXT_FLAG_ACTIVE_FRAME, sizeof(ctx), reinterpret_cast<BYTE *>(&ctx));
+ IfFailThrow(hr);
+
+ // unwind the temporary ICDStackWalk by one frame
+ hr = pTmpSW->Next();
+ IfFailThrow(hr);
+
+ // Unwinding from a managed stack frame will land us either in a managed stack frame or a native
+ // stack frame. In either case, we have a CONTEXT.
+ hr = pTmpSW->GetContext(ctx.ContextFlags, sizeof(ctx), NULL, reinterpret_cast<BYTE *>(&ctx));
+ IfFailThrow(hr);
+
+ // Get the SP from the CONTEXT. This is the caller SP.
+ CORDB_ADDRESS sp = PTR_TO_CORDB_ADDRESS(CORDbgGetSP(&ctx));
+
+ // get the frame address
+ CORDB_ADDRESS frameAddr = 0;
+ hr = pStackWalkInfo->GetCurrentInternalFrame()->GetAddress(&frameAddr);
+ IfFailThrow(hr);
+
+ // Compare the frame address with the caller SP of the stack frame for the IL method without metadata.
+ fIsInternalFrameFirst = (frameAddr < sp);
+ }
+ else
+ {
+ hr = pStackWalkInfo->GetCurrentInternalFrame()->IsCloserToLeaf(pNextStackFrame, &fIsInternalFrameFirst);
+ IfFailThrow(hr);
+ }
+
+ return fIsInternalFrameFirst;
+}
+
+// ----------------------------------------------------------------------------
+// ShimStackWalk::ConvertInternalFrameToDynamicMethod
+//
+// Description:
+// In V2, PrestubMethodFrames (PMFs) are exposed as one of two things: a chain of type
+// CHAIN_CLASS_INIT in most cases, or an internal frame of type STUBFRAME_LIGHTWEIGHT_FUNCTION if
+// the method being jitted is a dynamic method. On the other hand, in Arrowhead, we consistently expose
+// PMFs as STUBFRAME_JIT_COMPILATION. This function determines if a STUBFRAME_JIT_COMPILATION should
+// be exposed, and, if so, how to expose it. In the case where conversion is necessary, this function
+// also updates the stackwalk information with the converted frame.
+//
+// Here are the rules for conversion:
+// 1) If the method being jitted is an IL stub, we set the converted frame to NULL, and we return TRUE.
+// 2) If the method being jitted is an LCG method, we set the converted frame to a
+// STUBFRAME_LIGHTWEIGHT_FUNCTION, and we return NULL.
+// 3) Otherwise, we return FALSE.
+//
+// Arguments:
+// * pStackWalkInfo - information about the current stackwalk
+//
+// Return Value:
+// Return TRUE if a conversion has taken place.
+//
+
+BOOL ShimStackWalk::ConvertInternalFrameToDynamicMethod(StackWalkInfo * pStackWalkInfo)
+{
+ HRESULT hr = E_FAIL;
+
+ // QI for ICDFrame
+ RSExtSmartPtr<ICorDebugFrame> pOriginalFrame;
+ hr = pStackWalkInfo->GetCurrentInternalFrame()->QueryInterface(
+ IID_ICorDebugFrame,
+ reinterpret_cast<void **>(&pOriginalFrame));
+ IfFailThrow(hr);
+
+ // Ask the RS to do the real work.
+ CordbThread * pThread = static_cast<CordbThread *>(m_pThread.GetValue());
+ pStackWalkInfo->m_fHasConvertedFrame = (TRUE == pThread->ConvertFrameForILMethodWithoutMetadata(
+ pOriginalFrame,
+ &(pStackWalkInfo->m_pConvertedInternalFrame2)));
+
+ if (pStackWalkInfo->HasConvertedFrame())
+ {
+ // We have a conversion.
+ if (pStackWalkInfo->GetCurrentInternalFrame() != NULL)
+ {
+ // We have a converted internal frame, so let's update the internal frame type.
+ RSExtSmartPtr<ICorDebugInternalFrame> pInternalFrame;
+ hr = pStackWalkInfo->GetCurrentInternalFrame()->QueryInterface(
+ IID_ICorDebugInternalFrame,
+ reinterpret_cast<void **>(&pInternalFrame));
+ IfFailThrow(hr);
+
+ hr = pInternalFrame->GetFrameType(&(pStackWalkInfo->m_internalFrameType));
+ IfFailThrow(hr);
+ }
+ else
+ {
+ // The method being jitted is an IL stub, so let's not expose it.
+ pStackWalkInfo->m_internalFrameType = STUBFRAME_NONE;
+ }
+ }
+
+ return pStackWalkInfo->HasConvertedFrame();
+}
+
+// ----------------------------------------------------------------------------
+// ShimStackWalk::ConvertInternalFrameToDynamicMethod
+//
+// Description:
+// In V2, LCG methods are exposed as internal frames of type STUBFRAME_LIGHTWEIGHT_FUNCTION. However,
+// in Arrowhead, LCG methods are exposed as first-class stack frames, not internal frames. Thus,
+// the shim needs to convert an ICDNativeFrame for a dynamic method in Arrowhead to an
+// ICDInternalFrame of type STUBFRAME_LIGHTWEIGHT_FUNCTION in V2. Furthermore, IL stubs are not exposed
+// in V2 at all.
+//
+// Here are the rules for conversion:
+// 1) If the stack frame is for an IL stub, we set the converted frame to NULL, and we return TRUE.
+// 2) If the stack frame is for an LCG method, we set the converted frame to a
+// STUBFRAME_LIGHTWEIGHT_FUNCTION, and we return NULL.
+// 3) Otherwise, we return FALSE.
+//
+// Arguments:
+// * pFrame - the frame to be checked and converted if necessary
+// * pStackWalkInfo - information about the current stackwalk
+//
+// Return Value:
+// Return TRUE if a conversion has taken place.
+//
+
+BOOL ShimStackWalk::ConvertStackFrameToDynamicMethod(ICorDebugFrame * pFrame, StackWalkInfo * pStackWalkInfo)
+{
+ // If this is not a dynamic method (i.e. LCG method or IL stub), then we don't need to do a conversion.
+ if (!IsILFrameWithoutMetadata(pFrame))
+ {
+ return FALSE;
+ }
+
+ // Ask the RS to do the real work.
+ CordbThread * pThread = static_cast<CordbThread *>(m_pThread.GetValue());
+ pStackWalkInfo->m_fHasConvertedFrame = (TRUE == pThread->ConvertFrameForILMethodWithoutMetadata(
+ pFrame,
+ &(pStackWalkInfo->m_pConvertedInternalFrame2)));
+
+ return pStackWalkInfo->HasConvertedFrame();
+}
+
+// ----------------------------------------------------------------------------
+// ShimStackWalk::TrackUMChain
+//
+// Description:
+// Keep track of enter-unmanaged chains. Extend or cancel the chain as necesasry.
+//
+// Arguments:
+// * pChainInfo - information on the current chain we are tracking
+// * pStackWalkInfo - information regarding the current stackwalk
+//
+// Notes:
+// * This logic is based on code:TrackUMChain on the LS.
+//
+
+void ShimStackWalk::TrackUMChain(ChainInfo * pChainInfo, StackWalkInfo * pStackWalkInfo)
+{
+ if (!pChainInfo->IsTrackingUMChain())
+ {
+ if (pStackWalkInfo->m_fProcessingInternalFrame)
+ {
+ if (pStackWalkInfo->m_internalFrameType == STUBFRAME_M2U)
+ {
+ // If we hit an M2U frame out in the wild, convert it to an enter-unmanaged chain.
+
+ // We can't hit an M2U frame without hitting a native stack frame
+ // first (we filter those). We should have already saved the CONTEXT.
+ // So just update the chain reason.
+ pChainInfo->m_reason = CHAIN_ENTER_UNMANAGED;
+ }
+ }
+ }
+
+ BOOL fCreateUMChain = FALSE;
+ if (pChainInfo->IsTrackingUMChain())
+ {
+ if (pStackWalkInfo->m_fProcessingInternalFrame)
+ {
+ // Extend the root end of the unmanaged chain.
+ pChainInfo->m_rootFP = GetFramePointerForChain(pStackWalkInfo->GetCurrentInternalFrame());
+
+ // Sometimes we may not want to show an UM chain b/c we know it's just
+ // code inside of mscorwks. (Eg: Funcevals & AD transitions both fall into this category).
+ // These are perfectly valid UM chains and we could give them if we wanted to.
+ if ((pStackWalkInfo->m_internalFrameType == STUBFRAME_APPDOMAIN_TRANSITION) ||
+ (pStackWalkInfo->m_internalFrameType == STUBFRAME_FUNC_EVAL))
+ {
+ pChainInfo->CancelUMChain();
+ }
+ else if (pStackWalkInfo->m_internalFrameType == STUBFRAME_M2U)
+ {
+ // If we hit an M2U frame, then go ahead and dispatch the UM chain now.
+ // This will likely also be an exit frame.
+ fCreateUMChain = TRUE;
+ }
+ else if ((pStackWalkInfo->m_internalFrameType == STUBFRAME_CLASS_INIT) ||
+ (pStackWalkInfo->m_internalFrameType == STUBFRAME_EXCEPTION) ||
+ (pStackWalkInfo->m_internalFrameType == STUBFRAME_SECURITY) ||
+ (pStackWalkInfo->m_internalFrameType == STUBFRAME_JIT_COMPILATION))
+ {
+ fCreateUMChain = TRUE;
+ }
+ }
+ else
+ {
+ // If we hit a managed stack frame when we are processing an unmanaged chain, then
+ // the chain is done.
+ fCreateUMChain = TRUE;
+ }
+ }
+
+ if (fCreateUMChain)
+ {
+ // check whether we get any stack range
+ _ASSERTE(pChainInfo->m_fLeafNativeContextIsValid);
+ FramePointer fpLeaf = GetFramePointerForChain(&(pChainInfo->m_leafNativeContext));
+
+ // Don't bother creating an unmanaged chain if the stack range is empty.
+ if (fpLeaf != pChainInfo->m_rootFP)
+ {
+ AppendChain(pChainInfo, pStackWalkInfo);
+ }
+ pChainInfo->CancelUMChain();
+ }
+}
+
+BOOL ShimStackWalk::IsV3FrameType(CorDebugInternalFrameType type)
+{
+ // These frame types are either new in Arrowhead or not used in V2.
+ if ((type == STUBFRAME_INTERNALCALL) ||
+ (type == STUBFRAME_CLASS_INIT) ||
+ (type == STUBFRAME_EXCEPTION) ||
+ (type == STUBFRAME_SECURITY) ||
+ (type == STUBFRAME_JIT_COMPILATION))
+ {
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+// Check whether a stack frame is for a dynamic method. The way to tell is if the stack frame has
+// an ICDNativeFrame but no ICDILFrame.
+BOOL ShimStackWalk::IsILFrameWithoutMetadata(ICorDebugFrame * pFrame)
+{
+ HRESULT hr = E_FAIL;
+
+ RSExtSmartPtr<ICorDebugNativeFrame> pNativeFrame;
+ hr = pFrame->QueryInterface(IID_ICorDebugNativeFrame, reinterpret_cast<void **>(&pNativeFrame));
+ IfFailThrow(hr);
+
+ if (pNativeFrame != NULL)
+ {
+ RSExtSmartPtr<ICorDebugILFrame> pILFrame;
+ hr = pFrame->QueryInterface(IID_ICorDebugILFrame, reinterpret_cast<void **>(&pILFrame));
+
+ if (FAILED(hr) || (pILFrame == NULL))
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+ShimStackWalk::StackWalkInfo::StackWalkInfo()
+ : m_cChain(0),
+ m_cFrame(0),
+ m_firstFrameInChain(0),
+ m_cInternalFrames(0),
+ m_curInternalFrame(0),
+ m_internalFrameType(STUBFRAME_NONE),
+ m_fExhaustedAllStackFrames(false),
+ m_fProcessingInternalFrame(false),
+ m_fSkipChain(false),
+ m_fLeafFrame(true),
+ m_fHasConvertedFrame(false)
+{
+ m_pChildFrame.Assign(NULL);
+ m_pConvertedInternalFrame2.Assign(NULL);
+}
+
+ShimStackWalk::StackWalkInfo::~StackWalkInfo()
+{
+ if (m_pChildFrame != NULL)
+ {
+ m_pChildFrame.Clear();
+ }
+
+ if (m_pConvertedInternalFrame2 != NULL)
+ {
+ m_pConvertedInternalFrame2.Clear();
+ }
+
+ if (!m_ppInternalFrame2.IsEmpty())
+ {
+ m_ppInternalFrame2.Clear();
+ }
+}
+
+void ShimStackWalk::StackWalkInfo::ResetForNextFrame()
+{
+ m_pConvertedInternalFrame2.Clear();
+ m_internalFrameType = STUBFRAME_NONE;
+ m_fProcessingInternalFrame = false;
+ m_fSkipChain = false;
+ m_fHasConvertedFrame = false;
+}
+
+// Check whether we have exhausted both internal frames and stack frames.
+bool ShimStackWalk::StackWalkInfo::ExhaustedAllFrames()
+{
+ return (ExhaustedAllStackFrames() && ExhaustedAllInternalFrames());
+}
+
+bool ShimStackWalk::StackWalkInfo::ExhaustedAllStackFrames()
+{
+ return m_fExhaustedAllStackFrames;
+}
+
+bool ShimStackWalk::StackWalkInfo::ExhaustedAllInternalFrames()
+{
+ return (m_curInternalFrame == m_cInternalFrames);
+}
+
+ICorDebugInternalFrame2 * ShimStackWalk::StackWalkInfo::GetCurrentInternalFrame()
+{
+ _ASSERTE(!ExhaustedAllInternalFrames() || HasConvertedFrame());
+
+ if (HasConvertedFrame())
+ {
+ return m_pConvertedInternalFrame2;
+ }
+ else
+ {
+ return m_ppInternalFrame2[m_curInternalFrame];
+ }
+}
+
+BOOL ShimStackWalk::StackWalkInfo::IsLeafFrame()
+{
+ return m_fLeafFrame;
+}
+
+BOOL ShimStackWalk::StackWalkInfo::IsSkippingFrame()
+{
+ return (m_pChildFrame != NULL);
+}
+
+BOOL ShimStackWalk::StackWalkInfo::HasConvertedFrame()
+{
+ return m_fHasConvertedFrame;
+}
+
+
+ShimChain::ShimChain(ShimStackWalk * pSW,
+ DT_CONTEXT * pContext,
+ FramePointer fpRoot,
+ UINT32 chainIndex,
+ UINT32 frameStartIndex,
+ UINT32 frameEndIndex,
+ CorDebugChainReason chainReason,
+ BOOL fIsManaged,
+ RSLock * pShimLock)
+ : m_context(*pContext),
+ m_fpRoot(fpRoot),
+ m_pStackWalk(pSW),
+ m_refCount(0),
+ m_chainIndex(chainIndex),
+ m_frameStartIndex(frameStartIndex),
+ m_frameEndIndex(frameEndIndex),
+ m_chainReason(chainReason),
+ m_fIsManaged(fIsManaged),
+ m_fIsNeutered(FALSE),
+ m_pShimLock(pShimLock)
+{
+}
+
+ShimChain::~ShimChain()
+{
+ _ASSERTE(IsNeutered());
+}
+
+void ShimChain::Neuter()
+{
+ m_fIsNeutered = TRUE;
+}
+
+BOOL ShimChain::IsNeutered()
+{
+ return m_fIsNeutered;
+}
+
+ULONG STDMETHODCALLTYPE ShimChain::AddRef()
+{
+ return InterlockedIncrement((LONG *)&m_refCount);
+}
+
+ULONG STDMETHODCALLTYPE ShimChain::Release()
+{
+ LONG newRefCount = InterlockedDecrement((LONG *)&m_refCount);
+ _ASSERTE(newRefCount >= 0);
+
+ if (newRefCount == 0)
+ {
+ delete this;
+ }
+ return newRefCount;
+}
+
+HRESULT ShimChain::QueryInterface(REFIID id, void ** pInterface)
+{
+ if (id == IID_ICorDebugChain)
+ {
+ *pInterface = static_cast<ICorDebugChain *>(this);
+ }
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown *>(static_cast<ICorDebugChain *>(this));
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+// Returns the thread to which this chain belongs.
+HRESULT ShimChain::GetThread(ICorDebugThread ** ppThread)
+{
+ RSLockHolder lockHolder(m_pShimLock);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppThread, ICorDebugThread **);
+
+ *ppThread = m_pStackWalk->GetThread();
+ (*ppThread)->AddRef();
+
+ return S_OK;
+}
+
+// Get the range on the stack that this chain matches against.
+// pStart is the leafmost; pEnd is the rootmost.
+// This is particularly used in interop-debugging to get native stack traces
+// for the UM portions of the stack
+HRESULT ShimChain::GetStackRange(CORDB_ADDRESS * pStart, CORDB_ADDRESS * pEnd)
+{
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ THROW_IF_NEUTERED(this);
+
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pStart, CORDB_ADDRESS *);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pEnd, CORDB_ADDRESS *);
+
+ // Return the leafmost end of the stack range.
+ // The leafmost end is represented by the register set.
+ if (pStart)
+ {
+ *pStart = PTR_TO_CORDB_ADDRESS(CORDbgGetSP(&m_context));
+ }
+
+ // Return the rootmost end of the stack range. It is represented by the frame pointer of the chain.
+ if (pEnd)
+ {
+ *pEnd = PTR_TO_CORDB_ADDRESS(m_fpRoot.GetSPValue());
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+HRESULT ShimChain::GetContext(ICorDebugContext ** ppContext)
+{
+ return E_NOTIMPL;
+}
+
+// Return the next chain which is closer to the root.
+// Currently this is just a wrapper over GetNext().
+HRESULT ShimChain::GetCaller(ICorDebugChain ** ppChain)
+{
+ RSLockHolder lockHolder(m_pShimLock);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppChain, ICorDebugChain **);
+
+ return GetNext(ppChain);
+}
+
+// Return the previous chain which is closer to the leaf.
+// Currently this is just a wrapper over GetPrevious().
+HRESULT ShimChain::GetCallee(ICorDebugChain ** ppChain)
+{
+ RSLockHolder lockHolder(m_pShimLock);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppChain, ICorDebugChain **);
+
+ return GetPrevious(ppChain);
+}
+
+// Return the previous chain which is closer to the leaf.
+HRESULT ShimChain::GetPrevious(ICorDebugChain ** ppChain)
+{
+ RSLockHolder lockHolder(m_pShimLock);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppChain, ICorDebugChain **);
+
+ *ppChain = NULL;
+ if (m_chainIndex != 0)
+ {
+ *ppChain = m_pStackWalk->GetChain(m_chainIndex - 1);
+ }
+
+ if (*ppChain != NULL)
+ {
+ (*ppChain)->AddRef();
+ }
+
+ return S_OK;
+}
+
+// Return the next chain which is closer to the root.
+HRESULT ShimChain::GetNext(ICorDebugChain ** ppChain)
+{
+ RSLockHolder lockHolder(m_pShimLock);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppChain, ICorDebugChain **);
+
+ *ppChain = m_pStackWalk->GetChain(m_chainIndex + 1);
+ if (*ppChain != NULL)
+ {
+ (*ppChain)->AddRef();
+ }
+
+ return S_OK;
+}
+
+// Return whether the chain contains frames running managed code.
+HRESULT ShimChain::IsManaged(BOOL * pManaged)
+{
+ RSLockHolder lockHolder(m_pShimLock);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pManaged, BOOL *);
+
+ *pManaged = m_fIsManaged;
+
+ return S_OK;
+}
+
+// Return an enumerator to iterate through the frames contained in this chain.
+HRESULT ShimChain::EnumerateFrames(ICorDebugFrameEnum ** ppFrames)
+{
+ RSLockHolder lockHolder(m_pShimLock);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppFrames, ICorDebugFrameEnum **);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ ShimStackWalk * pSW = GetShimStackWalk();
+ NewHolder<ShimFrameEnum> pFrameEnum(new ShimFrameEnum(pSW, this, m_frameStartIndex, m_frameEndIndex, m_pShimLock));
+
+ *ppFrames = pFrameEnum;
+ (*ppFrames)->AddRef();
+
+ // link the new ShimFramEnum into the list on the ShimStackWalk
+ pSW->AddFrameEnum(pFrameEnum);
+
+ pFrameEnum.SuppressRelease();
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+// Return an enumerator to iterate through the frames contained in this chain.
+// Note that this function will only succeed if the cached stack trace is valid.
+HRESULT ShimChain::GetActiveFrame(ICorDebugFrame ** ppFrame)
+{
+ RSLockHolder lockHolder(m_pShimLock);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppFrame, ICorDebugFrame **);
+ (*ppFrame) = NULL;
+
+ HRESULT hr = S_OK;
+
+ // Chains may be empty, so they have no active frame.
+ if (m_frameStartIndex == m_frameEndIndex)
+ {
+ *ppFrame = NULL;
+ }
+ else
+ {
+ *ppFrame = m_pStackWalk->GetFrame(m_frameStartIndex);
+ (*ppFrame)->AddRef();
+ }
+
+ return hr;
+}
+
+// Return the register set of the leaf end of the chain
+HRESULT ShimChain::GetRegisterSet(ICorDebugRegisterSet ** ppRegisters)
+{
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppRegisters, ICorDebugRegisterSet **);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ CordbThread * pThread = static_cast<CordbThread *>(m_pStackWalk->GetThread());
+
+ // This is a private hook for calling back into the RS. Alternatively, we could have created a
+ // ShimRegisterSet, but that's too much work for now.
+ pThread->CreateCordbRegisterSet(&m_context,
+ (m_chainIndex == 0),
+ m_chainReason,
+ ppRegisters);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+// Return the chain reason
+HRESULT ShimChain::GetReason(CorDebugChainReason * pReason)
+{
+ RSLockHolder lockHolder(m_pShimLock);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pReason, CorDebugChainReason *);
+
+ *pReason = m_chainReason;
+
+ return S_OK;
+}
+
+ShimStackWalk * ShimChain::GetShimStackWalk()
+{
+ return m_pStackWalk;
+}
+
+UINT32 ShimChain::GetFirstFrameIndex()
+{
+ return this->m_frameStartIndex;
+}
+
+UINT32 ShimChain::GetLastFrameIndex()
+{
+ return this->m_frameEndIndex;
+}
+
+
+ShimChainEnum::ShimChainEnum(ShimStackWalk * pSW, RSLock * pShimLock)
+ : m_pStackWalk(pSW),
+ m_pNext(NULL),
+ m_currentChainIndex(0),
+ m_refCount(0),
+ m_fIsNeutered(FALSE),
+ m_pShimLock(pShimLock)
+{
+}
+
+ShimChainEnum::~ShimChainEnum()
+{
+ _ASSERTE(IsNeutered());
+}
+
+void ShimChainEnum::Neuter()
+{
+ if (IsNeutered())
+ {
+ return;
+ }
+
+ m_fIsNeutered = TRUE;
+}
+
+BOOL ShimChainEnum::IsNeutered()
+{
+ return m_fIsNeutered;
+}
+
+
+ULONG STDMETHODCALLTYPE ShimChainEnum::AddRef()
+{
+ return InterlockedIncrement((LONG *)&m_refCount);
+}
+
+ULONG STDMETHODCALLTYPE ShimChainEnum::Release()
+{
+ LONG newRefCount = InterlockedDecrement((LONG *)&m_refCount);
+ _ASSERTE(newRefCount >= 0);
+
+ if (newRefCount == 0)
+ {
+ delete this;
+ }
+ return newRefCount;
+}
+
+HRESULT ShimChainEnum::QueryInterface(REFIID id, void ** ppInterface)
+{
+ if (id == IID_ICorDebugChainEnum)
+ {
+ *ppInterface = static_cast<ICorDebugChainEnum *>(this);
+ }
+ else if (id == IID_ICorDebugEnum)
+ {
+ *ppInterface = static_cast<ICorDebugEnum *>(static_cast<ICorDebugChainEnum *>(this));
+ }
+ else if (id == IID_IUnknown)
+ {
+ *ppInterface = static_cast<IUnknown *>(static_cast<ICorDebugChainEnum *>(this));
+ }
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+// Skip the specified number of chains.
+HRESULT ShimChainEnum::Skip(ULONG celt)
+{
+ RSLockHolder lockHolder(m_pShimLock);
+ FAIL_IF_NEUTERED(this);
+
+ // increment the index by the specified amount
+ m_currentChainIndex += celt;
+ return S_OK;
+}
+
+HRESULT ShimChainEnum::Reset()
+{
+ m_currentChainIndex = 0;
+ return S_OK;
+}
+
+// Clone the chain enumerator and set the new one to the same current chain
+HRESULT ShimChainEnum::Clone(ICorDebugEnum ** ppEnum)
+{
+ RSLockHolder lockHolder(m_pShimLock);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppEnum, ICorDebugEnum **);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ NewHolder<ShimChainEnum> pChainEnum(new ShimChainEnum(m_pStackWalk, m_pShimLock));
+
+ // set the index in the new enumerator
+ pChainEnum->m_currentChainIndex = this->m_currentChainIndex;
+
+ *ppEnum = pChainEnum;
+ (*ppEnum)->AddRef();
+ m_pStackWalk->AddChainEnum(pChainEnum);
+
+ pChainEnum.SuppressRelease();
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+// Return the number of chains on the thread
+HRESULT ShimChainEnum::GetCount(ULONG * pcChains)
+{
+ RSLockHolder lockHolder(m_pShimLock);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pcChains, ULONG *);
+
+ *pcChains = m_pStackWalk->GetChainCount();
+ return S_OK;
+}
+
+// Retrieve the next x number of chains on the thread into "chains", where x is specified by "celt".
+// "pcChainsFetched" is set to be the actual number of chains retrieved.
+// Return S_FALSE if the number of chains actually retrieved is less than the number of chains requested.
+HRESULT ShimChainEnum::Next(ULONG cChains, ICorDebugChain * rgpChains[], ULONG * pcChainsFetched)
+{
+ RSLockHolder lockHolder(m_pShimLock);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(rgpChains, ICorDebugChain *, cChains, true, true);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pcChainsFetched, ULONG *);
+
+ // if the out parameter is NULL, then we can only return one chain at a time
+ if ((pcChainsFetched == NULL) && (cChains != 1))
+ {
+ return E_INVALIDARG;
+ }
+
+ // Check for the trivial case where no chain is actually requested.
+ // This is probably a user error.
+ if (cChains == 0)
+ {
+ if (pcChainsFetched != NULL)
+ {
+ *pcChainsFetched = 0;
+ }
+ return S_OK;
+ }
+
+ ICorDebugChain ** ppCurrentChain = rgpChains;
+
+ while ((m_currentChainIndex < m_pStackWalk->GetChainCount()) &&
+ (cChains > 0))
+ {
+ *ppCurrentChain = m_pStackWalk->GetChain(m_currentChainIndex);
+ (*ppCurrentChain)->AddRef();
+
+ ppCurrentChain++; // increment the pointer into the buffer
+ m_currentChainIndex++; // increment the index
+ cChains--;
+ }
+
+ // set the number of chains actually returned
+ if (pcChainsFetched != NULL)
+ {
+ *pcChainsFetched = (ULONG)(ppCurrentChain - rgpChains);
+ }
+
+ //
+ // If we reached the end of the enumeration, but not the end
+ // of the number of requested items, we return S_FALSE.
+ //
+ if (cChains > 0)
+ {
+ return S_FALSE;
+ }
+
+ return S_OK;
+}
+
+ShimChainEnum * ShimChainEnum::GetNext()
+{
+ return m_pNext;
+}
+
+void ShimChainEnum::SetNext(ShimChainEnum * pNext)
+{
+ if (m_pNext != NULL)
+ {
+ m_pNext->Release();
+ }
+
+ m_pNext = pNext;
+
+ if (m_pNext != NULL)
+ {
+ m_pNext->AddRef();
+ }
+}
+
+
+ShimFrameEnum::ShimFrameEnum(ShimStackWalk * pSW,
+ ShimChain * pChain,
+ UINT32 frameStartIndex,
+ UINT32 frameEndIndex,
+ RSLock * pShimLock)
+ : m_pStackWalk(pSW),
+ m_pChain(pChain),
+ m_pShimLock(pShimLock),
+ m_pNext(NULL),
+ m_currentFrameIndex(frameStartIndex),
+ m_endFrameIndex(frameEndIndex),
+ m_refCount(0),
+ m_fIsNeutered(FALSE)
+{
+}
+
+ShimFrameEnum::~ShimFrameEnum()
+{
+ _ASSERTE(IsNeutered());
+}
+
+void ShimFrameEnum::Neuter()
+{
+ if (IsNeutered())
+ {
+ return;
+ }
+
+ m_fIsNeutered = TRUE;
+}
+
+BOOL ShimFrameEnum::IsNeutered()
+{
+ return m_fIsNeutered;
+}
+
+
+ULONG STDMETHODCALLTYPE ShimFrameEnum::AddRef()
+{
+ return InterlockedIncrement((LONG *)&m_refCount);
+}
+
+ULONG STDMETHODCALLTYPE ShimFrameEnum::Release()
+{
+ LONG newRefCount = InterlockedDecrement((LONG *)&m_refCount);
+ _ASSERTE(newRefCount >= 0);
+
+ if (newRefCount == 0)
+ {
+ delete this;
+ }
+ return newRefCount;
+}
+
+HRESULT ShimFrameEnum::QueryInterface(REFIID id, void ** ppInterface)
+{
+ if (id == IID_ICorDebugFrameEnum)
+ {
+ *ppInterface = static_cast<ICorDebugFrameEnum *>(this);
+ }
+ else if (id == IID_ICorDebugEnum)
+ {
+ *ppInterface = static_cast<ICorDebugEnum *>(static_cast<ICorDebugFrameEnum *>(this));
+ }
+ else if (id == IID_IUnknown)
+ {
+ *ppInterface = static_cast<IUnknown *>(static_cast<ICorDebugFrameEnum *>(this));
+ }
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+// Skip the specified number of chains.
+HRESULT ShimFrameEnum::Skip(ULONG celt)
+{
+ RSLockHolder lockHolder(m_pShimLock);
+ FAIL_IF_NEUTERED(this);
+
+ // increment the index by the specified amount
+ m_currentFrameIndex += celt;
+ return S_OK;
+}
+
+HRESULT ShimFrameEnum::Reset()
+{
+ RSLockHolder lockHolder(m_pShimLock);
+ FAIL_IF_NEUTERED(this);
+
+ m_currentFrameIndex = m_pChain->GetFirstFrameIndex();
+ return S_OK;
+}
+
+// Clone the chain enumerator and set the new one to the same current chain
+HRESULT ShimFrameEnum::Clone(ICorDebugEnum ** ppEnum)
+{
+ RSLockHolder lockHolder(m_pShimLock);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppEnum, ICorDebugEnum **);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ NewHolder<ShimFrameEnum> pFrameEnum(new ShimFrameEnum(m_pStackWalk,
+ m_pChain,
+ m_currentFrameIndex,
+ m_endFrameIndex,
+ m_pShimLock));
+
+ *ppEnum = pFrameEnum;
+ (*ppEnum)->AddRef();
+ m_pStackWalk->AddFrameEnum(pFrameEnum);
+
+ pFrameEnum.SuppressRelease();
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+// Return the number of chains on the thread
+HRESULT ShimFrameEnum::GetCount(ULONG * pcFrames)
+{
+ RSLockHolder lockHolder(m_pShimLock);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pcFrames, ULONG *);
+
+ *pcFrames = m_pChain->GetLastFrameIndex() - m_pChain->GetFirstFrameIndex();
+ return S_OK;
+}
+
+// Retrieve the next x number of chains on the thread into "chains", where x is specified by "celt".
+// "pcChainsFetched" is set to be the actual number of chains retrieved.
+// Return S_FALSE if the number of chains actually retrieved is less than the number of chains requested.
+HRESULT ShimFrameEnum::Next(ULONG cFrames, ICorDebugFrame * rgpFrames[], ULONG * pcFramesFetched)
+{
+ RSLockHolder lockHolder(m_pShimLock);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(rgpFrames, ICorDebugFrame *, cFrames, true, true);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pcFramesFetched, ULONG *);
+
+ // if the out parameter is NULL, then we can only return one chain at a time
+ if ((pcFramesFetched == NULL) && (cFrames != 1))
+ {
+ return E_INVALIDARG;
+ }
+
+ // Check for the trivial case where no chain is actually requested.
+ // This is probably a user error.
+ if (cFrames == 0)
+ {
+ if (pcFramesFetched != NULL)
+ {
+ *pcFramesFetched = 0;
+ }
+ return S_OK;
+ }
+
+ ICorDebugFrame ** ppCurrentFrame = rgpFrames;
+
+ while ((m_currentFrameIndex < m_endFrameIndex) &&
+ (cFrames > 0))
+ {
+ *ppCurrentFrame = m_pStackWalk->GetFrame(m_currentFrameIndex);
+ (*ppCurrentFrame)->AddRef();
+
+ ppCurrentFrame++; // increment the pointer into the buffer
+ m_currentFrameIndex++; // increment the index
+ cFrames--;
+ }
+
+ // set the number of chains actually returned
+ if (pcFramesFetched != NULL)
+ {
+ *pcFramesFetched = (ULONG)(ppCurrentFrame - rgpFrames);
+ }
+
+ //
+ // If we reached the end of the enumeration, but not the end
+ // of the number of requested items, we return S_FALSE.
+ //
+ if (cFrames > 0)
+ {
+ return S_FALSE;
+ }
+
+ return S_OK;
+}
+
+ShimFrameEnum * ShimFrameEnum::GetNext()
+{
+ return m_pNext;
+}
+
+void ShimFrameEnum::SetNext(ShimFrameEnum * pNext)
+{
+ if (m_pNext != NULL)
+ {
+ m_pNext->Release();
+ }
+
+ m_pNext = pNext;
+
+ if (m_pNext != NULL)
+ {
+ m_pNext->AddRef();
+ }
+}
diff --git a/src/debug/di/stdafx.cpp b/src/debug/di/stdafx.cpp
new file mode 100644
index 0000000000..91eba13666
--- /dev/null
+++ b/src/debug/di/stdafx.cpp
@@ -0,0 +1,12 @@
+// 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.
+//*****************************************************************************
+// stdafx.cpp
+//
+
+//
+// Host for precompiled header.
+//
+//*****************************************************************************
+#include "stdafx.h" // Precompiled header key.
diff --git a/src/debug/di/stdafx.h b/src/debug/di/stdafx.h
new file mode 100644
index 0000000000..fde3e77211
--- /dev/null
+++ b/src/debug/di/stdafx.h
@@ -0,0 +1,63 @@
+// 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.
+//*****************************************************************************
+// stdafx.h
+//
+
+//
+// Common include file for utility code.
+//*****************************************************************************
+#include <stdio.h>
+#include <windows.h>
+#include <winnt.h>
+
+#include <dbgtargetcontext.h>
+
+#define RIGHT_SIDE_COMPILE
+
+//-----------------------------------------------------------------------------
+// Contracts for RS threading.
+// We only do this for debug builds and not for inproc
+//-----------------------------------------------------------------------------
+#if defined(_DEBUG)
+ #define RSCONTRACTS
+#endif
+
+
+// In case of FEATURE_DBGIPC_TRANSPORT_DI we use pipe for debugger debugee communication
+// and event redirection is not needed. (won't work anyway)
+#ifndef FEATURE_DBGIPC_TRANSPORT_DI
+// Currently, we only can redirect exception events. Since real interop-debugging
+// neeeds all events, redirection can't work in real-interop.
+// However, whether we're interop-debugging is determined at runtime, so we always
+// enable at compile time and then we need a runtime check later.
+#define ENABLE_EVENT_REDIRECTION_PIPELINE
+#endif
+
+#include "ex.h"
+
+#include "sigparser.h"
+#include "corpub.h"
+#include "rspriv.h"
+
+// This is included to deal with GCC limitations around templates.
+// For GCC, if a compilation unit refers to a templated class (like Ptr<T>), GCC requires the compilation
+// unit to have T's definitions for anything that Ptr may call.
+// RsPriv.h has a RSExtSmartPtr<ShimProcess>, which will call ShimProcess::AddRef, which means the same compilation unit
+// must have the definition of ShimProcess::AddRef, and therefore the whole ShimProcess class.
+// CL.exe does not have this problem.
+// Practically, this means that anybody that includes rspriv.h must include shimpriv.h.
+#include "shimpriv.h"
+
+#ifdef _DEBUG
+#include "utilcode.h"
+#endif
+
+#ifndef _TARGET_ARM_
+#define DbiGetThreadContext(hThread, lpContext) ::GetThreadContext(hThread, (CONTEXT*)(lpContext))
+#define DbiSetThreadContext(hThread, lpContext) ::SetThreadContext(hThread, (CONTEXT*)(lpContext))
+#else
+BOOL DbiGetThreadContext(HANDLE hThread, DT_CONTEXT *lpContext);
+BOOL DbiSetThreadContext(HANDLE hThread, const DT_CONTEXT *lpContext);
+#endif
diff --git a/src/debug/di/symbolinfo.cpp b/src/debug/di/symbolinfo.cpp
new file mode 100644
index 0000000000..f16a47b974
--- /dev/null
+++ b/src/debug/di/symbolinfo.cpp
@@ -0,0 +1,1501 @@
+// 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.
+
+
+// callbacks for diasymreader when using SymConverter
+
+
+#include "stdafx.h"
+#include "symbolinfo.h"
+#include "ex.h"
+
+
+SymbolInfo::SymbolInfo()
+{
+ m_cRef=1;
+}
+
+SymbolInfo::~SymbolInfo()
+{
+ for (COUNT_T i = 0;i < m_Documents.GetCount();i++)
+ {
+ if (m_Documents.Get(i) != NULL)
+ ((ISymUnmanagedDocumentWriter*)m_Documents.Get(i))->Release();
+ }
+}
+
+HRESULT SymbolInfo::AddDocument(DWORD id, ISymUnmanagedDocumentWriter* pDocument)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr=S_OK;
+ EX_TRY
+ {
+ while(m_Documents.GetCount()<=id)
+ m_Documents.Append(NULL);
+ _ASSERTE(m_Documents.Get(id) == NULL);
+ m_Documents.Set(id,pDocument);
+ pDocument->AddRef();
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+HRESULT SymbolInfo::MapDocument(DWORD id, ISymUnmanagedDocumentWriter** pDocument)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr=E_FAIL;
+ if(m_Documents.GetCount()>id)
+ {
+ *pDocument=(ISymUnmanagedDocumentWriter*)m_Documents.Get(id);
+ if (*pDocument == NULL)
+ return E_FAIL;
+ (*pDocument)->AddRef();
+ hr=S_OK;
+ }
+ return hr;
+}
+
+HRESULT SymbolInfo::SetClassProps(mdToken cls, DWORD flags, LPCWSTR wszName, mdToken parent)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr=S_OK;
+ EX_TRY
+ {
+ if(m_Classes.Lookup(cls) == NULL)
+ {
+ NewHolder<ClassProps> classProps (new ClassProps(cls,flags,wszName,parent));
+ m_Classes.Add(classProps);
+ classProps.SuppressRelease();
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+HRESULT SymbolInfo::AddSignature(SBuffer& sig, mdSignature token)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr=S_OK;
+ EX_TRY
+ {
+ if ( m_Signatures.Lookup(sig) == NULL)
+ {
+ NewHolder<SignatureProps> sigProps (new SignatureProps(sig,token));
+ m_Signatures.Add(sigProps);
+ sigProps.SuppressRelease();
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+
+SymbolInfo::ClassProps* SymbolInfo::FindClass(mdToken cls)
+{
+ WRAPPER_NO_CONTRACT;
+ return m_Classes.Lookup(cls);
+}
+
+SymbolInfo::SignatureProps* SymbolInfo::FindSignature(SBuffer& sig)
+{
+ WRAPPER_NO_CONTRACT;
+ return m_Signatures.Lookup(sig);
+}
+
+HRESULT SymbolInfo::AddScope(ULONG32 left, ULONG32 right)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr=S_OK;
+ EX_TRY
+ {
+ if (m_Scopes.Lookup(left) == NULL)
+ {
+ NewHolder<ScopeMap> map (new ScopeMap(left,right));
+ m_Scopes.Add(map);
+ map.SuppressRelease();
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+
+}
+
+HRESULT SymbolInfo::MapScope(ULONG32 left, ULONG32* pRight)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ }
+ CONTRACTL_END;
+
+ ScopeMap* props = m_Scopes.Lookup(left);
+ if(props == NULL)
+ {
+ _ASSERTE(FALSE);
+ return E_FAIL;
+ }
+ *pRight=props->right;
+ return S_OK;
+}
+
+
+
+HRESULT SymbolInfo::SetMethodProps(mdToken method, mdToken cls, LPCWSTR wszName)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr=S_OK;
+ EX_TRY
+ {
+ m_LastMethod.method=method;
+ m_LastMethod.cls=cls;
+ m_LastMethod.wszName=wszName;
+ m_LastMethod.wszName.Normalize();
+ }
+ EX_CATCH_HRESULT(hr)
+ return hr;
+}
+
+
+// IUnknown methods
+STDMETHODIMP SymbolInfo::QueryInterface (REFIID riid, LPVOID * ppvObj)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ }
+ CONTRACTL_END;
+
+ if(ppvObj==NULL)
+ return E_POINTER;
+
+ if (riid == IID_IMetaDataEmit)
+ *ppvObj=static_cast<IMetaDataEmit*>(this);
+ else
+ if (riid == IID_IMetaDataImport)
+ *ppvObj=static_cast<IMetaDataImport*>(this);
+ else
+ if (riid == IID_IUnknown)
+ *ppvObj=static_cast<IMetaDataImport*>(this);
+ else
+ return E_NOTIMPL;
+
+ AddRef();
+ return S_OK;
+}
+
+STDMETHODIMP_(ULONG) SymbolInfo::AddRef ()
+{
+ LIMITED_METHOD_CONTRACT;
+ return InterlockedIncrement(&m_cRef);
+}
+
+STDMETHODIMP_(ULONG) SymbolInfo::Release ()
+{
+ LIMITED_METHOD_CONTRACT;
+ ULONG retval=InterlockedDecrement(&m_cRef);
+ if(retval==0)
+ delete this;
+
+ return retval;
+}
+
+STDMETHODIMP SymbolInfo::GetTypeDefProps ( // S_OK or error.
+ mdTypeDef td, // [IN] TypeDef token for inquiry.
+ __out_ecount_part_opt(cchTypeDef, pchTypeDef)
+ LPWSTR szTypeDef, // [OUT] Put name here.
+ ULONG cchTypeDef, // [IN] size of name buffer in wide chars.
+ ULONG *pchTypeDef, // [OUT] put size of name (wide chars) here.
+ DWORD *pdwTypeDefFlags, // [OUT] Put flags here.
+ mdToken *ptkExtends) // [OUT] Put base class TypeDef/TypeRef here.
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ PRECONDITION(ptkExtends==NULL);
+ PRECONDITION(CheckPointer(szTypeDef));
+ }
+ CONTRACTL_END;
+
+ if (szTypeDef == NULL)
+ return E_POINTER;
+
+ ClassProps* classInfo=FindClass(td);
+ _ASSERTE(classInfo);
+ if(classInfo == NULL)
+ return E_UNEXPECTED;
+
+ if(pdwTypeDefFlags)
+ *pdwTypeDefFlags=classInfo->flags;
+
+
+ SIZE_T cch=wcslen(classInfo->wszName)+1;
+ if (cch > ULONG_MAX)
+ return E_UNEXPECTED;
+ *pchTypeDef=(ULONG)cch;
+
+ if (cchTypeDef < cch)
+ return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
+
+ wcscpy_s(szTypeDef,cchTypeDef,classInfo->wszName);
+
+ if(pdwTypeDefFlags)
+ *pdwTypeDefFlags=classInfo->flags;
+
+
+ return S_OK;
+}
+
+STDMETHODIMP SymbolInfo::GetMethodProps (
+ mdMethodDef mb, // The method for which to get props.
+ mdTypeDef *pClass, // Put method's class here.
+ __out_ecount_part_opt(cchMethod, *pchMethod)
+ LPWSTR szMethod, // Put method's name here.
+ ULONG cchMethod, // Size of szMethod buffer in wide chars.
+ ULONG *pchMethod, // Put actual size here
+ DWORD *pdwAttr, // Put flags here.
+ PCCOR_SIGNATURE *ppvSigBlob, // [OUT] point to the blob value of meta data
+ ULONG *pcbSigBlob, // [OUT] actual size of signature blob
+ ULONG *pulCodeRVA, // [OUT] codeRVA
+ DWORD *pdwImplFlags) // [OUT] Impl. Flags
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ PRECONDITION(m_LastMethod.method==mb);
+ PRECONDITION(pClass!=NULL);
+ PRECONDITION(pchMethod!=NULL);
+
+ PRECONDITION(pdwAttr == NULL);
+ PRECONDITION(ppvSigBlob == NULL);
+ PRECONDITION(pcbSigBlob == NULL);
+ PRECONDITION(pulCodeRVA == NULL);
+ PRECONDITION(pdwImplFlags == NULL);
+ PRECONDITION(CheckPointer(szMethod));
+ }
+ CONTRACTL_END;
+
+ if (szMethod == NULL)
+ return E_POINTER;
+
+
+ *pClass=m_LastMethod.cls;
+ SIZE_T cch=wcslen(m_LastMethod.wszName)+1;
+ if(cch > ULONG_MAX)
+ return E_UNEXPECTED;
+ *pchMethod=(ULONG)cch;
+
+ if (cchMethod < cch)
+ return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
+
+ wcscpy_s(szMethod,cchMethod,m_LastMethod.wszName);
+
+ return S_OK;
+}
+
+
+STDMETHODIMP SymbolInfo::GetNestedClassProps ( // S_OK or error.
+ mdTypeDef tdNestedClass, // [IN] NestedClass token.
+ mdTypeDef *ptdEnclosingClass) // [OUT] EnclosingClass token.
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ PRECONDITION(CheckPointer(ptdEnclosingClass));
+ }
+ CONTRACTL_END;
+
+ if(ptdEnclosingClass == NULL)
+ return E_POINTER;
+
+ ClassProps* classInfo=FindClass(tdNestedClass);
+ _ASSERTE(classInfo);
+ if(classInfo == NULL)
+ return E_UNEXPECTED;
+
+ *ptdEnclosingClass=classInfo->tkEnclosing;
+
+
+ return S_OK;
+
+}
+
+
+STDMETHODIMP SymbolInfo::GetTokenFromSig ( // S_OK or error.
+ PCCOR_SIGNATURE pvSig, // [IN] Signature to define.
+ ULONG cbSig, // [IN] Size of signature data.
+ mdSignature *pmsig) // [OUT] returned signature token.
+{
+ SBuffer sig;
+ sig.SetImmutable(pvSig,cbSig);
+ SignatureProps* sigProps=FindSignature(sig);
+ _ASSERTE(sigProps);
+ if(sigProps == NULL)
+ return E_UNEXPECTED;
+
+ *pmsig=sigProps->tkSig;
+ return S_OK;
+}
+
+
+//////////////////////////////////////////////
+// All the functions below are just stubs
+
+STDMETHODIMP_(void) SymbolInfo::CloseEnum (HCORENUM hEnum)
+{
+ _ASSERTE(!"NYI");
+}
+
+STDMETHODIMP SymbolInfo::CountEnum (HCORENUM hEnum, ULONG *pulCount)
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::ResetEnum (HCORENUM hEnum, ULONG ulPos)
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::EnumTypeDefs (HCORENUM *phEnum, mdTypeDef rTypeDefs[],
+ ULONG cMax, ULONG *pcTypeDefs)
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::EnumInterfaceImpls (HCORENUM *phEnum, mdTypeDef td,
+ mdInterfaceImpl rImpls[], ULONG cMax,
+ ULONG* pcImpls)
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::EnumTypeRefs (HCORENUM *phEnum, mdTypeRef rTypeRefs[],
+ ULONG cMax, ULONG* pcTypeRefs)
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::FindTypeDefByName ( // S_OK or error.
+ LPCWSTR szTypeDef, // [IN] Name of the Type.
+ mdToken tkEnclosingClass, // [IN] TypeDef/TypeRef for Enclosing class.
+ mdTypeDef *ptd) // [OUT] Put the TypeDef token here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetScopeProps ( // S_OK or error.
+ __out_ecount_part_opt(cchName, *pchName)
+ LPWSTR szName, // [OUT] Put the name here.
+ ULONG cchName, // [IN] Size of name buffer in wide chars.
+ ULONG *pchName, // [OUT] Put size of name (wide chars) here.
+ GUID *pmvid) // [OUT, OPTIONAL] Put MVID here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetModuleFromScope ( // S_OK.
+ mdModule *pmd) // [OUT] Put mdModule token here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+
+STDMETHODIMP SymbolInfo::GetInterfaceImplProps ( // S_OK or error.
+ mdInterfaceImpl iiImpl, // [IN] InterfaceImpl token.
+ mdTypeDef *pClass, // [OUT] Put implementing class token here.
+ mdToken *ptkIface) // [OUT] Put implemented interface token here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetTypeRefProps ( // S_OK or error.
+ mdTypeRef tr, // [IN] TypeRef token.
+ mdToken *ptkResolutionScope, // [OUT] Resolution scope, ModuleRef or AssemblyRef.
+ __out_ecount_part_opt(cchName, *pchName)
+ LPWSTR szName, // [OUT] Name of the TypeRef.
+ ULONG cchName, // [IN] Size of buffer.
+ ULONG *pchName) // [OUT] Size of Name.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::ResolveTypeRef (mdTypeRef tr, REFIID riid, IUnknown **ppIScope, mdTypeDef *ptd)
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+
+STDMETHODIMP SymbolInfo::EnumMembers ( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdTypeDef cl, // [IN] TypeDef to scope the enumeration.
+ mdToken rMembers[], // [OUT] Put MemberDefs here.
+ ULONG cMax, // [IN] Max MemberDefs to put.
+ ULONG *pcTokens) // [OUT] Put # put here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::EnumMembersWithName ( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdTypeDef cl, // [IN] TypeDef to scope the enumeration.
+ LPCWSTR szName, // [IN] Limit results to those with this name.
+ mdToken rMembers[], // [OUT] Put MemberDefs here.
+ ULONG cMax, // [IN] Max MemberDefs to put.
+ ULONG *pcTokens) // [OUT] Put # put here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::EnumMethods ( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdTypeDef cl, // [IN] TypeDef to scope the enumeration.
+ mdMethodDef rMethods[], // [OUT] Put MethodDefs here.
+ ULONG cMax, // [IN] Max MethodDefs to put.
+ ULONG *pcTokens) // [OUT] Put # put here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::EnumMethodsWithName ( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdTypeDef cl, // [IN] TypeDef to scope the enumeration.
+ LPCWSTR szName, // [IN] Limit results to those with this name.
+ mdMethodDef rMethods[], // [OU] Put MethodDefs here.
+ ULONG cMax, // [IN] Max MethodDefs to put.
+ ULONG *pcTokens) // [OUT] Put # put here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::EnumFields ( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdTypeDef cl, // [IN] TypeDef to scope the enumeration.
+ mdFieldDef rFields[], // [OUT] Put FieldDefs here.
+ ULONG cMax, // [IN] Max FieldDefs to put.
+ ULONG *pcTokens) // [OUT] Put # put here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::EnumFieldsWithName ( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdTypeDef cl, // [IN] TypeDef to scope the enumeration.
+ LPCWSTR szName, // [IN] Limit results to those with this name.
+ mdFieldDef rFields[], // [OUT] Put MemberDefs here.
+ ULONG cMax, // [IN] Max MemberDefs to put.
+ ULONG *pcTokens) // [OUT] Put # put here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+
+STDMETHODIMP SymbolInfo::EnumParams ( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdMethodDef mb, // [IN] MethodDef to scope the enumeration.
+ mdParamDef rParams[], // [OUT] Put ParamDefs here.
+ ULONG cMax, // [IN] Max ParamDefs to put.
+ ULONG *pcTokens) // [OUT] Put # put here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::EnumMemberRefs ( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdToken tkParent, // [IN] Parent token to scope the enumeration.
+ mdMemberRef rMemberRefs[], // [OUT] Put MemberRefs here.
+ ULONG cMax, // [IN] Max MemberRefs to put.
+ ULONG *pcTokens) // [OUT] Put # put here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::EnumMethodImpls ( // S_OK, S_FALSE, or error
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdTypeDef td, // [IN] TypeDef to scope the enumeration.
+ mdToken rMethodBody[], // [OUT] Put Method Body tokens here.
+ mdToken rMethodDecl[], // [OUT] Put Method Declaration tokens here.
+ ULONG cMax, // [IN] Max tokens to put.
+ ULONG *pcTokens) // [OUT] Put # put here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::EnumPermissionSets ( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdToken tk, // [IN] if !NIL, token to scope the enumeration.
+ DWORD dwActions, // [IN] if !0, return only these actions.
+ mdPermission rPermission[], // [OUT] Put Permissions here.
+ ULONG cMax, // [IN] Max Permissions to put.
+ ULONG *pcTokens) // [OUT] Put # put here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::FindMember (
+ mdTypeDef td, // [IN] given typedef
+ LPCWSTR szName, // [IN] member name
+ PCCOR_SIGNATURE pvSigBlob, // [IN] point to a blob value of CLR signature
+ ULONG cbSigBlob, // [IN] count of bytes in the signature blob
+ mdToken *pmb) // [OUT] matching memberdef
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::FindMethod (
+ mdTypeDef td, // [IN] given typedef
+ LPCWSTR szName, // [IN] member name
+ PCCOR_SIGNATURE pvSigBlob, // [IN] point to a blob value of CLR signature
+ ULONG cbSigBlob, // [IN] count of bytes in the signature blob
+ mdMethodDef *pmb) // [OUT] matching memberdef
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::FindField (
+ mdTypeDef td, // [IN] given typedef
+ LPCWSTR szName, // [IN] member name
+ PCCOR_SIGNATURE pvSigBlob, // [IN] point to a blob value of CLR signature
+ ULONG cbSigBlob, // [IN] count of bytes in the signature blob
+ mdFieldDef *pmb) // [OUT] matching memberdef
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::FindMemberRef (
+ mdTypeRef td, // [IN] given typeRef
+ LPCWSTR szName, // [IN] member name
+ PCCOR_SIGNATURE pvSigBlob, // [IN] point to a blob value of CLR signature
+ ULONG cbSigBlob, // [IN] count of bytes in the signature blob
+ mdMemberRef *pmr) // [OUT] matching memberref
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+
+STDMETHODIMP SymbolInfo::GetMemberRefProps ( // S_OK or error.
+ mdMemberRef mr, // [IN] given memberref
+ mdToken *ptk, // [OUT] Put classref or classdef here.
+ __out_ecount_part_opt(cchMember, *pchMember)
+ LPWSTR szMember, // [OUT] buffer to fill for member's name
+ ULONG cchMember, // [IN] the count of char of szMember
+ ULONG *pchMember, // [OUT] actual count of char in member name
+ PCCOR_SIGNATURE *ppvSigBlob, // [OUT] point to meta data blob value
+ ULONG *pbSig) // [OUT] actual size of signature blob
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::EnumProperties ( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdTypeDef td, // [IN] TypeDef to scope the enumeration.
+ mdProperty rProperties[], // [OUT] Put Properties here.
+ ULONG cMax, // [IN] Max properties to put.
+ ULONG *pcProperties) // [OUT] Put # put here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::EnumEvents ( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdTypeDef td, // [IN] TypeDef to scope the enumeration.
+ mdEvent rEvents[], // [OUT] Put events here.
+ ULONG cMax, // [IN] Max events to put.
+ ULONG *pcEvents) // [OUT] Put # put here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetEventProps ( // S_OK, S_FALSE, or error.
+ mdEvent ev, // [IN] event token
+ mdTypeDef *pClass, // [OUT] typedef containing the event declarion.
+ LPCWSTR szEvent, // [OUT] Event name
+ ULONG cchEvent, // [IN] the count of wchar of szEvent
+ ULONG *pchEvent, // [OUT] actual count of wchar for event's name
+ DWORD *pdwEventFlags, // [OUT] Event flags.
+ mdToken *ptkEventType, // [OUT] EventType class
+ mdMethodDef *pmdAddOn, // [OUT] AddOn method of the event
+ mdMethodDef *pmdRemoveOn, // [OUT] RemoveOn method of the event
+ mdMethodDef *pmdFire, // [OUT] Fire method of the event
+ mdMethodDef rmdOtherMethod[], // [OUT] other method of the event
+ ULONG cMax, // [IN] size of rmdOtherMethod
+ ULONG *pcOtherMethod) // [OUT] total number of other method of this event
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::EnumMethodSemantics ( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdMethodDef mb, // [IN] MethodDef to scope the enumeration.
+ mdToken rEventProp[], // [OUT] Put Event/Property here.
+ ULONG cMax, // [IN] Max properties to put.
+ ULONG *pcEventProp) // [OUT] Put # put here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetMethodSemantics ( // S_OK, S_FALSE, or error.
+ mdMethodDef mb, // [IN] method token
+ mdToken tkEventProp, // [IN] event/property token.
+ DWORD *pdwSemanticsFlags) // [OUT] the role flags for the method/propevent pair
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetClassLayout (
+ mdTypeDef td, // [IN] give typedef
+ DWORD *pdwPackSize, // [OUT] 1, 2, 4, 8, or 16
+ COR_FIELD_OFFSET rFieldOffset[], // [OUT] field offset array
+ ULONG cMax, // [IN] size of the array
+ ULONG *pcFieldOffset, // [OUT] needed array size
+ ULONG *pulClassSize) // [OUT] the size of the class
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetFieldMarshal (
+ mdToken tk, // [IN] given a field's memberdef
+ PCCOR_SIGNATURE *ppvNativeType, // [OUT] native type of this field
+ ULONG *pcbNativeType) // [OUT] the count of bytes of *ppvNativeType
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetRVA ( // S_OK or error.
+ mdToken tk, // Member for which to set offset
+ ULONG *pulCodeRVA, // The offset
+ DWORD *pdwImplFlags) // the implementation flags
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetPermissionSetProps (
+ mdPermission pm, // [IN] the permission token.
+ DWORD *pdwAction, // [OUT] CorDeclSecurity.
+ void const **ppvPermission, // [OUT] permission blob.
+ ULONG *pcbPermission) // [OUT] count of bytes of pvPermission.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetSigFromToken ( // S_OK or error.
+ mdSignature mdSig, // [IN] Signature token.
+ PCCOR_SIGNATURE *ppvSig, // [OUT] return pointer to token.
+ ULONG *pcbSig) // [OUT] return size of signature.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetModuleRefProps ( // S_OK or error.
+ mdModuleRef mur, // [IN] moduleref token.
+ __out_ecount_part_opt(cchName, *pchName)
+ LPWSTR szName, // [OUT] buffer to fill with the moduleref name.
+ ULONG cchName, // [IN] size of szName in wide characters.
+ ULONG *pchName) // [OUT] actual count of characters in the name.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::EnumModuleRefs ( // S_OK or error.
+ HCORENUM *phEnum, // [IN|OUT] pointer to the enum.
+ mdModuleRef rModuleRefs[], // [OUT] put modulerefs here.
+ ULONG cmax, // [IN] max memberrefs to put.
+ ULONG *pcModuleRefs) // [OUT] put # put here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetTypeSpecFromToken ( // S_OK or error.
+ mdTypeSpec typespec, // [IN] TypeSpec token.
+ PCCOR_SIGNATURE *ppvSig, // [OUT] return pointer to TypeSpec signature
+ ULONG *pcbSig) // [OUT] return size of signature.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetNameFromToken ( // Not Recommended! May be removed!
+ mdToken tk, // [IN] Token to get name from. Must have a name.
+ MDUTF8CSTR *pszUtf8NamePtr) // [OUT] Return pointer to UTF8 name in heap.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::EnumUnresolvedMethods ( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdToken rMethods[], // [OUT] Put MemberDefs here.
+ ULONG cMax, // [IN] Max MemberDefs to put.
+ ULONG *pcTokens) // [OUT] Put # put here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetUserString ( // S_OK or error.
+ mdString stk, // [IN] String token.
+ __out_ecount_part_opt(cchString, *pchString)
+ LPWSTR szString, // [OUT] Copy of string.
+ ULONG cchString, // [IN] Max chars of room in szString.
+ ULONG *pchString) // [OUT] How many chars in actual string.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetPinvokeMap ( // S_OK or error.
+ mdToken tk, // [IN] FieldDef or MethodDef.
+ DWORD *pdwMappingFlags, // [OUT] Flags used for mapping.
+ __out_ecount_part_opt(cchImportName, *pchImportName)
+ LPWSTR szImportName, // [OUT] Import name.
+ ULONG cchImportName, // [IN] Size of the name buffer.
+ ULONG *pchImportName, // [OUT] Actual number of characters stored.
+ mdModuleRef *pmrImportDLL) // [OUT] ModuleRef token for the target DLL.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::EnumSignatures ( // S_OK or error.
+ HCORENUM *phEnum, // [IN|OUT] pointer to the enum.
+ mdSignature rSignatures[], // [OUT] put signatures here.
+ ULONG cmax, // [IN] max signatures to put.
+ ULONG *pcSignatures) // [OUT] put # put here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::EnumTypeSpecs ( // S_OK or error.
+ HCORENUM *phEnum, // [IN|OUT] pointer to the enum.
+ mdTypeSpec rTypeSpecs[], // [OUT] put TypeSpecs here.
+ ULONG cmax, // [IN] max TypeSpecs to put.
+ ULONG *pcTypeSpecs) // [OUT] put # put here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::EnumUserStrings ( // S_OK or error.
+ HCORENUM *phEnum, // [IN/OUT] pointer to the enum.
+ mdString rStrings[], // [OUT] put Strings here.
+ ULONG cmax, // [IN] max Strings to put.
+ ULONG *pcStrings) // [OUT] put # put here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetParamForMethodIndex ( // S_OK or error.
+ mdMethodDef md, // [IN] Method token.
+ ULONG ulParamSeq, // [IN] Parameter sequence.
+ mdParamDef *ppd) // [IN] Put Param token here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::EnumCustomAttributes ( // S_OK or error.
+ HCORENUM *phEnum, // [IN, OUT] COR enumerator.
+ mdToken tk, // [IN] Token to scope the enumeration, 0 for all.
+ mdToken tkType, // [IN] Type of interest, 0 for all.
+ mdCustomAttribute rCustomAttributes[], // [OUT] Put custom attribute tokens here.
+ ULONG cMax, // [IN] Size of rCustomAttributes.
+ ULONG *pcCustomAttributes) // [OUT, OPTIONAL] Put count of token values here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetCustomAttributeProps ( // S_OK or error.
+ mdCustomAttribute cv, // [IN] CustomAttribute token.
+ mdToken *ptkObj, // [OUT, OPTIONAL] Put object token here.
+ mdToken *ptkType, // [OUT, OPTIONAL] Put AttrType token here.
+ void const **ppBlob, // [OUT, OPTIONAL] Put pointer to data here.
+ ULONG *pcbSize) // [OUT, OPTIONAL] Put size of date here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::FindTypeRef (
+ mdToken tkResolutionScope, // [IN] ModuleRef, AssemblyRef or TypeRef.
+ LPCWSTR szName, // [IN] TypeRef Name.
+ mdTypeRef *ptr) // [OUT] matching TypeRef.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetMemberProps (
+ mdToken mb, // The member for which to get props.
+ mdTypeDef *pClass, // Put member's class here.
+ __out_ecount_part_opt(cchMember, *pchMember)
+ LPWSTR szMember, // Put member's name here.
+ ULONG cchMember, // Size of szMember buffer in wide chars.
+ ULONG *pchMember, // Put actual size here
+ DWORD *pdwAttr, // Put flags here.
+ PCCOR_SIGNATURE *ppvSigBlob, // [OUT] point to the blob value of meta data
+ ULONG *pcbSigBlob, // [OUT] actual size of signature blob
+ ULONG *pulCodeRVA, // [OUT] codeRVA
+ DWORD *pdwImplFlags, // [OUT] Impl. Flags
+ DWORD *pdwCPlusTypeFlag, // [OUT] flag for value type. selected ELEMENT_TYPE_*
+ UVCP_CONSTANT *ppValue, // [OUT] constant value
+ ULONG *pcchValue) // [OUT] size of constant string in chars, 0 for non-strings.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetFieldProps (
+ mdFieldDef mb, // The field for which to get props.
+ mdTypeDef *pClass, // Put field's class here.
+ __out_ecount_part_opt(cchField, *pchField)
+ LPWSTR szField, // Put field's name here.
+ ULONG cchField, // Size of szField buffer in wide chars.
+ ULONG *pchField, // Put actual size here
+ DWORD *pdwAttr, // Put flags here.
+ PCCOR_SIGNATURE *ppvSigBlob, // [OUT] point to the blob value of meta data
+ ULONG *pcbSigBlob, // [OUT] actual size of signature blob
+ DWORD *pdwCPlusTypeFlag, // [OUT] flag for value type. selected ELEMENT_TYPE_*
+ UVCP_CONSTANT *ppValue, // [OUT] constant value
+ ULONG *pcchValue) // [OUT] size of constant string in chars, 0 for non-strings.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetPropertyProps ( // S_OK, S_FALSE, or error.
+ mdProperty prop, // [IN] property token
+ mdTypeDef *pClass, // [OUT] typedef containing the property declarion.
+ LPCWSTR szProperty, // [OUT] Property name
+ ULONG cchProperty, // [IN] the count of wchar of szProperty
+ ULONG *pchProperty, // [OUT] actual count of wchar for property name
+ DWORD *pdwPropFlags, // [OUT] property flags.
+ PCCOR_SIGNATURE *ppvSig, // [OUT] property type. pointing to meta data internal blob
+ ULONG *pbSig, // [OUT] count of bytes in *ppvSig
+ DWORD *pdwCPlusTypeFlag, // [OUT] flag for value type. selected ELEMENT_TYPE_*
+ UVCP_CONSTANT *ppDefaultValue, // [OUT] constant value
+ ULONG *pcchDefaultValue, // [OUT] size of constant string in chars, 0 for non-strings.
+ mdMethodDef *pmdSetter, // [OUT] setter method of the property
+ mdMethodDef *pmdGetter, // [OUT] getter method of the property
+ mdMethodDef rmdOtherMethod[], // [OUT] other method of the property
+ ULONG cMax, // [IN] size of rmdOtherMethod
+ ULONG *pcOtherMethod) // [OUT] total number of other method of this property
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetParamProps ( // S_OK or error.
+ mdParamDef tk, // [IN]The Parameter.
+ mdMethodDef *pmd, // [OUT] Parent Method token.
+ ULONG *pulSequence, // [OUT] Parameter sequence.
+ __out_ecount_part_opt(cchName, *pchName)
+ LPWSTR szName, // [OUT] Put name here.
+ ULONG cchName, // [OUT] Size of name buffer.
+ ULONG *pchName, // [OUT] Put actual size of name here.
+ DWORD *pdwAttr, // [OUT] Put flags here.
+ DWORD *pdwCPlusTypeFlag, // [OUT] Flag for value type. selected ELEMENT_TYPE_*.
+ UVCP_CONSTANT *ppValue, // [OUT] Constant value.
+ ULONG *pcchValue) // [OUT] size of constant string in chars, 0 for non-strings.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetCustomAttributeByName ( // S_OK or error.
+ mdToken tkObj, // [IN] Object with Custom Attribute.
+ LPCWSTR szName, // [IN] Name of desired Custom Attribute.
+ const void **ppData, // [OUT] Put pointer to data here.
+ ULONG *pcbData) // [OUT] Put size of data here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP_(BOOL) SymbolInfo::IsValidToken ( // True or False.
+ mdToken tk) // [IN] Given token.
+{
+ _ASSERTE(!"NYI");
+ return FALSE;
+}
+
+
+STDMETHODIMP SymbolInfo::GetNativeCallConvFromSig ( // S_OK or error.
+ void const *pvSig, // [IN] Pointer to signature.
+ ULONG cbSig, // [IN] Count of signature bytes.
+ ULONG *pCallConv) // [OUT] Put calling conv here (see CorPinvokemap).
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::IsGlobal ( // S_OK or error.
+ mdToken pd, // [IN] Type, Field, or Method token.
+ int *pbGlobal) // [OUT] Put 1 if global, 0 otherwise.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+
+// IMetaDataEmit functions
+
+STDMETHODIMP SymbolInfo::SetModuleProps ( // S_OK or error.
+ LPCWSTR szName) // [IN] If not NULL, the name of the module to set.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::Save ( // S_OK or error.
+ LPCWSTR szFile, // [IN] The filename to save to.
+ DWORD dwSaveFlags) // [IN] Flags for the save.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::SaveToStream ( // S_OK or error.
+ IStream *pIStream, // [IN] A writable stream to save to.
+ DWORD dwSaveFlags) // [IN] Flags for the save.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetSaveSize ( // S_OK or error.
+ CorSaveSize fSave, // [IN] cssAccurate or cssQuick.
+ DWORD *pdwSaveSize) // [OUT] Put the size here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::DefineTypeDef ( // S_OK or error.
+ LPCWSTR szTypeDef, // [IN] Name of TypeDef
+ DWORD dwTypeDefFlags, // [IN] CustomAttribute flags
+ mdToken tkExtends, // [IN] extends this TypeDef or typeref
+ mdToken rtkImplements[], // [IN] Implements interfaces
+ mdTypeDef *ptd) // [OUT] Put TypeDef token here
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::DefineNestedType ( // S_OK or error.
+ LPCWSTR szTypeDef, // [IN] Name of TypeDef
+ DWORD dwTypeDefFlags, // [IN] CustomAttribute flags
+ mdToken tkExtends, // [IN] extends this TypeDef or typeref
+ mdToken rtkImplements[], // [IN] Implements interfaces
+ mdTypeDef tdEncloser, // [IN] TypeDef token of the enclosing type.
+ mdTypeDef *ptd) // [OUT] Put TypeDef token here
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::SetHandler ( // S_OK.
+ IUnknown *pUnk) // [IN] The new error handler.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::DefineMethod ( // S_OK or error.
+ mdTypeDef td, // Parent TypeDef
+ LPCWSTR szName, // Name of member
+ DWORD dwMethodFlags, // Member attributes
+ PCCOR_SIGNATURE pvSigBlob, // [IN] point to a blob value of CLR signature
+ ULONG cbSigBlob, // [IN] count of bytes in the signature blob
+ ULONG ulCodeRVA,
+ DWORD dwImplFlags,
+ mdMethodDef *pmd) // Put member token here
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::DefineMethodImpl ( // S_OK or error.
+ mdTypeDef td, // [IN] The class implementing the method
+ mdToken tkBody, // [IN] Method body - MethodDef or MethodRef
+ mdToken tkDecl) // [IN] Method declaration - MethodDef or MethodRef
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::DefineTypeRefByName ( // S_OK or error.
+ mdToken tkResolutionScope, // [IN] ModuleRef, AssemblyRef or TypeRef.
+ LPCWSTR szName, // [IN] Name of the TypeRef.
+ mdTypeRef *ptr) // [OUT] Put TypeRef token here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::DefineImportType ( // S_OK or error.
+ IMetaDataAssemblyImport *pAssemImport, // [IN] Assembly containing the TypeDef.
+ const void *pbHashValue, // [IN] Hash Blob for Assembly.
+ ULONG cbHashValue, // [IN] Count of bytes.
+ IMetaDataImport *pImport, // [IN] Scope containing the TypeDef.
+ mdTypeDef tdImport, // [IN] The imported TypeDef.
+ IMetaDataAssemblyEmit *pAssemEmit, // [IN] Assembly into which the TypeDef is imported.
+ mdTypeRef *ptr) // [OUT] Put TypeRef token here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::DefineMemberRef ( // S_OK or error
+ mdToken tkImport, // [IN] ClassRef or ClassDef importing a member.
+ LPCWSTR szName, // [IN] member's name
+ PCCOR_SIGNATURE pvSigBlob, // [IN] point to a blob value of CLR signature
+ ULONG cbSigBlob, // [IN] count of bytes in the signature blob
+ mdMemberRef *pmr) // [OUT] memberref token
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::DefineImportMember ( // S_OK or error.
+ IMetaDataAssemblyImport *pAssemImport, // [IN] Assembly containing the Member.
+ const void *pbHashValue, // [IN] Hash Blob for Assembly.
+ ULONG cbHashValue, // [IN] Count of bytes.
+ IMetaDataImport *pImport, // [IN] Import scope, with member.
+ mdToken mbMember, // [IN] Member in import scope.
+ IMetaDataAssemblyEmit *pAssemEmit, // [IN] Assembly into which the Member is imported.
+ mdToken tkParent, // [IN] Classref or classdef in emit scope.
+ mdMemberRef *pmr) // [OUT] Put member ref here.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::DefineEvent(
+ mdTypeDef td, // [IN] the class/interface on which the event is being defined
+ LPCWSTR szEvent, // [IN] Name of the event
+ DWORD dwEventFlags, // [IN] CorEventAttr
+ mdToken tkEventType, // [IN] a reference (mdTypeRef or mdTypeRef) to the Event class
+ mdMethodDef mdAddOn, // [IN] required add method
+ mdMethodDef mdRemoveOn, // [IN] required remove method
+ mdMethodDef mdFire, // [IN] optional fire method
+ mdMethodDef rmdOtherMethods[], // [IN] optional array of other methods associate with the event
+ mdEvent *pmdEvent) // [OUT] output event token
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::SetClassLayout(
+ mdTypeDef td, // [IN] typedef
+ DWORD dwPackSize, // [IN] packing size specified as 1, 2, 4, 8, or 16
+ COR_FIELD_OFFSET rFieldOffsets[], // [IN] array of layout specification
+ ULONG ulClassSize) // [IN] size of the class
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::DeleteClassLayout(
+ mdTypeDef td) // [IN] typedef whose layout is to be deleted.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::SetFieldMarshal(
+ mdToken tk, // [IN] given a fieldDef or paramDef token
+ PCCOR_SIGNATURE pvNativeType, // [IN] native type specification
+ ULONG cbNativeType) // [IN] count of bytes of pvNativeType
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::DeleteFieldMarshal(
+ mdToken tk) // [IN] given a fieldDef or paramDef token
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::DefinePermissionSet(
+ mdToken tk, // [IN] the object to be decorated.
+ DWORD dwAction, // [IN] CorDeclSecurity.
+ void const *pvPermission, // [IN] permission blob.
+ ULONG cbPermission, // [IN] count of bytes of pvPermission.
+ mdPermission *ppm) // [OUT] returned permission token.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::SetRVA ( // S_OK or error.
+ mdMethodDef md, // [IN] Method for which to set offset
+ ULONG ulRVA) // [IN] The offset
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::DefineModuleRef ( // S_OK or error.
+ LPCWSTR szName, // [IN] DLL name
+ mdModuleRef *pmur) // [OUT] returned
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::SetParent ( // S_OK or error.
+ mdMemberRef mr, // [IN] Token for the ref to be fixed up.
+ mdToken tk) // [IN] The ref parent.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::GetTokenFromTypeSpec ( // S_OK or error.
+ PCCOR_SIGNATURE pvSig, // [IN] TypeSpec Signature to define.
+ ULONG cbSig, // [IN] Size of signature data.
+ mdTypeSpec *ptypespec) // [OUT] returned TypeSpec token.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::SaveToMemory ( // S_OK or error.
+ void *pbData, // [OUT] Location to write data.
+ ULONG cbData) // [IN] Max size of data buffer.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::DefineUserString ( // Return code.
+ LPCWSTR szString, // [IN] User literal string.
+ ULONG cchString, // [IN] Length of string.
+ mdString *pstk) // [OUT] String token.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::DeleteToken ( // Return code.
+ mdToken tkObj) // [IN] The token to be deleted
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::SetMethodProps ( // S_OK or error.
+ mdMethodDef md, // [IN] The MethodDef.
+ DWORD dwMethodFlags, // [IN] Method attributes.
+ ULONG ulCodeRVA, // [IN] Code RVA.
+ DWORD dwImplFlags) // [IN] Impl flags.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::SetTypeDefProps ( // S_OK or error.
+ mdTypeDef td, // [IN] The TypeDef.
+ DWORD dwTypeDefFlags, // [IN] TypeDef flags.
+ mdToken tkExtends, // [IN] Base TypeDef or TypeRef.
+ mdToken rtkImplements[]) // [IN] Implemented interfaces.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::SetEventProps ( // S_OK or error.
+ mdEvent ev, // [IN] The event token.
+ DWORD dwEventFlags, // [IN] CorEventAttr.
+ mdToken tkEventType, // [IN] A reference (mdTypeRef or mdTypeRef) to the Event class.
+ mdMethodDef mdAddOn, // [IN] Add method.
+ mdMethodDef mdRemoveOn, // [IN] Remove method.
+ mdMethodDef mdFire, // [IN] Fire method.
+ mdMethodDef rmdOtherMethods[])// [IN] Array of other methods associate with the event.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::SetPermissionSetProps ( // S_OK or error.
+ mdToken tk, // [IN] The object to be decorated.
+ DWORD dwAction, // [IN] CorDeclSecurity.
+ void const *pvPermission, // [IN] Permission blob.
+ ULONG cbPermission, // [IN] Count of bytes of pvPermission.
+ mdPermission *ppm) // [OUT] Permission token.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::DefinePinvokeMap ( // Return code.
+ mdToken tk, // [IN] FieldDef or MethodDef.
+ DWORD dwMappingFlags, // [IN] Flags used for mapping.
+ LPCWSTR szImportName, // [IN] Import name.
+ mdModuleRef mrImportDLL) // [IN] ModuleRef token for the target DLL.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::SetPinvokeMap ( // Return code.
+ mdToken tk, // [IN] FieldDef or MethodDef.
+ DWORD dwMappingFlags, // [IN] Flags used for mapping.
+ LPCWSTR szImportName, // [IN] Import name.
+ mdModuleRef mrImportDLL) // [IN] ModuleRef token for the target DLL.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::DeletePinvokeMap ( // Return code.
+ mdToken tk) // [IN] FieldDef or MethodDef.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+// New CustomAttribute functions.
+STDMETHODIMP SymbolInfo::DefineCustomAttribute ( // Return code.
+ mdToken tkObj, // [IN] The object to put the value on.
+ mdToken tkType, // [IN] Type of the CustomAttribute (TypeRef/TypeDef).
+ void const *pCustomAttribute, // [IN] The custom value data.
+ ULONG cbCustomAttribute, // [IN] The custom value data length.
+ mdCustomAttribute *pcv) // [OUT] The custom value token value on return.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::SetCustomAttributeValue ( // Return code.
+ mdCustomAttribute pcv, // [IN] The custom value token whose value to replace.
+ void const *pCustomAttribute, // [IN] The custom value data.
+ ULONG cbCustomAttribute)// [IN] The custom value data length.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::DefineField ( // S_OK or error.
+ mdTypeDef td, // Parent TypeDef
+ LPCWSTR szName, // Name of member
+ DWORD dwFieldFlags, // Member attributes
+ PCCOR_SIGNATURE pvSigBlob, // [IN] point to a blob value of CLR signature
+ ULONG cbSigBlob, // [IN] count of bytes in the signature blob
+ DWORD dwCPlusTypeFlag, // [IN] flag for value type. selected ELEMENT_TYPE_*
+ void const *pValue, // [IN] constant value
+ ULONG cchValue, // [IN] size of constant value (string, in wide chars).
+ mdFieldDef *pmd) // [OUT] Put member token here
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::DefineProperty (
+ mdTypeDef td, // [IN] the class/interface on which the property is being defined
+ LPCWSTR szProperty, // [IN] Name of the property
+ DWORD dwPropFlags, // [IN] CorPropertyAttr
+ PCCOR_SIGNATURE pvSig, // [IN] the required type signature
+ ULONG cbSig, // [IN] the size of the type signature blob
+ DWORD dwCPlusTypeFlag, // [IN] flag for value type. selected ELEMENT_TYPE_*
+ void const *pValue, // [IN] constant value
+ ULONG cchValue, // [IN] size of constant value (string, in wide chars).
+ mdMethodDef mdSetter, // [IN] optional setter of the property
+ mdMethodDef mdGetter, // [IN] optional getter of the property
+ mdMethodDef rmdOtherMethods[], // [IN] an optional array of other methods
+ mdProperty *pmdProp) // [OUT] output property token
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::DefineParam (
+ mdMethodDef md, // [IN] Owning method
+ ULONG ulParamSeq, // [IN] Which param
+ LPCWSTR szName, // [IN] Optional param name
+ DWORD dwParamFlags, // [IN] Optional param flags
+ DWORD dwCPlusTypeFlag, // [IN] flag for value type. selected ELEMENT_TYPE_*
+ void const *pValue, // [IN] constant value
+ ULONG cchValue, // [IN] size of constant value (string, in wide chars).
+ mdParamDef *ppd) // [OUT] Put param token here
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::SetFieldProps ( // S_OK or error.
+ mdFieldDef fd, // [IN] The FieldDef.
+ DWORD dwFieldFlags, // [IN] Field attributes.
+ DWORD dwCPlusTypeFlag, // [IN] Flag for the value type, selected ELEMENT_TYPE_*
+ void const *pValue, // [IN] Constant value.
+ ULONG cchValue) // [IN] size of constant value (string, in wide chars).
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::SetPropertyProps ( // S_OK or error.
+ mdProperty pr, // [IN] Property token.
+ DWORD dwPropFlags, // [IN] CorPropertyAttr.
+ DWORD dwCPlusTypeFlag, // [IN] Flag for value type, selected ELEMENT_TYPE_*
+ void const *pValue, // [IN] Constant value.
+ ULONG cchValue, // [IN] size of constant value (string, in wide chars).
+ mdMethodDef mdSetter, // [IN] Setter of the property.
+ mdMethodDef mdGetter, // [IN] Getter of the property.
+ mdMethodDef rmdOtherMethods[])// [IN] Array of other methods.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::SetParamProps ( // Return code.
+ mdParamDef pd, // [IN] Param token.
+ LPCWSTR szName, // [IN] Param name.
+ DWORD dwParamFlags, // [IN] Param flags.
+ DWORD dwCPlusTypeFlag, // [IN] Flag for value type. selected ELEMENT_TYPE_*.
+ void const *pValue, // [OUT] Constant value.
+ ULONG cchValue) // [IN] size of constant value (string, in wide chars).
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+// Specialized Custom Attributes for security.
+STDMETHODIMP SymbolInfo::DefineSecurityAttributeSet ( // Return code.
+ mdToken tkObj, // [IN] Class or method requiring security attributes.
+ COR_SECATTR rSecAttrs[], // [IN] Array of security attribute descriptions.
+ ULONG cSecAttrs, // [IN] Count of elements in above array.
+ ULONG *pulErrorAttr) // [OUT] On error, index of attribute causing problem.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::ApplyEditAndContinue ( // S_OK or error.
+ IUnknown *pImport) // [IN] Metadata from the delta PE.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::TranslateSigWithScope (
+ IMetaDataAssemblyImport *pAssemImport, // [IN] importing assembly interface
+ const void *pbHashValue, // [IN] Hash Blob for Assembly.
+ ULONG cbHashValue, // [IN] Count of bytes.
+ IMetaDataImport *import, // [IN] importing interface
+ PCCOR_SIGNATURE pbSigBlob, // [IN] signature in the importing scope
+ ULONG cbSigBlob, // [IN] count of bytes of signature
+ IMetaDataAssemblyEmit *pAssemEmit, // [IN] emit assembly interface
+ IMetaDataEmit *emit, // [IN] emit interface
+ PCOR_SIGNATURE pvTranslatedSig, // [OUT] buffer to hold translated signature
+ ULONG cbTranslatedSigMax,
+ ULONG *pcbTranslatedSig)// [OUT] count of bytes in the translated signature
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::SetMethodImplFlags ( // [IN] S_OK or error.
+ mdMethodDef md, // [IN] Method for which to set ImplFlags
+ DWORD dwImplFlags)
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::SetFieldRVA ( // [IN] S_OK or error.
+ mdFieldDef fd, // [IN] Field for which to set offset
+ ULONG ulRVA) // [IN] The offset
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::Merge ( // S_OK or error.
+ IMetaDataImport *pImport, // [IN] The scope to be merged.
+ IMapToken *pHostMapToken, // [IN] Host IMapToken interface to receive token remap notification
+ IUnknown *pHandler) // [IN] An object to receive to receive error notification.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SymbolInfo::MergeEnd () // S_OK or error.
+{
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+
diff --git a/src/debug/di/symbolinfo.h b/src/debug/di/symbolinfo.h
new file mode 100644
index 0000000000..a4402cb7e6
--- /dev/null
+++ b/src/debug/di/symbolinfo.h
@@ -0,0 +1,816 @@
+// 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.
+
+
+// callbacks for diasymreader when using SymConverter
+
+
+#ifndef SYMBOLINFO_H
+#define SYMBOLINFO_H
+#include "arraylist.h"
+#include "shash.h"
+#include "corsym.h"
+#include "sstring.h"
+
+class SymbolInfo: IMetaDataEmit, IMetaDataImport
+{
+
+ struct ScopeMap
+ {
+ ULONG32 left;
+ ULONG32 right;
+ ScopeMap(ULONG32 l, ULONG32 r):left(l), right(r){};
+ ULONG32 GetKey() {return left;};
+ static COUNT_T Hash(ULONG32 key) {return key;};
+ };
+
+ struct MethodProps
+ {
+ mdMethodDef method;
+ mdTypeDef cls;
+ SString wszName;
+ MethodProps() : method(mdTokenNil) {};
+ };
+
+ struct ClassProps
+ {
+ mdTypeDef cls;
+ DWORD flags;
+ SString wszName;
+ mdTypeDef tkEnclosing;
+ ClassProps(mdToken c, DWORD f, LPCWSTR name, mdToken tkE):
+ cls(c),flags(f),wszName(name),tkEnclosing(tkE) {wszName.Normalize();};
+
+ mdTypeDef GetKey() {return cls;};
+ static COUNT_T Hash(mdTypeDef key) {return key;};
+ };
+
+ struct SignatureProps
+ {
+ SBuffer sig;
+ DWORD tkSig;
+ SignatureProps(SBuffer& signature, DWORD token):
+ tkSig(token) {sig.Set(signature);};
+ SBuffer& GetKey() {return sig;};
+ static COUNT_T Hash(SBuffer& key) {return HashBytes(key,key.GetSize());};
+ };
+
+ Volatile<LONG> m_cRef;
+ ArrayList m_Documents;
+ PtrSHashWithCleanup<ScopeMap,ULONG32> m_Scopes;
+ PtrSHashWithCleanup<SignatureProps,SBuffer&> m_Signatures;
+ PtrSHashWithCleanup<ClassProps,mdTypeDef> m_Classes;
+
+ MethodProps m_LastMethod; // diasymreader supports only one open method at a time
+
+ ClassProps* FindClass(mdToken cls);
+ SignatureProps* FindSignature(SBuffer& sig);
+
+ virtual ~SymbolInfo(); // protected, b/c the lifetime is controlled by refcount
+
+public:
+ SymbolInfo();
+
+ HRESULT AddDocument(DWORD id, ISymUnmanagedDocumentWriter* Document);
+ HRESULT MapDocument(DWORD id, ISymUnmanagedDocumentWriter** Document);
+ HRESULT AddScope(ULONG32 left, ULONG32 right);
+ HRESULT MapScope(ULONG32 left, ULONG32* pRight);
+ HRESULT SetMethodProps(mdToken method, mdTypeDef cls, LPCWSTR wszName);
+ HRESULT SetClassProps(mdTypeDef cls, DWORD flags, LPCWSTR wszName, mdTypeDef enclosingCls);
+ HRESULT AddSignature(SBuffer& sig, mdSignature token);
+
+ // IUnknown methods
+ STDMETHOD(QueryInterface) (REFIID riid, LPVOID * ppvObj);
+ STDMETHOD_(ULONG,AddRef) ();
+ STDMETHOD_(ULONG,Release) ();
+
+
+ // IMetaDataImport functions
+
+ STDMETHOD_(void, CloseEnum)(HCORENUM hEnum);
+ STDMETHOD(CountEnum)(HCORENUM hEnum, ULONG *pulCount);
+ STDMETHOD(ResetEnum)(HCORENUM hEnum, ULONG ulPos);
+ STDMETHOD(EnumTypeDefs)(HCORENUM *phEnum, mdTypeDef rTypeDefs[],
+ ULONG cMax, ULONG *pcTypeDefs);
+ STDMETHOD(EnumInterfaceImpls)(HCORENUM *phEnum, mdTypeDef td,
+ mdInterfaceImpl rImpls[], ULONG cMax,
+ ULONG* pcImpls);
+ STDMETHOD(EnumTypeRefs)(HCORENUM *phEnum, mdTypeRef rTypeRefs[],
+ ULONG cMax, ULONG* pcTypeRefs);
+
+ STDMETHOD(FindTypeDefByName)( // S_OK or error.
+ LPCWSTR szTypeDef, // [IN] Name of the Type.
+ mdToken tkEnclosingClass, // [IN] TypeDef/TypeRef for Enclosing class.
+ mdTypeDef *ptd); // [OUT] Put the TypeDef token here.
+
+ STDMETHOD(GetScopeProps)( // S_OK or error.
+ __out_ecount_part_opt(cchName, *pchName)
+ LPWSTR szName, // [OUT] Put the name here.
+ ULONG cchName, // [IN] Size of name buffer in wide chars.
+ ULONG *pchName, // [OUT] Put size of name (wide chars) here.
+ GUID *pmvid); // [OUT, OPTIONAL] Put MVID here.
+
+ STDMETHOD(GetModuleFromScope)( // S_OK.
+ mdModule *pmd); // [OUT] Put mdModule token here.
+
+ STDMETHOD(GetTypeDefProps)( // S_OK or error.
+ mdTypeDef td, // [IN] TypeDef token for inquiry.
+ __out_ecount_part_opt(cchTypeDef, *pchTypeDef)
+ LPWSTR szTypeDef, // [OUT] Put name here.
+ ULONG cchTypeDef, // [IN] size of name buffer in wide chars.
+ ULONG *pchTypeDef, // [OUT] put size of name (wide chars) here.
+ DWORD *pdwTypeDefFlags, // [OUT] Put flags here.
+ mdToken *ptkExtends); // [OUT] Put base class TypeDef/TypeRef here.
+
+ STDMETHOD(GetInterfaceImplProps)( // S_OK or error.
+ mdInterfaceImpl iiImpl, // [IN] InterfaceImpl token.
+ mdTypeDef *pClass, // [OUT] Put implementing class token here.
+ mdToken *ptkIface); // [OUT] Put implemented interface token here.
+
+ STDMETHOD(GetTypeRefProps)( // S_OK or error.
+ mdTypeRef tr, // [IN] TypeRef token.
+ mdToken *ptkResolutionScope, // [OUT] Resolution scope, ModuleRef or AssemblyRef.
+ __out_ecount_part_opt(cchName, *pchName)
+ LPWSTR szName, // [OUT] Name of the TypeRef.
+ ULONG cchName, // [IN] Size of buffer.
+ ULONG *pchName); // [OUT] Size of Name.
+
+ STDMETHOD(ResolveTypeRef)(mdTypeRef tr, REFIID riid, IUnknown **ppIScope, mdTypeDef *ptd);
+
+ STDMETHOD(EnumMembers)( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdTypeDef cl, // [IN] TypeDef to scope the enumeration.
+ mdToken rMembers[], // [OUT] Put MemberDefs here.
+ ULONG cMax, // [IN] Max MemberDefs to put.
+ ULONG *pcTokens); // [OUT] Put # put here.
+
+ STDMETHOD(EnumMembersWithName)( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdTypeDef cl, // [IN] TypeDef to scope the enumeration.
+ LPCWSTR szName, // [IN] Limit results to those with this name.
+ mdToken rMembers[], // [OUT] Put MemberDefs here.
+ ULONG cMax, // [IN] Max MemberDefs to put.
+ ULONG *pcTokens); // [OUT] Put # put here.
+
+ STDMETHOD(EnumMethods)( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdTypeDef cl, // [IN] TypeDef to scope the enumeration.
+ mdMethodDef rMethods[], // [OUT] Put MethodDefs here.
+ ULONG cMax, // [IN] Max MethodDefs to put.
+ ULONG *pcTokens); // [OUT] Put # put here.
+
+ STDMETHOD(EnumMethodsWithName)( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdTypeDef cl, // [IN] TypeDef to scope the enumeration.
+ LPCWSTR szName, // [IN] Limit results to those with this name.
+ mdMethodDef rMethods[], // [OU] Put MethodDefs here.
+ ULONG cMax, // [IN] Max MethodDefs to put.
+ ULONG *pcTokens); // [OUT] Put # put here.
+
+ STDMETHOD(EnumFields)( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdTypeDef cl, // [IN] TypeDef to scope the enumeration.
+ mdFieldDef rFields[], // [OUT] Put FieldDefs here.
+ ULONG cMax, // [IN] Max FieldDefs to put.
+ ULONG *pcTokens); // [OUT] Put # put here.
+
+ STDMETHOD(EnumFieldsWithName)( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdTypeDef cl, // [IN] TypeDef to scope the enumeration.
+ LPCWSTR szName, // [IN] Limit results to those with this name.
+ mdFieldDef rFields[], // [OUT] Put MemberDefs here.
+ ULONG cMax, // [IN] Max MemberDefs to put.
+ ULONG *pcTokens); // [OUT] Put # put here.
+
+
+ STDMETHOD(EnumParams)( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdMethodDef mb, // [IN] MethodDef to scope the enumeration.
+ mdParamDef rParams[], // [OUT] Put ParamDefs here.
+ ULONG cMax, // [IN] Max ParamDefs to put.
+ ULONG *pcTokens); // [OUT] Put # put here.
+
+ STDMETHOD(EnumMemberRefs)( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdToken tkParent, // [IN] Parent token to scope the enumeration.
+ mdMemberRef rMemberRefs[], // [OUT] Put MemberRefs here.
+ ULONG cMax, // [IN] Max MemberRefs to put.
+ ULONG *pcTokens); // [OUT] Put # put here.
+
+ STDMETHOD(EnumMethodImpls)( // S_OK, S_FALSE, or error
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdTypeDef td, // [IN] TypeDef to scope the enumeration.
+ mdToken rMethodBody[], // [OUT] Put Method Body tokens here.
+ mdToken rMethodDecl[], // [OUT] Put Method Declaration tokens here.
+ ULONG cMax, // [IN] Max tokens to put.
+ ULONG *pcTokens); // [OUT] Put # put here.
+
+ STDMETHOD(EnumPermissionSets)( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdToken tk, // [IN] if !NIL, token to scope the enumeration.
+ DWORD dwActions, // [IN] if !0, return only these actions.
+ mdPermission rPermission[], // [OUT] Put Permissions here.
+ ULONG cMax, // [IN] Max Permissions to put.
+ ULONG *pcTokens); // [OUT] Put # put here.
+
+ STDMETHOD(FindMember)(
+ mdTypeDef td, // [IN] given typedef
+ LPCWSTR szName, // [IN] member name
+ PCCOR_SIGNATURE pvSigBlob, // [IN] point to a blob value of CLR signature
+ ULONG cbSigBlob, // [IN] count of bytes in the signature blob
+ mdToken *pmb); // [OUT] matching memberdef
+
+ STDMETHOD(FindMethod)(
+ mdTypeDef td, // [IN] given typedef
+ LPCWSTR szName, // [IN] member name
+ PCCOR_SIGNATURE pvSigBlob, // [IN] point to a blob value of CLR signature
+ ULONG cbSigBlob, // [IN] count of bytes in the signature blob
+ mdMethodDef *pmb); // [OUT] matching memberdef
+
+ STDMETHOD(FindField)(
+ mdTypeDef td, // [IN] given typedef
+ LPCWSTR szName, // [IN] member name
+ PCCOR_SIGNATURE pvSigBlob, // [IN] point to a blob value of CLR signature
+ ULONG cbSigBlob, // [IN] count of bytes in the signature blob
+ mdFieldDef *pmb); // [OUT] matching memberdef
+
+ STDMETHOD(FindMemberRef)(
+ mdTypeRef td, // [IN] given typeRef
+ LPCWSTR szName, // [IN] member name
+ PCCOR_SIGNATURE pvSigBlob, // [IN] point to a blob value of CLR signature
+ ULONG cbSigBlob, // [IN] count of bytes in the signature blob
+ mdMemberRef *pmr); // [OUT] matching memberref
+
+ STDMETHOD (GetMethodProps)(
+ mdMethodDef mb, // The method for which to get props.
+ mdTypeDef *pClass, // Put method's class here.
+ __out_ecount_part_opt(cchMethod, *pchMethod)
+ LPWSTR szMethod, // Put method's name here.
+ ULONG cchMethod, // Size of szMethod buffer in wide chars.
+ ULONG *pchMethod, // Put actual size here
+ DWORD *pdwAttr, // Put flags here.
+ PCCOR_SIGNATURE *ppvSigBlob, // [OUT] point to the blob value of meta data
+ ULONG *pcbSigBlob, // [OUT] actual size of signature blob
+ ULONG *pulCodeRVA, // [OUT] codeRVA
+ DWORD *pdwImplFlags); // [OUT] Impl. Flags
+
+ STDMETHOD(GetMemberRefProps)( // S_OK or error.
+ mdMemberRef mr, // [IN] given memberref
+ mdToken *ptk, // [OUT] Put classref or classdef here.
+ __out_ecount_part_opt(cchMember, *pchMember)
+ LPWSTR szMember, // [OUT] buffer to fill for member's name
+ ULONG cchMember, // [IN] the count of char of szMember
+ ULONG *pchMember, // [OUT] actual count of char in member name
+ PCCOR_SIGNATURE *ppvSigBlob, // [OUT] point to meta data blob value
+ ULONG *pbSig); // [OUT] actual size of signature blob
+
+ STDMETHOD(EnumProperties)( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdTypeDef td, // [IN] TypeDef to scope the enumeration.
+ mdProperty rProperties[], // [OUT] Put Properties here.
+ ULONG cMax, // [IN] Max properties to put.
+ ULONG *pcProperties); // [OUT] Put # put here.
+
+ STDMETHOD(EnumEvents)( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdTypeDef td, // [IN] TypeDef to scope the enumeration.
+ mdEvent rEvents[], // [OUT] Put events here.
+ ULONG cMax, // [IN] Max events to put.
+ ULONG *pcEvents); // [OUT] Put # put here.
+
+ STDMETHOD(GetEventProps)( // S_OK, S_FALSE, or error.
+ mdEvent ev, // [IN] event token
+ mdTypeDef *pClass, // [OUT] typedef containing the event declarion.
+ LPCWSTR szEvent, // [OUT] Event name
+ ULONG cchEvent, // [IN] the count of wchar of szEvent
+ ULONG *pchEvent, // [OUT] actual count of wchar for event's name
+ DWORD *pdwEventFlags, // [OUT] Event flags.
+ mdToken *ptkEventType, // [OUT] EventType class
+ mdMethodDef *pmdAddOn, // [OUT] AddOn method of the event
+ mdMethodDef *pmdRemoveOn, // [OUT] RemoveOn method of the event
+ mdMethodDef *pmdFire, // [OUT] Fire method of the event
+ mdMethodDef rmdOtherMethod[], // [OUT] other method of the event
+ ULONG cMax, // [IN] size of rmdOtherMethod
+ ULONG *pcOtherMethod); // [OUT] total number of other method of this event
+
+ STDMETHOD(EnumMethodSemantics)( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdMethodDef mb, // [IN] MethodDef to scope the enumeration.
+ mdToken rEventProp[], // [OUT] Put Event/Property here.
+ ULONG cMax, // [IN] Max properties to put.
+ ULONG *pcEventProp); // [OUT] Put # put here.
+
+ STDMETHOD(GetMethodSemantics)( // S_OK, S_FALSE, or error.
+ mdMethodDef mb, // [IN] method token
+ mdToken tkEventProp, // [IN] event/property token.
+ DWORD *pdwSemanticsFlags); // [OUT] the role flags for the method/propevent pair
+
+ STDMETHOD(GetClassLayout) (
+ mdTypeDef td, // [IN] give typedef
+ DWORD *pdwPackSize, // [OUT] 1, 2, 4, 8, or 16
+ COR_FIELD_OFFSET rFieldOffset[], // [OUT] field offset array
+ ULONG cMax, // [IN] size of the array
+ ULONG *pcFieldOffset, // [OUT] needed array size
+ ULONG *pulClassSize); // [OUT] the size of the class
+
+ STDMETHOD(GetFieldMarshal) (
+ mdToken tk, // [IN] given a field's memberdef
+ PCCOR_SIGNATURE *ppvNativeType, // [OUT] native type of this field
+ ULONG *pcbNativeType); // [OUT] the count of bytes of *ppvNativeType
+
+ STDMETHOD(GetRVA)( // S_OK or error.
+ mdToken tk, // Member for which to set offset
+ ULONG *pulCodeRVA, // The offset
+ DWORD *pdwImplFlags); // the implementation flags
+
+ STDMETHOD(GetPermissionSetProps) (
+ mdPermission pm, // [IN] the permission token.
+ DWORD *pdwAction, // [OUT] CorDeclSecurity.
+ void const **ppvPermission, // [OUT] permission blob.
+ ULONG *pcbPermission); // [OUT] count of bytes of pvPermission.
+
+ STDMETHOD(GetSigFromToken)( // S_OK or error.
+ mdSignature mdSig, // [IN] Signature token.
+ PCCOR_SIGNATURE *ppvSig, // [OUT] return pointer to token.
+ ULONG *pcbSig); // [OUT] return size of signature.
+
+ STDMETHOD(GetModuleRefProps)( // S_OK or error.
+ mdModuleRef mur, // [IN] moduleref token.
+ __out_ecount_part_opt(cchName, *pchName)
+ LPWSTR szName, // [OUT] buffer to fill with the moduleref name.
+ ULONG cchName, // [IN] size of szName in wide characters.
+ ULONG *pchName); // [OUT] actual count of characters in the name.
+
+ STDMETHOD(EnumModuleRefs)( // S_OK or error.
+ HCORENUM *phEnum, // [IN|OUT] pointer to the enum.
+ mdModuleRef rModuleRefs[], // [OUT] put modulerefs here.
+ ULONG cmax, // [IN] max memberrefs to put.
+ ULONG *pcModuleRefs); // [OUT] put # put here.
+
+ STDMETHOD(GetTypeSpecFromToken)( // S_OK or error.
+ mdTypeSpec typespec, // [IN] TypeSpec token.
+ PCCOR_SIGNATURE *ppvSig, // [OUT] return pointer to TypeSpec signature
+ ULONG *pcbSig); // [OUT] return size of signature.
+
+ STDMETHOD(GetNameFromToken)( // Not Recommended! May be removed!
+ mdToken tk, // [IN] Token to get name from. Must have a name.
+ MDUTF8CSTR *pszUtf8NamePtr); // [OUT] Return pointer to UTF8 name in heap.
+
+ STDMETHOD(EnumUnresolvedMethods)( // S_OK, S_FALSE, or error.
+ HCORENUM *phEnum, // [IN|OUT] Pointer to the enum.
+ mdToken rMethods[], // [OUT] Put MemberDefs here.
+ ULONG cMax, // [IN] Max MemberDefs to put.
+ ULONG *pcTokens); // [OUT] Put # put here.
+
+ STDMETHOD(GetUserString)( // S_OK or error.
+ mdString stk, // [IN] String token.
+ __out_ecount_part_opt(cchString, *pchString)
+ LPWSTR szString, // [OUT] Copy of string.
+ ULONG cchString, // [IN] Max chars of room in szString.
+ ULONG *pchString); // [OUT] How many chars in actual string.
+
+ STDMETHOD(GetPinvokeMap)( // S_OK or error.
+ mdToken tk, // [IN] FieldDef or MethodDef.
+ DWORD *pdwMappingFlags, // [OUT] Flags used for mapping.
+ __out_ecount_part_opt(cchImportName, *pchImportName)
+ LPWSTR szImportName, // [OUT] Import name.
+ ULONG cchImportName, // [IN] Size of the name buffer.
+ ULONG *pchImportName, // [OUT] Actual number of characters stored.
+ mdModuleRef *pmrImportDLL); // [OUT] ModuleRef token for the target DLL.
+
+ STDMETHOD(EnumSignatures)( // S_OK or error.
+ HCORENUM *phEnum, // [IN|OUT] pointer to the enum.
+ mdSignature rSignatures[], // [OUT] put signatures here.
+ ULONG cmax, // [IN] max signatures to put.
+ ULONG *pcSignatures); // [OUT] put # put here.
+
+ STDMETHOD(EnumTypeSpecs)( // S_OK or error.
+ HCORENUM *phEnum, // [IN|OUT] pointer to the enum.
+ mdTypeSpec rTypeSpecs[], // [OUT] put TypeSpecs here.
+ ULONG cmax, // [IN] max TypeSpecs to put.
+ ULONG *pcTypeSpecs); // [OUT] put # put here.
+
+ STDMETHOD(EnumUserStrings)( // S_OK or error.
+ HCORENUM *phEnum, // [IN/OUT] pointer to the enum.
+ mdString rStrings[], // [OUT] put Strings here.
+ ULONG cmax, // [IN] max Strings to put.
+ ULONG *pcStrings); // [OUT] put # put here.
+
+ STDMETHOD(GetParamForMethodIndex)( // S_OK or error.
+ mdMethodDef md, // [IN] Method token.
+ ULONG ulParamSeq, // [IN] Parameter sequence.
+ mdParamDef *ppd); // [IN] Put Param token here.
+
+ STDMETHOD(EnumCustomAttributes)( // S_OK or error.
+ HCORENUM *phEnum, // [IN, OUT] COR enumerator.
+ mdToken tk, // [IN] Token to scope the enumeration, 0 for all.
+ mdToken tkType, // [IN] Type of interest, 0 for all.
+ mdCustomAttribute rCustomAttributes[], // [OUT] Put custom attribute tokens here.
+ ULONG cMax, // [IN] Size of rCustomAttributes.
+ ULONG *pcCustomAttributes); // [OUT, OPTIONAL] Put count of token values here.
+
+ STDMETHOD(GetCustomAttributeProps)( // S_OK or error.
+ mdCustomAttribute cv, // [IN] CustomAttribute token.
+ mdToken *ptkObj, // [OUT, OPTIONAL] Put object token here.
+ mdToken *ptkType, // [OUT, OPTIONAL] Put AttrType token here.
+ void const **ppBlob, // [OUT, OPTIONAL] Put pointer to data here.
+ ULONG *pcbSize); // [OUT, OPTIONAL] Put size of date here.
+
+ STDMETHOD(FindTypeRef)(
+ mdToken tkResolutionScope, // [IN] ModuleRef, AssemblyRef or TypeRef.
+ LPCWSTR szName, // [IN] TypeRef Name.
+ mdTypeRef *ptr); // [OUT] matching TypeRef.
+
+ STDMETHOD(GetMemberProps)(
+ mdToken mb, // The member for which to get props.
+ mdTypeDef *pClass, // Put member's class here.
+ __out_ecount_part_opt(cchMember, *pchMember)
+ LPWSTR szMember, // Put member's name here.
+ ULONG cchMember, // Size of szMember buffer in wide chars.
+ ULONG *pchMember, // Put actual size here
+ DWORD *pdwAttr, // Put flags here.
+ PCCOR_SIGNATURE *ppvSigBlob, // [OUT] point to the blob value of meta data
+ ULONG *pcbSigBlob, // [OUT] actual size of signature blob
+ ULONG *pulCodeRVA, // [OUT] codeRVA
+ DWORD *pdwImplFlags, // [OUT] Impl. Flags
+ DWORD *pdwCPlusTypeFlag, // [OUT] flag for value type. selected ELEMENT_TYPE_*
+ UVCP_CONSTANT *ppValue, // [OUT] constant value
+ ULONG *pcchValue); // [OUT] size of constant string in chars, 0 for non-strings.
+
+ STDMETHOD(GetFieldProps)(
+ mdFieldDef mb, // The field for which to get props.
+ mdTypeDef *pClass, // Put field's class here.
+ __out_ecount_part_opt(cchField, *pchField)
+ LPWSTR szField, // Put field's name here.
+ ULONG cchField, // Size of szField buffer in wide chars.
+ ULONG *pchField, // Put actual size here
+ DWORD *pdwAttr, // Put flags here.
+ PCCOR_SIGNATURE *ppvSigBlob, // [OUT] point to the blob value of meta data
+ ULONG *pcbSigBlob, // [OUT] actual size of signature blob
+ DWORD *pdwCPlusTypeFlag, // [OUT] flag for value type. selected ELEMENT_TYPE_*
+ UVCP_CONSTANT *ppValue, // [OUT] constant value
+ ULONG *pcchValue); // [OUT] size of constant string in chars, 0 for non-strings.
+
+ STDMETHOD(GetPropertyProps)( // S_OK, S_FALSE, or error.
+ mdProperty prop, // [IN] property token
+ mdTypeDef *pClass, // [OUT] typedef containing the property declarion.
+ LPCWSTR szProperty, // [OUT] Property name
+ ULONG cchProperty, // [IN] the count of wchar of szProperty
+ ULONG *pchProperty, // [OUT] actual count of wchar for property name
+ DWORD *pdwPropFlags, // [OUT] property flags.
+ PCCOR_SIGNATURE *ppvSig, // [OUT] property type. pointing to meta data internal blob
+ ULONG *pbSig, // [OUT] count of bytes in *ppvSig
+ DWORD *pdwCPlusTypeFlag, // [OUT] flag for value type. selected ELEMENT_TYPE_*
+ UVCP_CONSTANT *ppDefaultValue, // [OUT] constant value
+ ULONG *pcchDefaultValue, // [OUT] size of constant string in chars, 0 for non-strings.
+ mdMethodDef *pmdSetter, // [OUT] setter method of the property
+ mdMethodDef *pmdGetter, // [OUT] getter method of the property
+ mdMethodDef rmdOtherMethod[], // [OUT] other method of the property
+ ULONG cMax, // [IN] size of rmdOtherMethod
+ ULONG *pcOtherMethod); // [OUT] total number of other method of this property
+
+ STDMETHOD(GetParamProps)( // S_OK or error.
+ mdParamDef tk, // [IN]The Parameter.
+ mdMethodDef *pmd, // [OUT] Parent Method token.
+ ULONG *pulSequence, // [OUT] Parameter sequence.
+ __out_ecount_part_opt(cchName, *pchName)
+ LPWSTR szName, // [OUT] Put name here.
+ ULONG cchName, // [OUT] Size of name buffer.
+ ULONG *pchName, // [OUT] Put actual size of name here.
+ DWORD *pdwAttr, // [OUT] Put flags here.
+ DWORD *pdwCPlusTypeFlag, // [OUT] Flag for value type. selected ELEMENT_TYPE_*.
+ UVCP_CONSTANT *ppValue, // [OUT] Constant value.
+ ULONG *pcchValue); // [OUT] size of constant string in chars, 0 for non-strings.
+
+ STDMETHOD(GetCustomAttributeByName)( // S_OK or error.
+ mdToken tkObj, // [IN] Object with Custom Attribute.
+ LPCWSTR szName, // [IN] Name of desired Custom Attribute.
+ const void **ppData, // [OUT] Put pointer to data here.
+ ULONG *pcbData); // [OUT] Put size of data here.
+
+ STDMETHOD_(BOOL, IsValidToken)( // True or False.
+ mdToken tk); // [IN] Given token.
+
+ STDMETHOD(GetNestedClassProps)( // S_OK or error.
+ mdTypeDef tdNestedClass, // [IN] NestedClass token.
+ mdTypeDef *ptdEnclosingClass); // [OUT] EnclosingClass token.
+
+ STDMETHOD(GetNativeCallConvFromSig)( // S_OK or error.
+ void const *pvSig, // [IN] Pointer to signature.
+ ULONG cbSig, // [IN] Count of signature bytes.
+ ULONG *pCallConv); // [OUT] Put calling conv here (see CorPinvokemap).
+
+ STDMETHOD(IsGlobal)( // S_OK or error.
+ mdToken pd, // [IN] Type, Field, or Method token.
+ int *pbGlobal); // [OUT] Put 1 if global, 0 otherwise.
+
+
+ // IMetaDataEmit functions
+
+ STDMETHOD(SetModuleProps)( // S_OK or error.
+ LPCWSTR szName); // [IN] If not NULL, the name of the module to set.
+
+ STDMETHOD(Save)( // S_OK or error.
+ LPCWSTR szFile, // [IN] The filename to save to.
+ DWORD dwSaveFlags); // [IN] Flags for the save.
+
+ STDMETHOD(SaveToStream)( // S_OK or error.
+ IStream *pIStream, // [IN] A writable stream to save to.
+ DWORD dwSaveFlags); // [IN] Flags for the save.
+
+ STDMETHOD(GetSaveSize)( // S_OK or error.
+ CorSaveSize fSave, // [IN] cssAccurate or cssQuick.
+ DWORD *pdwSaveSize); // [OUT] Put the size here.
+
+ STDMETHOD(DefineTypeDef)( // S_OK or error.
+ LPCWSTR szTypeDef, // [IN] Name of TypeDef
+ DWORD dwTypeDefFlags, // [IN] CustomAttribute flags
+ mdToken tkExtends, // [IN] extends this TypeDef or typeref
+ mdToken rtkImplements[], // [IN] Implements interfaces
+ mdTypeDef *ptd); // [OUT] Put TypeDef token here
+
+ STDMETHOD(DefineNestedType)( // S_OK or error.
+ LPCWSTR szTypeDef, // [IN] Name of TypeDef
+ DWORD dwTypeDefFlags, // [IN] CustomAttribute flags
+ mdToken tkExtends, // [IN] extends this TypeDef or typeref
+ mdToken rtkImplements[], // [IN] Implements interfaces
+ mdTypeDef tdEncloser, // [IN] TypeDef token of the enclosing type.
+ mdTypeDef *ptd); // [OUT] Put TypeDef token here
+
+ STDMETHOD(SetHandler)( // S_OK.
+ IUnknown *pUnk); // [IN] The new error handler.
+
+ STDMETHOD(DefineMethod)( // S_OK or error.
+ mdTypeDef td, // Parent TypeDef
+ LPCWSTR szName, // Name of member
+ DWORD dwMethodFlags, // Member attributes
+ PCCOR_SIGNATURE pvSigBlob, // [IN] point to a blob value of CLR signature
+ ULONG cbSigBlob, // [IN] count of bytes in the signature blob
+ ULONG ulCodeRVA,
+ DWORD dwImplFlags,
+ mdMethodDef *pmd); // Put member token here
+
+ STDMETHOD(DefineMethodImpl)( // S_OK or error.
+ mdTypeDef td, // [IN] The class implementing the method
+ mdToken tkBody, // [IN] Method body - MethodDef or MethodRef
+ mdToken tkDecl); // [IN] Method declaration - MethodDef or MethodRef
+
+ STDMETHOD(DefineTypeRefByName)( // S_OK or error.
+ mdToken tkResolutionScope, // [IN] ModuleRef, AssemblyRef or TypeRef.
+ LPCWSTR szName, // [IN] Name of the TypeRef.
+ mdTypeRef *ptr); // [OUT] Put TypeRef token here.
+
+ STDMETHOD(DefineImportType)( // S_OK or error.
+ IMetaDataAssemblyImport *pAssemImport, // [IN] Assembly containing the TypeDef.
+ const void *pbHashValue, // [IN] Hash Blob for Assembly.
+ ULONG cbHashValue, // [IN] Count of bytes.
+ IMetaDataImport *pImport, // [IN] Scope containing the TypeDef.
+ mdTypeDef tdImport, // [IN] The imported TypeDef.
+ IMetaDataAssemblyEmit *pAssemEmit, // [IN] Assembly into which the TypeDef is imported.
+ mdTypeRef *ptr); // [OUT] Put TypeRef token here.
+
+ STDMETHOD(DefineMemberRef)( // S_OK or error
+ mdToken tkImport, // [IN] ClassRef or ClassDef importing a member.
+ LPCWSTR szName, // [IN] member's name
+ PCCOR_SIGNATURE pvSigBlob, // [IN] point to a blob value of CLR signature
+ ULONG cbSigBlob, // [IN] count of bytes in the signature blob
+ mdMemberRef *pmr); // [OUT] memberref token
+
+ STDMETHOD(DefineImportMember)( // S_OK or error.
+ IMetaDataAssemblyImport *pAssemImport, // [IN] Assembly containing the Member.
+ const void *pbHashValue, // [IN] Hash Blob for Assembly.
+ ULONG cbHashValue, // [IN] Count of bytes.
+ IMetaDataImport *pImport, // [IN] Import scope, with member.
+ mdToken mbMember, // [IN] Member in import scope.
+ IMetaDataAssemblyEmit *pAssemEmit, // [IN] Assembly into which the Member is imported.
+ mdToken tkParent, // [IN] Classref or classdef in emit scope.
+ mdMemberRef *pmr); // [OUT] Put member ref here.
+
+ STDMETHOD(DefineEvent) (
+ mdTypeDef td, // [IN] the class/interface on which the event is being defined
+ LPCWSTR szEvent, // [IN] Name of the event
+ DWORD dwEventFlags, // [IN] CorEventAttr
+ mdToken tkEventType, // [IN] a reference (mdTypeRef or mdTypeRef) to the Event class
+ mdMethodDef mdAddOn, // [IN] required add method
+ mdMethodDef mdRemoveOn, // [IN] required remove method
+ mdMethodDef mdFire, // [IN] optional fire method
+ mdMethodDef rmdOtherMethods[], // [IN] optional array of other methods associate with the event
+ mdEvent *pmdEvent); // [OUT] output event token
+
+ STDMETHOD(SetClassLayout) (
+ mdTypeDef td, // [IN] typedef
+ DWORD dwPackSize, // [IN] packing size specified as 1, 2, 4, 8, or 16
+ COR_FIELD_OFFSET rFieldOffsets[], // [IN] array of layout specification
+ ULONG ulClassSize); // [IN] size of the class
+
+ STDMETHOD(DeleteClassLayout) (
+ mdTypeDef td); // [IN] typedef whose layout is to be deleted.
+
+ STDMETHOD(SetFieldMarshal) (
+ mdToken tk, // [IN] given a fieldDef or paramDef token
+ PCCOR_SIGNATURE pvNativeType, // [IN] native type specification
+ ULONG cbNativeType); // [IN] count of bytes of pvNativeType
+
+ STDMETHOD(DeleteFieldMarshal) (
+ mdToken tk); // [IN] given a fieldDef or paramDef token
+
+ STDMETHOD(DefinePermissionSet) (
+ mdToken tk, // [IN] the object to be decorated.
+ DWORD dwAction, // [IN] CorDeclSecurity.
+ void const *pvPermission, // [IN] permission blob.
+ ULONG cbPermission, // [IN] count of bytes of pvPermission.
+ mdPermission *ppm); // [OUT] returned permission token.
+
+ STDMETHOD(SetRVA)( // S_OK or error.
+ mdMethodDef md, // [IN] Method for which to set offset
+ ULONG ulRVA); // [IN] The offset
+
+ STDMETHOD(GetTokenFromSig)( // S_OK or error.
+ PCCOR_SIGNATURE pvSig, // [IN] Signature to define.
+ ULONG cbSig, // [IN] Size of signature data.
+ mdSignature *pmsig); // [OUT] returned signature token.
+
+ STDMETHOD(DefineModuleRef)( // S_OK or error.
+ LPCWSTR szName, // [IN] DLL name
+ mdModuleRef *pmur); // [OUT] returned
+
+ // <TODO>This should go away once everyone starts using SetMemberRefProps.</TODO>
+ STDMETHOD(SetParent)( // S_OK or error.
+ mdMemberRef mr, // [IN] Token for the ref to be fixed up.
+ mdToken tk); // [IN] The ref parent.
+
+ STDMETHOD(GetTokenFromTypeSpec)( // S_OK or error.
+ PCCOR_SIGNATURE pvSig, // [IN] TypeSpec Signature to define.
+ ULONG cbSig, // [IN] Size of signature data.
+ mdTypeSpec *ptypespec); // [OUT] returned TypeSpec token.
+
+ STDMETHOD(SaveToMemory)( // S_OK or error.
+ void *pbData, // [OUT] Location to write data.
+ ULONG cbData); // [IN] Max size of data buffer.
+
+ STDMETHOD(DefineUserString)( // Return code.
+ LPCWSTR szString, // [IN] User literal string.
+ ULONG cchString, // [IN] Length of string.
+ mdString *pstk); // [OUT] String token.
+
+ STDMETHOD(DeleteToken)( // Return code.
+ mdToken tkObj); // [IN] The token to be deleted
+
+ STDMETHOD(SetMethodProps)( // S_OK or error.
+ mdMethodDef md, // [IN] The MethodDef.
+ DWORD dwMethodFlags, // [IN] Method attributes.
+ ULONG ulCodeRVA, // [IN] Code RVA.
+ DWORD dwImplFlags); // [IN] Impl flags.
+
+ STDMETHOD(SetTypeDefProps)( // S_OK or error.
+ mdTypeDef td, // [IN] The TypeDef.
+ DWORD dwTypeDefFlags, // [IN] TypeDef flags.
+ mdToken tkExtends, // [IN] Base TypeDef or TypeRef.
+ mdToken rtkImplements[]); // [IN] Implemented interfaces.
+
+ STDMETHOD(SetEventProps)( // S_OK or error.
+ mdEvent ev, // [IN] The event token.
+ DWORD dwEventFlags, // [IN] CorEventAttr.
+ mdToken tkEventType, // [IN] A reference (mdTypeRef or mdTypeRef) to the Event class.
+ mdMethodDef mdAddOn, // [IN] Add method.
+ mdMethodDef mdRemoveOn, // [IN] Remove method.
+ mdMethodDef mdFire, // [IN] Fire method.
+ mdMethodDef rmdOtherMethods[]);// [IN] Array of other methods associate with the event.
+
+ STDMETHOD(SetPermissionSetProps)( // S_OK or error.
+ mdToken tk, // [IN] The object to be decorated.
+ DWORD dwAction, // [IN] CorDeclSecurity.
+ void const *pvPermission, // [IN] Permission blob.
+ ULONG cbPermission, // [IN] Count of bytes of pvPermission.
+ mdPermission *ppm); // [OUT] Permission token.
+
+ STDMETHOD(DefinePinvokeMap)( // Return code.
+ mdToken tk, // [IN] FieldDef or MethodDef.
+ DWORD dwMappingFlags, // [IN] Flags used for mapping.
+ LPCWSTR szImportName, // [IN] Import name.
+ mdModuleRef mrImportDLL); // [IN] ModuleRef token for the target DLL.
+
+ STDMETHOD(SetPinvokeMap)( // Return code.
+ mdToken tk, // [IN] FieldDef or MethodDef.
+ DWORD dwMappingFlags, // [IN] Flags used for mapping.
+ LPCWSTR szImportName, // [IN] Import name.
+ mdModuleRef mrImportDLL); // [IN] ModuleRef token for the target DLL.
+
+ STDMETHOD(DeletePinvokeMap)( // Return code.
+ mdToken tk); // [IN] FieldDef or MethodDef.
+
+ // New CustomAttribute functions.
+ STDMETHOD(DefineCustomAttribute)( // Return code.
+ mdToken tkObj, // [IN] The object to put the value on.
+ mdToken tkType, // [IN] Type of the CustomAttribute (TypeRef/TypeDef).
+ void const *pCustomAttribute, // [IN] The custom value data.
+ ULONG cbCustomAttribute, // [IN] The custom value data length.
+ mdCustomAttribute *pcv); // [OUT] The custom value token value on return.
+
+ STDMETHOD(SetCustomAttributeValue)( // Return code.
+ mdCustomAttribute pcv, // [IN] The custom value token whose value to replace.
+ void const *pCustomAttribute, // [IN] The custom value data.
+ ULONG cbCustomAttribute);// [IN] The custom value data length.
+
+ STDMETHOD(DefineField)( // S_OK or error.
+ mdTypeDef td, // Parent TypeDef
+ LPCWSTR szName, // Name of member
+ DWORD dwFieldFlags, // Member attributes
+ PCCOR_SIGNATURE pvSigBlob, // [IN] point to a blob value of CLR signature
+ ULONG cbSigBlob, // [IN] count of bytes in the signature blob
+ DWORD dwCPlusTypeFlag, // [IN] flag for value type. selected ELEMENT_TYPE_*
+ void const *pValue, // [IN] constant value
+ ULONG cchValue, // [IN] size of constant value (string, in wide chars).
+ mdFieldDef *pmd); // [OUT] Put member token here
+
+ STDMETHOD(DefineProperty)(
+ mdTypeDef td, // [IN] the class/interface on which the property is being defined
+ LPCWSTR szProperty, // [IN] Name of the property
+ DWORD dwPropFlags, // [IN] CorPropertyAttr
+ PCCOR_SIGNATURE pvSig, // [IN] the required type signature
+ ULONG cbSig, // [IN] the size of the type signature blob
+ DWORD dwCPlusTypeFlag, // [IN] flag for value type. selected ELEMENT_TYPE_*
+ void const *pValue, // [IN] constant value
+ ULONG cchValue, // [IN] size of constant value (string, in wide chars).
+ mdMethodDef mdSetter, // [IN] optional setter of the property
+ mdMethodDef mdGetter, // [IN] optional getter of the property
+ mdMethodDef rmdOtherMethods[], // [IN] an optional array of other methods
+ mdProperty *pmdProp); // [OUT] output property token
+
+ STDMETHOD(DefineParam)(
+ mdMethodDef md, // [IN] Owning method
+ ULONG ulParamSeq, // [IN] Which param
+ LPCWSTR szName, // [IN] Optional param name
+ DWORD dwParamFlags, // [IN] Optional param flags
+ DWORD dwCPlusTypeFlag, // [IN] flag for value type. selected ELEMENT_TYPE_*
+ void const *pValue, // [IN] constant value
+ ULONG cchValue, // [IN] size of constant value (string, in wide chars).
+ mdParamDef *ppd); // [OUT] Put param token here
+
+ STDMETHOD(SetFieldProps)( // S_OK or error.
+ mdFieldDef fd, // [IN] The FieldDef.
+ DWORD dwFieldFlags, // [IN] Field attributes.
+ DWORD dwCPlusTypeFlag, // [IN] Flag for the value type, selected ELEMENT_TYPE_*
+ void const *pValue, // [IN] Constant value.
+ ULONG cchValue); // [IN] size of constant value (string, in wide chars).
+
+ STDMETHOD(SetPropertyProps)( // S_OK or error.
+ mdProperty pr, // [IN] Property token.
+ DWORD dwPropFlags, // [IN] CorPropertyAttr.
+ DWORD dwCPlusTypeFlag, // [IN] Flag for value type, selected ELEMENT_TYPE_*
+ void const *pValue, // [IN] Constant value.
+ ULONG cchValue, // [IN] size of constant value (string, in wide chars).
+ mdMethodDef mdSetter, // [IN] Setter of the property.
+ mdMethodDef mdGetter, // [IN] Getter of the property.
+ mdMethodDef rmdOtherMethods[]);// [IN] Array of other methods.
+
+ STDMETHOD(SetParamProps)( // Return code.
+ mdParamDef pd, // [IN] Param token.
+ LPCWSTR szName, // [IN] Param name.
+ DWORD dwParamFlags, // [IN] Param flags.
+ DWORD dwCPlusTypeFlag, // [IN] Flag for value type. selected ELEMENT_TYPE_*.
+ void const *pValue, // [OUT] Constant value.
+ ULONG cchValue); // [IN] size of constant value (string, in wide chars).
+
+ // Specialized Custom Attributes for security.
+ STDMETHOD(DefineSecurityAttributeSet)( // Return code.
+ mdToken tkObj, // [IN] Class or method requiring security attributes.
+ COR_SECATTR rSecAttrs[], // [IN] Array of security attribute descriptions.
+ ULONG cSecAttrs, // [IN] Count of elements in above array.
+ ULONG *pulErrorAttr); // [OUT] On error, index of attribute causing problem.
+
+ STDMETHOD(ApplyEditAndContinue)( // S_OK or error.
+ IUnknown *pImport); // [IN] Metadata from the delta PE.
+
+ STDMETHOD(TranslateSigWithScope)(
+ IMetaDataAssemblyImport *pAssemImport, // [IN] importing assembly interface
+ const void *pbHashValue, // [IN] Hash Blob for Assembly.
+ ULONG cbHashValue, // [IN] Count of bytes.
+ IMetaDataImport *import, // [IN] importing interface
+ PCCOR_SIGNATURE pbSigBlob, // [IN] signature in the importing scope
+ ULONG cbSigBlob, // [IN] count of bytes of signature
+ IMetaDataAssemblyEmit *pAssemEmit, // [IN] emit assembly interface
+ IMetaDataEmit *emit, // [IN] emit interface
+ PCOR_SIGNATURE pvTranslatedSig, // [OUT] buffer to hold translated signature
+ ULONG cbTranslatedSigMax,
+ ULONG *pcbTranslatedSig);// [OUT] count of bytes in the translated signature
+
+ STDMETHOD(SetMethodImplFlags)( // [IN] S_OK or error.
+ mdMethodDef md, // [IN] Method for which to set ImplFlags
+ DWORD dwImplFlags);
+
+ STDMETHOD(SetFieldRVA)( // [IN] S_OK or error.
+ mdFieldDef fd, // [IN] Field for which to set offset
+ ULONG ulRVA); // [IN] The offset
+
+ STDMETHOD(Merge)( // S_OK or error.
+ IMetaDataImport *pImport, // [IN] The scope to be merged.
+ IMapToken *pHostMapToken, // [IN] Host IMapToken interface to receive token remap notification
+ IUnknown *pHandler); // [IN] An object to receive to receive error notification.
+
+ STDMETHOD(MergeEnd)(); // S_OK or error.
+
+};
+
+#endif // SYMBOLINFO_H
diff --git a/src/debug/di/valuehome.cpp b/src/debug/di/valuehome.cpp
new file mode 100644
index 0000000000..837afd5f8b
--- /dev/null
+++ b/src/debug/di/valuehome.cpp
@@ -0,0 +1,1062 @@
+// 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.
+//*****************************************************************************
+// File: ValueHome.cpp
+//
+
+//
+//*****************************************************************************
+#include "stdafx.h"
+#include "primitives.h"
+
+// constructor to initialize an instance of EnregisteredValueHome
+// Arguments:
+// input: pFrame - frame to which the value belongs
+// output: no out parameters, but the instance has been initialized
+EnregisteredValueHome::EnregisteredValueHome(const CordbNativeFrame * pFrame):
+ m_pFrame(pFrame)
+{
+ _ASSERTE(pFrame != NULL);
+}
+
+// ----------------------------------------------------------------------------
+// RegValueHome member function implementations
+// ----------------------------------------------------------------------------
+
+// initialize an instance of RemoteAddress for use in an IPC event buffer with values from this
+// instance of a derived class of EnregisteredValueHome (see EnregisteredValueHome::CopyToIPCEType for full
+// header comment)
+void RegValueHome::CopyToIPCEType(RemoteAddress * pRegAddr)
+{
+ pRegAddr->kind = RAK_REG;
+ pRegAddr->reg1 = m_reg1Info.m_kRegNumber;
+ pRegAddr->reg1Addr = CORDB_ADDRESS_TO_PTR(m_reg1Info.m_regAddr);
+ pRegAddr->reg1Value = m_reg1Info.m_regValue;
+} // RegValueHome::CopyToIPCEType
+
+// RegValueHome::SetContextRegister
+// This will update a register in a given context, and in the regdisplay of a given frame.
+// Arguments:
+// input: pContext - context from which the register comes
+// regnum - enumeration constant indicating which register is to be updated
+// newVal - the new value for the register contents
+// output: no out parameters, but the new value will be written to the context and the frame
+// Notes: We don't take a data target here because we are directly writing process memory and passing
+// in a context, which has the location to update.
+// Throws
+void RegValueHome::SetContextRegister(DT_CONTEXT * pContext,
+ CorDebugRegister regNum,
+ SIZE_T newVal)
+{
+ LPVOID rdRegAddr;
+
+#define _UpdateFrame() \
+ if (m_pFrame != NULL) \
+ { \
+ rdRegAddr = m_pFrame->GetAddressOfRegister(regNum); \
+ *(SIZE_T *)rdRegAddr = newVal; \
+ }
+
+ switch(regNum)
+ {
+ case REGISTER_INSTRUCTION_POINTER: CORDbgSetIP(pContext, (LPVOID)newVal); break;
+ case REGISTER_STACK_POINTER: CORDbgSetSP(pContext, (LPVOID)newVal); break;
+
+#if defined(DBG_TARGET_X86)
+ case REGISTER_FRAME_POINTER: CORDbgSetFP(pContext, (LPVOID)newVal);
+ _UpdateFrame(); break;
+
+ case REGISTER_X86_EAX: pContext->Eax = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_X86_ECX: pContext->Ecx = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_X86_EDX: pContext->Edx = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_X86_EBX: pContext->Ebx = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_X86_ESI: pContext->Esi = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_X86_EDI: pContext->Edi = newVal;
+ _UpdateFrame(); break;
+
+#elif defined(DBG_TARGET_AMD64)
+ case REGISTER_AMD64_RBP: pContext->Rbp = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_AMD64_RAX: pContext->Rax = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_AMD64_RCX: pContext->Rcx = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_AMD64_RDX: pContext->Rdx = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_AMD64_RBX: pContext->Rbx = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_AMD64_RSI: pContext->Rsi = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_AMD64_RDI: pContext->Rdi = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_AMD64_R8: pContext->R8 = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_AMD64_R9: pContext->R9 = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_AMD64_R10: pContext->R10 = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_AMD64_R11: pContext->R11 = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_AMD64_R12: pContext->R12 = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_AMD64_R13: pContext->R13 = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_AMD64_R14: pContext->R14 = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_AMD64_R15: pContext->R15 = newVal;
+ _UpdateFrame(); break;
+#elif defined(DBG_TARGET_ARM)
+ case REGISTER_ARM_R0: pContext->R0 = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_ARM_R1: pContext->R1 = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_ARM_R2: pContext->R2 = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_ARM_R3: pContext->R3 = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_ARM_R4: pContext->R4 = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_ARM_R5: pContext->R5 = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_ARM_R6: pContext->R6 = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_ARM_R7: pContext->R7 = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_ARM_R8: pContext->R8 = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_ARM_R9: pContext->R9 = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_ARM_R10: pContext->R10 = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_ARM_R11: pContext->R11 = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_ARM_R12: pContext->R12 = newVal;
+ _UpdateFrame(); break;
+ case REGISTER_ARM_LR: pContext->Lr = newVal;
+ _UpdateFrame(); break;
+#elif defined(DBG_TARGET_ARM64)
+ case REGISTER_ARM64_X0:
+ case REGISTER_ARM64_X1:
+ case REGISTER_ARM64_X2:
+ case REGISTER_ARM64_X3:
+ case REGISTER_ARM64_X4:
+ case REGISTER_ARM64_X5:
+ case REGISTER_ARM64_X6:
+ case REGISTER_ARM64_X7:
+ case REGISTER_ARM64_X8:
+ case REGISTER_ARM64_X9:
+ case REGISTER_ARM64_X10:
+ case REGISTER_ARM64_X11:
+ case REGISTER_ARM64_X12:
+ case REGISTER_ARM64_X13:
+ case REGISTER_ARM64_X14:
+ case REGISTER_ARM64_X15:
+ case REGISTER_ARM64_X16:
+ case REGISTER_ARM64_X17:
+ case REGISTER_ARM64_X18:
+ case REGISTER_ARM64_X19:
+ case REGISTER_ARM64_X20:
+ case REGISTER_ARM64_X21:
+ case REGISTER_ARM64_X22:
+ case REGISTER_ARM64_X23:
+ case REGISTER_ARM64_X24:
+ case REGISTER_ARM64_X25:
+ case REGISTER_ARM64_X26:
+ case REGISTER_ARM64_X27:
+ case REGISTER_ARM64_X28: pContext->X[regNum - REGISTER_ARM64_X0] = newVal;
+ _UpdateFrame(); break;
+
+ case REGISTER_ARM64_LR: pContext->Lr = newVal;
+ _UpdateFrame(); break;
+#endif
+ default:
+ _ASSERTE(!"Invalid register number!");
+ ThrowHR(E_FAIL);
+ }
+} // RegValueHome::SetContextRegister
+
+// RegValueHome::SetEnregisteredValue
+// set a remote enregistered location to a new value (see code:EnregisteredValueHome::SetEnregisteredValue
+// for full header comment)
+void RegValueHome::SetEnregisteredValue(MemoryRange newValue, DT_CONTEXT * pContext, bool fIsSigned)
+{
+ SIZE_T extendedVal = 0;
+
+ // If the value is in a reg, then it's going to be a register's width (regardless of
+ // the actual width of the data).
+ // For signed types, like i2, i1, make sure we sign extend.
+
+ if (fIsSigned)
+ {
+ // Sign extend. SSIZE_T is a register size signed value.
+ // Casting
+ switch(newValue.Size())
+ {
+ case 1: _ASSERTE(sizeof( BYTE) == 1);
+ extendedVal = (SSIZE_T) *(char*)newValue.StartAddress(); break;
+ case 2: _ASSERTE(sizeof( WORD) == 2);
+ extendedVal = (SSIZE_T) *(short*)newValue.StartAddress(); break;
+ case 4: _ASSERTE(sizeof(DWORD) == 4);
+ extendedVal = (SSIZE_T) *(int*)newValue.StartAddress(); break;
+#if defined(DBG_TARGET_WIN64)
+ case 8: _ASSERTE(sizeof(ULONGLONG) == 8);
+ extendedVal = (SSIZE_T) *(ULONGLONG*)newValue.StartAddress(); break;
+#endif // DBG_TARGET_WIN64
+ default: _ASSERTE(!"bad size");
+ }
+ }
+ else
+ {
+ // Zero extend.
+ switch(newValue.Size())
+ {
+ case 1: _ASSERTE(sizeof( BYTE) == 1);
+ extendedVal = *( BYTE*)newValue.StartAddress(); break;
+ case 2: _ASSERTE(sizeof( WORD) == 2);
+ extendedVal = *( WORD*)newValue.StartAddress(); break;
+ case 4: _ASSERTE(sizeof(DWORD) == 4);
+ extendedVal = *(DWORD*)newValue.StartAddress(); break;
+#if defined(DBG_TARGET_WIN64)
+ case 8: _ASSERTE(sizeof(ULONGLONG) == 8);
+ extendedVal = *(ULONGLONG*)newValue.StartAddress(); break;
+#endif // DBG_TARGET_WIN64
+ default: _ASSERTE(!"bad size");
+ }
+ }
+
+ SetContextRegister(pContext, m_reg1Info.m_kRegNumber, extendedVal); // throws
+} // RegValueHome::SetEnregisteredValue
+
+// RegValueHome::GetEnregisteredValue
+// Gets an enregistered value and returns it to the caller (see EnregisteredValueHome::GetEnregisteredValue
+// for full header comment)
+void RegValueHome::GetEnregisteredValue(MemoryRange valueOutBuffer)
+{
+ UINT_PTR* reg = m_pFrame->GetAddressOfRegister(m_reg1Info.m_kRegNumber);
+ PREFIX_ASSUME(reg != NULL);
+ _ASSERTE(sizeof(*reg) == valueOutBuffer.Size());
+
+ memcpy(valueOutBuffer.StartAddress(), reg, sizeof(*reg));
+} // RegValueHome::GetEnregisteredValue
+
+
+// ----------------------------------------------------------------------------
+// RegRegValueHome member function implementations
+// ----------------------------------------------------------------------------
+
+// initialize an instance of RemoteAddress for use in an IPC event buffer with values from this
+// instance of a derived class of EnregisteredValueHome (see EnregisteredValueHome::CopyToIPCEType for full
+// header comment)
+void RegRegValueHome::CopyToIPCEType(RemoteAddress * pRegAddr)
+{
+ pRegAddr->kind = RAK_REGREG;
+ pRegAddr->reg1 = m_reg1Info.m_kRegNumber;
+ pRegAddr->reg1Addr = CORDB_ADDRESS_TO_PTR(m_reg1Info.m_regAddr);
+ pRegAddr->reg1Value = m_reg1Info.m_regValue;
+ pRegAddr->u.reg2 = m_reg2Info.m_kRegNumber;
+ pRegAddr->u.reg2Addr = CORDB_ADDRESS_TO_PTR(m_reg2Info.m_regAddr);
+ pRegAddr->u.reg2Value = m_reg2Info.m_regValue;
+} // RegRegValueHome::CopyToIPCEType
+
+// RegRegValueHome::SetEnregisteredValue
+// set a remote enregistered location to a new value (see EnregisteredValueHome::SetEnregisteredValue
+// for full header comment)
+void RegRegValueHome::SetEnregisteredValue(MemoryRange newValue, DT_CONTEXT * pContext, bool fIsSigned)
+{
+ _ASSERTE(newValue.Size() == 8);
+ _ASSERTE(REG_SIZE == sizeof(void*));
+
+ // Split the new value into high and low parts.
+ SIZE_T highPart;
+ SIZE_T lowPart;
+
+ memcpy(&lowPart, newValue.StartAddress(), REG_SIZE);
+ memcpy(&highPart, (BYTE *)newValue.StartAddress() + REG_SIZE, REG_SIZE);
+
+ // Update the proper registers.
+ SetContextRegister(pContext, m_reg1Info.m_kRegNumber, highPart); // throws
+ SetContextRegister(pContext, m_reg2Info.m_kRegNumber, lowPart); // throws
+
+ // update the frame's register display
+ void * valueAddress = (void *)(m_pFrame->GetAddressOfRegister(m_reg1Info.m_kRegNumber));
+ memcpy(valueAddress, newValue.StartAddress(), newValue.Size());
+} // RegRegValueHome::SetEnregisteredValue
+
+// RegRegValueHome::GetEnregisteredValue
+// Gets an enregistered value and returns it to the caller (see EnregisteredValueHome::GetEnregisteredValue
+// for full header comment)
+void RegRegValueHome::GetEnregisteredValue(MemoryRange valueOutBuffer)
+{
+ UINT_PTR* highWordAddr = m_pFrame->GetAddressOfRegister(m_reg1Info.m_kRegNumber);
+ PREFIX_ASSUME(highWordAddr != NULL);
+
+ UINT_PTR* lowWordAddr = m_pFrame->GetAddressOfRegister(m_reg2Info.m_kRegNumber);
+ PREFIX_ASSUME(lowWordAddr != NULL);
+
+ _ASSERTE(sizeof(*highWordAddr) + sizeof(*lowWordAddr) == valueOutBuffer.Size());
+
+ memcpy(valueOutBuffer.StartAddress(), lowWordAddr, sizeof(*lowWordAddr));
+ memcpy((BYTE *)valueOutBuffer.StartAddress() + sizeof(*lowWordAddr), highWordAddr, sizeof(*highWordAddr));
+
+} // RegRegValueHome::GetEnregisteredValue
+
+
+// ----------------------------------------------------------------------------
+// RegMemValueHome member function implementations
+// ----------------------------------------------------------------------------
+
+// initialize an instance of RemoteAddress for use in an IPC event buffer with values from this
+// instance of a derived class of EnregisteredValueHome (see EnregisteredValueHome::CopyToIPCEType for full
+// header comment)
+void RegMemValueHome::CopyToIPCEType(RemoteAddress * pRegAddr)
+{
+ pRegAddr->kind = RAK_REGMEM;
+ pRegAddr->reg1 = m_reg1Info.m_kRegNumber;
+ pRegAddr->reg1Addr = CORDB_ADDRESS_TO_PTR(m_reg1Info.m_regAddr);
+ pRegAddr->reg1Value = m_reg1Info.m_regValue;
+ pRegAddr->addr = m_memAddr;
+} // RegMemValueHome::CopyToIPCEType
+
+// RegMemValueHome::SetEnregisteredValue
+// set a remote enregistered location to a new value (see EnregisteredValueHome::SetEnregisteredValue
+// for full header comment)
+void RegMemValueHome::SetEnregisteredValue(MemoryRange newValue, DT_CONTEXT * pContext, bool fIsSigned)
+{
+ _ASSERTE(newValue.Size() == REG_SIZE >> 1); // make sure we have bytes for two registers
+ _ASSERTE(REG_SIZE == sizeof(void*));
+
+ // Split the new value into high and low parts.
+ SIZE_T highPart;
+ SIZE_T lowPart;
+
+ memcpy(&lowPart, newValue.StartAddress(), REG_SIZE);
+ memcpy(&highPart, (BYTE *)newValue.StartAddress() + REG_SIZE, REG_SIZE);
+
+ // Update the proper registers.
+ SetContextRegister(pContext, m_reg1Info.m_kRegNumber, highPart); // throws
+
+ _ASSERTE(REG_SIZE == sizeof(lowPart));
+ HRESULT hr = m_pFrame->GetProcess()->SafeReadStruct(m_memAddr, &lowPart);
+ IfFailThrow(hr);
+
+} // RegMemValueHome::SetEnregisteredValue
+
+// RegMemValueHome::GetEnregisteredValue
+// Gets an enregistered value and returns it to the caller (see EnregisteredValueHome::GetEnregisteredValue
+// for full header comment)
+void RegMemValueHome::GetEnregisteredValue(MemoryRange valueOutBuffer)
+{
+ // Read the high bits from the register...
+ UINT_PTR* highBitsAddr = m_pFrame->GetAddressOfRegister(m_reg1Info.m_kRegNumber);
+ PREFIX_ASSUME(highBitsAddr != NULL);
+
+ // ... and the low bits from the remote process
+ DWORD lowBits;
+ HRESULT hr = m_pFrame->GetProcess()->SafeReadStruct(m_memAddr, &lowBits);
+ IfFailThrow(hr);
+
+ _ASSERTE(sizeof(lowBits)+sizeof(*highBitsAddr) == valueOutBuffer.Size());
+
+ memcpy(valueOutBuffer.StartAddress(), &lowBits, sizeof(lowBits));
+ memcpy((BYTE *)valueOutBuffer.StartAddress() + sizeof(lowBits), highBitsAddr, sizeof(*highBitsAddr));
+
+} // RegMemValueHome::GetEnregisteredValue
+
+
+// ----------------------------------------------------------------------------
+// MemRegValueHome member function implementations
+// ----------------------------------------------------------------------------
+
+// initialize an instance of RemoteAddress for use in an IPC event buffer with values from this
+// instance of a derived class of EnregisteredValueHome (see EnregisteredValueHome::CopyToIPCEType for full
+// header comment)
+void MemRegValueHome::CopyToIPCEType(RemoteAddress * pRegAddr)
+{
+ pRegAddr->kind = RAK_MEMREG;
+ pRegAddr->reg1 = m_reg1Info.m_kRegNumber;
+ pRegAddr->reg1Addr = CORDB_ADDRESS_TO_PTR(m_reg1Info.m_regAddr);
+ pRegAddr->reg1Value = m_reg1Info.m_regValue;
+ pRegAddr->addr = m_memAddr;
+} // MemRegValueHome::CopyToIPCEType
+
+// MemRegValueHome::SetEnregisteredValue
+// set a remote enregistered location to a new value (see EnregisteredValueHome::SetEnregisteredValue
+// for full header comment)
+void MemRegValueHome::SetEnregisteredValue(MemoryRange newValue, DT_CONTEXT * pContext, bool fIsSigned)
+{
+ _ASSERTE(newValue.Size() == REG_SIZE << 1); // make sure we have bytes for two registers
+ _ASSERTE(REG_SIZE == sizeof(void *));
+
+ // Split the new value into high and low parts.
+ SIZE_T highPart;
+ SIZE_T lowPart;
+
+ memcpy(&lowPart, newValue.StartAddress(), REG_SIZE);
+ memcpy(&highPart, (BYTE *)newValue.StartAddress() + REG_SIZE, REG_SIZE);
+
+ // Update the proper registers.
+ SetContextRegister(pContext, m_reg1Info.m_kRegNumber, lowPart); // throws
+
+ _ASSERTE(REG_SIZE == sizeof(highPart));
+ HRESULT hr = m_pFrame->GetProcess()->SafeWriteStruct(m_memAddr, &highPart);
+ IfFailThrow(hr);
+} // MemRegValueHome::SetEnregisteredValue
+
+// MemRegValueHome::GetEnregisteredValue
+// Gets an enregistered value and returns it to the caller (see EnregisteredValueHome::GetEnregisteredValue
+// for full header comment)
+void MemRegValueHome::GetEnregisteredValue(MemoryRange valueOutBuffer)
+{
+ // Read the high bits from the remote process' memory
+ DWORD highBits;
+ HRESULT hr = m_pFrame->GetProcess()->SafeReadStruct(m_memAddr, &highBits);
+ IfFailThrow(hr);
+
+
+ // and the low bits from a register
+ UINT_PTR* lowBitsAddr = m_pFrame->GetAddressOfRegister(m_reg1Info.m_kRegNumber);
+ PREFIX_ASSUME(lowBitsAddr != NULL);
+
+ _ASSERTE(sizeof(*lowBitsAddr)+sizeof(highBits) == valueOutBuffer.Size());
+
+ memcpy(valueOutBuffer.StartAddress(), lowBitsAddr, sizeof(*lowBitsAddr));
+ memcpy((BYTE *)valueOutBuffer.StartAddress() + sizeof(*lowBitsAddr), &highBits, sizeof(highBits));
+
+} // MemRegValueHome::GetEnregisteredValue
+
+#if !defined(DBG_TARGET_ARM) // @ARMTODO
+
+// ----------------------------------------------------------------------------
+// FloatRegValueHome member function implementations
+// ----------------------------------------------------------------------------
+
+// initialize an instance of RemoteAddress for use in an IPC event buffer with values from this
+// instance of a derived class of EnregisteredValueHome (see EnregisteredValueHome::CopyToIPCEType for full
+// header comment)
+void FloatRegValueHome::CopyToIPCEType(RemoteAddress * pRegAddr)
+{
+ pRegAddr->kind = RAK_FLOAT;
+ pRegAddr->reg1Addr = NULL;
+ pRegAddr->floatIndex = m_floatIndex;
+} // FloatRegValueHome::CopyToIPCEType
+
+// FloatValueHome::SetEnregisteredValue
+// set a remote enregistered location to a new value (see EnregisteredValueHome::SetEnregisteredValue
+// for full header comment)
+void FloatRegValueHome::SetEnregisteredValue(MemoryRange newValue,
+ DT_CONTEXT * pContext,
+ bool fIsSigned)
+{
+ // TODO: : implement CordbValue::SetEnregisteredValue for RAK_FLOAT
+ #if defined(DBG_TARGET_AMD64)
+ PORTABILITY_ASSERT("NYI: SetEnregisteredValue (divalue.cpp): RAK_FLOAT for AMD64");
+ #endif // DBG_TARGET_AMD64
+
+ _ASSERTE((newValue.Size() == 4) || (newValue.Size() == 8));
+
+ // Convert the input to a double.
+ double newVal = 0.0;
+
+ memcpy(&newVal, newValue.StartAddress(), newValue.Size());
+
+ #if defined(DBG_TARGET_X86)
+
+ // This is unfortunately non-portable. Luckily we can live with this for now since we only support
+ // Win/X86 debugging a Mac/X86 platform.
+
+ #if !defined(_TARGET_X86_)
+ #error Unsupported target platform
+ #endif // !_TARGET_X86_
+
+ // What a pain, on X86 take the floating
+ // point state in the context and make it our current FP
+ // state, set the value into the current FP state, then
+ // save out the FP state into the context again and
+ // restore our original state.
+ DT_FLOATING_SAVE_AREA currentFPUState;
+
+ __asm fnsave currentFPUState // save the current FPU state.
+
+ // Copy the state out of the context.
+ DT_FLOATING_SAVE_AREA floatarea = pContext->FloatSave;
+ floatarea.StatusWord &= 0xFF00; // remove any error codes.
+ floatarea.ControlWord |= 0x3F; // mask all exceptions.
+
+ __asm
+ {
+ fninit
+ frstor floatarea ;; reload the threads FPU state.
+ }
+
+ double td; // temp double
+ double popArea[DebuggerIPCE_FloatCount];
+
+ // Pop off until we reach the value we want to change.
+ DWORD i = 0;
+
+ while (i <= m_floatIndex)
+ {
+ __asm fstp td
+ popArea[i++] = td;
+ }
+
+ __asm fld newVal; // push on the new value.
+
+ // Push any values that we popled off back onto the stack,
+ // _except_ the last one, which was the one we changed.
+ i--;
+
+ while (i > 0)
+ {
+ td = popArea[--i];
+ __asm fld td
+ }
+
+ // Save out the modified float area.
+ __asm fnsave floatarea
+
+ // Put it into the context.
+ pContext->FloatSave= floatarea;
+
+ // Restore our FPU state
+ __asm
+ {
+ fninit
+ frstor currentFPUState ;; restore our saved FPU state.
+ }
+ #endif // DBG_TARGET_X86
+
+ // update the thread's floating point stack
+ void * valueAddress = (void *) &(m_pFrame->m_pThread->m_floatValues[m_floatIndex]);
+ memcpy(valueAddress, newValue.StartAddress(), newValue.Size());
+} // FloatValueHome::SetEnregisteredValue
+
+// FloatRegValueHome::GetEnregisteredValue
+// Throws E_NOTIMPL for attempts to get an enregistered value for a float register
+void FloatRegValueHome::GetEnregisteredValue(MemoryRange valueOutBuffer)
+{
+ _ASSERTE(!"invalid variable home");
+ ThrowHR(E_NOTIMPL);
+} // FloatRegValueHome::GetEnregisteredValue
+
+#endif // !DBG_TARGET_ARM @ARMTODO
+
+// ============================================================================
+// RemoteValueHome implementation
+// ============================================================================
+
+ // constructor
+ // Arguments:
+ // input: pProcess - the process to which the value belongs
+ // remoteValue - a buffer with the target address of the value and its size
+ // Note: It's possible a particular instance of CordbGenericValue may have neither a remote address nor a
+ // register address--FuncEval makes empty GenericValues for literals but for those, we will make a
+ // RegisterValueHome,so we can assert that we have a non-null remote address here
+ RemoteValueHome::RemoteValueHome(CordbProcess * pProcess, TargetBuffer remoteValue):
+ ValueHome(pProcess),
+ m_remoteValue(remoteValue)
+ {
+ _ASSERTE(remoteValue.pAddress != NULL);
+ } // RemoteValueHome::RemoteValueHome
+
+// Gets a value and returns it in dest
+// virtual
+void RemoteValueHome::GetValue(MemoryRange dest)
+{
+ _ASSERTE(dest.Size() == m_remoteValue.cbSize);
+ _ASSERTE((!m_remoteValue.IsEmpty()) && (dest.StartAddress() != NULL));
+ m_pProcess->SafeReadBuffer(m_remoteValue, (BYTE *)dest.StartAddress());
+} // RemoteValueHome::GetValue
+
+// Sets a location to the value provided in src
+// virtual
+void RemoteValueHome::SetValue(MemoryRange src, CordbType * pType)
+{
+ _ASSERTE(!m_remoteValue.IsEmpty());
+ _ASSERTE(src.Size() == m_remoteValue.cbSize);
+ _ASSERTE(src.StartAddress() != NULL);
+ m_pProcess->SafeWriteBuffer(m_remoteValue, (BYTE *)src.StartAddress());
+} // RemoteValueHome::SetValue
+
+// creates an ICDValue for a field or array element or for the value type of a boxed object
+// virtual
+void RemoteValueHome::CreateInternalValue(CordbType * pType,
+ SIZE_T offset,
+ void * localAddress,
+ ULONG32 size,
+ ICorDebugValue ** ppValue)
+{
+ // If we're creating an ICDValue for a field added with EnC, the local address will be null, since the field
+ // will not be included with the local cached copy of the ICDObjectValue to which the field belongs.
+ // This means we need to compute the size for the type of the field, and then determine whether this
+ // should also be the size for the localValue we pass to CreateValueByType. The only way we can tell if this
+ // is an EnC added field is if the local address is NULL.
+ ULONG32 localSize = localAddress != NULL ? size : 0;
+
+ CordbAppDomain * pAppdomain = pType->GetAppDomain();
+
+ CordbValue::CreateValueByType(pAppdomain,
+ pType,
+ kUnboxed,
+ TargetBuffer(m_remoteValue.pAddress + offset, size),
+ MemoryRange(localAddress, localSize),
+ NULL, // remote reg
+ ppValue); // throws
+} // RemoteValueHome::CreateInternalValue
+
+// Gets the value of a field or element of an existing ICDValue instance and returns it in dest
+void RemoteValueHome::GetInternalValue(MemoryRange dest, SIZE_T offset)
+{
+ _ASSERTE((!m_remoteValue.IsEmpty()) && (dest.StartAddress() != NULL));
+
+ m_pProcess->SafeReadBuffer(TargetBuffer(m_remoteValue.pAddress + offset, (ULONG)dest.Size()),
+ (BYTE *)dest.StartAddress());
+} // RemoteValueHome::GetInternalValue
+
+// copies the register information from this to a RemoteAddress instance
+// virtual
+void RemoteValueHome::CopyToIPCEType(RemoteAddress * pRegAddr)
+{
+ pRegAddr->kind = RAK_NONE;
+} // RegisterValueHome::CopyToIPCEType
+
+
+// ============================================================================
+// RegisterValueHome implementation
+// ============================================================================
+
+// constructor
+// Arguments:
+// input: pProcess - process for this value
+// ppRemoteRegAddr - enregistered value information
+//
+RegisterValueHome::RegisterValueHome(CordbProcess * pProcess,
+ EnregisteredValueHomeHolder * ppRemoteRegAddr):
+ ValueHome(pProcess)
+{
+ EnregisteredValueHome * pRemoteRegAddr = ppRemoteRegAddr == NULL ? NULL : ppRemoteRegAddr->GetValue();
+ // in the general case, we should have either a remote address or a register address, but FuncEval makes
+ // empty GenericValues for literals, so it's possible that we have neither address
+
+ if (pRemoteRegAddr != NULL)
+ {
+ m_pRemoteRegAddr = pRemoteRegAddr;
+ // be sure not to delete the remote register information on exit
+ ppRemoteRegAddr->SuppressRelease();
+ }
+ else
+ {
+ m_pRemoteRegAddr = NULL;
+ }
+ } // RegisterValueHome::RegisterValueHome
+
+// clean up resources as necessary
+void RegisterValueHome::Clear()
+{
+ if (m_pRemoteRegAddr != NULL)
+ {
+ delete m_pRemoteRegAddr;
+ m_pRemoteRegAddr = NULL;
+ }
+} // RegisterValueHome::Clear
+
+// Gets a value and returns it in dest
+// virtual
+void RegisterValueHome::GetValue(MemoryRange dest)
+{
+ // FuncEval makes empty CordbGenericValue instances for literals, which will have a RegisterValueHome,
+ // but we should not be calling this in that case; we should be able to assert that the register
+ // address isn't NULL
+ _ASSERTE(m_pRemoteRegAddr != NULL);
+ m_pRemoteRegAddr->GetEnregisteredValue(dest); // throws
+} // RegisterValueHome::GetValue
+
+// Sets a location to the value provided in src
+void RegisterValueHome::SetValue(MemoryRange src, CordbType * pType)
+{
+ SetEnregisteredValue(src, IsSigned(pType->m_elementType)); // throws
+} // RegisterValueHome::SetValue
+
+// creates an ICDValue for a field or array element or for the value type of a boxed object
+// virtual
+void RegisterValueHome::CreateInternalValue(CordbType * pType,
+ SIZE_T offset,
+ void * localAddress,
+ ULONG32 size,
+ ICorDebugValue ** ppValue)
+{
+ TargetBuffer remoteValue(PTR_TO_CORDB_ADDRESS((void *)NULL),0);
+ EnregisteredValueHomeHolder pRemoteReg(NULL);
+
+ _ASSERTE(m_pRemoteRegAddr != NULL);
+ // Remote register address is the same as the parent....
+ /*
+ * <TODO>
+ * nickbe 10/17/2002 07:31:53
+ * If this object consists of two register-sized fields, e.g.
+ * struct Point
+ * {
+ * int x;
+ * int y;
+ * };
+ * then the variable home of this object is not necessarily the variable
+ * home for member data within this object. For example, if we have
+ * Point p;
+ * and p.x is in a register, while p.y is in memory, then clearly the
+ * home of p (RAK_REGMEM) is not the same as the home of p.x (RAK_MEM).
+ *
+ * Currently the JIT does not split compound objects in this way. It
+ * will only split an object that has exactly one field that is twice
+ * the size of the register
+ * </TODO>
+ */
+ _ASSERTE(offset == 0);
+ pRemoteReg.Assign(m_pRemoteRegAddr->Clone());
+
+ EnregisteredValueHomeHolder * pRegHolder = pRemoteReg.GetAddr();
+
+ // create a value for the member field.
+ CordbValue::CreateValueByType(pType->GetAppDomain(),
+ pType,
+ kUnboxed,
+ EMPTY_BUFFER, // remote address
+ MemoryRange(localAddress, size),
+ pRegHolder,
+ ppValue); // throws
+} // RegisterValueHome::CreateInternalValue
+
+// Gets the value of a field or element of an existing ICDValue instance and returns it in dest
+// virtual
+void RegisterValueHome::GetInternalValue(MemoryRange dest, SIZE_T offset)
+{
+ // currently, we can't have an enregistered value that has more than one field or element, so
+ // there's nothing to do here but ASSERT. If the JIT changes what can be enregistered, we'll have
+ // work to do here
+ _ASSERTE(!"Compound types are not enregistered--we shouldn't be here");
+ ThrowHR(E_INVALIDARG);
+} // RegisterValueHome::GetInternalValue
+
+
+// copies the register information from this to a RemoteAddress instance
+// virtual
+void RegisterValueHome::CopyToIPCEType(RemoteAddress * pRegAddr)
+{
+ if(m_pRemoteRegAddr != NULL)
+ {
+ m_pRemoteRegAddr->CopyToIPCEType(pRegAddr);
+ }
+ else
+ {
+ pRegAddr->kind = RAK_NONE;
+ }
+} // RegisterValueHome::CopyToIPCEType
+
+// sets a remote enregistered location to a new value
+// Arguments:
+// input: src - contains the new value
+// fIsSigned - indicates whether the new value is signed (needed for proper extension
+// Return value: S_OK on success or CORDBG_E_SET_VALUE_NOT_ALLOWED_ON_NONLEAF_FRAME, CORDBG_E_CONTEXT_UNVAILABLE,
+// or HRESULT values from writing process memory
+void RegisterValueHome::SetEnregisteredValue(MemoryRange src, bool fIsSigned)
+{
+ _ASSERTE(m_pRemoteRegAddr != NULL);
+ // Get the thread's context so we can update it.
+ DT_CONTEXT * cTemp;
+ const CordbNativeFrame * frame = m_pRemoteRegAddr->GetFrame();
+
+ // Can't set an enregistered value unless the frame the value was
+ // from is also the current leaf frame. This is because we don't
+ // track where we get the registers from every frame from.
+
+ if (!frame->IsLeafFrame())
+ {
+ ThrowHR(CORDBG_E_SET_VALUE_NOT_ALLOWED_ON_NONLEAF_FRAME);
+ }
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // This may throw, in which case we want to return our own HRESULT.
+ hr = frame->m_pThread->GetManagedContext(&cTemp);
+ }
+ EX_CATCH_HRESULT(hr);
+ if (FAILED(hr))
+ {
+ // If we failed to get the context, then we must not be in a leaf frame.
+ ThrowHR(CORDBG_E_SET_VALUE_NOT_ALLOWED_ON_NONLEAF_FRAME);
+ }
+
+ // Its important to copy this context that we're given a ptr to.
+ DT_CONTEXT c;
+ c = *cTemp;
+
+ m_pRemoteRegAddr->SetEnregisteredValue(src, &c, fIsSigned);
+
+ // Set the thread's modified context.
+ IfFailThrow(frame->m_pThread->SetManagedContext(&c));
+} // RegisterValueHome::SetEnregisteredValue
+
+
+// Get an enregistered value from the register display of the native frame
+// Arguments:
+// output: dest - buffer will hold the register value
+// Note: Throws E_NOTIMPL for attempts to get an enregistered value for a float register
+// or for 64-bit platforms
+void RegisterValueHome::GetEnregisteredValue(MemoryRange dest)
+{
+#if !defined(DBG_TARGET_X86)
+ _ASSERTE(!"@TODO IA64/AMD64 -- Not Yet Implemented");
+ ThrowHR(E_NOTIMPL);
+#else // DBG_TARGET_X86
+ _ASSERTE(m_pRemoteRegAddr != NULL);
+
+ m_pRemoteRegAddr->GetEnregisteredValue(dest); // throws
+#endif // !DBG_TARGET_X86
+} // RegisterValueHome::GetEnregisteredValue
+
+// Is this a signed type or unsigned type?
+// Useful to known when we need to sign-extend.
+bool RegisterValueHome::IsSigned(CorElementType elementType)
+{
+ switch (elementType)
+ {
+ case ELEMENT_TYPE_I1:
+ case ELEMENT_TYPE_I2:
+ case ELEMENT_TYPE_I4:
+ case ELEMENT_TYPE_I8:
+ case ELEMENT_TYPE_I:
+ return true;
+
+ default:
+ return false;
+ }
+} // RegisterValueHome::IsSigned
+
+// ============================================================================
+// HandleValueHome implementation
+// ============================================================================
+
+//
+CORDB_ADDRESS HandleValueHome::GetAddress()
+{
+ _ASSERTE((m_pProcess != NULL) && !m_vmObjectHandle.IsNull());
+ CORDB_ADDRESS handle = PTR_TO_CORDB_ADDRESS((void *)NULL);
+ EX_TRY
+ {
+ handle = m_pProcess->GetDAC()->GetHandleAddressFromVmHandle(m_vmObjectHandle);
+ }
+ EX_CATCH
+ {
+ }
+ EX_END_CATCH(SwallowAllExceptions);
+ return handle;
+}
+
+// Gets a value and returns it in dest
+// virtual
+void HandleValueHome::GetValue(MemoryRange dest)
+{
+ _ASSERTE((m_pProcess != NULL) && !m_vmObjectHandle.IsNull());
+ CORDB_ADDRESS objPtr = PTR_TO_CORDB_ADDRESS((void *)NULL);
+ objPtr = m_pProcess->GetDAC()->GetHandleAddressFromVmHandle(m_vmObjectHandle);
+
+ _ASSERTE(dest.Size() <= sizeof(void *));
+ _ASSERTE(dest.StartAddress() != NULL);
+ _ASSERTE(objPtr != NULL);
+ m_pProcess->SafeReadBuffer(TargetBuffer(objPtr, sizeof(void *)), (BYTE *)dest.StartAddress());
+} // HandleValueHome::GetValue
+
+// Sets a location to the value provided in src
+// virtual
+void HandleValueHome::SetValue(MemoryRange src, CordbType * pType)
+{
+ _ASSERTE(!m_vmObjectHandle.IsNull());
+
+ DebuggerIPCEvent event;
+
+ m_pProcess->InitIPCEvent(&event, DB_IPCE_SET_REFERENCE, true, VMPTR_AppDomain::NullPtr());
+
+ event.SetReference.objectRefAddress = NULL;
+ event.SetReference.vmObjectHandle = m_vmObjectHandle;
+ event.SetReference.newReference = *((void **)src.StartAddress());
+
+ // Note: two-way event here...
+ IfFailThrow(m_pProcess->SendIPCEvent(&event, sizeof(DebuggerIPCEvent)));
+
+ _ASSERTE(event.type == DB_IPCE_SET_REFERENCE_RESULT);
+
+ IfFailThrow(event.hr);
+} // HandleValueHome::SetValue
+
+// creates an ICDValue for a field or array element or for the value type of a boxed object
+// virtual
+void HandleValueHome::CreateInternalValue(CordbType * pType,
+ SIZE_T offset,
+ void * localAddress,
+ ULONG32 size,
+ ICorDebugValue ** ppValue)
+{
+ _ASSERTE(!"References don't have sub-objects--we shouldn't be here");
+ ThrowHR(E_INVALIDARG);
+
+} // HandleValueHome::CreateInternalValue
+
+// Gets the value of a field or element of an existing ICDValue instance and returns it in dest
+// virtual
+void HandleValueHome::GetInternalValue(MemoryRange dest, SIZE_T offset)
+{
+ _ASSERTE(!"References don't have sub-objects--we shouldn't be here");
+ ThrowHR(E_INVALIDARG);
+} // HandleValueHome::GetInternalValue
+
+// copies the register information from this to a RemoteAddress instance
+// virtual
+void HandleValueHome::CopyToIPCEType(RemoteAddress * pRegAddr)
+{
+ pRegAddr->kind = RAK_NONE;
+} // HandleValueHome::CopyToIPCEType
+
+// ============================================================================
+// VCRemoteValueHome implementation
+// ============================================================================
+
+// Sets a location to the value provided in src
+// Arguments:
+// input: src - buffer containing the new value to be set--memory for this buffer is owned by the caller
+// pType - type information for the value
+// output: none, but sets the value on success
+// Note: Throws CORDBG_E_CLASS_NOT_LOADED or errors from WriteProcessMemory or
+// GetRemoteBuffer on failure
+void VCRemoteValueHome::SetValue(MemoryRange src, CordbType * pType)
+{
+ _ASSERTE(!m_remoteValue.IsEmpty());
+
+ // send a Set Value Class message to the right side with the address of this value class, the address of
+ // the new data, and the class of the value class that we're setting.
+ DebuggerIPCEvent event;
+
+ // First, we have to make room on the Left Side for the new data for the value class. We allocate
+ // memory on the Left Side for this, then write the new data across. The Set Value Class message will
+ // free the buffer when its done.
+ void *buffer = NULL;
+ IfFailThrow(m_pProcess->GetAndWriteRemoteBuffer(NULL,
+ m_remoteValue.cbSize,
+ CORDB_ADDRESS_TO_PTR(src.StartAddress()),
+ &buffer));
+
+ // Finally, send over the Set Value Class message.
+ m_pProcess->InitIPCEvent(&event, DB_IPCE_SET_VALUE_CLASS, true, VMPTR_AppDomain::NullPtr());
+ event.SetValueClass.oldData = CORDB_ADDRESS_TO_PTR(m_remoteValue.pAddress);
+ event.SetValueClass.newData = buffer;
+ IfFailThrow(pType->TypeToBasicTypeData(&event.SetValueClass.type));
+
+ // Note: two-way event here...
+ IfFailThrow(m_pProcess->SendIPCEvent(&event, sizeof(DebuggerIPCEvent)));
+
+ _ASSERTE(event.type == DB_IPCE_SET_VALUE_CLASS_RESULT);
+
+ IfFailThrow(event.hr);
+} // VCRemoteValueHome::SetValue
+
+
+// ============================================================================
+// RefRemoteValueHome implementation
+// ============================================================================
+
+// constructor
+// Arguments:
+// input: pProcess - process for this value
+// remoteValue - remote location information
+// vmObjHandle - object handle
+RefRemoteValueHome ::RefRemoteValueHome (CordbProcess * pProcess,
+ TargetBuffer remoteValue):
+ RemoteValueHome(pProcess, remoteValue)
+{
+ // caller supplies remoteValue, to work w/ Func-eval.
+ _ASSERTE((!remoteValue.IsEmpty()) && (remoteValue.cbSize == sizeof (void *)));
+
+} // RefRemoteValueHome::RefRemoteValueHome
+
+// Sets a location to the value provided in src
+// Arguments:
+// input: src - buffer containing the new value to be set--memory for this buffer is owned by the caller
+// pType - type information for the value
+// output: none, but sets the value on success
+// Return Value: S_OK on success or CORDBG_E_CLASS_NOT_LOADED or errors from WriteProcessMemory or
+// GetRemoteBuffer on failure
+void RefRemoteValueHome::SetValue(MemoryRange src, CordbType * pType)
+{
+ // We had better have a remote address.
+ _ASSERTE(!m_remoteValue.IsEmpty());
+
+ // send a Set Reference message to the right side with the address of this reference and whether or not
+ // the reference points to a handle.
+
+ // If it's a reference but not a GC root then just we can just treat it like raw data (like a DWORD).
+ // This would include things like "int*", and E_T_FNPTR. If it is a GC root, then we need to go over to
+ // the LS to update the WriteBarrier.
+ if ((pType != NULL) && !pType->IsGCRoot())
+ {
+ m_pProcess->SafeWriteBuffer(m_remoteValue, (BYTE *)src.StartAddress());
+ }
+ else
+ {
+ DebuggerIPCEvent event;
+
+ m_pProcess->InitIPCEvent(&event, DB_IPCE_SET_REFERENCE, true, VMPTR_AppDomain::NullPtr());
+
+ event.SetReference.objectRefAddress = CORDB_ADDRESS_TO_PTR(m_remoteValue.pAddress);
+ event.SetReference.vmObjectHandle = VMPTR_OBJECTHANDLE::NullPtr();
+ event.SetReference.newReference = *((void **)src.StartAddress());
+
+ // Note: two-way event here...
+ IfFailThrow(m_pProcess->SendIPCEvent(&event, sizeof(DebuggerIPCEvent)));
+
+ _ASSERTE(event.type == DB_IPCE_SET_REFERENCE_RESULT);
+
+ IfFailThrow(event.hr);
+ }
+} // RefRemoteValueHome::SetValue
+
+// ============================================================================
+// RefValueHome implementation
+// ============================================================================
+
+// constructor
+// Only one of the location types should be non-NULL, but we pass all of them to the
+// constructor so we can instantiate m_pHome correctly.
+// Arguments:
+// input: pProcess - process to which the value belongs
+// remoteValue - a target location holding the object reference
+// ppRemoteRegAddr - information about the register that holds the object ref
+// vmObjHandle - an object handle that holds the object ref
+RefValueHome::RefValueHome(CordbProcess * pProcess,
+ TargetBuffer remoteValue,
+ EnregisteredValueHomeHolder * ppRemoteRegAddr,
+ VMPTR_OBJECTHANDLE vmObjHandle)
+{
+ if (!remoteValue.IsEmpty())
+ {
+ NewHolder<ValueHome> pHome(new RefRemoteValueHome(pProcess, remoteValue));
+ m_fNullObjHandle = true;
+ }
+ else if (!vmObjHandle.IsNull())
+ {
+ NewHolder<ValueHome> pHome(new HandleValueHome(pProcess, vmObjHandle));
+ m_fNullObjHandle = false;
+ }
+ else
+ {
+ NewHolder<ValueHome> pHome(new RegisterValueHome(pProcess, ppRemoteRegAddr));
+ m_fNullObjHandle = true;
+ }
+
+
+} // RefValueHome::RefValueHome
+
diff --git a/src/debug/di/windowspipeline.cpp b/src/debug/di/windowspipeline.cpp
new file mode 100644
index 0000000000..c3050e3290
--- /dev/null
+++ b/src/debug/di/windowspipeline.cpp
@@ -0,0 +1,419 @@
+// 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.
+//*****************************************************************************
+// File: WindowsPipeline.cpp
+//
+
+//
+// Implements the native-pipeline on Windows OS.
+//*****************************************************************************
+
+#include "stdafx.h"
+#include "nativepipeline.h"
+
+#include <Tlhelp32.h>
+
+#include "holder.h"
+
+
+DWORD GetProcessId(const DEBUG_EVENT * pEvent)
+{
+ return pEvent->dwProcessId;
+}
+DWORD GetThreadId(const DEBUG_EVENT * pEvent)
+{
+ return pEvent->dwThreadId;
+}
+
+// Get exception event
+BOOL IsExceptionEvent(const DEBUG_EVENT * pEvent, BOOL * pfFirstChance, const EXCEPTION_RECORD ** ppRecord)
+{
+ if (pEvent->dwDebugEventCode != EXCEPTION_DEBUG_EVENT)
+ {
+ *pfFirstChance = FALSE;
+ *ppRecord = NULL;
+ return FALSE;
+ }
+ *pfFirstChance = pEvent->u.Exception.dwFirstChance;
+ *ppRecord = &(pEvent->u.Exception.ExceptionRecord);
+ return TRUE;
+}
+
+
+//---------------------------------------------------------------------------------------
+// Class serves as a connector to win32 native-debugging API.
+class WindowsNativePipeline :
+ public INativeEventPipeline
+{
+public:
+ WindowsNativePipeline()
+ {
+ // Default value for Win32.
+ m_fKillOnExit = true;
+ m_dwProcessId = 0;
+ }
+
+ // Call to free up the pipeline.
+ virtual void Delete();
+
+ virtual BOOL DebugSetProcessKillOnExit(bool fKillOnExit);
+
+ // Create
+ virtual HRESULT CreateProcessUnderDebugger(
+ MachineInfo machineInfo,
+ LPCWSTR lpApplicationName,
+ LPCWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ LPVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation);
+
+ // Attach
+ virtual HRESULT DebugActiveProcess(MachineInfo machineInfo, DWORD processId);
+
+ // Detach
+ virtual HRESULT DebugActiveProcessStop(DWORD processId);
+
+ virtual BOOL WaitForDebugEvent(DEBUG_EVENT * pEvent, DWORD dwTimeout, CordbProcess * pProcess);
+
+ virtual BOOL ContinueDebugEvent(
+ DWORD dwProcessId,
+ DWORD dwThreadId,
+ DWORD dwContinueStatus
+ );
+
+ // Return a handle for the debuggee process.
+ virtual HANDLE GetProcessHandle();
+
+ // Terminate the debuggee process.
+ virtual BOOL TerminateProcess(UINT32 exitCode);
+
+ // Resume any suspended threads
+ virtual HRESULT EnsureThreadsRunning();
+
+protected:
+ void UpdateDebugSetProcessKillOnExit();
+
+ HRESULT IsRemoteDebuggerPresent(DWORD processId, BOOL* pfDebuggerPresent);
+
+ // Cached value from DebugSetProcessKillOnExit.
+ // This is thread-local, and impacts all debuggees on the thread.
+ bool m_fKillOnExit;
+
+ DWORD m_dwProcessId;
+};
+
+// Allocate and return a pipeline object for this platform
+INativeEventPipeline * NewPipelineForThisPlatform()
+{
+ return new (nothrow) WindowsNativePipeline();
+}
+
+// Call to free up the pipeline.
+void WindowsNativePipeline::Delete()
+{
+ delete this;
+}
+
+
+// set whether to kill outstanding debuggees when the debugger exits.
+BOOL WindowsNativePipeline::DebugSetProcessKillOnExit(bool fKillOnExit)
+{
+ // Can't call kernel32!DebugSetProcessKillOnExit until after the event thread
+ // has spawned a debuggee. So cache the value now and call it later.
+ // This bit is enforced in code:WindowsNativePipeline::UpdateDebugSetProcessKillOnExit
+ m_fKillOnExit = fKillOnExit;
+ return TRUE;
+}
+
+// Enforces the bit set in code:WindowsNativePipeline::DebugSetProcessKillOnExit
+void WindowsNativePipeline::UpdateDebugSetProcessKillOnExit()
+{
+#if !defined(FEATURE_CORESYSTEM)
+ // Late bind to DebugSetProcessKillOnExit - WinXP and above only
+ HModuleHolder hKernel32;
+ hKernel32 = WszLoadLibrary(W("kernel32"));
+ SIMPLIFYING_ASSUMPTION(hKernel32 != NULL);
+ if (hKernel32 == NULL)
+ return;
+
+ typedef BOOL (*DebugSetProcessKillOnExitSig) (BOOL);
+ DebugSetProcessKillOnExitSig pDebugSetProcessKillOnExit =
+ reinterpret_cast<DebugSetProcessKillOnExitSig>(GetProcAddress(hKernel32, "DebugSetProcessKillOnExit"));
+
+ // If the API doesn't exist (eg. Win2k) - there isn't anything we can do, just
+ // silently ignore the request.
+ if (pDebugSetProcessKillOnExit == NULL)
+ return;
+
+ BOOL ret = pDebugSetProcessKillOnExit(m_fKillOnExit);
+
+ // Not a good failure path here.
+ // 1) This shouldn't fail.
+ // 2) Even if it does, this is likely called after the debuggee
+ // has already been created, and if this API fails, most scenarios will
+ // be unaffected, so we don't want to fail the overall debugging session.
+ SIMPLIFYING_ASSUMPTION(ret);
+
+#else
+ // The API doesn't exit on CoreSystem, just return
+ return;
+#endif
+}
+
+// Create an process under the debugger.
+HRESULT WindowsNativePipeline::CreateProcessUnderDebugger(
+ MachineInfo machineInfo,
+ LPCWSTR lpApplicationName,
+ LPCWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ LPVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation)
+{
+ // This is always doing Native-debugging at the OS-level.
+ dwCreationFlags |= (DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS);
+
+ BOOL ret = ::WszCreateProcess(
+ lpApplicationName,
+ lpCommandLine,
+ lpProcessAttributes,
+ lpThreadAttributes,
+ bInheritHandles,
+ dwCreationFlags,
+ lpEnvironment,
+ lpCurrentDirectory,
+ lpStartupInfo,
+ lpProcessInformation);
+ if (!ret)
+ {
+ return HRESULT_FROM_GetLastError();
+ }
+
+ m_dwProcessId = lpProcessInformation->dwProcessId;
+ UpdateDebugSetProcessKillOnExit();
+ return S_OK;
+}
+
+// Attach the debugger to this process.
+HRESULT WindowsNativePipeline::DebugActiveProcess(MachineInfo machineInfo, DWORD processId)
+{
+ HRESULT hr = E_FAIL;
+ BOOL ret = ::DebugActiveProcess(processId);
+
+ if (ret)
+ {
+ hr = S_OK;
+ m_dwProcessId = processId;
+ UpdateDebugSetProcessKillOnExit();
+ }
+ else
+ {
+ hr = HRESULT_FROM_GetLastError();
+
+ // There are at least two scenarios in which DebugActiveProcess() returns E_INVALIDARG:
+ // 1) if the specified process doesn't exist, or
+ // 2) if the specified process already has a debugger atttached
+ // We need to distinguish these two cases in order to return the correct HR.
+ if (hr == E_INVALIDARG)
+ {
+ // Check whether a debugger is known to be already attached.
+ // Note that this API won't work on some OSes, in which case we err on the side of returning E_INVALIDARG
+ // even though a debugger may be attached. Another approach could be to assume that if
+ // OpenProcess succeeded, then DebugActiveProcess must only have failed because a debugger is
+ // attached. But I think it's better to only return the specific error code if we know for sure
+ // the case is true.
+ BOOL fIsDebuggerPresent = FALSE;
+ if (SUCCEEDED(IsRemoteDebuggerPresent(processId, &fIsDebuggerPresent)))
+ {
+ if (fIsDebuggerPresent)
+ {
+ hr = CORDBG_E_DEBUGGER_ALREADY_ATTACHED;
+ }
+ }
+ }
+ }
+
+ return hr;
+}
+
+// Determine (if possible) whether a debugger is attached to the target process
+HRESULT WindowsNativePipeline::IsRemoteDebuggerPresent(DWORD processId, BOOL* pfDebuggerPresent)
+{
+#if !defined(FEATURE_CORESYSTEM)
+
+ // Get a process handle for the process ID.
+ HandleHolder hProc = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, processId);
+ if (hProc == NULL)
+ return HRESULT_FROM_GetLastError();
+
+ // Delay-bind to CheckRemoteDebuggerPresent - WinXP SP1 and above only
+ HModuleHolder hKernel32;
+ hKernel32 = WszLoadLibrary(W("kernel32"));
+ if (hKernel32 == NULL)
+ return HRESULT_FROM_GetLastError();
+
+ typedef BOOL (*CheckRemoteDebuggerPresentSig) (HANDLE, PBOOL);
+ CheckRemoteDebuggerPresentSig pCheckRemoteDebuggerPresent =
+ reinterpret_cast<CheckRemoteDebuggerPresentSig>(GetProcAddress(hKernel32, "CheckRemoteDebuggerPresent"));
+ if (pCheckRemoteDebuggerPresent == NULL)
+ return HRESULT_FROM_GetLastError();
+
+ // API exists - call it
+ if (!pCheckRemoteDebuggerPresent(hProc, pfDebuggerPresent))
+ return HRESULT_FROM_GetLastError();
+
+ return S_OK;
+#else
+
+ //CoreSystem doesn't have this API
+ return E_FAIL;
+#endif
+}
+
+// Detach
+HRESULT WindowsNativePipeline::DebugActiveProcessStop(DWORD processId)
+{
+#if !defined(FEATURE_CORESYSTEM)
+ // Late-bind to DebugActiveProcessStop since it's WinXP and above only
+ HModuleHolder hKernel32;
+ hKernel32 = WszLoadLibrary(W("kernel32"));
+ if (hKernel32 == NULL)
+ return HRESULT_FROM_GetLastError();
+
+ typedef BOOL (*DebugActiveProcessStopSig) (DWORD);
+ DebugActiveProcessStopSig pDebugActiveProcessStop =
+ reinterpret_cast<DebugActiveProcessStopSig>(GetProcAddress(hKernel32, "DebugActiveProcessStop"));
+
+ // Win2K will fail here - can't find DebugActiveProcessStop
+ if (pDebugActiveProcessStop == NULL)
+ return HRESULT_FROM_GetLastError();
+
+ // Ok, the API exists, call it
+ if (!pDebugActiveProcessStop(processId))
+ {
+ // Detach itself failed
+ return HRESULT_FROM_GetLastError();
+ }
+#else
+ // The API exists, call it
+ if (!::DebugActiveProcessStop(processId))
+ {
+ // Detach itself failed
+ return HRESULT_FROM_GetLastError();
+ }
+#endif
+ return S_OK;
+}
+
+BOOL WindowsNativePipeline::WaitForDebugEvent(DEBUG_EVENT * pEvent, DWORD dwTimeout, CordbProcess * pProcess)
+{
+ return ::WaitForDebugEvent(pEvent, dwTimeout);
+}
+
+BOOL WindowsNativePipeline::ContinueDebugEvent(
+ DWORD dwProcessId,
+ DWORD dwThreadId,
+ DWORD dwContinueStatus
+)
+{
+ return ::ContinueDebugEvent(dwProcessId, dwThreadId, dwContinueStatus);
+}
+
+// Return a handle for the debuggee process.
+HANDLE WindowsNativePipeline::GetProcessHandle()
+{
+ _ASSERTE(m_dwProcessId != 0);
+
+ return ::OpenProcess(PROCESS_DUP_HANDLE |
+ PROCESS_QUERY_INFORMATION |
+ PROCESS_TERMINATE |
+ PROCESS_VM_OPERATION |
+ PROCESS_VM_READ |
+ PROCESS_VM_WRITE |
+ SYNCHRONIZE,
+ FALSE,
+ m_dwProcessId);
+}
+
+// Terminate the debuggee process.
+BOOL WindowsNativePipeline::TerminateProcess(UINT32 exitCode)
+{
+ _ASSERTE(m_dwProcessId != 0);
+
+ // Get a process handle for the process ID.
+ HandleHolder hProc = OpenProcess(PROCESS_TERMINATE, FALSE, m_dwProcessId);
+
+ if (hProc == NULL)
+ {
+ return FALSE;
+ }
+
+ return ::TerminateProcess(hProc, exitCode);
+}
+
+// Resume any suspended threads (but just once)
+HRESULT WindowsNativePipeline::EnsureThreadsRunning()
+{
+#ifdef FEATURE_CORESYSTEM
+ _ASSERTE("NYI");
+ return E_FAIL;
+#else
+ _ASSERTE(m_dwProcessId != 0);
+
+ // Take a snapshot of all running threads (similar to ShimProcess::QueueFakeThreadAttachEventsNativeOrder)
+ // Alternately we could return thread creation/exit in WaitForDebugEvent. But we expect this to be used
+ // very rarely, so no need to complicate more common codepaths.
+ HANDLE hThreadSnap = INVALID_HANDLE_VALUE;
+ THREADENTRY32 te32;
+
+ hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
+ if (hThreadSnap == INVALID_HANDLE_VALUE)
+ return HRESULT_FROM_GetLastError();
+
+ // HandleHolder doesn't deal with INVALID_HANDLE_VALUE, so we only assign if we have a legal value.
+ HandleHolder hSnapshotHolder(hThreadSnap);
+
+ // Fill in the size of the structure before using it.
+ te32.dwSize = sizeof(THREADENTRY32);
+
+ // Retrieve information about the first thread, and exit if unsuccessful
+ if (!Thread32First(hThreadSnap, &te32))
+ return HRESULT_FROM_GetLastError();
+
+ // Now walk the thread list of the system and attempt to resume any that are part of this process
+ // Ignore errors - this is a best effort (but ASSERT in CHK builds since we don't expect errors
+ // in practice - we expect the process to be frozen at a debug event, so no races etc.)
+
+ HRESULT hr = S_FALSE; // no thread was resumed
+ do
+ {
+ if (te32.th32OwnerProcessID == m_dwProcessId)
+ {
+ HandleHolder hThread = ::OpenThread(THREAD_SUSPEND_RESUME, FALSE, te32.th32ThreadID);
+ _ASSERTE(hThread != NULL);
+ if (hThread != NULL)
+ {
+ // Resume each thread exactly once (if they were suspended multiple times,
+ // then EnsureThreadsRunning would need to be called multiple times until it
+ // returned S_FALSE.
+ DWORD prevCount = ::ResumeThread(hThread);
+ _ASSERTE(prevCount >= 0);
+ if (prevCount >= 1)
+ hr = S_OK; // some thread was resumed
+ }
+ }
+ } while(Thread32Next(hThreadSnap, &te32));
+
+ return hr;
+#endif
+}
diff --git a/src/debug/dirs.proj b/src/debug/dirs.proj
new file mode 100644
index 0000000000..43aa2dd262
--- /dev/null
+++ b/src/debug/dirs.proj
@@ -0,0 +1,23 @@
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <!--Import the settings-->
+ <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\clr.props" />
+
+ <PropertyGroup>
+ <BuildInPhase1>true</BuildInPhase1>
+ <BuildInPhaseDefault>false</BuildInPhaseDefault>
+ <BuildCoreBinaries>true</BuildCoreBinaries>
+ <BuildSysBinaries>true</BuildSysBinaries>
+ </PropertyGroup>
+
+ <!--The following projects will build during PHASE 1-->
+ <ItemGroup Condition="'$(BuildExePhase)' == '1'">
+ <ProjectFile Include="ee\dirs.proj" />
+ <ProjectFile Condition="'$(FeatureDbiDebugging)'=='true'" Include="di\dirs.proj" />
+ <ProjectFile Include="ildbsymlib\dirs.proj" />
+ <ProjectFile Include="daccess\dirs.proj" />
+ <ProjectFile Include="shim\dirs.proj" />
+ </ItemGroup>
+
+ <!--Import the targets-->
+ <Import Project="$(_NTDRIVE)$(_NTROOT)\tools\Microsoft.DevDiv.Traversal.targets" />
+</Project>
diff --git a/src/debug/ee/.gitmirror b/src/debug/ee/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/debug/ee/.gitmirror
@@ -0,0 +1 @@
+Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror. \ No newline at end of file
diff --git a/src/debug/ee/CMakeLists.txt b/src/debug/ee/CMakeLists.txt
new file mode 100644
index 0000000000..85170df713
--- /dev/null
+++ b/src/debug/ee/CMakeLists.txt
@@ -0,0 +1,62 @@
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+add_definitions(-DFEATURE_NO_HOST)
+
+include_directories(BEFORE ${VM_DIR})
+include_directories(BEFORE ${VM_DIR}/${ARCH_SOURCES_DIR})
+include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR})
+
+if(CLR_CMAKE_PLATFORM_UNIX)
+ add_compile_options(-fPIC)
+endif(CLR_CMAKE_PLATFORM_UNIX)
+
+set(CORDBEE_SOURCES_DAC_AND_WKS
+ controller.cpp
+ debugger.cpp
+ debuggermodule.cpp
+ functioninfo.cpp
+)
+
+set(CORDBEE_SOURCES_WKS
+ ${CORDBEE_SOURCES_DAC_AND_WKS}
+ funceval.cpp
+ rcthread.cpp
+ canary.cpp
+ shared.cpp
+ frameinfo.cpp
+ ${ARCH_SOURCES_DIR}/primitives.cpp
+)
+
+set(CORDBEE_SOURCES_DAC
+ ${CORDBEE_SOURCES_DAC_AND_WKS}
+)
+
+if(CLR_CMAKE_PLATFORM_UNIX)
+ list(APPEND CORDBEE_SOURCES_WKS
+ dactable.cpp
+ )
+endif(CLR_CMAKE_PLATFORM_UNIX)
+
+if(CLR_CMAKE_TARGET_ARCH_AMD64)
+ list(APPEND CORDBEE_SOURCES_WKS
+ ${ARCH_SOURCES_DIR}/debuggerregdisplayhelper.cpp
+ ${ARCH_SOURCES_DIR}/amd64walker.cpp
+ )
+elseif(CLR_CMAKE_TARGET_ARCH_I386)
+ list(APPEND CORDBEE_SOURCES_WKS
+ ${ARCH_SOURCES_DIR}/debuggerregdisplayhelper.cpp
+ ${ARCH_SOURCES_DIR}/x86walker.cpp
+ )
+elseif(CLR_CMAKE_TARGET_ARCH_ARM)
+ list(APPEND CORDBEE_SOURCES_WKS ${ARCH_SOURCES_DIR}/armwalker.cpp)
+elseif(CLR_CMAKE_TARGET_ARCH_ARM64)
+ list(APPEND CORDBEE_SOURCES_WKS ${ARCH_SOURCES_DIR}/arm64walker.cpp)
+endif()
+
+convert_to_absolute_path(CORDBEE_SOURCES_DAC ${CORDBEE_SOURCES_DAC})
+convert_to_absolute_path(CORDBEE_SOURCES_WKS ${CORDBEE_SOURCES_WKS})
+
+set(CORDBEE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
+
+add_subdirectory(dac)
+add_subdirectory(wks)
diff --git a/src/debug/ee/DIRS.proj b/src/debug/ee/DIRS.proj
new file mode 100644
index 0000000000..63dd0c8afb
--- /dev/null
+++ b/src/debug/ee/DIRS.proj
@@ -0,0 +1,20 @@
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <!--Import the settings-->
+ <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\clr.props" />
+
+ <!--The following projects will build during PHASE 1-->
+ <PropertyGroup>
+ <BuildInPhase1>true</BuildInPhase1>
+ <BuildInPhaseDefault>false</BuildInPhaseDefault>
+ <BuildCoreBinaries>true</BuildCoreBinaries>
+ <BuildSysBinaries>true</BuildSysBinaries>
+ </PropertyGroup>
+
+ <ItemGroup Condition="'$(BuildExePhase)' == '1'">
+ <ProjectFile Include="wks\wks.nativeproj" />
+ <ProjectFile Include="dac\dirs.proj" />
+ </ItemGroup>
+
+ <!--Import the targets-->
+ <Import Project="$(_NTDRIVE)$(_NTROOT)\tools\Microsoft.DevDiv.Traversal.targets" />
+</Project>
diff --git a/src/debug/ee/DebuggerEE.vcproj b/src/debug/ee/DebuggerEE.vcproj
new file mode 100644
index 0000000000..6df51a0fd6
--- /dev/null
+++ b/src/debug/ee/DebuggerEE.vcproj
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="8.00"
+ Name="DebuggerEE"
+ ProjectGUID="{31EEC9FD-A233-4B36-8762-2D30A030C319}"
+ Keyword="MakeFileProj">
+ <Platforms>
+ <Platform
+ Name="Win32"/>
+ </Platforms>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ OutputDirectory="Debug"
+ IntermediateDirectory="Debug"
+ ConfigurationType="0">
+ <Tool
+ Name="VCNMakeTool"
+ Output="DebuggerEE.exe"/>
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ OutputDirectory="Release"
+ IntermediateDirectory="Release"
+ ConfigurationType="0">
+ <Tool
+ Name="VCNMakeTool"
+ Output="DebuggerEE.exe"/>
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="Source Files"
+ Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm">
+ <File
+ RelativePath="controller.cpp">
+ </File>
+ <File
+ RelativePath="debugger.cpp">
+ </File>
+ <File
+ RelativePath="frameinfo.cpp">
+ </File>
+ <File
+ RelativePath="ilwalker.cpp">
+ </File>
+ <File
+ RelativePath="lscommon.cpp">
+ </File>
+ <File
+ RelativePath="lsdivalue.cpp">
+ </File>
+ <File
+ RelativePath="lshash.cpp">
+ </File>
+ <File
+ RelativePath="lsmodule.cpp">
+ </File>
+ <File
+ RelativePath="lsprocess.cpp">
+ </File>
+ <File
+ RelativePath="lsthread.cpp">
+ </File>
+ <File
+ RelativePath="rcthread.cpp">
+ </File>
+ <File
+ RelativePath="stdafx.cpp">
+ </File>
+ <File
+ RelativePath="thread.cpp">
+ </File>
+ <File
+ RelativePath="i386\x86walker.cpp">
+ </File>
+ </Filter>
+ <Filter
+ Name="Header Files"
+ Filter="h;hpp;hxx;hm;inl;inc">
+ <File
+ RelativePath="controller.h">
+ </File>
+ <File
+ RelativePath="debugger.h">
+ </File>
+ <File
+ RelativePath="frameinfo.h">
+ </File>
+ <File
+ RelativePath="stdafx.h">
+ </File>
+ <File
+ RelativePath="walker.h">
+ </File>
+ </Filter>
+ <Filter
+ Name="Resource Files"
+ Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe">
+ </Filter>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/src/debug/ee/EE.props b/src/debug/ee/EE.props
new file mode 100644
index 0000000000..90b9f815fd
--- /dev/null
+++ b/src/debug/ee/EE.props
@@ -0,0 +1,60 @@
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <!--*****************************************************-->
+ <!--This MSBuild project file was automatically generated-->
+ <!--from the original SOURCES/DIRS file by the KBC tool.-->
+ <!--*****************************************************-->
+ <!--Import the settings-->
+ <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\clr.props" />
+ <Import Project="$(Clrbase)\src\Debug\SetDebugTargetLocal.props" />
+ <!--Leaf project Properties-->
+ <PropertyGroup>
+ <UserIncludes>$(UserIncludes);
+ $(Clrbase)\src\Debug\EE;
+ $(Clrbase)\src\vm;
+ $(Clrbase)\src\vm\$(TargetCpu);
+ $(Clrbase)\src\Debug\inc;
+ $(Clrbase)\src\Debug\inc\$(TargetCpu);
+ $(Clrbase)\src\Debug\inc\dump;
+ $(VCToolsIncPath);
+ $(Clrbase)\src\strongname\inc</UserIncludes>
+ <ClAdditionalOptions>$(ClAdditionalOptions) -DUNICODE -D_UNICODE -DFEATURE_NO_HOST</ClAdditionalOptions>
+ <PCHHeader Condition="'$(CCOVER)' == ''">stdafx.h</PCHHeader>
+ <EnableCxxPCHHeaders Condition="'$(CCOVER)' == ''">true</EnableCxxPCHHeaders>
+ <!--PCH: Both precompiled header and cpp are on the same ..\ path this is likely to be wrong.-->
+ <PCHCompile Condition="'$(CCOVER)' == ''">$(Clrbase)\src\Debug\EE\stdafx.cpp</PCHCompile>
+ </PropertyGroup>
+ <!--Leaf Project Items-->
+ <ItemGroup>
+ <CppCompile Include="$(Clrbase)\src\Debug\EE\controller.cpp" />
+ <CppCompile Include="$(Clrbase)\src\Debug\EE\Debugger.cpp" />
+ <CppCompile Include="$(Clrbase)\src\Debug\EE\DebuggerModule.cpp" />
+ <CppCompile Include="$(Clrbase)\src\Debug\EE\functioninfo.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <SourcesNodac Include="$(Clrbase)\src\Debug\EE\funceval.cpp" />
+ <SourcesNodac Include="$(Clrbase)\src\Debug\EE\RCThread.cpp" />
+ <SourcesNodac Include="$(Clrbase)\src\Debug\EE\Canary.cpp" />
+ <SourcesNodac Include="$(Clrbase)\src\Debug\EE\shared.cpp" />
+ <SourcesNodac Include="$(Clrbase)\src\Debug\EE\frameinfo.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <I386Sources Condition="'$(TargetArch)' == 'i386'" Include="$(Clrbase)\src\Debug\EE\i386\x86walker.cpp" />
+ <I386Sources Condition="'$(TargetArch)' == 'i386'" Include="$(Clrbase)\src\Debug\EE\i386\primitives.cpp" />
+ <I386Sources Condition="'$(TargetArch)' == 'i386'" Include="$(Clrbase)\src\Debug\EE\i386\DebuggerRegDisplayHelper.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <Amd64Sources Condition="'$(TargetArch)' == 'amd64'" Include="$(Clrbase)\src\Debug\EE\amd64\primitives.cpp" />
+ <Amd64Sources Condition="'$(TargetArch)' == 'amd64'" Include="$(Clrbase)\src\Debug\EE\amd64\Amd64walker.cpp" />
+ <Amd64Sources Condition="'$(TargetArch)' == 'amd64'" Include="$(Clrbase)\src\Debug\EE\amd64\DebuggerRegDisplayHelper.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ArmSources Condition="'$(TargetArch)' == 'arm'" Include="$(Clrbase)\src\Debug\EE.\arm\primitives.cpp" />
+ <ArmSources Condition="'$(TargetArch)' == 'arm'" Include="$(Clrbase)\src\Debug\EE\arm\ArmWalker.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <Arm64Sources Condition="'$(TargetArch)' == 'arm64'" Include="$(Clrbase)\src\Debug\EE\arm64\primitives.cpp" />
+ <Arm64Sources Condition="'$(TargetArch)' == 'arm64'" Include="$(Clrbase)\src\Debug\EE\arm64\Arm64Walker.cpp" />
+ </ItemGroup>
+
+ <!--Import the targets-->
+</Project>
diff --git a/src/debug/ee/amd64/.gitmirror b/src/debug/ee/amd64/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/debug/ee/amd64/.gitmirror
@@ -0,0 +1 @@
+Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror. \ No newline at end of file
diff --git a/src/debug/ee/amd64/amd64walker.cpp b/src/debug/ee/amd64/amd64walker.cpp
new file mode 100644
index 0000000000..836d21486e
--- /dev/null
+++ b/src/debug/ee/amd64/amd64walker.cpp
@@ -0,0 +1,1181 @@
+// 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.
+//*****************************************************************************
+// File: Amd64walker.cpp
+//
+
+//
+// AMD64 instruction decoding/stepping logic
+//
+//*****************************************************************************
+
+#include "stdafx.h"
+
+#include "walker.h"
+
+#include "frames.h"
+#include "openum.h"
+
+#ifdef _TARGET_AMD64_
+
+//
+// The AMD64 walker is currently pretty minimal. It only recognizes call and return opcodes, plus a few jumps. The rest
+// is treated as unknown.
+//
+void NativeWalker::Decode()
+{
+ const BYTE *ip = m_ip;
+
+ m_type = WALK_UNKNOWN;
+ m_skipIP = NULL;
+ m_nextIP = NULL;
+
+ BYTE rex = NULL;
+
+ LOG((LF_CORDB, LL_INFO100000, "NW:Decode: m_ip 0x%x\n", m_ip));
+
+ BYTE prefix = *ip;
+ if (prefix == 0xcc)
+ {
+ prefix = (BYTE)DebuggerController::GetPatchedOpcode(m_ip);
+ LOG((LF_CORDB, LL_INFO100000, "NW:Decode 1st byte was patched, might have been prefix\n"));
+ }
+
+ //
+ // Skip instruction prefixes
+ //
+ do
+ {
+ switch (prefix)
+ {
+ // Segment overrides
+ case 0x26: // ES
+ case 0x2E: // CS
+ case 0x36: // SS
+ case 0x3E: // DS
+ case 0x64: // FS
+ case 0x65: // GS
+
+ // Size overrides
+ case 0x66: // Operand-Size
+ case 0x67: // Address-Size
+
+ // Lock
+ case 0xf0:
+
+ // String REP prefixes
+ case 0xf2: // REPNE/REPNZ
+ case 0xf3:
+ LOG((LF_CORDB, LL_INFO10000, "NW:Decode: prefix:%0.2x ", prefix));
+ ip++;
+ continue;
+
+ // REX register extension prefixes
+ case 0x40:
+ case 0x41:
+ case 0x42:
+ case 0x43:
+ case 0x44:
+ case 0x45:
+ case 0x46:
+ case 0x47:
+ case 0x48:
+ case 0x49:
+ case 0x4a:
+ case 0x4b:
+ case 0x4c:
+ case 0x4d:
+ case 0x4e:
+ case 0x4f:
+ LOG((LF_CORDB, LL_INFO10000, "NW:Decode: REX prefix:%0.2x ", prefix));
+ // make sure to set rex to prefix, not *ip because *ip still represents the
+ // codestream which has a 0xcc in it.
+ rex = prefix;
+ ip++;
+ continue;
+
+ default:
+ break;
+ }
+ } while (0);
+
+ // Read the opcode
+ m_opcode = *ip++;
+
+ LOG((LF_CORDB, LL_INFO100000, "NW:Decode: ip 0x%x, m_opcode:%0.2x\n", ip, m_opcode));
+
+ // Don't remove this, when we did the check above for the prefix we didn't modify the codestream
+ // and since m_opcode was just taken directly from the code stream it will be patched if we
+ // didn't have a prefix
+ if (m_opcode == 0xcc)
+ {
+ m_opcode = (BYTE)DebuggerController::GetPatchedOpcode(m_ip);
+ LOG((LF_CORDB, LL_INFO100000, "NW:Decode after patch look up: m_opcode:%0.2x\n", m_opcode));
+ }
+
+ // Setup rex bits if needed
+ BYTE rex_b = 0;
+ BYTE rex_x = 0;
+ BYTE rex_r = 0;
+
+ if (rex != NULL)
+ {
+ rex_b = (rex & 0x1); // high bit to modrm r/m field or SIB base field or OPCODE reg field -- Hmm, when which?
+ rex_x = (rex & 0x2) >> 1; // high bit to sib index field
+ rex_r = (rex & 0x4) >> 2; // high bit to modrm reg field
+ }
+
+ // Analyze what we can of the opcode
+ switch (m_opcode)
+ {
+ case 0xff:
+ {
+
+ BYTE modrm = *ip++;
+
+ _ASSERT(modrm != NULL);
+
+ BYTE mod = (modrm & 0xC0) >> 6;
+ BYTE reg = (modrm & 0x38) >> 3;
+ BYTE rm = (modrm & 0x07);
+
+ reg |= (rex_r << 3);
+ rm |= (rex_b << 3);
+
+ if ((reg < 2) || (reg > 5 && reg < 8) || (reg > 15)) {
+ // not a valid register for a CALL or BRANCH
+ return;
+ }
+
+ BYTE *result;
+ WORD displace;
+
+ // See: Tables A-15,16,17 in AMD Dev Manual 3 for information
+ // about how the ModRM/SIB/REX bytes interact.
+
+ switch (mod)
+ {
+ case 0:
+ case 1:
+ case 2:
+ if ((rm & 0x07) == 4) // we have an SIB byte following
+ {
+ //
+ // Get values from the SIB byte
+ //
+ BYTE sib = *ip;
+
+ _ASSERT(sib != NULL);
+
+ BYTE ss = (sib & 0xC0) >> 6;
+ BYTE index = (sib & 0x38) >> 3;
+ BYTE base = (sib & 0x07);
+
+ index |= (rex_x << 3);
+ base |= (rex_b << 3);
+
+ ip++;
+
+ //
+ // Get starting value
+ //
+ if ((mod == 0) && ((base & 0x07) == 5))
+ {
+ result = 0;
+ }
+ else
+ {
+ result = (BYTE *)(size_t)GetRegisterValue(base);
+ }
+
+ //
+ // Add in the [index]
+ //
+ if (index != 0x4)
+ {
+ result = result + (GetRegisterValue(index) << ss);
+ }
+
+ //
+ // Finally add in the offset
+ //
+ if (mod == 0)
+ {
+ if ((base & 0x07) == 5)
+ {
+ result = result + *((INT32*)ip);
+ displace = 7;
+ }
+ else
+ {
+ displace = 3;
+ }
+ }
+ else if (mod == 1)
+ {
+ result = result + *((INT8*)ip);
+ displace = 4;
+ }
+ else // mod == 2
+ {
+ result = result + *((INT32*)ip);
+ displace = 7;
+ }
+
+ }
+ else
+ {
+ //
+ // Get the value we need from the register.
+ //
+
+ // Check for RIP-relative addressing mode.
+ if ((mod == 0) && ((rm & 0x07) == 5))
+ {
+ displace = 6; // 1 byte opcode + 1 byte modrm + 4 byte displacement (signed)
+ result = const_cast<BYTE *>(m_ip) + displace + *(reinterpret_cast<const INT32*>(ip));
+ }
+ else
+ {
+ result = (BYTE *)GetRegisterValue(rm);
+
+ if (mod == 0)
+ {
+ displace = 2;
+ }
+ else if (mod == 1)
+ {
+ result = result + *((INT8*)ip);
+ displace = 3;
+ }
+ else // mod == 2
+ {
+ result = result + *((INT32*)ip);
+ displace = 6;
+ }
+ }
+ }
+
+ //
+ // Now dereference thru the result to get the resulting IP.
+ //
+ result = (BYTE *)(*((UINT64*)result));
+
+ break;
+
+ case 3:
+ default:
+ // The operand is stored in a register.
+ result = (BYTE *)GetRegisterValue(rm);
+ displace = 2;
+
+ break;
+
+ }
+
+ // the instruction uses r8-r15, add in the extra byte to the displacement
+ // for the REX prefix which was used to specify the extended register
+ if (rex != NULL)
+ {
+ displace++;
+ }
+
+ // because we already checked register validity for CALL/BRANCH
+ // instructions above we can assume that there is no other option
+ if ((reg == 4) || (reg == 5))
+ {
+ m_type = WALK_BRANCH;
+ }
+ else
+ {
+ m_type = WALK_CALL;
+ }
+ m_nextIP = result;
+ m_skipIP = m_ip + displace;
+ break;
+ }
+ case 0xe8:
+ {
+ m_type = WALK_CALL;
+
+ // Sign-extend the displacement is necessary.
+ INT32 disp = *((INT32*)ip);
+ m_nextIP = ip + 4 + (disp < 0 ? (disp | 0xffffffff00000000) : disp);
+ m_skipIP = ip + 4;
+
+ break;
+ }
+ case 0xe9:
+ {
+ m_type = WALK_BRANCH;
+
+ // Sign-extend the displacement is necessary.
+ INT32 disp = *((INT32*)ip);
+ m_nextIP = ip + 4 + (disp < 0 ? (disp | 0xffffffff00000000) : disp);
+ m_skipIP = ip + 4;
+
+ break;
+ }
+ case 0xc2:
+ case 0xc3:
+ case 0xca:
+ case 0xcb:
+ {
+ m_type = WALK_RETURN;
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+
+//
+// Given a regdisplay and a register number, return the value of the register.
+//
+
+UINT64 NativeWalker::GetRegisterValue(int registerNumber)
+{
+ if (m_registers == NULL) {
+ return 0;
+ }
+
+ switch (registerNumber)
+ {
+ case 0:
+ return m_registers->pCurrentContext->Rax;
+ break;
+ case 1:
+ return m_registers->pCurrentContext->Rcx;
+ break;
+ case 2:
+ return m_registers->pCurrentContext->Rdx;
+ break;
+ case 3:
+ return m_registers->pCurrentContext->Rbx;
+ break;
+ case 4:
+ return m_registers->pCurrentContext->Rsp;
+ break;
+ case 5:
+ return m_registers->pCurrentContext->Rbp;
+ break;
+ case 6:
+ return m_registers->pCurrentContext->Rsi;
+ break;
+ case 7:
+ return m_registers->pCurrentContext->Rdi;
+ break;
+ case 8:
+ return m_registers->pCurrentContext->R8;
+ break;
+ case 9:
+ return m_registers->pCurrentContext->R9;
+ break;
+ case 10:
+ return m_registers->pCurrentContext->R10;
+ break;
+ case 11:
+ return m_registers->pCurrentContext->R11;
+ break;
+ case 12:
+ return m_registers->pCurrentContext->R12;
+ break;
+ case 13:
+ return m_registers->pCurrentContext->R13;
+ break;
+ case 14:
+ return m_registers->pCurrentContext->R14;
+ break;
+ case 15:
+ return m_registers->pCurrentContext->R15;
+ break;
+ default:
+ _ASSERTE(!"Invalid register number!");
+ }
+
+ return 0;
+}
+
+
+// mod reg r/m
+// bits 7-6 5-3 2-0
+struct ModRMByte
+{
+ BYTE rm :3;
+ BYTE reg:3;
+ BYTE mod:2;
+};
+
+// fixed W R X B
+// bits 7-4 3 2 1 0
+struct RexByte
+{
+ BYTE b:1;
+ BYTE x:1;
+ BYTE r:1;
+ BYTE w:1;
+ BYTE fixed:4;
+};
+
+// static
+void NativeWalker::DecodeInstructionForPatchSkip(const BYTE *address, InstructionAttribute * pInstrAttrib)
+{
+ //
+ // Skip instruction prefixes
+ //
+
+ LOG((LF_CORDB, LL_INFO10000, "Patch decode: "));
+
+ // for reads and writes where the destination is a RIP-relative address pInstrAttrib->m_cOperandSize will contain the size in bytes of the pointee; in all other
+ // cases it will be zero. if the RIP-relative address is being written to then pInstrAttrib->m_fIsWrite will be true; in all other cases it will be false.
+ // similar to cbImmedSize in some cases we'll set pInstrAttrib->m_cOperandSize to 0x3 meaning that the prefix will determine the size if one is specified.
+ pInstrAttrib->m_cOperandSize = 0;
+ pInstrAttrib->m_fIsWrite = false;
+
+ if (pInstrAttrib == NULL)
+ {
+ return;
+ }
+
+ // These three legacy prefixes are used to modify some of the two-byte opcodes.
+ bool fPrefix66 = false;
+ bool fPrefixF2 = false;
+ bool fPrefixF3 = false;
+
+ bool fRex = false;
+ bool fModRM = false;
+
+ RexByte rex = {0};
+ ModRMByte modrm = {0};
+
+ // We use 0x3 to indicate that we need to look at the operand-size override and the rex byte
+ // to determine whether the immediate size is 2 bytes or 4 bytes.
+ BYTE cbImmedSize = 0;
+
+ const BYTE* originalAddr = address;
+
+ do
+ {
+ switch (*address)
+ {
+ // Operand-Size override
+ case 0x66:
+ fPrefix66 = true;
+ goto LLegacyPrefix;
+
+ // Repeat (REP/REPE/REPZ)
+ case 0xf2:
+ fPrefixF2 = true;
+ goto LLegacyPrefix;
+
+ // Repeat (REPNE/REPNZ)
+ case 0xf3:
+ fPrefixF3 = true;
+ goto LLegacyPrefix;
+
+ // Address-Size override
+ case 0x67: // fall through
+
+ // Segment overrides
+ case 0x26: // ES
+ case 0x2E: // CS
+ case 0x36: // SS
+ case 0x3E: // DS
+ case 0x64: // FS
+ case 0x65: // GS // fall through
+
+ // Lock
+ case 0xf0:
+LLegacyPrefix:
+ LOG((LF_CORDB, LL_INFO10000, "prefix:%0.2x ", *address));
+ address++;
+ continue;
+
+ // REX register extension prefixes
+ case 0x40:
+ case 0x41:
+ case 0x42:
+ case 0x43:
+ case 0x44:
+ case 0x45:
+ case 0x46:
+ case 0x47:
+ case 0x48:
+ case 0x49:
+ case 0x4a:
+ case 0x4b:
+ case 0x4c:
+ case 0x4d:
+ case 0x4e:
+ case 0x4f:
+ LOG((LF_CORDB, LL_INFO10000, "prefix:%0.2x ", *address));
+ fRex = true;
+ rex = *(RexByte*)address;
+ address++;
+ continue;
+
+ default:
+ break;
+ }
+ } while (0);
+
+ pInstrAttrib->Reset();
+
+ BYTE opcode0 = *address;
+ BYTE opcode1 = *(address + 1); // this is only valid if the first opcode byte is 0x0F
+
+ // Handle AVX encodings. Note that these can mostly be handled as if they are aliases
+ // for a corresponding SSE encoding.
+ // See Figure 2-9 in "Intel 64 and IA-32 Architectures Software Developer's Manual".
+
+ if (opcode0 == 0xC4 || opcode0 == 0xC5)
+ {
+ BYTE pp;
+ if (opcode0 == 0xC4)
+ {
+ BYTE opcode2 = *(address + 2);
+ address++;
+
+ // REX bits are encoded in inverted form.
+ // R,X, and B are the top bits (in that order) of opcode1.
+ // W is the top bit of opcode2.
+ if ((opcode1 & 0x80) != 0)
+ {
+ rex.b = 1;
+ fRex = true;
+ }
+ if ((opcode1 & 0x40) == 0)
+ {
+ rex.x = 1;
+ fRex = true;
+ }
+ if ((opcode1 & 0x20) == 0)
+ {
+ rex.b = 1;
+ fRex = true;
+ }
+ if ((opcode2 & 0x80) != 0)
+ {
+ rex.w = 1;
+ fRex = true;
+ }
+
+ pp = opcode2 & 0x3;
+
+ BYTE mmBits = opcode1 & 0x1f;
+ BYTE impliedOpcode1 = 0;
+ switch(mmBits)
+ {
+ case 1: break; // No implied leading byte.
+ case 2: impliedOpcode1 = 0x38; break;
+ case 3: impliedOpcode1 = 0x3A; break;
+ default: _ASSERTE(!"NW::DIFPS - invalid opcode"); break;
+ }
+
+ if (impliedOpcode1 != 0)
+ {
+ opcode1 = impliedOpcode1;
+ }
+ else
+ {
+ opcode1 = *address;
+ address++;
+ }
+ }
+ else
+ {
+ pp = opcode1 & 0x3;
+ if ((opcode1 & 0x80) == 0)
+ {
+ // The two-byte VEX encoding only encodes the 'R' bit.
+ fRex = true;
+ rex.r = 1;
+ }
+ opcode1 = *address;
+ address++;
+ }
+ opcode0 = 0x0f;
+ switch (pp)
+ {
+ case 1: fPrefix66 = true; break;
+ case 2: fPrefixF3 = true; break;
+ case 3: fPrefixF2 = true; break;
+ }
+ }
+
+ // The following opcode decoding follows the tables in "Appendix A Opcode and Operand Encodings" of
+ // "AMD64 Architecture Programmer's Manual Volume 3"
+
+ // one-byte opcodes
+ if (opcode0 != 0x0F)
+ {
+ BYTE highNibble = (opcode0 & 0xF0) >> 4;
+ BYTE lowNibble = (opcode0 & 0x0F);
+
+ switch (highNibble)
+ {
+ case 0x0:
+ case 0x1:
+ case 0x2:
+ case 0x3:
+ if ((lowNibble == 0x6) || (lowNibble == 0x7) || (lowNibble == 0xE) || (lowNibble == 0xF))
+ {
+ _ASSERTE(!"NW::DIFPS - invalid opcode");
+ }
+
+ // CMP
+ if ( (lowNibble <= 0x3) ||
+ ((lowNibble >= 0x8) && (lowNibble <= 0xB)) )
+ {
+ fModRM = true;
+ }
+
+ // ADD/XOR reg/mem, reg
+ if (lowNibble == 0x0)
+ {
+ pInstrAttrib->m_cOperandSize = 0x1;
+ pInstrAttrib->m_fIsWrite = true;
+ }
+ else if (lowNibble == 0x1)
+ {
+ pInstrAttrib->m_cOperandSize = 0x3;
+ pInstrAttrib->m_fIsWrite = true;
+ }
+ // XOR reg, reg/mem
+ else if (lowNibble == 0x2)
+ {
+ pInstrAttrib->m_cOperandSize = 0x1;
+ }
+ else if (lowNibble == 0x3)
+ {
+ pInstrAttrib->m_cOperandSize = 0x3;
+ }
+
+ break;
+
+ case 0x4:
+ case 0x5:
+ break;
+
+ case 0x6:
+ // IMUL
+ if (lowNibble == 0x9)
+ {
+ fModRM = true;
+ cbImmedSize = 0x3;
+ }
+ else if (lowNibble == 0xB)
+ {
+ fModRM = true;
+ cbImmedSize = 0x1;
+ }
+ else if (lowNibble == 0x3)
+ {
+ if (fRex)
+ {
+ // MOVSXD
+ fModRM = true;
+ }
+ }
+ break;
+
+ case 0x7:
+ break;
+
+ case 0x8:
+ fModRM = true;
+
+ // Group 1: lowNibble in [0x0, 0x3]
+ _ASSERTE(lowNibble != 0x2);
+
+ // ADD/XOR reg/mem, imm
+ if (lowNibble == 0x0)
+ {
+ cbImmedSize = 1;
+ pInstrAttrib->m_cOperandSize = 1;
+ pInstrAttrib->m_fIsWrite = true;
+ }
+ else if (lowNibble == 0x1)
+ {
+ cbImmedSize = 3;
+ pInstrAttrib->m_cOperandSize = 3;
+ pInstrAttrib->m_fIsWrite = true;
+ }
+ else if (lowNibble == 0x3)
+ {
+ cbImmedSize = 1;
+ pInstrAttrib->m_cOperandSize = 3;
+ pInstrAttrib->m_fIsWrite = true;
+ }
+ // MOV reg/mem, reg
+ else if (lowNibble == 0x8)
+ {
+ pInstrAttrib->m_cOperandSize = 0x1;
+ pInstrAttrib->m_fIsWrite = true;
+ }
+ else if (lowNibble == 0x9)
+ {
+ pInstrAttrib->m_cOperandSize = 0x3;
+ pInstrAttrib->m_fIsWrite = true;
+ }
+ // MOV reg, reg/mem
+ else if (lowNibble == 0xA)
+ {
+ pInstrAttrib->m_cOperandSize = 0x1;
+ }
+ else if (lowNibble == 0xB)
+ {
+ pInstrAttrib->m_cOperandSize = 0x3;
+ }
+
+ break;
+
+ case 0x9:
+ case 0xA:
+ case 0xB:
+ break;
+
+ case 0xC:
+ if ((lowNibble == 0x4) || (lowNibble == 0x5) || (lowNibble == 0xE))
+ {
+ _ASSERTE(!"NW::DIFPS - invalid opcode");
+ }
+
+ // RET
+ if ((lowNibble == 0x2) || (lowNibble == 0x3))
+ {
+ break;
+ }
+
+ // Group 2 (part 1): lowNibble in [0x0, 0x1]
+ // RCL reg/mem, imm
+ if (lowNibble == 0x0)
+ {
+ fModRM = true;
+ cbImmedSize = 0x1;
+ pInstrAttrib->m_cOperandSize = 0x1;
+ pInstrAttrib->m_fIsWrite = true;
+ }
+ else if (lowNibble == 0x1)
+ {
+ fModRM = true;
+ cbImmedSize = 0x1;
+ pInstrAttrib->m_cOperandSize = 0x3;
+ pInstrAttrib->m_fIsWrite = true;
+ }
+ // Group 11: lowNibble in [0x6, 0x7]
+ // MOV reg/mem, imm
+ else if (lowNibble == 0x6)
+ {
+ fModRM = true;
+ cbImmedSize = 1;
+ pInstrAttrib->m_cOperandSize = 1;
+ pInstrAttrib->m_fIsWrite = true;
+ }
+ else if (lowNibble == 0x7)
+ {
+ fModRM = true;
+ cbImmedSize = 3;
+ pInstrAttrib->m_cOperandSize = 3;
+ pInstrAttrib->m_fIsWrite = true;
+ }
+ break;
+
+ case 0xD:
+ // Group 2 (part 2): lowNibble in [0x0, 0x3]
+ // RCL reg/mem, 1/reg
+ if (lowNibble == 0x0 || lowNibble == 0x2)
+ {
+ fModRM = true;
+ pInstrAttrib->m_cOperandSize = 0x1;
+ pInstrAttrib->m_fIsWrite = true;
+ }
+ else if (lowNibble == 0x1 || lowNibble == 0x3)
+ {
+ fModRM = true;
+ pInstrAttrib->m_cOperandSize = 0x3;
+ pInstrAttrib->m_fIsWrite = true;
+ }
+
+ // x87 instructions: lowNibble in [0x8, 0xF]
+ // - the entire ModRM byte is used to modify the opcode,
+ // so the ModRM byte cannot be used in RIP-relative addressing
+ break;
+
+ case 0xE:
+ break;
+
+ case 0xF:
+ // Group 3: lowNibble in [0x6, 0x7]
+ // TEST
+ if ((lowNibble == 0x6) || (lowNibble == 0x7))
+ {
+ fModRM = true;
+
+ modrm = *(ModRMByte*)(address + 1);
+ if ((modrm.reg == 0x0) || (modrm.reg == 0x1))
+ {
+ if (lowNibble == 0x6)
+ {
+ cbImmedSize = 0x1;
+ }
+ else
+ {
+ cbImmedSize = 0x3;
+ }
+ }
+ }
+ // Group 4: lowNibble == 0xE
+ // INC reg/mem
+ else if (lowNibble == 0xE)
+ {
+ fModRM = true;
+ pInstrAttrib->m_cOperandSize = 1;
+ pInstrAttrib->m_fIsWrite = true;
+ }
+ // Group 5: lowNibble == 0xF
+ else if (lowNibble == 0xF)
+ {
+ fModRM = true;
+ pInstrAttrib->m_cOperandSize = 3;
+ pInstrAttrib->m_fIsWrite = true;
+ }
+ break;
+ }
+
+ address += 1;
+ if (fModRM)
+ {
+ modrm = *(ModRMByte*)address;
+ address += 1;
+ }
+ }
+ // two-byte opcodes
+ else
+ {
+ BYTE highNibble = (opcode1 & 0xF0) >> 4;
+ BYTE lowNibble = (opcode1 & 0x0F);
+
+ switch (highNibble)
+ {
+ case 0x0:
+ // Group 6: lowNibble == 0x0
+ if (lowNibble == 0x0)
+ {
+ fModRM = true;
+ }
+ // Group 7: lowNibble == 0x1
+ else if (lowNibble == 0x1)
+ {
+ fModRM = true;
+ }
+ else if ((lowNibble == 0x2) || (lowNibble == 0x3))
+ {
+ fModRM = true;
+ }
+ // Group p: lowNibble == 0xD
+ else if (lowNibble == 0xD)
+ {
+ fModRM = true;
+ }
+ // 3DNow! instructions: lowNibble == 0xF
+ // - all 3DNow! instructions use the ModRM byte
+ else if (lowNibble == 0xF)
+ {
+ fModRM = true;
+ cbImmedSize = 0x1;
+ }
+ break;
+
+ case 0x1: // Group 16: lowNibble == 0x8
+ // MOVSS xmm, xmm/mem (low nibble 0x0)
+ // MOVSS xmm/mem, xmm (low nibble 0x1)
+ if (lowNibble <= 0x1)
+ {
+ fModRM = true;
+ if (fPrefixF2 || fPrefixF3)
+ pInstrAttrib->m_cOperandSize = 0x8;
+ else
+ pInstrAttrib->m_cOperandSize = 0x10;
+
+ if (lowNibble == 0x1)
+ pInstrAttrib->m_fIsWrite = true;
+
+ break;
+ }
+ case 0x2: // fall through
+ fModRM = true;
+ if (lowNibble == 0x8 || lowNibble == 0x9)
+ {
+ pInstrAttrib->m_cOperandSize = 0x10;
+
+ if (lowNibble == 0x9)
+ pInstrAttrib->m_fIsWrite = true;
+ }
+ break;
+
+ case 0x3:
+ break;
+
+ case 0x4:
+ case 0x5:
+ case 0x6: // fall through
+ fModRM = true;
+ break;
+
+ case 0x7:
+ if (lowNibble == 0x0)
+ {
+ fModRM = true;
+ cbImmedSize = 0x1;
+ }
+ else if ((lowNibble >= 0x1) && (lowNibble <= 0x3))
+ {
+ _ASSERTE(!fPrefixF2 && !fPrefixF3);
+
+ // Group 12: lowNibble == 0x1
+ // Group 13: lowNibble == 0x2
+ // Group 14: lowNibble == 0x3
+ fModRM = true;
+ cbImmedSize = 0x1;
+ }
+ else if ((lowNibble >= 0x4) && (lowNibble <= 0x6))
+ {
+ fModRM = true;
+ }
+ // MOVD reg/mem, mmx for 0F 7E
+ else if ((lowNibble == 0xE) || (lowNibble == 0xF))
+ {
+ _ASSERTE(!fPrefixF2);
+
+ fModRM = true;
+ }
+ break;
+
+ case 0x8:
+ break;
+
+ case 0x9:
+ fModRM = true;
+ break;
+
+ case 0xA:
+ if ((lowNibble >= 0x3) && (lowNibble <= 0x5))
+ {
+ // BT reg/mem, reg
+ fModRM = true;
+ if (lowNibble == 0x3)
+ {
+ pInstrAttrib->m_cOperandSize = 0x3;
+ pInstrAttrib->m_fIsWrite = true;
+ }
+ // SHLD reg/mem, imm
+ else if (lowNibble == 0x4)
+ {
+ cbImmedSize = 0x1;
+ }
+ }
+ else if (lowNibble >= 0xB)
+ {
+ fModRM = true;
+ // BTS reg/mem, reg
+ if (lowNibble == 0xB)
+ {
+ pInstrAttrib->m_cOperandSize = 0x3;
+ pInstrAttrib->m_fIsWrite = true;
+ }
+ // SHRD reg/mem, imm
+ else if (lowNibble == 0xC)
+ {
+ cbImmedSize = 0x1;
+ }
+ // Group 15: lowNibble == 0xE
+ }
+ break;
+
+ case 0xB:
+ // Group 10: lowNibble == 0x9
+ // - this entire group is invalid
+ _ASSERTE((lowNibble != 0x8) && (lowNibble != 0x9));
+
+ fModRM = true;
+ // CMPXCHG reg/mem, reg
+ if (lowNibble == 0x0)
+ {
+ pInstrAttrib->m_cOperandSize = 0x1;
+ pInstrAttrib->m_fIsWrite = true;
+ }
+ else if (lowNibble == 0x1)
+ {
+ pInstrAttrib->m_cOperandSize = 0x3;
+ pInstrAttrib->m_fIsWrite = true;
+ }
+ // Group 8: lowNibble == 0xA
+ // BTS reg/mem, imm
+ else if (lowNibble == 0xA)
+ {
+ cbImmedSize = 0x1;
+ pInstrAttrib->m_cOperandSize = 0x3;
+ pInstrAttrib->m_fIsWrite = true;
+ }
+ // MOVSX reg, reg/mem
+ else if (lowNibble == 0xE)
+ {
+ pInstrAttrib->m_cOperandSize = 1;
+ }
+ else if (lowNibble == 0xF)
+ {
+ pInstrAttrib->m_cOperandSize = 2;
+ }
+ break;
+
+ case 0xC:
+ if (lowNibble <= 0x7)
+ {
+ fModRM = true;
+ // XADD reg/mem, reg
+ if (lowNibble == 0x0)
+ {
+ pInstrAttrib->m_cOperandSize = 0x1;
+ pInstrAttrib->m_fIsWrite = true;
+ }
+ else if (lowNibble == 0x1)
+ {
+ pInstrAttrib->m_cOperandSize = 0x3;
+ pInstrAttrib->m_fIsWrite = true;
+ }
+ else if ( (lowNibble == 0x2) ||
+ ((lowNibble >= 0x4) && (lowNibble <= 0x6)) )
+ {
+ cbImmedSize = 0x1;
+ }
+ }
+ break;
+
+ case 0xD:
+ case 0xE:
+ case 0xF: // fall through
+ fModRM = true;
+ break;
+ }
+
+ address += 2;
+ if (fModRM)
+ {
+ modrm = *(ModRMByte*)address;
+ address += 1;
+ }
+ }
+
+ // Check for RIP-relative addressing
+ if (fModRM && (modrm.mod == 0x0) && (modrm.rm == 0x5))
+ {
+ // SIB byte cannot be present with RIP-relative addressing.
+
+ pInstrAttrib->m_dwOffsetToDisp = (DWORD)(address - originalAddr);
+ _ASSERTE(pInstrAttrib->m_dwOffsetToDisp <= MAX_INSTRUCTION_LENGTH);
+
+ // Add 4 to the address for the displacement.
+ address += 4;
+
+ // Further adjust the address by the size of the cbImmedSize (if any).
+ if (cbImmedSize == 0x3)
+ {
+ // The size of the cbImmedSizeiate depends on the effective operand size:
+ // 2 bytes if the effective operand size is 16-bit, or
+ // 4 bytes if the effective operand size is 32- or 64-bit.
+ if (fPrefix66)
+ {
+ cbImmedSize = 0x2;
+ }
+ else
+ {
+ cbImmedSize = 0x4;
+ }
+ }
+ address += cbImmedSize;
+
+ // if this is a read or write to a RIP-relative address then update pInstrAttrib->m_cOperandSize with the size of the pointee.
+ if (pInstrAttrib->m_cOperandSize == 0x3)
+ {
+ if (fPrefix66)
+ pInstrAttrib->m_cOperandSize = 0x2; // WORD*
+ else
+ pInstrAttrib->m_cOperandSize = 0x4; // DWORD*
+
+ if (fRex && rex.w == 0x1)
+ {
+ _ASSERTE(pInstrAttrib->m_cOperandSize == 0x4);
+ pInstrAttrib->m_cOperandSize = 0x8; // QWORD*
+ }
+ }
+
+ pInstrAttrib->m_cbInstr = (DWORD)(address - originalAddr);
+ _ASSERTE(pInstrAttrib->m_cbInstr <= MAX_INSTRUCTION_LENGTH);
+ }
+ else
+ {
+ // not a RIP-relative address so set to default values
+ pInstrAttrib->m_cOperandSize = 0;
+ pInstrAttrib->m_fIsWrite = false;
+ }
+
+ //
+ // Look at opcode to tell if it's a call or an
+ // absolute branch.
+ //
+ switch (opcode0)
+ {
+ case 0xC2: // RET
+ case 0xC3: // RET N
+ pInstrAttrib->m_fIsAbsBranch = true;
+ LOG((LF_CORDB, LL_INFO10000, "ABS:%0.2x\n", opcode0));
+ break;
+
+ case 0xE8: // CALL relative
+ pInstrAttrib->m_fIsCall = true;
+ LOG((LF_CORDB, LL_INFO10000, "CALL REL:%0.2x\n", opcode0));
+ break;
+
+ case 0xC8: // ENTER
+ pInstrAttrib->m_fIsCall = true;
+ pInstrAttrib->m_fIsAbsBranch = true;
+ LOG((LF_CORDB, LL_INFO10000, "CALL ABS:%0.2x\n", opcode0));
+ break;
+
+ case 0xFF: // CALL/JMP modr/m
+ //
+ // Read opcode modifier from modr/m
+ //
+
+ _ASSERTE(fModRM);
+ switch (modrm.reg)
+ {
+ case 2:
+ case 3:
+ pInstrAttrib->m_fIsCall = true;
+ // fall through
+ case 4:
+ case 5:
+ pInstrAttrib->m_fIsAbsBranch = true;
+ }
+ LOG((LF_CORDB, LL_INFO10000, "CALL/JMP modr/m:%0.2x\n", opcode0));
+ break;
+
+ default:
+ LOG((LF_CORDB, LL_INFO10000, "NORMAL:%0.2x\n", opcode0));
+ }
+
+ if (pInstrAttrib->m_cOperandSize == 0x0)
+ {
+ // if an operand size wasn't computed (likely because the decoder didn't understand the instruction) then set
+ // the size to the max buffer size. this is a fall-back to the dev10 behavior and is applicable for reads only.
+ _ASSERTE(!pInstrAttrib->m_fIsWrite);
+ pInstrAttrib->m_cOperandSize = SharedPatchBypassBuffer::cbBufferBypass;
+ }
+}
+
+
+#endif
diff --git a/src/debug/ee/amd64/dbghelpers.S b/src/debug/ee/amd64/dbghelpers.S
new file mode 100644
index 0000000000..85ec80c701
--- /dev/null
+++ b/src/debug/ee/amd64/dbghelpers.S
@@ -0,0 +1,156 @@
+// 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.
+
+.intel_syntax noprefix
+#include "unixasmmacros.inc"
+
+//extern FuncEvalHijackWorker:proc
+
+// @dbgtodo- once we port Funceval, use the ExceptionHijack stub instead of this func-eval stub.
+NESTED_ENTRY FuncEvalHijack, _TEXT, UnhandledExceptionHandlerUnix
+ // the stack should be aligned at this point, since we do not call this
+ // function explicitly
+ alloc_stack 0x20
+ END_PROLOGUE
+
+ mov [rsp], rdi
+ call C_FUNC(FuncEvalHijackWorker)
+
+ //
+ // The following nop is crucial. It is important that the OS *not* recognize
+ // the instruction immediately following the call above as an epilog, if it
+ // does recognize it as an epilogue, it unwinds this function itself rather
+ // than calling our personality routine to do the unwind, and then stack
+ // tracing is hosed.
+ //
+ nop
+
+ //
+ // epilogue
+ //
+ add rsp, 20h
+ TAILJMP_RAX
+NESTED_END FuncEvalHijack, _TEXT
+
+//extern ExceptionHijackWorker:proc
+
+// This is the general purpose hijacking stub. The DacDbi Hijack primitive will
+// set up the stack and then set the IP here, and so this just makes the call.
+NESTED_ENTRY ExceptionHijack, _TEXT, UnhandledExceptionHandlerUnix
+ // the stack should be aligned at this point, since we do not call this
+ // function explicitly
+ //
+ // There is a problem here. The Orcas assembler doesn't like a 0-sized stack frame.
+ // So we allocate 4 stack slots as the outgoing argument home and just copy the
+ // arguments set up by DacDbi into these stack slots. We will take a perf hit,
+ // but this is not a perf critical code path anyway.
+
+ // There is an additional dependency on this alloc_stack: the
+ // ExceptionHijackPersonalityRoutine assumes that it can find
+ // the first argument to HijackWorker in the stack frame of
+ // ExceptionHijack, at an offset of exactly 0x20 bytes from
+ // ExceptionHijackWorker's stack frame. Therefore it is
+ // important that we move the stack pointer by the same amount.
+ alloc_stack 0x20
+ END_PROLOGUE
+
+ // We used to do an "alloc_stack 0h" because the stack has been allocated for us
+ // by the OOP hijacking routine. Our arguments have also been pushed onto the
+ // stack for us. However, the Orcas compilers don't like a 0-sized frame, so
+ // we need to allocate something here and then just copy the stack arguments to
+ // their new argument homes.
+
+ // In x86, ExceptionHijackWorker is marked STDCALL, so it finds
+ // its arguments on the stack. In x64, it gets its arguments in
+ // registers (set up for us by DacDbiInterfaceImpl::Hijack),
+ // and this stack space may be reused.
+ mov rax, [rsp + 20h]
+ mov [rsp], rax
+ mov rax, [rsp + 28h]
+ mov [rsp + 8h], rax
+ mov rax, [rsp + 30h]
+ mov [rsp + 10h], rax
+ mov rax, [rsp + 38h]
+ mov [rsp + 18h], rax
+
+ // DD Hijack primitive already set the stack. So just make the call now.
+ call C_FUNC(ExceptionHijackWorker)
+
+ //
+ // The following nop is crucial. It is important that the OS *not* recognize
+ // the instruction immediately following the call above as an epilog, if it
+ // does recognize it as an epilogue, it unwinds this function itself rather
+ // than calling our personality routine to do the unwind, and then stack
+ // tracing is hosed.
+ //
+ nop
+
+ // *** Should never get here ***
+ // Hijack should have restored itself first.
+ int 3
+
+ //
+ // epilogue
+ //
+ add rsp, 20h
+ TAILJMP_RAX
+
+// Put a label here to tell the debugger where the end of this function is.
+PATCH_LABEL ExceptionHijackEnd
+
+NESTED_END ExceptionHijack, _TEXT
+
+//
+// Flares for interop debugging.
+// Flares are exceptions (breakpoints) at well known addresses which the RS
+// listens for when interop debugging.
+//
+
+// This exception is from managed code.
+LEAF_ENTRY SignalHijackStartedFlare, _TEXT
+ int 3
+ // make sure that the basic block is unique
+ test rax,1
+ ret
+LEAF_END SignalHijackStartedFlare, _TEXT
+
+// Start the handoff
+LEAF_ENTRY ExceptionForRuntimeHandoffStartFlare, _TEXT
+ int 3
+ // make sure that the basic block is unique
+ test rax,2
+ ret
+LEAF_END ExceptionForRuntimeHandoffStartFlare, _TEXT
+
+// Finish the handoff.
+LEAF_ENTRY ExceptionForRuntimeHandoffCompleteFlare, _TEXT
+ int 3
+ // make sure that the basic block is unique
+ test rax,3
+ ret
+LEAF_END ExceptionForRuntimeHandoffCompleteFlare, _TEXT
+
+// Signal execution return to unhijacked state
+LEAF_ENTRY SignalHijackCompleteFlare, _TEXT
+ int 3
+ // make sure that the basic block is unique
+ test rax,4
+ ret
+LEAF_END SignalHijackCompleteFlare, _TEXT
+
+// This exception is from unmanaged code.
+LEAF_ENTRY ExceptionNotForRuntimeFlare, _TEXT
+ int 3
+ // make sure that the basic block is unique
+ test rax,5
+ ret
+LEAF_END ExceptionNotForRuntimeFlare, _TEXT
+
+// The Runtime is synchronized.
+LEAF_ENTRY NotifyRightSideOfSyncCompleteFlare, _TEXT
+ int 3
+ // make sure that the basic block is unique
+ test rax,6
+ ret
+LEAF_END NotifyRightSideOfSyncCompleteFlare, _TEXT
diff --git a/src/debug/ee/amd64/dbghelpers.asm b/src/debug/ee/amd64/dbghelpers.asm
new file mode 100644
index 0000000000..5836257f46
--- /dev/null
+++ b/src/debug/ee/amd64/dbghelpers.asm
@@ -0,0 +1,164 @@
+; 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 AsmMacros.inc
+
+extern FuncEvalHijackWorker:proc
+extern FuncEvalHijackPersonalityRoutine:proc
+
+; @dbgtodo- once we port Funceval, use the ExceptionHijack stub instead of this func-eval stub.
+NESTED_ENTRY FuncEvalHijack, _TEXT, FuncEvalHijackPersonalityRoutine
+ ; the stack should be aligned at this point, since we do not call this
+ ; function explicitly
+ alloc_stack 20h
+ END_PROLOGUE
+
+ mov [rsp], rcx
+ call FuncEvalHijackWorker
+
+ ;
+ ; The following nop is crucial. It is important that the OS *not* recognize
+ ; the instruction immediately following the call above as an epilog, if it
+ ; does recognize it as an epilogue, it unwinds this function itself rather
+ ; than calling our personality routine to do the unwind, and then stack
+ ; tracing is hosed.
+ ;
+ nop
+
+ ;
+ ; epilogue
+ ;
+ add rsp, 20h
+ TAILJMP_RAX
+NESTED_END FuncEvalHijack, _TEXT
+
+
+
+extern ExceptionHijackWorker:proc
+extern ExceptionHijackPersonalityRoutine:proc
+
+; This is the general purpose hijacking stub. The DacDbi Hijack primitive will
+; set up the stack and then set the IP here, and so this just makes the call.
+NESTED_ENTRY ExceptionHijack, _TEXT, ExceptionHijackPersonalityRoutine
+ ; the stack should be aligned at this point, since we do not call this
+ ; function explicitly
+ ;
+ ; There is a problem here. The Orcas assembler doesn't like a 0-sized stack frame.
+ ; So we allocate 4 stack slots as the outgoing argument home and just copy the
+ ; arguments set up by DacDbi into these stack slots. We will take a perf hit,
+ ; but this is not a perf critical code path anyway.
+
+ ; There is an additional dependency on this alloc_stack: the
+ ; ExceptionHijackPersonalityRoutine assumes that it can find
+ ; the first argument to HijackWorker in the stack frame of
+ ; ExceptionHijack, at an offset of exactly 0x20 bytes from
+ ; ExceptionHijackWorker's stack frame. Therefore it is
+ ; important that we move the stack pointer by the same amount.
+ alloc_stack 20h
+ END_PROLOGUE
+
+ ; We used to do an "alloc_stack 0h" because the stack has been allocated for us
+ ; by the OOP hijacking routine. Our arguments have also been pushed onto the
+ ; stack for us. However, the Orcas compilers don't like a 0-sized frame, so
+ ; we need to allocate something here and then just copy the stack arguments to
+ ; their new argument homes.
+
+ ; In x86, ExceptionHijackWorker is marked STDCALL, so it finds
+ ; its arguments on the stack. In x64, it gets its arguments in
+ ; registers (set up for us by DacDbiInterfaceImpl::Hijack),
+ ; and this stack space may be reused.
+ mov rax, [rsp + 20h]
+ mov [rsp], rax
+ mov rax, [rsp + 28h]
+ mov [rsp + 8h], rax
+ mov rax, [rsp + 30h]
+ mov [rsp + 10h], rax
+ mov rax, [rsp + 38h]
+ mov [rsp + 18h], rax
+
+ ; DD Hijack primitive already set the stack. So just make the call now.
+ call ExceptionHijackWorker
+
+ ;
+ ; The following nop is crucial. It is important that the OS *not* recognize
+ ; the instruction immediately following the call above as an epilog, if it
+ ; does recognize it as an epilogue, it unwinds this function itself rather
+ ; than calling our personality routine to do the unwind, and then stack
+ ; tracing is hosed.
+ ;
+ nop
+
+ ; *** Should never get here ***
+ ; Hijack should have restored itself first.
+ int 3
+
+ ;
+ ; epilogue
+ ;
+ add rsp, 20h
+ TAILJMP_RAX
+
+; Put a label here to tell the debugger where the end of this function is.
+PATCH_LABEL ExceptionHijackEnd
+
+NESTED_END ExceptionHijack, _TEXT
+
+;
+; Flares for interop debugging.
+; Flares are exceptions (breakpoints) at well known addresses which the RS
+; listens for when interop debugging.
+;
+
+; This exception is from managed code.
+LEAF_ENTRY SignalHijackStartedFlare, _TEXT
+ int 3
+ ; make sure that the basic block is unique
+ test rax,1
+ ret
+LEAF_END SignalHijackStartedFlare, _TEXT
+
+; Start the handoff
+LEAF_ENTRY ExceptionForRuntimeHandoffStartFlare, _TEXT
+ int 3
+ ; make sure that the basic block is unique
+ test rax,2
+ ret
+LEAF_END ExceptionForRuntimeHandoffStartFlare, _TEXT
+
+; Finish the handoff.
+LEAF_ENTRY ExceptionForRuntimeHandoffCompleteFlare, _TEXT
+ int 3
+ ; make sure that the basic block is unique
+ test rax,3
+ ret
+LEAF_END ExceptionForRuntimeHandoffCompleteFlare, _TEXT
+
+; Signal execution return to unhijacked state
+LEAF_ENTRY SignalHijackCompleteFlare, _TEXT
+ int 3
+ ; make sure that the basic block is unique
+ test rax,4
+ ret
+LEAF_END SignalHijackCompleteFlare, _TEXT
+
+; This exception is from unmanaged code.
+LEAF_ENTRY ExceptionNotForRuntimeFlare, _TEXT
+ int 3
+ ; make sure that the basic block is unique
+ test rax,5
+ ret
+LEAF_END ExceptionNotForRuntimeFlare, _TEXT
+
+; The Runtime is synchronized.
+LEAF_ENTRY NotifyRightSideOfSyncCompleteFlare, _TEXT
+ int 3
+ ; make sure that the basic block is unique
+ test rax,6
+ ret
+LEAF_END NotifyRightSideOfSyncCompleteFlare, _TEXT
+
+
+
+; This goes at the end of the assembly file
+ end
diff --git a/src/debug/ee/amd64/debuggerregdisplayhelper.cpp b/src/debug/ee/amd64/debuggerregdisplayhelper.cpp
new file mode 100644
index 0000000000..0d48a67eea
--- /dev/null
+++ b/src/debug/ee/amd64/debuggerregdisplayhelper.cpp
@@ -0,0 +1,41 @@
+// 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.
+/* ------------------------------------------------------------------------- *
+ * DebuggerRegDisplayHelper.cpp -- implementation of the platform-dependent
+//
+
+ * methods for transferring information between
+ * REGDISPLAY and DebuggerREGDISPLAY
+ * ------------------------------------------------------------------------- */
+
+#include "stdafx.h"
+
+void CopyREGDISPLAY(REGDISPLAY* pDst, REGDISPLAY* pSrc)
+{
+ memcpy((BYTE*)pDst, (BYTE*)pSrc, sizeof(REGDISPLAY));
+
+ pDst->pContext = pSrc->pContext;
+
+ if (pSrc->pCurrentContextPointers == &(pSrc->ctxPtrsOne))
+ {
+ pDst->pCurrentContextPointers = &(pDst->ctxPtrsOne);
+ pDst->pCallerContextPointers = &(pDst->ctxPtrsTwo);
+ }
+ else
+ {
+ pDst->pCurrentContextPointers = &(pDst->ctxPtrsTwo);
+ pDst->pCallerContextPointers = &(pDst->ctxPtrsOne);
+ }
+
+ if (pSrc->pCurrentContext == &(pSrc->ctxOne))
+ {
+ pDst->pCurrentContext = &(pDst->ctxOne);
+ pDst->pCallerContext = &(pDst->ctxTwo);
+ }
+ else
+ {
+ pDst->pCurrentContext = &(pDst->ctxTwo);
+ pDst->pCallerContext = &(pDst->ctxOne);
+ }
+}
diff --git a/src/debug/ee/amd64/primitives.cpp b/src/debug/ee/amd64/primitives.cpp
new file mode 100644
index 0000000000..055b75d120
--- /dev/null
+++ b/src/debug/ee/amd64/primitives.cpp
@@ -0,0 +1,13 @@
+// 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 "stdafx.h"
+
+#include "../../shared/amd64/primitives.cpp"
+
+
diff --git a/src/debug/ee/arm/.gitmirror b/src/debug/ee/arm/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/debug/ee/arm/.gitmirror
@@ -0,0 +1 @@
+Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror. \ No newline at end of file
diff --git a/src/debug/ee/arm/armwalker.cpp b/src/debug/ee/arm/armwalker.cpp
new file mode 100644
index 0000000000..01e77b1890
--- /dev/null
+++ b/src/debug/ee/arm/armwalker.cpp
@@ -0,0 +1,407 @@
+// 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.
+//*****************************************************************************
+// File: armwalker.cpp
+//
+
+//
+// ARM instruction decoding/stepping logic
+//
+//*****************************************************************************
+
+#include "stdafx.h"
+
+#include "walker.h"
+
+#include "frames.h"
+#include "openum.h"
+
+
+#ifdef _TARGET_ARM_
+
+void NativeWalker::Decode()
+{
+ // Set default next and skip instruction pointers.
+ m_nextIP = NULL;
+ m_skipIP = NULL;
+ m_type = WALK_UNKNOWN;
+
+ // We can't walk reliably without registers (because we need to know the IT state to determine whether or
+ // not the current instruction will be executed).
+ if (m_registers == NULL)
+ return;
+
+ // Determine whether we're executing in an IT block. If so, check the condition codes and IT state to see
+ // whether we'll execute the current instruction.
+ BYTE bITState = (BYTE)((BitExtract((WORD)m_registers->pCurrentContext->Cpsr, 15, 10) << 2) |
+ BitExtract((WORD)(m_registers->pCurrentContext->Cpsr >> 16), 10, 9));
+ if ((bITState & 0x1f) && !ConditionHolds(BitExtract(bITState, 7, 4)))
+ {
+ // We're in an IT block and the state is such that the current instruction is not scheduled to
+ // execute. Just return WALK_UNKNOWN so the caller will invoke single-step to update the register
+ // context correctly for the next instruction.
+
+ LOG((LF_CORDB, LL_INFO100000, "ArmWalker::Decode: IT block at %x\n", m_ip));
+ return;
+ }
+
+ // Fetch first word of the current instruction. From this we can determine if we've gotten the whole thing
+ // or we're dealing with a 32-bit instruction. If the current instruction is a break instruction, we'll
+ // need to check the patch table to get the correct instruction.
+ WORD opcode1 = CORDbgGetInstruction(m_ip);
+ PRD_TYPE unpatchedOpcode;
+ if (DebuggerController::CheckGetPatchedOpcode(m_ip, &unpatchedOpcode))
+ {
+ opcode1 = (WORD) unpatchedOpcode;
+ }
+
+
+ if (Is32BitInstruction(opcode1))
+ {
+ // Fetch second word of 32-bit instruction.
+ WORD opcode2 = CORDbgGetInstruction((BYTE*)((DWORD)m_ip) + 2);
+
+ LOG((LF_CORDB, LL_INFO100000, "ArmWalker::Decode 32bit instruction at %x, opcode: %x%x\n", m_ip, (DWORD)opcode1, (DWORD)opcode2));
+
+ // WALK_RETURN
+ if (((opcode1 & 0xffd0) == 0xe890) &&
+ ((opcode2 & 0x2000) == 0x0000))
+ {
+ // LDM.W : T2, POP.W : T2
+ DWORD registerList = opcode2;
+
+ if (registerList & 0x8000)
+ {
+ m_type = WALK_RETURN;
+ return;
+ }
+ }
+
+ // WALK_BRANCH
+ else if (((opcode1 & 0xf800) == 0xf000) &&
+ ((opcode2 & 0xd000) == 0x8000) &&
+ ((opcode1 & 0x0380) != 0x0380))
+ {
+ // B.W : T3
+ DWORD S = BitExtract(opcode1, 10, 10);
+ DWORD cond = BitExtract(opcode1, 9, 6);
+ DWORD imm6 = BitExtract(opcode1, 5, 0);
+ DWORD J1 = BitExtract(opcode2, 13, 13);
+ DWORD J2 = BitExtract(opcode2, 11, 11);
+ DWORD imm11 = BitExtract(opcode2, 10, 0);
+
+ if (ConditionHolds(cond))
+ {
+ DWORD disp = (S ? 0xfff00000 : 0) | (J2 << 19) | (J1 << 18) | (imm6 << 12) | (imm11 << 1);
+ m_nextIP = (BYTE*)((GetReg(15) + disp) | THUMB_CODE);
+ m_skipIP = m_nextIP;
+ m_type = WALK_BRANCH;
+ return;
+ }
+ }
+ else if (((opcode1 & 0xf800) == 0xf000) &&
+ ((opcode2 & 0xd000) == 0x9000))
+ {
+ // B.W : T4
+ DWORD S = BitExtract(opcode1, 10, 10);
+ DWORD imm10 = BitExtract(opcode1, 9, 0);
+ DWORD J1 = BitExtract(opcode2, 13, 13);
+ DWORD J2 = BitExtract(opcode2, 11, 11);
+ DWORD imm11 = BitExtract(opcode2, 10, 0);
+
+ DWORD I1 = (J1 ^ S) ^ 1;
+ DWORD I2 = (J2 ^ S) ^ 1;
+
+ DWORD disp = (S ? 0xff000000 : 0) | (I1 << 23) | (I2 << 22) | (imm10 << 12) | (imm11 << 1);
+
+ m_nextIP = (BYTE*)((GetReg(15) + disp) | THUMB_CODE);
+ m_skipIP = m_nextIP;
+ m_type = WALK_BRANCH;
+ return;
+ }
+ else if (((opcode1 & 0xfff0) == 0xf8d0) &&
+ ((opcode1 & 0x000f) != 0x000f))
+ {
+ // LDR.W (immediate): T3
+ DWORD Rn = BitExtract(opcode1, 3, 0);
+ DWORD Rt = BitExtract(opcode2, 15, 12);
+ DWORD imm12 = BitExtract(opcode2, 11, 0);
+
+ if (Rt == 15)
+ {
+ DWORD value = *(DWORD*)(GetReg(Rn) + imm12);
+
+ m_nextIP = (BYTE*)(value | THUMB_CODE);
+ m_skipIP = m_nextIP;
+ m_type = WALK_BRANCH;
+ return;
+ }
+ }
+ else if (((opcode1 & 0xfff0) == 0xf850) &&
+ ((opcode2 & 0x0800) == 0x0800) &&
+ ((opcode1 & 0x000f) != 0x000f))
+ {
+ // LDR (immediate) : T4, POP : T3
+ DWORD Rn = BitExtract(opcode1, 3, 0);
+ DWORD Rt = BitExtract(opcode2, 15, 12);
+ DWORD P = BitExtract(opcode2, 10, 10);
+ DWORD U = BitExtract(opcode2, 9, 9);
+ DWORD imm8 = BitExtract(opcode2, 7, 0);
+
+ if (Rt == 15)
+ {
+ DWORD offset_addr = U ? GetReg(Rn) + imm8 : GetReg(Rn) - imm8;
+ DWORD addr = P ? offset_addr : GetReg(Rn);
+
+ DWORD value = *(DWORD*)addr;
+
+ m_nextIP = (BYTE*)(value | THUMB_CODE);
+ m_skipIP = m_nextIP;
+ m_type = WALK_BRANCH;
+ return;
+ }
+ }
+ else if (((opcode1 & 0xff7f) == 0xf85f))
+ {
+ // LDR.W (literal) : T2
+ DWORD U = BitExtract(opcode1, 7, 7);
+ DWORD Rt = BitExtract(opcode2, 15, 12);
+ DWORD imm12 = BitExtract(opcode2, 11, 0);
+
+ if (Rt == 15)
+ {
+ DWORD addr = GetReg(15) & ~3;
+ addr = U ? addr + imm12 : addr - imm12;
+
+ DWORD value = *(DWORD*)addr;
+
+ m_nextIP = (BYTE*)(value | THUMB_CODE);
+ m_skipIP = m_nextIP;
+ m_type = WALK_BRANCH;
+ return;
+ }
+ }
+ else if (((opcode1 & 0xfff0) == 0xf850) &&
+ ((opcode2 & 0x0fc0) == 0x0000) &&
+ ((opcode1 & 0x000f) != 0x000f))
+ {
+ // LDR.W : T2
+ DWORD Rn = BitExtract(opcode1, 3, 0);
+ DWORD Rt = BitExtract(opcode2, 15, 12);
+ DWORD imm2 = BitExtract(opcode2, 5, 4);
+ DWORD Rm = BitExtract(opcode2, 3, 0);
+
+ if (Rt == 15)
+ {
+ DWORD addr = GetReg(Rn) + (GetReg(Rm) << imm2);
+
+ DWORD value = *(DWORD*)addr;
+
+ m_nextIP = (BYTE*)(value | THUMB_CODE);
+ m_skipIP = m_nextIP;
+ m_type = WALK_BRANCH;
+ return;
+ }
+ }
+ else if (((opcode1 & 0xfff0) == 0xe8d0) &&
+ ((opcode2 & 0xffe0) == 0xf000))
+ {
+ // TBB/TBH : T1
+ DWORD Rn = BitExtract(opcode1, 3, 0);
+ DWORD H = BitExtract(opcode2, 4, 4);
+ DWORD Rm = BitExtract(opcode2, 3, 0);
+
+ DWORD addr = GetReg(Rn);
+
+ DWORD value;
+ if (H)
+ value = *(WORD*)(addr + (GetReg(Rm) << 1));
+ else
+ value = *(BYTE*)(addr + GetReg(Rm));
+
+ m_nextIP = (BYTE*)((GetReg(15) + (value << 1)) | THUMB_CODE);
+ m_skipIP = m_nextIP;
+ m_type = WALK_BRANCH;
+ return;
+ }
+
+ // WALK_CALL
+ else if (((opcode1 & 0xf800) == 0xf000) &&
+ ((opcode2 & 0xd000) == 0xd000))
+ {
+ // BL (immediate) : T1
+ DWORD S = BitExtract(opcode1, 10, 10);
+ DWORD imm10 = BitExtract(opcode1, 9, 0);
+ DWORD J1 = BitExtract(opcode2, 13, 13);
+ DWORD J2 = BitExtract(opcode2, 11, 11);
+ DWORD imm11 = BitExtract(opcode2, 10, 0);
+
+ DWORD I1 = (J1 ^ S) ^ 1;
+ DWORD I2 = (J2 ^ S) ^ 1;
+
+ DWORD disp = (S ? 0xff000000 : 0) | (I1 << 23) | (I2 << 22) | (imm10 << 12) | (imm11 << 1);
+
+ m_nextIP = (BYTE*)((GetReg(15) + disp) | THUMB_CODE);
+ m_skipIP =(BYTE*)(((DWORD)m_ip) + 4);
+ m_type = WALK_CALL;
+ return;
+ }
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO100000, "ArmWalker::Decode 16bit instruction at %x, opcode: %x\n", m_ip, (DWORD)opcode1));
+ // WALK_RETURN
+ if ((opcode1 & 0xfe00) == 0xbc00)
+ {
+ // POP : T1
+ DWORD P = BitExtract(opcode1, 8, 8);
+ DWORD registerList = (P << 15) | BitExtract(opcode1, 7, 0);
+
+ if (registerList & 0x8000)
+ {
+ m_type = WALK_RETURN;
+ return;
+ }
+ }
+
+ // WALK_BRANCH
+ else if (((opcode1 & 0xf000) == 0xd000) &&
+ ((opcode1 & 0x0f00) != 0x0e00) )
+ {
+ // B : T1
+ DWORD cond = BitExtract(opcode1, 11, 8);
+ DWORD imm8 = BitExtract(opcode1, 7, 0);
+
+ if (ConditionHolds(cond))
+ {
+ DWORD disp = (imm8 << 1) | ((imm8 & 0x80) ? 0xffffff00 : 0);
+
+ m_nextIP = (BYTE*)((GetReg(15) + disp) | THUMB_CODE);
+ m_skipIP = m_nextIP;
+ m_type = WALK_BRANCH;
+ return;
+ }
+ }
+ else if ((opcode1 & 0xf800) == 0xe000)
+ {
+ // B : T2
+ DWORD imm11 = BitExtract(opcode1, 10, 0);
+
+ DWORD disp = (imm11 << 1) | ((imm11 & 0x400) ? 0xfffff000 : 0);
+
+ m_nextIP = (BYTE*)((GetReg(15) + disp) | THUMB_CODE);
+ m_skipIP = m_nextIP;
+ m_type = WALK_BRANCH;
+ return;
+ }
+ else if ((opcode1 & 0xff87) == 0x4700)
+ {
+ // BX : T1
+ DWORD Rm = BitExtract(opcode1, 6, 3);
+
+ m_nextIP = (BYTE*)(GetReg(Rm) | THUMB_CODE);
+ m_skipIP = m_nextIP;
+ m_type = (Rm != 14) ? WALK_BRANCH : WALK_RETURN;
+ return;
+ }
+ else if ((opcode1 & 0xff00) == 0x4600)
+ {
+ // MOV (register) : T1
+ DWORD D = BitExtract(opcode1, 7, 7);
+ DWORD Rm = BitExtract(opcode1, 6, 3);
+ DWORD Rd = (D << 3) | BitExtract(opcode1, 2, 0);
+
+ if (Rd == 15)
+ {
+ m_nextIP = (BYTE*)(GetReg(Rm) | THUMB_CODE);
+ m_skipIP = m_nextIP;
+ m_type = WALK_BRANCH;
+ return;
+ }
+ }
+
+ // WALK_CALL
+ else if ((opcode1 & 0xff87) == 0x4780)
+ {
+ // BLX (register) : T1
+ DWORD Rm = BitExtract(opcode1, 6, 3);
+
+ m_nextIP = (BYTE*)(GetReg(Rm) | THUMB_CODE);
+ m_skipIP = (BYTE*)(((DWORD)m_ip) + 2);
+ m_type = WALK_CALL;
+ return;
+ }
+ }
+}
+
+// Get the current value of a register. PC (register 15) is always reported as the current instruction PC + 4
+// as per the ARM architecture.
+DWORD NativeWalker::GetReg(DWORD reg)
+{
+ _ASSERTE(reg <= 15);
+
+ if (reg == 15)
+ return (m_registers->pCurrentContext->Pc + 4) & ~THUMB_CODE;
+
+ return (&m_registers->pCurrentContext->R0)[reg];
+}
+
+// Returns true if the current context indicates the ARM condition specified holds.
+bool NativeWalker::ConditionHolds(DWORD cond)
+{
+ // Bit numbers of the condition flags in the CPSR.
+ enum APSRBits
+ {
+ APSR_N = 31,
+ APSR_Z = 30,
+ APSR_C = 29,
+ APSR_V = 28,
+ };
+
+// Return true if the given condition (C, N, Z or V) holds in the current context.
+#define GET_FLAG(_flag) \
+ ((m_registers->pCurrentContext->Cpsr & (1 << APSR_##_flag)) != 0)
+
+ switch (cond)
+ {
+ case 0: // EQ (Z==1)
+ return GET_FLAG(Z);
+ case 1: // NE (Z==0)
+ return !GET_FLAG(Z);
+ case 2: // CS (C==1)
+ return GET_FLAG(C);
+ case 3: // CC (C==0)
+ return !GET_FLAG(C);
+ case 4: // MI (N==1)
+ return GET_FLAG(N);
+ case 5: // PL (N==0)
+ return !GET_FLAG(N);
+ case 6: // VS (V==1)
+ return GET_FLAG(V);
+ case 7: // VC (V==0)
+ return !GET_FLAG(V);
+ case 8: // HI (C==1 && Z==0)
+ return GET_FLAG(C) && !GET_FLAG(Z);
+ case 9: // LS (C==0 || Z==1)
+ return !GET_FLAG(C) || GET_FLAG(Z);
+ case 10: // GE (N==V)
+ return GET_FLAG(N) == GET_FLAG(V);
+ case 11: // LT (N!=V)
+ return GET_FLAG(N) != GET_FLAG(V);
+ case 12: // GT (Z==0 && N==V)
+ return !GET_FLAG(Z) && (GET_FLAG(N) == GET_FLAG(V));
+ case 13: // LE (Z==1 || N!=V)
+ return GET_FLAG(Z) || (GET_FLAG(N) != GET_FLAG(V));
+ case 14: // AL
+ return true;
+ case 15:
+ _ASSERTE(!"Unsupported condition code: 15");
+ return false;
+ default:
+ UNREACHABLE();
+ return false;
+ }
+}
+
+#endif
diff --git a/src/debug/ee/arm/dbghelpers.S b/src/debug/ee/arm/dbghelpers.S
new file mode 100644
index 0000000000..85e20a6c0c
--- /dev/null
+++ b/src/debug/ee/arm/dbghelpers.S
@@ -0,0 +1,60 @@
+// 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 "unixasmmacros.inc"
+
+.syntax unified
+.thumb
+
+//
+// hijacking stub used to perform a func-eval, see Debugger::FuncEvalSetup() for use.
+//
+// on entry:
+// r0 : pointer to DebuggerEval object
+//
+
+NESTED_ENTRY FuncEvalHijack, _TEXT, UnhandledExceptionHandlerUnix
+
+ // push arg to the stack so our personality routine can find it
+ // push lr to get good stacktrace in debugger
+ push {r0,lr}
+
+ CHECK_STACK_ALIGNMENT
+
+ // FuncEvalHijackWorker returns the address we should jump to.
+ bl C_FUNC(FuncEvalHijackWorker)
+
+ // effective NOP to terminate unwind
+ mov r2, r2
+
+ free_stack 8
+ bx r0
+
+NESTED_END FuncEvalHijack, _TEXT
+
+//
+// This is the general purpose hijacking stub. DacDbiInterfaceImpl::Hijack() will
+// set the registers with the appropriate parameters from out-of-process.
+//
+// on entry:
+// r0 : pointer to CONTEXT
+// r1 : pointer to EXCEPTION_RECORD
+// r2 : EHijackReason
+// r3 : void* pdata
+//
+
+NESTED_ENTRY ExceptionHijack, _TEXT, UnhandledExceptionHandlerUnix
+
+ CHECK_STACK_ALIGNMENT
+
+ // make the call
+ bl C_FUNC(ExceptionHijackWorker)
+
+ // effective NOP to terminate unwind
+ mov r3, r3
+
+ // *** should never get here ***
+ EMIT_BREAKPOINT
+
+NESTED_END ExceptionHijackEnd, _TEXT
diff --git a/src/debug/ee/arm/dbghelpers.asm b/src/debug/ee/arm/dbghelpers.asm
new file mode 100644
index 0000000000..9a0d3c8b66
--- /dev/null
+++ b/src/debug/ee/arm/dbghelpers.asm
@@ -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.
+
+#include "ksarm.h"
+#include "asmconstants.h"
+
+ IMPORT FuncEvalHijackWorker
+ IMPORT FuncEvalHijackPersonalityRoutine
+ IMPORT ExceptionHijackWorker
+ IMPORT ExceptionHijackPersonalityRoutine
+ EXPORT ExceptionHijackEnd
+
+ MACRO
+ CHECK_STACK_ALIGNMENT
+
+#ifdef _DEBUG
+ push {r0}
+ add r0, sp, #4
+ tst r0, #7
+ pop {r0}
+ beq %0
+ EMIT_BREAKPOINT
+0
+#endif
+ MEND
+
+ TEXTAREA
+
+;
+; hijacking stub used to perform a func-eval, see Debugger::FuncEvalSetup() for use.
+;
+; on entry:
+; r0 : pointer to DebuggerEval object
+;
+
+ NESTED_ENTRY FuncEvalHijack,,FuncEvalHijackPersonalityRoutine
+
+ ; NOTE: FuncEvalHijackPersonalityRoutine is dependent on the stack layout so if
+ ; you change the prolog you will also need to update the personality routine.
+
+ ; push arg to the stack so our personality routine can find it
+ ; push lr to get good stacktrace in debugger
+ PROLOG_PUSH {r0,lr}
+
+ CHECK_STACK_ALIGNMENT
+
+ ; FuncEvalHijackWorker returns the address we should jump to.
+ bl FuncEvalHijackWorker
+
+ ; effective NOP to terminate unwind
+ mov r2, r2
+
+ EPILOG_STACK_FREE 8
+ EPILOG_BRANCH_REG r0
+
+ NESTED_END FuncEvalHijack
+
+;
+; This is the general purpose hijacking stub. DacDbiInterfaceImpl::Hijack() will
+; set the registers with the appropriate parameters from out-of-process.
+;
+; on entry:
+; r0 : pointer to CONTEXT
+; r1 : pointer to EXCEPTION_RECORD
+; r2 : EHijackReason
+; r3 : void* pdata
+;
+
+ NESTED_ENTRY ExceptionHijack,,ExceptionHijackPersonalityRoutine
+
+ CHECK_STACK_ALIGNMENT
+
+ ; make the call
+ bl ExceptionHijackWorker
+
+ ; effective NOP to terminate unwind
+ mov r3, r3
+
+ ; *** should never get here ***
+ EMIT_BREAKPOINT
+
+; exported label so the debugger knows where the end of this function is
+ExceptionHijackEnd
+ NESTED_END
+
+
+ ; must be at end of file
+ END
+
diff --git a/src/debug/ee/arm/primitives.cpp b/src/debug/ee/arm/primitives.cpp
new file mode 100644
index 0000000000..030b43136c
--- /dev/null
+++ b/src/debug/ee/arm/primitives.cpp
@@ -0,0 +1,37 @@
+// 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 "stdafx.h"
+#include "threads.h"
+#include "../../shared/arm/primitives.cpp"
+
+void CopyREGDISPLAY(REGDISPLAY* pDst, REGDISPLAY* pSrc)
+{
+ CONTEXT tmp;
+ CopyRegDisplay(pSrc, pDst, &tmp);
+}
+
+void SetSSFlag(DT_CONTEXT *, Thread *pThread)
+{
+ _ASSERTE(pThread != NULL);
+
+ pThread->EnableSingleStep();
+}
+
+void UnsetSSFlag(DT_CONTEXT *, Thread *pThread)
+{
+ _ASSERTE(pThread != NULL);
+
+ pThread->DisableSingleStep();
+}
+
+// Check if single stepping is enabled.
+bool IsSSFlagEnabled(DT_CONTEXT *, Thread *pThread)
+{
+ _ASSERTE(pThread != NULL);
+
+ return pThread->IsSingleStepEnabled();
+}
diff --git a/src/debug/ee/arm64/.gitmirror b/src/debug/ee/arm64/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/debug/ee/arm64/.gitmirror
@@ -0,0 +1 @@
+Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror. \ No newline at end of file
diff --git a/src/debug/ee/arm64/arm64walker.cpp b/src/debug/ee/arm64/arm64walker.cpp
new file mode 100644
index 0000000000..96aff1708f
--- /dev/null
+++ b/src/debug/ee/arm64/arm64walker.cpp
@@ -0,0 +1,476 @@
+// 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.
+//*****************************************************************************
+// File: Arm64walker.cpp
+//
+
+//
+// ARM64 instruction decoding/stepping logic
+//
+//*****************************************************************************
+
+#include "stdafx.h"
+#include "walker.h"
+#include "frames.h"
+#include "openum.h"
+
+#ifdef _TARGET_ARM64_
+
+PCODE Expand19bitoffset(PCODE opcode)
+{
+ opcode = opcode >> 5;
+ PCODE offset = (opcode & 0x7FFFF) << 2; //imm19:00 -> 21 bits
+
+ //Sign Extension
+ if ((offset & 0x100000)) //Check for 21'st bit
+ {
+ offset = offset | 0xFFFFFFFFFFE00000;
+ }
+ return offset;
+}
+
+void NativeWalker::Decode()
+{
+
+ PT_CONTEXT context = NULL;
+ int RegNum = -1;
+ PCODE offset = MAX_INSTRUCTION_LENGTH;
+
+ //Reset so that we do not provide bogus info
+ m_type = WALK_UNKNOWN;
+ m_skipIP = NULL;
+ m_nextIP = NULL;
+
+ if (m_registers == NULL)
+ {
+ //walker does not use WALK_NEXT
+ //Without registers decoding will work only for handful of instructions
+ return;
+ }
+
+ m_skipIP = m_ip + MAX_INSTRUCTION_LENGTH;
+
+ context = m_registers->pCurrentContext;
+ // Fetch first word of the current instruction.If the current instruction is a break instruction, we'll
+ // need to check the patch table to get the correct instruction.
+ PRD_TYPE opcode = CORDbgGetInstruction(m_ip);
+ PRD_TYPE unpatchedOpcode;
+ if (DebuggerController::CheckGetPatchedOpcode(m_ip, &unpatchedOpcode))
+ {
+ opcode = unpatchedOpcode;
+ }
+
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decode instruction at %p, opcode: %x\n", m_ip,opcode));
+
+
+
+ if (NativeWalker::DecodeCallInst(opcode, RegNum, m_type)) //Unconditional Branch (register) instructions
+ {
+ if (m_type == WALK_RETURN)
+ {
+ m_skipIP = NULL;
+ }
+ m_nextIP = (BYTE*)GetReg(context, RegNum);
+ return;
+ }
+
+
+ if (NativeWalker::DecodePCRelativeBranchInst(context, opcode, offset, m_type))
+ {
+ if (m_type == WALK_BRANCH)
+ {
+ m_skipIP = NULL;
+ }
+ }
+
+ m_nextIP = m_ip + offset;
+
+
+ return;
+}
+
+
+//When control reaches here m_pSharedPatchBypassBuffer has the original instructions in m_pSharedPatchBypassBuffer->PatchBypass
+BYTE* NativeWalker::SetupOrSimulateInstructionForPatchSkip(T_CONTEXT * context, SharedPatchBypassBuffer* m_pSharedPatchBypassBuffer, const BYTE *address, PRD_TYPE opcode)
+{
+
+ BYTE* patchBypass = m_pSharedPatchBypassBuffer->PatchBypass;
+ PCODE offset = 0;
+ PCODE ip = 0;
+ WALK_TYPE walk = WALK_UNKNOWN;
+ int RegNum =-1;
+
+
+ /*
+ Modify the patchBypass if the opcode is IP-relative, otherwise return it
+ The following are the instructions that are IP-relative :
+ • ADR and ADRP.
+ • The Load register (literal) instruction class.
+ • Direct branches that use an immediate offset.
+ • The unconditional branch with link instructions, BL and BLR, that use the PC to create the return link
+ address.
+ */
+
+ _ASSERTE((UINT_PTR)address == context->Pc);
+
+ if ((opcode & 0x1F000000) == 0x10000000) //ADR & ADRP
+ {
+
+ TADDR immhigh = ((opcode >> 5) & 0x007FFFF) << 2;
+ TADDR immlow = (opcode & 0x60000000) >> 29;
+ offset = immhigh | immlow; //ADR
+ RegNum = (opcode & 0x1F);
+
+ //Sign Extension
+ if ((offset & 0x100000)) //Check for 21'st bit
+ {
+ offset = offset | 0xFFFFFFFFFFE00000;
+ }
+
+ if ((opcode & 0x80000000) != 0) //ADRP
+ {
+ offset = offset << 12;
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate opcode: %x to ADRP X%d %p\n", opcode, RegNum, offset));
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate opcode: %x to ADR X%d %p\n", opcode, RegNum, offset));
+ }
+
+
+ }
+
+ else if ((opcode & 0x3B000000) == 0x18000000) //LDR Literal (General or SIMD)
+ {
+
+ offset = Expand19bitoffset(opcode);
+ RegNum = (opcode & 0x1F);
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate opcode: %x to LDR[SW] | PRFM X%d %p\n", opcode, RegNum, offset));
+ }
+ else if (NativeWalker::DecodePCRelativeBranchInst(context,opcode, offset, walk))
+ {
+ _ASSERTE(RegNum == -1);
+ }
+ else if (NativeWalker::DecodeCallInst(opcode, RegNum, walk))
+ {
+ _ASSERTE(offset == 0);
+ }
+ //else Just execute the opcodes as is
+ //{
+ //}
+
+ if (offset != 0) // calculate the next ip from current ip
+ {
+ ip = (PCODE)address + offset;
+ }
+ else if(RegNum >= 0)
+ {
+ ip = GetReg(context, RegNum);
+ }
+
+ //Do instruction emulation inplace here
+
+ if (walk == WALK_BRANCH || walk == WALK_CALL || walk == WALK_RETURN)
+ {
+ CORDbgSetInstruction((CORDB_ADDRESS_TYPE *)patchBypass, 0xd503201f); //Add Nop in buffer
+
+ m_pSharedPatchBypassBuffer->RipTargetFixup = ip; //Control Flow simulation alone is done DebuggerPatchSkip::TriggerExceptionHook
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate opcode: %x is a Control Flow instr \n", opcode));
+
+ if (walk == WALK_CALL) //initialize Lr
+ {
+ SetLR(context, (PCODE)address + MAX_INSTRUCTION_LENGTH);
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate opcode: %x is a Call instr, setting LR to %p \n", opcode,GetLR(context)));
+ }
+ }
+ else if(RegNum >= 0)
+ {
+ CORDbgSetInstruction((CORDB_ADDRESS_TYPE *)patchBypass, 0xd503201f); //Add Nop in buffer
+
+ PCODE RegContents;
+ if ((opcode & 0x3B000000) == 0x18000000) //LDR Literal
+ {
+ RegContents = (PCODE)GetMem(ip);
+ if ((opcode & 0x4000000)) //LDR literal for SIMD
+ {
+ NEON128 SimdRegContents;
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate opcode: %x to LDR V%d %p\n", opcode, RegNum, offset));
+ short opc = (opcode >> 30);
+ switch (opc)
+ {
+ case 0: //4byte data into St
+ RegContents = 0xFFFFFFFF & RegContents; //zero the upper 32bit
+ SetReg(context, RegNum, RegContents);
+ case 1: //8byte data into Dt
+ SetReg(context, RegNum, RegContents);
+ break;
+
+ case 2: //SIMD 16 byte data
+ SimdRegContents = GetSimdMem(ip);
+ SetSimdReg(context, RegNum, SimdRegContents);
+ break;
+ default:
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate Unknown opcode: %x [LDR(litera,SIMD &FP)] \n", opcode));
+ _ASSERTE(!("Arm64Walker::Simulated Unknown opcode"));
+
+ }
+ }
+ else
+ {
+ short opc = (opcode >> 30);
+ switch (opc)
+ {
+ case 0: //4byte data into Wt
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate opcode: %x to LDR W%d %p\n", opcode, RegNum, offset));
+ RegContents = 0xFFFFFFFF & RegContents; //zero the upper 32bits
+ SetReg(context, RegNum, RegContents);
+ break;
+
+ case 1: //8byte data into Xt
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate opcode: %x to LDR X%d %p\n", opcode, RegNum, offset));
+ SetReg(context, RegNum, RegContents);
+ break;
+
+ case 2: //LDRSW
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate opcode: %x to LDRSW X%d %p\n", opcode, RegNum, offset));
+ RegContents = 0xFFFFFFFF & RegContents;
+
+ if (RegContents & 0x80000000) //Sign extend the Word
+ {
+ RegContents = 0xFFFFFFFF00000000 | RegContents;
+ }
+ SetReg(context, RegNum, RegContents);
+ break;
+ case 3:
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate opcode: %x as PRFM ,but do nothing \n", opcode));
+
+ break;
+ default:
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate Unknown opcode: %x [LDR(literal)] \n", opcode));
+ _ASSERTE(!("Arm64Walker::Simulated Unknown opcode"));
+
+ }
+ }
+ }
+ else
+ {
+ RegContents = ip;
+ SetReg(context, RegNum, RegContents);
+ }
+
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate opcode: %x to update Reg X[V]%d, as %p \n", opcode, RegNum, GetReg(context, RegNum)));
+ }
+ //else Just execute the opcodes as IS
+ //{
+ //}
+
+ return patchBypass;
+}
+
+//Decodes PC Relative Branch Instructions
+//This code is shared between the NativeWalker and DebuggerPatchSkip.
+//So ENSURE THIS FUNCTION DOES NOT CHANGE ANY STATE OF THE DEBUGEE
+//This Function Decodes :
+// BL offset
+// B offset
+// B.Cond offset
+// CB[N]Z X<r> offset
+// TB[N]Z X<r> offset
+
+//Output of the Function are:
+//offset - Offset from current PC to which control will go next
+//WALK_TYPE
+
+BOOL NativeWalker::DecodePCRelativeBranchInst(PT_CONTEXT context, const PRD_TYPE& opcode, PCODE& offset, WALK_TYPE& walk)
+{
+#ifdef _DEBUG
+ PCODE incomingoffset = offset;
+ WALK_TYPE incomingwalk = walk;
+#endif
+
+ if ((opcode & 0x7C000000) == 0x14000000) // Decode B & BL
+ {
+ offset = (opcode & 0x03FFFFFF) << 2;
+ // Sign extension
+ if ((offset & 0x4000000)) //Check for 26'st bit
+ {
+ offset = offset | 0xFFFFFFFFF8000000;
+ }
+
+ if ((opcode & 0x80000000) != 0) //BL
+ {
+ walk = WALK_CALL;
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to BL %p \n", opcode, offset));
+ }
+ else
+ {
+ walk = WALK_BRANCH; //B
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to B %p \n", opcode, offset));
+ }
+ return TRUE;
+ }
+
+ //Conditional Branches
+ _ASSERTE(context != NULL);
+
+
+ if ((opcode & 0xFF000010) == 0x54000000) // B.cond
+ {
+ WORD cond = opcode & 0xF;
+ bool result = false;
+ switch (cond >> 1)
+ {
+ case 0x0: result = (context->Cpsr & NZCV_Z) != 0; // EQ or NE
+ break;
+ case 0x1: result = (context->Cpsr & NZCV_C) != 0; // CS or CC
+ break;
+ case 0x2: result = (context->Cpsr & NZCV_N) != 0; // MI or PL
+ break;
+ case 0x3: result = (context->Cpsr & NZCV_V) != 0; // VS or VC
+ break;
+ case 0x4: result = ((context->Cpsr & NZCV_C) != 0) && ((context->Cpsr & NZCV_Z) == 0); // HI or LS
+ break;
+ case 0x5: result = ((context->Cpsr & NZCV_N) >> NZCV_N_BIT) == ((context->Cpsr & NZCV_V) >> NZCV_V_BIT); // GE or LT
+ break;
+ case 0x6: result = ((context->Cpsr & NZCV_N) >> NZCV_N_BIT) == ((context->Cpsr & NZCV_V) >> NZCV_V_BIT) && ((context->Cpsr & NZCV_Z) == 0); // GT or LE
+ break;
+ case 0x7: result = true; // AL
+ break;
+ }
+
+ if ((cond & 0x1) && (cond & 0xF) != 0) { result = !result; }
+
+ if (result)
+ {
+ walk = WALK_BRANCH;
+ offset = Expand19bitoffset(opcode);
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to B.cond %p \n", opcode, offset));
+ }
+ else // NOP
+ {
+ walk = WALK_UNKNOWN;
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to B.cond but evaluated as NOP \n", opcode));
+ offset = MAX_INSTRUCTION_LENGTH;
+ }
+
+ return TRUE;
+
+ }
+
+
+ int RegNum = opcode & 0x1F;
+ PCODE RegContent = GetReg(context, RegNum);
+
+ if ((opcode & 0xFE000000) == 0x34000000) // CBNZ || CBZ
+ {
+ bool result = false;
+
+ if (!(opcode & 0x80000000)) //if sf == '1' the 64 else 32
+ {
+ RegContent = 0xFFFFFFFF & RegContent; //zero the upper 32bit
+ }
+
+ if (opcode & 0x01000000) //CBNZ
+ {
+ result = RegContent != 0;
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to CBNZ X%d \n", opcode, RegNum));
+ }
+ else //CBZ
+ {
+ result = RegContent == 0;
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to CBZ X%d \n", opcode, RegNum));
+ }
+
+ if (result)
+ {
+ walk = WALK_BRANCH;
+ offset = Expand19bitoffset(opcode);
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to CB[N]Z X%d %p \n", opcode, RegNum, offset));
+ }
+ else // NOP
+ {
+ walk = WALK_UNKNOWN;
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to B.cond but evaluated as NOP \n", opcode));
+ offset = MAX_INSTRUCTION_LENGTH;
+ }
+
+
+ return TRUE;
+ }
+ if ((opcode & 0x7E000000) == 0x36000000) // TBNZ || TBZ
+ {
+ bool result = false;
+ int bit_pos = ((opcode >> 19) & 0x1F);
+
+ if (opcode & 0x80000000)
+ {
+ bit_pos = bit_pos + 32;
+ }
+
+ PCODE bit_val = 1 << bit_pos;
+ if (opcode & 0x01000000) //TBNZ
+ {
+ result = (RegContent & bit_val) != 0;
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to TBNZ X%d \n", opcode, RegNum));
+ }
+ else //TBZ
+ {
+ result = (RegContent & bit_val) == 0;
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to CB[N]Z X%d \n", opcode, RegNum));
+ }
+ if (result)
+ {
+ walk = WALK_BRANCH;
+ offset = ((opcode >> 5) & 0x3FFF) << 2; //imm14:00 -> 16 bits
+ if (offset & 0x8000) //sign extension check for 16'th bit
+ {
+ offset = offset | 0xFFFFFFFFFFFF0000;
+ }
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to TB[N]Z X%d %p \n", opcode, RegNum, offset));
+ }
+ else // NOP
+ {
+ walk = WALK_UNKNOWN;
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to B.cond but evaluated as NOP \n", opcode));
+ offset = MAX_INSTRUCTION_LENGTH;
+ }
+
+ return TRUE;
+ }
+
+ _ASSERTE(offset == incomingoffset);
+ _ASSERTE(walk == incomingwalk);
+ return FALSE;
+}
+
+BOOL NativeWalker::DecodeCallInst(const PRD_TYPE& opcode, int& RegNum, WALK_TYPE& walk)
+{
+ if ((opcode & 0xFF9FFC1F) == 0xD61F0000) // BR, BLR or RET -Unconditional Branch (register) instructions
+ {
+
+ RegNum = (opcode & 0x3E0) >> 5;
+
+
+ short op = (opcode & 0x00600000) >> 21; //Checking for 23 and 22 bits
+ switch (op)
+ {
+ case 0: LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to BR X%d\n", opcode, RegNum));
+ walk = WALK_BRANCH;
+ break;
+ case 1: LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to BLR X%d\n", opcode, RegNum));
+ walk = WALK_CALL;
+ break;
+ case 2: LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Decoded opcode: %x to Ret X%d\n", opcode, RegNum));
+ walk = WALK_RETURN;
+ break;
+ default:
+ LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate Unknown opcode: %x [Branch] \n", opcode));
+ _ASSERTE(!("Arm64Walker::Decoded Unknown opcode"));
+ }
+
+ return TRUE;
+ }
+ return FALSE;
+}
+#endif
diff --git a/src/debug/ee/arm64/dbghelpers.asm b/src/debug/ee/arm64/dbghelpers.asm
new file mode 100644
index 0000000000..ded1a0d184
--- /dev/null
+++ b/src/debug/ee/arm64/dbghelpers.asm
@@ -0,0 +1,54 @@
+; 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 "ksarm64.h"
+#include "asmconstants.h"
+#include "asmmacros.h"
+
+ IMPORT FuncEvalHijackWorker
+ IMPORT FuncEvalHijackPersonalityRoutine
+ IMPORT ExceptionHijackWorker
+ IMPORT ExceptionHijackPersonalityRoutine
+ EXPORT ExceptionHijackEnd
+;
+; hijacking stub used to perform a func-eval, see Debugger::FuncEvalSetup() for use.
+;
+; on entry:
+; x0 : pointer to DebuggerEval object
+;
+
+ NESTED_ENTRY FuncEvalHijack,,FuncEvalHijackPersonalityRoutine
+
+ ; NOTE: FuncEvalHijackPersonalityRoutine is dependent on the stack layout so if
+ ; you change the prolog you will also need to update the personality routine.
+
+ ; push arg to the stack so our personality routine can find it
+ ; push lr to get good stacktrace in debugger
+ PROLOG_SAVE_REG_PAIR fp, lr, #-32!
+ str x0, [sp, #16]
+ ; FuncEvalHijackWorker returns the address we should jump to.
+ bl FuncEvalHijackWorker
+
+ EPILOG_STACK_FREE 32
+ EPILOG_BRANCH_REG x0
+ NESTED_END FuncEvalHijack
+
+ NESTED_ENTRY ExceptionHijack,,ExceptionHijackPersonalityRoutine
+
+ ; make the call
+ bl ExceptionHijackWorker
+
+ ; effective NOP to terminate unwind
+ mov x3, x3
+
+ ; *** should never get here ***
+ EMIT_BREAKPOINT
+
+; exported label so the debugger knows where the end of this function is
+ExceptionHijackEnd
+ NESTED_END ExceptionHijack
+
+ ; must be at end of file
+ END
+
diff --git a/src/debug/ee/arm64/primitives.cpp b/src/debug/ee/arm64/primitives.cpp
new file mode 100644
index 0000000000..6895f784c5
--- /dev/null
+++ b/src/debug/ee/arm64/primitives.cpp
@@ -0,0 +1,15 @@
+// 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 "stdafx.h"
+#include "threads.h"
+#include "../../shared/arm64/primitives.cpp"
+
+void CopyREGDISPLAY(REGDISPLAY* pDst, REGDISPLAY* pSrc)
+{
+ CONTEXT tmp;
+ CopyRegDisplay(pSrc, pDst, &tmp);
+}
diff --git a/src/debug/ee/canary.cpp b/src/debug/ee/canary.cpp
new file mode 100644
index 0000000000..03090583fc
--- /dev/null
+++ b/src/debug/ee/canary.cpp
@@ -0,0 +1,324 @@
+// 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.
+//*****************************************************************************
+// File: Canary.cpp
+//
+
+//
+// Canary for debugger helper thread. This will sniff out if it's safe to take locks.
+//
+//*****************************************************************************
+
+#include "stdafx.h"
+
+
+//-----------------------------------------------------------------------------
+// Ctor for HelperCanary class
+//-----------------------------------------------------------------------------
+HelperCanary::HelperCanary()
+{
+ m_hCanaryThread = NULL;
+ m_CanaryThreadId = 0;
+ m_RequestCounter = 0;
+ m_AnswerCounter = 0;
+ m_fStop = false;
+
+ m_fCachedValid = false;
+ m_fCachedAnswer = false;
+ m_initialized = false;
+}
+
+//-----------------------------------------------------------------------------
+// Dtor for class
+//-----------------------------------------------------------------------------
+HelperCanary::~HelperCanary()
+{
+ // Since we're deleting this memory, we need to kill the canary thread.
+ m_fStop = true;
+ SetEvent(m_hPingEvent);
+
+ // m_hPingEvent dtor will close handle
+ WaitForSingleObject(m_hCanaryThread, INFINITE);
+}
+
+//-----------------------------------------------------------------------------
+// Clear the cached value for AreLocksAvailable();
+//-----------------------------------------------------------------------------
+void HelperCanary::ClearCache()
+{
+ _ASSERTE(ThisIsHelperThreadWorker());
+ m_fCachedValid = false;
+}
+
+//-----------------------------------------------------------------------------
+// The helper thread can call this to determine if it can safely take a certain
+// set of locks (mainly the heap lock(s)). The canary thread will go off and
+// try and take these and report back to the helper w/o ever blocking the
+// helper.
+//
+// Returns 'true' if it's safe for helper to take locks; else false.
+// We err on the side of safety (returning false).
+//-----------------------------------------------------------------------------
+bool HelperCanary::AreLocksAvailable()
+{
+ // If we're not on the helper thread, then we're guaranteed safe.
+ // We check this to support MaybeHelperThread code.
+ if (!ThisIsHelperThreadWorker())
+ {
+ return true;
+ }
+
+ if (m_fCachedValid)
+ {
+ return m_fCachedAnswer;
+ }
+
+ // Cache the answer.
+ m_fCachedAnswer = AreLocksAvailableWorker();
+ m_fCachedValid = true;
+
+#ifdef _DEBUG
+ // For managed-only debugging, we should always be safe.
+ if (!g_pRCThread->GetDCB()->m_rightSideIsWin32Debugger)
+ {
+ _ASSERTE(m_fCachedAnswer || !"Canary returned false in Managed-debugger");
+ }
+
+ // For debug, nice to be able to enable an assert that tells us if this situation is actually happening.
+ if (!m_fCachedAnswer)
+ {
+ static BOOL shouldBreak = -1;
+ if (shouldBreak == -1)
+ {
+ shouldBreak = UnsafeGetConfigDWORD(CLRConfig::INTERNAL_DbgBreakIfLocksUnavailable);
+ }
+ if (shouldBreak)
+ {
+ _ASSERTE(!"Potential deadlock detected.\nLocks that the helper thread may need are currently held by other threads.");
+ }
+ }
+#endif // _DEBUG
+
+ return m_fCachedAnswer;
+}
+
+//-----------------------------------------------------------------------------
+// Creates the canary thread and signaling events.
+//-----------------------------------------------------------------------------
+void HelperCanary::Init()
+{
+ // You can only run the init code once. The debugger attempts to lazy-init
+ // the canary at several points but if the canary is already inited then
+ // we just eagerly return. See issue 841005 for more details.
+ if(m_initialized)
+ {
+ return;
+ }
+ else
+ {
+ m_initialized = true;
+ }
+
+ m_hPingEvent = WszCreateEvent(NULL, (BOOL) kAutoResetEvent, FALSE, NULL);
+ if (m_hPingEvent == NULL)
+ {
+ STRESS_LOG1(LF_CORDB, LL_ALWAYS, "Canary failed to create ping event. gle=%d\n", GetLastError());
+ // in the past if we failed to start the thread we just assumed it was unsafe
+ // so I am preserving that behavior. However I am going to assert that this
+ // doesn't really happen
+ _ASSERTE(!"Canary failed to create ping event");
+ return;
+ }
+
+ m_hWaitEvent = WszCreateEvent(NULL, (BOOL) kManualResetEvent, FALSE, NULL);
+ if (m_hWaitEvent == NULL)
+ {
+ STRESS_LOG1(LF_CORDB, LL_ALWAYS, "Canary failed to create wait event. gle=%d\n", GetLastError());
+ // in the past if we failed to start the thread we just assumed it was unsafe
+ // so I am preserving that behavior. However I am going to assert that this
+ // doesn't really happen
+ _ASSERTE(!"Canary failed to create wait event");
+ return;
+ }
+
+ // Spin up the canary. This will call dllmain, but that's ok because it just
+ // degenerates to our timeout case.
+ const DWORD flags = CREATE_SUSPENDED;
+ m_hCanaryThread = CreateThread(NULL, 0,
+ HelperCanary::ThreadProc, this,
+ flags, &m_CanaryThreadId);
+
+ // in the past if we failed to start the thread we just assumed it was unsafe
+ // so I am preserving that behavior. However I am going to assert that this
+ // doesn't really happen
+ if(m_hCanaryThread == NULL)
+ {
+ _ASSERTE(!"CreateThread() failed to create Canary thread");
+ return;
+ }
+
+ // Capture the Canary thread's TID so that the RS can mark it as a can't-stop region.
+ // This is essential so that the RS doesn't view it as some external thread to be suspended when we hit
+ // debug events.
+ _ASSERTE(g_pRCThread != NULL);
+ g_pRCThread->GetDCB()->m_CanaryThreadId = m_CanaryThreadId;
+
+ ResumeThread(m_hCanaryThread);
+}
+
+
+//-----------------------------------------------------------------------------
+// Does real work for AreLocksAvailable(), minus caching.
+//-----------------------------------------------------------------------------
+bool HelperCanary::AreLocksAvailableWorker()
+{
+#if _DEBUG
+ // For debugging, allow a way to force the canary to fail, and thus test our
+ // failure paths.
+ static BOOL fShortcut= -1;
+ if (fShortcut == -1)
+ {
+ fShortcut = UnsafeGetConfigDWORD(CLRConfig::INTERNAL_DbgShortcutCanary);
+ }
+ if (fShortcut == 1)
+ {
+ return false;
+ }
+ if (fShortcut == 2)
+ {
+ return true;
+ }
+#endif
+
+ // We used to do lazy init but that is dangerous... CreateThread
+ // allocates some memory which can block on a lock, exactly the
+ // situation we are attempting to detect and not block on.
+ // Instead we spin up the canary in advance and if that failed then
+ // assume unsafe
+ if(m_CanaryThreadId == 0)
+ {
+ _ASSERTE(!"We shouldn't be lazy initing the canary anymore");
+ return false;
+ }
+
+ // Canary will take the locks of interest and then set the Answer counter equal to our request counter.
+ m_RequestCounter = m_RequestCounter + 1;
+ ResetEvent(m_hWaitEvent);
+ SetEvent(m_hPingEvent);
+
+ // Spin waiting for answer. If canary gets back to us, then the locks must be free and so it's safe for helper-thread.
+ // If we timeout, then we err on the side of safety and assume canary blocked on a lock and so it's not safe
+ // for the helper thread to take those locks.
+ // We explicitly have a simple spin-wait instead of using win32 events because we want something simple and
+ // provably correct. Since we already need the spin-wait for the counters, adding an extra win32 event
+ // to get rid of the sleep would be additional complexity and race windows without a clear benefit.
+
+ // We need to track what iteration of "AreLocksAvailable" the helper is on. Say canary sniffs two locks, now Imagine if:
+ // 1) Helper calls AreLocksAvailable,
+ // 2) the canary does get blocked on lock #1,
+ // 3) process resumes, canary now gets + releases lock #1,
+ // 4) another random thread takes lock #1
+ // 5) then helper calls AreLocksAvailable again later
+ // 6) then the canary finally finishes. Note it's never tested lock #1 on the 2nd iteration.
+ // We don't want the canary's response initiated from the 1st request to impact the Helper's 2nd request.
+ // Thus we keep a request / answer counter to make sure that the canary tests all locks on the same iteration.
+ DWORD retry = 0;
+
+ const DWORD msSleepSteadyState = 150; // sleep time in ms
+ const DWORD maxRetry = 15; // number of times to try.
+ DWORD msSleep = 80; // how much to sleep on first iteration.
+
+ while(m_RequestCounter != m_AnswerCounter)
+ {
+ retry ++;
+ if (retry > maxRetry)
+ {
+ STRESS_LOG0(LF_CORDB, LL_ALWAYS, "Canary timed out!\n");
+ return false;
+ }
+
+ // We'll either timeout (in which case it's like a Sleep(), or
+ // get the event, which shortcuts the sleep.
+ WaitForSingleObject(m_hWaitEvent, msSleep);
+
+ // In case a stale answer sets the wait event high, reset it now to avoid us doing
+ // a live spin-lock.
+ ResetEvent(m_hWaitEvent);
+
+
+ msSleep = msSleepSteadyState;
+ }
+
+ // Canary made it on same Request iteration, so it must be safe!
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Real OS thread proc for Canary thread.
+// param - 'this' pointer for HelperCanary
+// return value - meaningless, but threads need to return something.
+//-----------------------------------------------------------------------------
+DWORD HelperCanary::ThreadProc(LPVOID param)
+{
+ _ASSERTE(!ThisIsHelperThreadWorker());
+
+ STRESS_LOG0(LF_CORDB, LL_ALWAYS, "Canary thread spun up\n");
+ HelperCanary * pThis = reinterpret_cast<HelperCanary*> (param);
+ pThis->ThreadProc();
+ _ASSERTE(pThis->m_fStop);
+ STRESS_LOG0(LF_CORDB, LL_ALWAYS, "Canary thread exiting\n");
+
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+// Real implementation of Canary Thread.
+// Single canary thread is reused after creation.
+//-----------------------------------------------------------------------------
+void HelperCanary::ThreadProc()
+{
+ _ASSERTE(m_CanaryThreadId == GetCurrentThreadId());
+
+ while(true)
+ {
+ WaitForSingleObject(m_hPingEvent, INFINITE);
+
+ m_AnswerCounter = 0;
+ DWORD dwRequest = m_RequestCounter;
+
+ if (m_fStop)
+ {
+ return;
+ }
+ STRESS_LOG2(LF_CORDB, LL_ALWAYS, "stage:%d,req:%d", 0, dwRequest);
+
+ // Now take the locks of interest. This could block indefinitely. If this blocks, we may even get multiple requests.
+ TakeLocks();
+
+ m_AnswerCounter = dwRequest;
+
+ // Set wait event to let Requesting thread shortcut its spin lock. This is purely an
+ // optimization because requesting thread will still check Answer/Request counters.
+ // That protects us from recyling bugs.
+ SetEvent(m_hWaitEvent);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Try and take locks.
+//-----------------------------------------------------------------------------
+void HelperCanary::TakeLocks()
+{
+ _ASSERTE(::GetThread() == NULL); // Canary Thread should always be outside the runtime.
+ _ASSERTE(m_CanaryThreadId == GetCurrentThreadId());
+
+ // Call new, which will take whatever standard heap locks there are.
+ // We don't care about what memory we get; we just want to take the heap lock(s).
+ DWORD * p = new (nothrow) DWORD();
+ delete p;
+
+ STRESS_LOG1(LF_CORDB, LL_ALWAYS, "canary stage:%d\n", 1);
+}
+
+
diff --git a/src/debug/ee/canary.h b/src/debug/ee/canary.h
new file mode 100644
index 0000000000..20d777ffa8
--- /dev/null
+++ b/src/debug/ee/canary.h
@@ -0,0 +1,80 @@
+// 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.
+//*****************************************************************************
+// File: Canary.h
+//
+
+//
+// Header file Debugger Canary
+//
+//*****************************************************************************
+
+#ifndef CANARY_H
+#define CANARY_H
+
+//-----------------------------------------------------------------------------
+// Canary.
+//
+// The helper thread needs to be very careful about what locks it takes. If it takes a lock
+// held by a suspended thread, then the whole process deadlocks (Since the suspended thread
+// is waiting for the helper to resume it).
+// In general, we try to avoid having the helper take such locks, but the problem is unsolvable
+// because:
+// - we don't know what that set of locks are (eg, OS apis may take new locks between versions)
+// - the helper may call into the EE and that takes unsafe locks.
+// The most prominent dangerous lock is the heap lock, which is why we have the "InteropSafe" heap.
+// Since we don't even know what locks are bad (eg, we can't actually find the Heaplock), we can't
+// explicitly check if the lock is safe to take.
+// So we spin up an auxiallary "Canary" thread which can sniff for locks that the helper thread will
+// need to take. Thus the helper thread can find out if the locks are available without actually taking them.
+// The "Canary" can call APIs that take the locks (such as regular "new" for the process heap lock).
+// The helper will wait on the canary with timeout. If the canary returns, the helper knows it's
+// safe to take the locks. If the canary times out, then the helper assumes it's blocked on the
+// locks and thus not safe for the helper to take them.
+//-----------------------------------------------------------------------------
+class HelperCanary
+{
+public:
+ HelperCanary();
+ ~HelperCanary();
+
+ void Init();
+ bool AreLocksAvailable();
+ void ClearCache();
+
+protected:
+ static DWORD WINAPI ThreadProc(LPVOID param);
+ void ThreadProc();
+ void TakeLocks();
+ bool AreLocksAvailableWorker();
+
+ // Flag to tell Canary thread to exit.
+ bool m_fStop;
+
+ // Flag to indicate Init has been run
+ bool m_initialized;
+
+ // Cache the answers between stops so that we don't have to ping the canary every time.
+ bool m_fCachedValid;
+ bool m_fCachedAnswer;
+
+ HANDLE m_hCanaryThread; // handle for canary thread
+ DWORD m_CanaryThreadId; // canary thread OS Thread ID
+
+ // These counters are read + written by both helper and canary thread.
+ // These need to be volatile because of how they're being accessed from different threads.
+ // However, since each is only read from 1 thread, and written by another, and the WFSO/SetEvent
+ // will give us a memory barrier, and we have a flexible polling operation, volatile is
+ // sufficient to deal with memory barrier issues.
+ Volatile<DWORD> m_RequestCounter;
+ Volatile<DWORD> m_AnswerCounter;
+ HandleHolder m_hPingEvent;
+
+ // We use a Manual wait event to replace Sleep.
+ HandleHolder m_hWaitEvent;
+};
+
+
+#endif // CANARY_H
+
diff --git a/src/debug/ee/controller.cpp b/src/debug/ee/controller.cpp
new file mode 100644
index 0000000000..7f4d44568d
--- /dev/null
+++ b/src/debug/ee/controller.cpp
@@ -0,0 +1,8892 @@
+// 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.
+
+// ==++==
+//
+
+//
+// ==--==
+// ****************************************************************************
+// File: controller.cpp
+//
+
+//
+// controller.cpp: Debugger execution control routines
+//
+// ****************************************************************************
+// Putting code & #includes, #defines, etc, before the stdafx.h will
+// cause the code,etc, to be silently ignored
+#include "stdafx.h"
+#include "openum.h"
+#include "../inc/common.h"
+#include "eeconfig.h"
+
+#include "../../vm/methoditer.h"
+
+const char *GetTType( TraceType tt);
+
+#define IsSingleStep(exception) (exception == EXCEPTION_SINGLE_STEP)
+
+
+
+
+
+// -------------------------------------------------------------------------
+// DebuggerController routines
+// -------------------------------------------------------------------------
+
+SPTR_IMPL_INIT(DebuggerPatchTable, DebuggerController, g_patches, NULL);
+SVAL_IMPL_INIT(BOOL, DebuggerController, g_patchTableValid, FALSE);
+
+#if !defined(DACCESS_COMPILE)
+
+DebuggerController *DebuggerController::g_controllers = NULL;
+DebuggerControllerPage *DebuggerController::g_protections = NULL;
+CrstStatic DebuggerController::g_criticalSection;
+int DebuggerController::g_cTotalMethodEnter = 0;
+
+
+// Is this patch at a position at which it's safe to take a stack?
+bool DebuggerControllerPatch::IsSafeForStackTrace()
+{
+ LIMITED_METHOD_CONTRACT;
+
+ TraceType tt = this->trace.GetTraceType();
+ Module *module = this->key.module;
+ BOOL managed = this->IsManagedPatch();
+
+ // Patches placed by MgrPush can come at lots of illegal spots. Can't take a stack trace here.
+ if ((module == NULL) && managed && (tt == TRACE_MGR_PUSH))
+ {
+ return false;
+ }
+
+ // Consider everything else legal.
+ // This is a little shady for TRACE_FRAME_PUSH. But TraceFrame() needs a stackInfo
+ // to get a RegDisplay (though almost nobody uses it, so perhaps it could be removed).
+ return true;
+
+}
+
+#ifndef _TARGET_ARM_
+// returns a pointer to the shared buffer. each call will AddRef() the object
+// before returning it so callers only need to Release() when they're finished with it.
+SharedPatchBypassBuffer* DebuggerControllerPatch::GetOrCreateSharedPatchBypassBuffer()
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ if (m_pSharedPatchBypassBuffer == NULL)
+ {
+ m_pSharedPatchBypassBuffer = new (interopsafeEXEC) SharedPatchBypassBuffer();
+ _ASSERTE(m_pSharedPatchBypassBuffer);
+ TRACE_ALLOC(m_pSharedPatchBypassBuffer);
+ }
+
+ m_pSharedPatchBypassBuffer->AddRef();
+
+ return m_pSharedPatchBypassBuffer;
+}
+#endif // _TARGET_ARM_
+
+// @todo - remove all this splicing trash
+// This Sort/Splice stuff just reorders the patches within a particular chain such
+// that when we iterate through by calling GetPatch() and GetNextPatch(DebuggerControllerPatch),
+// we'll get patches in increasing order of DebuggerControllerTypes.
+// Practically, this means that calling GetPatch() will return EnC patches before stepping patches.
+//
+#if 1
+void DebuggerPatchTable::SortPatchIntoPatchList(DebuggerControllerPatch **ppPatch)
+{
+ LOG((LF_CORDB, LL_EVERYTHING, "DPT::SPIPL called.\n"));
+#ifdef _DEBUG
+ DebuggerControllerPatch *patchFirst
+ = (DebuggerControllerPatch *) Find(Hash((*ppPatch)), Key((*ppPatch)));
+ _ASSERTE(patchFirst == (*ppPatch));
+ _ASSERTE((*ppPatch)->controller->GetDCType() != DEBUGGER_CONTROLLER_STATIC);
+#endif //_DEBUG
+ DebuggerControllerPatch *patchNext = GetNextPatch((*ppPatch));
+LOG((LF_CORDB, LL_EVERYTHING, "DPT::SPIPL GetNextPatch passed\n"));
+ //List contains one, (sorted) element
+ if (patchNext == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO10000,
+ "DPT::SPIPL: Patch 0x%x is a sorted singleton\n", (*ppPatch)));
+ return;
+ }
+
+ // If we decide to reorder the list, we'll need to keep the element
+ // indexed by the hash function as the (sorted)first item. Everything else
+ // chains off this element, can can thus stay put.
+ // Thus, either the element we just added is already sorted, or else we'll
+ // have to move it elsewhere in the list, meaning that we'll have to swap
+ // the second item & the new item, so that the index points to the proper
+ // first item in the list.
+
+ //use Cur ptr for case where patch gets appended to list
+ DebuggerControllerPatch *patchCur = patchNext;
+
+ while (patchNext != NULL &&
+ ((*ppPatch)->controller->GetDCType() >
+ patchNext->controller->GetDCType()) )
+ {
+ patchCur = patchNext;
+ patchNext = GetNextPatch(patchNext);
+ }
+
+ if (patchNext == GetNextPatch((*ppPatch)))
+ {
+ LOG((LF_CORDB, LL_INFO10000,
+ "DPT::SPIPL: Patch 0x%x is already sorted\n", (*ppPatch)));
+ return; //already sorted
+ }
+
+ LOG((LF_CORDB, LL_INFO10000,
+ "DPT::SPIPL: Patch 0x%x will be moved \n", (*ppPatch)));
+
+ //remove it from the list
+ SpliceOutOfList((*ppPatch));
+
+ // the kinda neat thing is: since we put it originally at the front of the list,
+ // and it's not in order, then it must be behind another element of this list,
+ // so we don't have to write any 'SpliceInFrontOf' code.
+
+ _ASSERTE(patchCur != NULL);
+ SpliceInBackOf((*ppPatch), patchCur);
+
+ LOG((LF_CORDB, LL_INFO10000,
+ "DPT::SPIPL: Patch 0x%x is now sorted\n", (*ppPatch)));
+}
+
+// This can leave the list empty, so don't do this unless you put
+// the patch back somewhere else.
+void DebuggerPatchTable::SpliceOutOfList(DebuggerControllerPatch *patch)
+{
+ // We need to get iHash, the index of the ptr within
+ // m_piBuckets, ie it's entry in the hashtable.
+ ULONG iHash = Hash(patch) % m_iBuckets;
+ ULONG iElement = m_piBuckets[iHash];
+ DebuggerControllerPatch *patchFirst
+ = (DebuggerControllerPatch *) EntryPtr(iElement);
+
+ // Fix up pointers to chain
+ if (patchFirst == patch)
+ {
+ // The first patch shouldn't have anything behind it.
+ _ASSERTE(patch->entry.iPrev == DPT_INVALID_SLOT);
+
+ if (patch->entry.iNext != DPT_INVALID_SLOT)
+ {
+ m_piBuckets[iHash] = patch->entry.iNext;
+ }
+ else
+ {
+ m_piBuckets[iHash] = DPT_INVALID_SLOT;
+ }
+ }
+
+ if (patch->entry.iNext != DPT_INVALID_SLOT)
+ {
+ EntryPtr(patch->entry.iNext)->iPrev = patch->entry.iPrev;
+ }
+
+ if (patch->entry.iPrev != DPT_INVALID_SLOT)
+ {
+ EntryPtr(patch->entry.iNext)->iNext = patch->entry.iNext;
+ }
+
+ patch->entry.iNext = DPT_INVALID_SLOT;
+ patch->entry.iPrev = DPT_INVALID_SLOT;
+}
+
+void DebuggerPatchTable::SpliceInBackOf(DebuggerControllerPatch *patchAppend,
+ DebuggerControllerPatch *patchEnd)
+{
+ ULONG iAppend = ItemIndex((HASHENTRY*)patchAppend);
+ ULONG iEnd = ItemIndex((HASHENTRY*)patchEnd);
+
+ patchAppend->entry.iPrev = iEnd;
+ patchAppend->entry.iNext = patchEnd->entry.iNext;
+
+ if (patchAppend->entry.iNext != DPT_INVALID_SLOT)
+ EntryPtr(patchAppend->entry.iNext)->iPrev = iAppend;
+
+ patchEnd->entry.iNext = iAppend;
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// Stack safety rules.
+// In general, we're safe to crawl whenever we're in preemptive mode.
+// We're also must be safe at any spot the thread could get synchronized,
+// because that means that the thread will be stopped to let the debugger shell
+// inspect it and that can definitely take stack traces.
+// Basically the only unsafe spot is in the middle of goofy stub with some
+// partially constructed frame while in coop mode.
+//-----------------------------------------------------------------------------
+
+// Safe if we're at certain types of patches.
+// See Patch::IsSafeForStackTrace for details.
+StackTraceTicket::StackTraceTicket(DebuggerControllerPatch * patch)
+{
+ _ASSERTE(patch != NULL);
+ _ASSERTE(patch->IsSafeForStackTrace());
+}
+
+// Safe if there was already another stack trace at this spot. (Grandfather clause)
+// This is commonly used for StepOut, which takes runs stacktraces to crawl up
+// the stack to find a place to patch.
+StackTraceTicket::StackTraceTicket(ControllerStackInfo * info)
+{
+ _ASSERTE(info != NULL);
+
+ // Ensure that the other stack info object actually executed (and thus was
+ // actually valid).
+ _ASSERTE(info->m_dbgExecuted);
+}
+
+// Safe b/c the context shows we're in native managed code.
+// This must be safe because we could always set a managed breakpoint by native
+// offset and thus synchronize the shell at this spot. So this is
+// a specific example of the Synchronized case. The fact that we don't actually
+// synchronize doesn't make us any less safe.
+StackTraceTicket::StackTraceTicket(const BYTE * ip)
+{
+ _ASSERTE(g_pEEInterface->IsManagedNativeCode(ip));
+}
+
+// Safe it we're at a Synchronized point point.
+StackTraceTicket::StackTraceTicket(Thread * pThread)
+{
+ _ASSERTE(pThread != NULL);
+
+ // If we're synchronized, the debugger should be stopped.
+ // That means all threads are synced and must be safe to take a stacktrace.
+ // Thus we don't even need to do a thread-specific check.
+ _ASSERTE(g_pDebugger->IsStopped());
+}
+
+// DebuggerUserBreakpoint has a special case of safety. See that ctor for details.
+StackTraceTicket::StackTraceTicket(DebuggerUserBreakpoint * p)
+{
+ _ASSERTE(p != NULL);
+}
+
+//void ControllerStackInfo::GetStackInfo(): GetStackInfo
+// is invoked by the user to trigger the stack walk. This will
+// cause the stack walk detailed in the class description to happen.
+// Thread* thread: The thread to do the stack walk on.
+// void* targetFP: Can be either NULL (meaning that the bottommost
+// frame is the target), or an frame pointer, meaning that the
+// caller wants information about a specific frame.
+// CONTEXT* pContext: A pointer to a CONTEXT structure. Can be null,
+// we use our temp context.
+// bool suppressUMChainFromComPlusMethodFrameGeneric - A ridiculous flag that is trying to narrowly
+// target a fix for issue 650903.
+// StackTraceTicket - ticket to ensure that we actually have permission for this stacktrace
+void ControllerStackInfo::GetStackInfo(
+ StackTraceTicket ticket,
+ Thread *thread,
+ FramePointer targetFP,
+ CONTEXT *pContext,
+ bool suppressUMChainFromComPlusMethodFrameGeneric
+ )
+{
+ _ASSERTE(thread != NULL);
+
+ BOOL contextValid = (pContext != NULL);
+ if (!contextValid)
+ {
+ // We're assuming the thread is protected w/ a frame (which includes the redirection
+ // case). The stackwalker will use that protection to prime the context.
+ pContext = &this->m_tempContext;
+ }
+ else
+ {
+ // If we provided an explicit context for this thread, it better not be redirected.
+ _ASSERTE(!ISREDIRECTEDTHREAD(thread));
+ }
+
+ // Mark this stackwalk as valid so that it can in turn be used to grandfather
+ // in other stackwalks.
+ INDEBUG(m_dbgExecuted = true);
+
+ m_activeFound = false;
+ m_returnFound = false;
+ m_bottomFP = LEAF_MOST_FRAME;
+ m_targetFP = targetFP;
+ m_targetFrameFound = (m_targetFP == LEAF_MOST_FRAME);
+ m_specialChainReason = CHAIN_NONE;
+ m_suppressUMChainFromComPlusMethodFrameGeneric = suppressUMChainFromComPlusMethodFrameGeneric;
+
+ int result = DebuggerWalkStack(thread,
+ LEAF_MOST_FRAME,
+ pContext,
+ contextValid,
+ WalkStack,
+ (void *) this,
+ FALSE);
+
+ _ASSERTE(m_activeFound); // All threads have at least one unmanaged frame
+
+ if (result == SWA_DONE)
+ {
+ _ASSERTE(!m_returnFound);
+ m_returnFrame = m_activeFrame;
+ }
+}
+
+//---------------------------------------------------------------------------------------
+//
+// This function "undoes" an unwind, i.e. it takes the active frame (the current frame)
+// and sets it to be the return frame (the caller frame). Currently it is only used by
+// the stepper to step out of an LCG method. See DebuggerStepper::DetectHandleLCGMethods()
+// for more information.
+//
+// Assumptions:
+// The current frame is valid on entry.
+//
+// Notes:
+// After this function returns, the active frame on this instance of ControllerStackInfo will no longer be valid.
+//
+// This function is specifically for DebuggerStepper::DetectHandleLCGMethods(). Using it in other scencarios may
+// require additional changes.
+//
+
+void ControllerStackInfo::SetReturnFrameWithActiveFrame()
+{
+ // Copy the active frame into the return frame.
+ m_returnFound = true;
+ m_returnFrame = m_activeFrame;
+
+ // Invalidate the active frame.
+ m_activeFound = false;
+ memset(&(m_activeFrame), 0, sizeof(m_activeFrame));
+ m_activeFrame.fp = LEAF_MOST_FRAME;
+}
+
+// Fill in a controller-stack info.
+StackWalkAction ControllerStackInfo::WalkStack(FrameInfo *pInfo, void *data)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ _ASSERTE(!pInfo->HasStubFrame()); // we didn't ask for stub frames.
+
+ ControllerStackInfo *i = (ControllerStackInfo *) data;
+
+ //save this info away for later use
+ if (i->m_bottomFP == LEAF_MOST_FRAME)
+ i->m_bottomFP = pInfo->fp;
+
+ // This is part of the targetted fix for issue 650903. (See the other
+ // parts in in code:TrackUMChain and code:DebuggerStepper::TrapStepOut.)
+ // pInfo->fIgnoreThisFrameIfSuppressingUMChainFromComPlusMethodFrameGeneric has been
+ // set by TrackUMChain to help us remember that the current frame we're looking at is
+ // ComPlusMethodFrameGeneric (we can't rely on looking at pInfo->frame to check
+ // this), and i->m_suppressUMChainFromComPlusMethodFrameGeneric has been set by the
+ // dude initiating this walk to remind us that our goal in life is to do a Step Out
+ // during managed-only debugging. These two things together tell us we should ignore
+ // this frame, rather than erroneously identifying it as the target frame.
+#ifdef FEATURE_COMINTEROP
+ if(i->m_suppressUMChainFromComPlusMethodFrameGeneric &&
+ (pInfo->chainReason == CHAIN_ENTER_UNMANAGED) &&
+ (pInfo->fIgnoreThisFrameIfSuppressingUMChainFromComPlusMethodFrameGeneric))
+ {
+ return SWA_CONTINUE;
+ }
+#endif // FEATURE_COMINTEROP
+
+ //have we reached the correct frame yet?
+ if (!i->m_targetFrameFound &&
+ IsEqualOrCloserToLeaf(i->m_targetFP, pInfo->fp))
+ {
+ i->m_targetFrameFound = true;
+ }
+
+ if (i->m_targetFrameFound )
+ {
+ // Ignore Enter-managed chains.
+ if (pInfo->chainReason == CHAIN_ENTER_MANAGED)
+ {
+ return SWA_CONTINUE;
+ }
+
+ if (i->m_activeFound )
+ {
+ // We care if the current frame is unmanaged (in case a managed stepper is initiated
+ // on a thread currently in unmanaged code). But since we can't step-out to UM frames,
+ // we can just skip them in the stack walk.
+ if (!pInfo->managed)
+ {
+ return SWA_CONTINUE;
+ }
+
+ if (pInfo->chainReason == CHAIN_CLASS_INIT)
+ i->m_specialChainReason = pInfo->chainReason;
+
+ if (pInfo->fp != i->m_activeFrame.fp) // avoid dups
+ {
+ i->m_returnFrame = *pInfo;
+
+#if defined(WIN64EXCEPTIONS)
+ CopyREGDISPLAY(&(i->m_returnFrame.registers), &(pInfo->registers));
+#endif // WIN64EXCEPTIONS
+
+ i->m_returnFound = true;
+
+ return SWA_ABORT;
+ }
+ }
+ else
+ {
+ i->m_activeFrame = *pInfo;
+
+#if defined(WIN64EXCEPTIONS)
+ CopyREGDISPLAY(&(i->m_activeFrame.registers), &(pInfo->registers));
+#endif // WIN64EXCEPTIONS
+
+ i->m_activeFound = true;
+
+ return SWA_CONTINUE;
+ }
+ }
+
+ return SWA_CONTINUE;
+}
+
+
+//
+// Note that patches may be reallocated - do not keep a pointer to a patch.
+//
+DebuggerControllerPatch *DebuggerPatchTable::AddPatchForMethodDef(DebuggerController *controller,
+ Module *module,
+ mdMethodDef md,
+ size_t offset,
+ DebuggerPatchKind kind,
+ FramePointer fp,
+ AppDomain *pAppDomain,
+ SIZE_T masterEnCVersion,
+ DebuggerJitInfo *dji)
+{
+ CONTRACTL
+ {
+ THROWS;
+ MODE_ANY;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ LOG( (LF_CORDB,LL_INFO10000,"DCP:AddPatchForMethodDef unbound "
+ "relative in methodDef 0x%x with dji 0x%x "
+ "controller:0x%x AD:0x%x\n", md,
+ dji, controller, pAppDomain));
+
+ DebuggerFunctionKey key;
+
+ key.module = module;
+ key.md = md;
+
+ // Get a new uninitialized patch object
+ DebuggerControllerPatch *patch =
+ (DebuggerControllerPatch *) Add(HashKey(&key));
+ if (patch == NULL)
+ {
+ ThrowOutOfMemory();
+ }
+#ifndef _TARGET_ARM_
+ patch->Initialize();
+#endif
+
+ //initialize the patch data structure.
+ InitializePRD(&(patch->opcode));
+ patch->controller = controller;
+ patch->key.module = module;
+ patch->key.md = md;
+ patch->offset = offset;
+ patch->offsetIsIL = (kind == PATCH_KIND_IL_MASTER);
+ patch->address = NULL;
+ patch->fp = fp;
+ patch->trace.Bad_SetTraceType(DPT_DEFAULT_TRACE_TYPE); // TRACE_OTHER
+ patch->refCount = 1; // AddRef()
+ patch->fSaveOpcode = false;
+ patch->pAppDomain = pAppDomain;
+ patch->pid = m_pid++;
+
+ if (kind == PATCH_KIND_IL_MASTER)
+ {
+ _ASSERTE(dji == NULL);
+ patch->encVersion = masterEnCVersion;
+ }
+ else
+ {
+ patch->dji = dji;
+ }
+ patch->kind = kind;
+
+ if (dji)
+ LOG((LF_CORDB,LL_INFO10000,"AddPatchForMethodDef w/ version 0x%04x, "
+ "pid:0x%x\n", dji->m_encVersion, patch->pid));
+ else if (kind == PATCH_KIND_IL_MASTER)
+ LOG((LF_CORDB,LL_INFO10000,"AddPatchForMethodDef w/ version 0x%04x, "
+ "pid:0x%x\n", masterEnCVersion,patch->pid));
+ else
+ LOG((LF_CORDB,LL_INFO10000,"AddPatchForMethodDef w/ no dji or dmi, pid:0x%x\n",patch->pid));
+
+
+ // This patch is not yet bound or activated
+ _ASSERTE( !patch->IsBound() );
+ _ASSERTE( !patch->IsActivated() );
+
+ // The only kind of patch with IL offset is the IL master patch.
+ _ASSERTE(patch->IsILMasterPatch() || patch->offsetIsIL == FALSE);
+ return patch;
+}
+
+// Create and bind a patch to the specified address
+// The caller should immediately activate the patch since we typically expect bound patches
+// will always be activated.
+DebuggerControllerPatch *DebuggerPatchTable::AddPatchForAddress(DebuggerController *controller,
+ MethodDesc *fd,
+ size_t offset,
+ DebuggerPatchKind kind,
+ CORDB_ADDRESS_TYPE *address,
+ FramePointer fp,
+ AppDomain *pAppDomain,
+ DebuggerJitInfo *dji,
+ SIZE_T pid,
+ TraceType traceType)
+
+{
+ CONTRACTL
+ {
+ THROWS;
+ MODE_ANY;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+
+ _ASSERTE(kind == PATCH_KIND_NATIVE_MANAGED || kind == PATCH_KIND_NATIVE_UNMANAGED);
+ LOG((LF_CORDB,LL_INFO10000,"DCP:AddPatchForAddress bound "
+ "absolute to 0x%x with dji 0x%x (mdDef:0x%x) "
+ "controller:0x%x AD:0x%x\n",
+ address, dji, (fd!=NULL?fd->GetMemberDef():0), controller,
+ pAppDomain));
+
+ // get new uninitialized patch object
+ DebuggerControllerPatch *patch =
+ (DebuggerControllerPatch *) Add(HashAddress(address));
+
+ if (patch == NULL)
+ {
+ ThrowOutOfMemory();
+ }
+#ifndef _TARGET_ARM_
+ patch->Initialize();
+#endif
+
+ // initialize the patch data structure
+ InitializePRD(&(patch->opcode));
+ patch->controller = controller;
+
+ if (fd == NULL)
+ {
+ patch->key.module = NULL;
+ patch->key.md = mdTokenNil;
+ }
+ else
+ {
+ patch->key.module = g_pEEInterface->MethodDescGetModule(fd);
+ patch->key.md = fd->GetMemberDef();
+ }
+ patch->offset = offset;
+ patch->offsetIsIL = FALSE;
+ patch->address = address;
+ patch->fp = fp;
+ patch->trace.Bad_SetTraceType(traceType);
+ patch->refCount = 1; // AddRef()
+ patch->fSaveOpcode = false;
+ patch->pAppDomain = pAppDomain;
+ if (pid == DCP_PID_INVALID)
+ patch->pid = m_pid++;
+ else
+ patch->pid = pid;
+
+ patch->dji = dji;
+ patch->kind = kind;
+
+ if (dji == NULL)
+ LOG((LF_CORDB,LL_INFO10000,"AddPatchForAddress w/ version with no dji, pid:0x%x\n", patch->pid));
+ else
+ {
+ LOG((LF_CORDB,LL_INFO10000,"AddPatchForAddress w/ version 0x%04x, "
+ "pid:0x%x\n", dji->m_methodInfo->GetCurrentEnCVersion(), patch->pid));
+
+ _ASSERTE( fd==NULL || fd == dji->m_fd );
+ }
+
+ SortPatchIntoPatchList(&patch);
+
+ // This patch is bound but not yet activated
+ _ASSERTE( patch->IsBound() );
+ _ASSERTE( !patch->IsActivated() );
+
+ // The only kind of patch with IL offset is the IL master patch.
+ _ASSERTE(patch->IsILMasterPatch() || patch->offsetIsIL == FALSE);
+ return patch;
+}
+
+// Set the native address for this patch.
+void DebuggerPatchTable::BindPatch(DebuggerControllerPatch *patch, CORDB_ADDRESS_TYPE *address)
+{
+ _ASSERTE(patch != NULL);
+ _ASSERTE(address != NULL);
+ _ASSERTE( !patch->IsILMasterPatch() );
+ _ASSERTE(!patch->IsBound() );
+
+ //Since the actual patch doesn't move, we don't have to worry about
+ //zeroing out the opcode field (see lenghty comment above)
+ // Since the patch is double-hashed based off Address, if we change the address,
+ // we must remove and reinsert the patch.
+ CHashTable::Delete(HashKey(&patch->key), ItemIndex((HASHENTRY*)patch));
+
+ patch->address = address;
+
+ CHashTable::Add(HashAddress(address), ItemIndex((HASHENTRY*)patch));
+
+ SortPatchIntoPatchList(&patch);
+
+ _ASSERTE(patch->IsBound() );
+ _ASSERTE(!patch->IsActivated() );
+}
+
+// Disassociate a patch from a specific code address.
+void DebuggerPatchTable::UnbindPatch(DebuggerControllerPatch *patch)
+{
+ _ASSERTE(patch != NULL);
+ _ASSERTE(patch->kind != PATCH_KIND_IL_MASTER);
+ _ASSERTE(patch->IsBound() );
+ _ASSERTE(!patch->IsActivated() );
+
+ //<REVISIT_TODO>@todo We're hosed if the patch hasn't been primed with
+ // this info & we can't get it...</REVISIT_TODO>
+ if (patch->key.module == NULL ||
+ patch->key.md == mdTokenNil)
+ {
+ MethodDesc *fd = g_pEEInterface->GetNativeCodeMethodDesc(
+ dac_cast<PCODE>(patch->address));
+ _ASSERTE( fd != NULL );
+ patch->key.module = g_pEEInterface->MethodDescGetModule(fd);
+ patch->key.md = fd->GetMemberDef();
+ }
+
+ // Update it's index entry in the table to use it's unbound key
+ // Since the patch is double-hashed based off Address, if we change the address,
+ // we must remove and reinsert the patch.
+ CHashTable::Delete( HashAddress(patch->address),
+ ItemIndex((HASHENTRY*)patch));
+
+ patch->address = NULL; // we're no longer bound to this address
+
+ CHashTable::Add( HashKey(&patch->key),
+ ItemIndex((HASHENTRY*)patch));
+
+ _ASSERTE(!patch->IsBound() );
+
+}
+
+void DebuggerPatchTable::RemovePatch(DebuggerControllerPatch *patch)
+{
+ // Since we're deleting this patch, it must not be activated (i.e. it must not have a stored opcode)
+ _ASSERTE( !patch->IsActivated() );
+#ifndef _TARGET_ARM_
+ patch->DoCleanup();
+#endif
+
+ //
+ // Because of the implementation of CHashTable, we can safely
+ // delete elements while iterating through the table. This
+ // behavior is relied upon - do not change to a different
+ // implementation without considering this fact.
+ //
+ Delete(Hash(patch), (HASHENTRY *) patch);
+
+}
+
+DebuggerControllerPatch *DebuggerPatchTable::GetNextPatch(DebuggerControllerPatch *prev)
+{
+ ULONG iNext;
+ HASHENTRY *psEntry;
+
+ // Start at the next entry in the chain.
+ // @todo - note that: EntryPtr(ItemIndex(x)) == x
+ iNext = EntryPtr(ItemIndex((HASHENTRY*)prev))->iNext;
+
+ // Search until we hit the end.
+ while (iNext != UINT32_MAX)
+ {
+ // Compare the keys.
+ psEntry = EntryPtr(iNext);
+
+ // Careful here... we can hash the entries in this table
+ // by two types of keys. In this type of search, the type
+ // of the second key (psEntry) does not necessarily
+ // indicate the type of the first key (prev), so we have
+ // to check for sure.
+ DebuggerControllerPatch *pc2 = (DebuggerControllerPatch*)psEntry;
+
+ if (((pc2->address == NULL) && (prev->address == NULL)) ||
+ ((pc2->address != NULL) && (prev->address != NULL)))
+ if (!Cmp(Key(prev), psEntry))
+ return pc2;
+
+ // Advance to the next item in the chain.
+ iNext = psEntry->iNext;
+ }
+
+ return NULL;
+}
+
+#ifdef _DEBUG_PATCH_TABLE
+ // DEBUG An internal debugging routine, it iterates
+ // through the hashtable, stopping at every
+ // single entry, no matter what it's state. For this to
+ // compile, you're going to have to add friend status
+ // of this class to CHashTableAndData in
+ // to $\Com99\Src\inc\UtilCode.h
+void DebuggerPatchTable::CheckPatchTable()
+{
+ if (NULL != m_pcEntries)
+ {
+ DebuggerControllerPatch *dcp;
+ int i = 0;
+ while (i++ <m_iEntries)
+ {
+ dcp = (DebuggerControllerPatch*)&(((DebuggerControllerPatch *)m_pcEntries)[i]);
+ if (dcp->opcode != 0 )
+ {
+ LOG((LF_CORDB,LL_INFO1000, "dcp->addr:0x%8x "
+ "mdMD:0x%8x, offset:0x%x, native:%d\n",
+ dcp->address, dcp->key.md, dcp->offset,
+ dcp->IsNativePatch()));
+ }
+ }
+ }
+}
+
+#endif // _DEBUG_PATCH_TABLE
+
+// Count how many patches are in the table.
+// Use for asserts
+int DebuggerPatchTable::GetNumberOfPatches()
+{
+ int total = 0;
+
+ if (NULL != m_pcEntries)
+ {
+ DebuggerControllerPatch *dcp;
+ ULONG i = 0;
+
+ while (i++ <m_iEntries)
+ {
+ dcp = (DebuggerControllerPatch*)&(((DebuggerControllerPatch *)m_pcEntries)[i]);
+
+ if (dcp->IsActivated() || !dcp->IsFree())
+ total++;
+ }
+ }
+ return total;
+}
+
+#if defined(_DEBUG)
+//-----------------------------------------------------------------------------
+// Debug check that we only have 1 thread-starter per thread.
+// pNew - the new DTS. We'll make sure there's not already a DTS on this thread.
+//-----------------------------------------------------------------------------
+void DebuggerController::EnsureUniqueThreadStarter(DebuggerThreadStarter * pNew)
+{
+ // This lock should be safe to take since our base class ctor takes it.
+ ControllerLockHolder lockController;
+ DebuggerController * pExisting = g_controllers;
+ while(pExisting != NULL)
+ {
+ if (pExisting->GetDCType() == DEBUGGER_CONTROLLER_THREAD_STARTER)
+ {
+ if (pExisting != pNew)
+ {
+ // If we have 2 thread starters, they'd better be on different threads.
+ _ASSERTE((pExisting->GetThread() != pNew->GetThread()));
+ }
+ }
+ pExisting = pExisting->m_next;
+ }
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// If we have a thread-starter on the given EE thread, make sure it's cancel.
+// Thread-Starters normally delete themselves when they fire. But if the EE
+// destroys the thread before it fires, then we'd still have an active DTS.
+//-----------------------------------------------------------------------------
+void DebuggerController::CancelOutstandingThreadStarter(Thread * pThread)
+{
+ _ASSERTE(pThread != NULL);
+ LOG((LF_CORDB, LL_EVERYTHING, "DC:CancelOutstandingThreadStarter - checking on thread =0x%p\n", pThread));
+
+ ControllerLockHolder lockController;
+ DebuggerController * p = g_controllers;
+ while(p != NULL)
+ {
+ if (p->GetDCType() == DEBUGGER_CONTROLLER_THREAD_STARTER)
+ {
+ if (p->GetThread() == pThread)
+ {
+ LOG((LF_CORDB, LL_EVERYTHING, "DC:CancelOutstandingThreadStarter, pThread=0x%p, Found=0x%p\n", p));
+
+ // There's only 1 DTS per thread, so once we find it, we can quit.
+ p->Delete();
+ p = NULL;
+ break;
+ }
+ }
+ p = p->m_next;
+ }
+ // The common case is that our DTS hit its patch and did a SendEvent (and
+ // deleted itself). So usually we'll get through the whole list w/o deleting anything.
+
+}
+
+//void DebuggerController::Initialize() Sets up the static
+// variables for the static DebuggerController class.
+// How: Sets g_runningOnWin95, initializes the critical section
+HRESULT DebuggerController::Initialize()
+{
+ CONTRACT(HRESULT)
+ {
+ THROWS;
+ GC_NOTRIGGER;
+ // This can be called in an "early attach" case, so DebuggerIsInvolved()
+ // will be b/c we don't realize the debugger's attaching to us.
+ //PRECONDITION(DebuggerIsInvolved());
+ POSTCONDITION(CheckPointer(g_patches));
+ POSTCONDITION(RETVAL == S_OK);
+ }
+ CONTRACT_END;
+
+ if (g_patches == NULL)
+ {
+ ZeroMemory(&g_criticalSection, sizeof(g_criticalSection)); // Init() expects zero-init memory.
+
+ // NOTE: CRST_UNSAFE_ANYMODE prevents a GC mode switch when entering this crst.
+ // If you remove this flag, we will switch to preemptive mode when entering
+ // g_criticalSection, which means all functions that enter it will become
+ // GC_TRIGGERS. (This includes all uses of ControllerLockHolder.) So be sure
+ // to update the contracts if you remove this flag.
+ g_criticalSection.Init(CrstDebuggerController,
+ (CrstFlags)(CRST_UNSAFE_ANYMODE | CRST_REENTRANCY | CRST_DEBUGGER_THREAD));
+
+ g_patches = new (interopsafe) DebuggerPatchTable();
+ _ASSERTE(g_patches != NULL); // throws on oom
+
+ HRESULT hr = g_patches->Init();
+
+ if (FAILED(hr))
+ {
+ DeleteInteropSafe(g_patches);
+ ThrowHR(hr);
+ }
+
+ g_patchTableValid = TRUE;
+ TRACE_ALLOC(g_patches);
+ }
+
+ _ASSERTE(g_patches != NULL);
+
+ RETURN (S_OK);
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Constructor for a controller
+//
+// Arguments:
+// pThread - thread that controller has affinity to. NULL if no thread - affinity.
+// pAppdomain - appdomain that controller has affinity to. NULL if no AD affinity.
+//
+//
+// Notes:
+// "Affinity" is per-controller specific. Affinity is generally passed on to
+// any patches the controller creates. So if a controller has affinity to Thread X,
+// then any patches it creates will only fire on Thread-X.
+//
+//---------------------------------------------------------------------------------------
+
+DebuggerController::DebuggerController(Thread * pThread, AppDomain * pAppDomain)
+ : m_pAppDomain(pAppDomain),
+ m_thread(pThread),
+ m_singleStep(false),
+ m_exceptionHook(false),
+ m_traceCall(0),
+ m_traceCallFP(ROOT_MOST_FRAME),
+ m_unwindFP(LEAF_MOST_FRAME),
+ m_eventQueuedCount(0),
+ m_deleted(false),
+ m_fEnableMethodEnter(false)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ CONSTRUCTOR_CHECK;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_INFO10000, "DC: 0x%x m_eventQueuedCount to 0 - DC::DC\n", this));
+ ControllerLockHolder lockController;
+ {
+ m_next = g_controllers;
+ g_controllers = this;
+ }
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Debugger::Controller::DeleteAllControlers - deletes all debugger contollers
+//
+// Arguments:
+// None
+//
+// Return Value:
+// None
+//
+// Notes:
+// This is used at detach time to remove all DebuggerControllers. This will remove all
+// patches and do whatever other cleanup individual DebuggerControllers consider
+// necessary to allow the debugger to detach and the process to run normally.
+//
+
+void DebuggerController::DeleteAllControllers()
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ ControllerLockHolder lockController;
+ DebuggerController * pDebuggerController = g_controllers;
+ DebuggerController * pNextDebuggerController = NULL;
+
+ while (pDebuggerController != NULL)
+ {
+ pNextDebuggerController = pDebuggerController->m_next;
+ pDebuggerController->DebuggerDetachClean();
+ pDebuggerController->Delete();
+ pDebuggerController = pNextDebuggerController;
+ }
+}
+
+DebuggerController::~DebuggerController()
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ DESTRUCTOR_CHECK;
+ }
+ CONTRACTL_END;
+
+ ControllerLockHolder lockController;
+
+ _ASSERTE(m_eventQueuedCount == 0);
+
+ DisableAll();
+
+ //
+ // Remove controller from list
+ //
+
+ DebuggerController **c;
+
+ c = &g_controllers;
+ while (*c != this)
+ c = &(*c)->m_next;
+
+ *c = m_next;
+
+}
+
+// void DebuggerController::Delete()
+// What: Marks an instance as deletable. If it's ref count
+// (see Enqueue, Dequeue) is currently zero, it actually gets deleted
+// How: Set m_deleted to true. If m_eventQueuedCount==0, delete this
+void DebuggerController::Delete()
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ if (m_eventQueuedCount == 0)
+ {
+ LOG((LF_CORDB|LF_ENC, LL_INFO100000, "DC::Delete: actual delete of this:0x%x!\n", this));
+ TRACE_FREE(this);
+ DeleteInteropSafe(this);
+ }
+ else
+ {
+ LOG((LF_CORDB|LF_ENC, LL_INFO100000, "DC::Delete: marked for "
+ "future delete of this:0x%x!\n", this));
+ LOG((LF_CORDB|LF_ENC, LL_INFO10000, "DC:0x%x m_eventQueuedCount at 0x%x\n",
+ this, m_eventQueuedCount));
+ m_deleted = true;
+ }
+}
+
+void DebuggerController::DebuggerDetachClean()
+{
+ //do nothing here
+}
+
+//static
+void DebuggerController::AddRef(DebuggerControllerPatch *patch)
+{
+ patch->refCount++;
+}
+
+//static
+void DebuggerController::Release(DebuggerControllerPatch *patch)
+{
+ patch->refCount--;
+ if (patch->refCount == 0)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "DCP::R: patch deleted, deactivating\n"));
+ DeactivatePatch(patch);
+ GetPatchTable()->RemovePatch(patch);
+ }
+}
+
+// void DebuggerController::DisableAll() DisableAll removes
+// all control from the controller. This includes all patches & page
+// protection. This will invoke Disable* for unwind,singlestep,
+// exceptionHook, and tracecall. It will also go through the patch table &
+// attempt to remove any and all patches that belong to this controller.
+// If the patch is currently triggering, then a Dispatch* method expects the
+// patch to be there after we return, so we instead simply mark the patch
+// itself as deleted.
+void DebuggerController::DisableAll()
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB,LL_INFO1000, "DC::DisableAll\n"));
+ _ASSERTE(g_patches != NULL);
+
+ ControllerLockHolder ch;
+ {
+ //
+ // Remove controller's patches from list.
+ // Don't do this on shutdown because the shutdown thread may have killed another thread asynchronously
+ // thus leaving the patchtable in an inconsistent state such that we may fail trying to walk it.
+ // Since we're exiting anyways, leaving int3 in the code can't harm anybody.
+ //
+ if (!g_fProcessDetach)
+ {
+ HASHFIND f;
+ for (DebuggerControllerPatch *patch = g_patches->GetFirstPatch(&f);
+ patch != NULL;
+ patch = g_patches->GetNextPatch(&f))
+ {
+ if (patch->controller == this)
+ {
+ Release(patch);
+ }
+ }
+ }
+
+ if (m_singleStep)
+ DisableSingleStep();
+ if (m_exceptionHook)
+ DisableExceptionHook();
+ if (m_unwindFP != LEAF_MOST_FRAME)
+ DisableUnwind();
+ if (m_traceCall)
+ DisableTraceCall();
+ if (m_fEnableMethodEnter)
+ DisableMethodEnter();
+ }
+}
+
+// void DebuggerController::Enqueue() What: Does
+// reference counting so we don't toast a
+// DebuggerController while it's in a Dispatch queue.
+// Why: In DispatchPatchOrSingleStep, we can't hold locks when going
+// into PreEmptiveGC mode b/c we'll create a deadlock.
+// So we have to UnLock() prior to
+// EnablePreEmptiveGC(). But somebody else can show up and delete the
+// DebuggerControllers since we no longer have the lock. So we have to
+// do this reference counting thing to make sure that the controllers
+// don't get toasted as we're trying to invoke SendEvent on them. We have to
+// reaquire the lock before invoking Dequeue because Dequeue may
+// result in the controller being deleted, which would change the global
+// controller list.
+// How: InterlockIncrement( m_eventQueuedCount )
+void DebuggerController::Enqueue()
+{
+ LIMITED_METHOD_CONTRACT;
+
+ m_eventQueuedCount++;
+ LOG((LF_CORDB, LL_INFO10000, "DC::Enq DC:0x%x m_eventQueuedCount at 0x%x\n",
+ this, m_eventQueuedCount));
+}
+
+// void DebuggerController::Dequeue() What: Does
+// reference counting so we don't toast a
+// DebuggerController while it's in a Dispatch queue.
+// How: InterlockDecrement( m_eventQueuedCount ), delete this if
+// m_eventQueuedCount == 0 AND m_deleted has been set to true
+void DebuggerController::Dequeue()
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_INFO10000, "DC::Deq DC:0x%x m_eventQueuedCount at 0x%x\n",
+ this, m_eventQueuedCount));
+ if (--m_eventQueuedCount == 0)
+ {
+ if (m_deleted)
+ {
+ TRACE_FREE(this);
+ DeleteInteropSafe(this);
+ }
+ }
+}
+
+
+// bool DebuggerController::BindPatch() If the method has
+// been JITted and isn't hashed by address already, then hash
+// it into the hashtable by address and not DebuggerFunctionKey.
+// If the patch->address field is nonzero, we're done.
+// Otherwise ask g_pEEInterface to FindLoadedMethodRefOrDef, then
+// GetFunctionAddress of the method, if the method is in IL,
+// MapILOffsetToNative. If everything else went Ok, we can now invoke
+// g_patches->BindPatch.
+// Returns: false if we know that we can't bind the patch immediately.
+// true if we either can bind the patch right now, or can't right now,
+// but might be able to in the future (eg, the method hasn't been JITted)
+
+// Have following outcomes:
+// 1) Succeeded in binding the patch to a raw address. patch->address is set.
+// (Note we still must apply the patch to put the int 3 in.)
+// returns true, *pFail = false
+//
+// 2) Fails to bind, but a future attempt may succeed. Obvious ex, for an IL-only
+// patch on an unjitted method.
+// returns false, *pFail = false
+//
+// 3) Fails to bind because something's wrong. Ex: bad IL offset, no DJI to do a
+// mapping with. Future calls will fail too.
+// returns false, *pFail = true
+bool DebuggerController::BindPatch(DebuggerControllerPatch *patch,
+ MethodDesc *fd,
+ CORDB_ADDRESS_TYPE *startAddr)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ THROWS; // from GetJitInfo
+ GC_NOTRIGGER;
+ MODE_ANY; // don't really care what mode we're in.
+
+ PRECONDITION(ThisMaybeHelperThread());
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(patch != NULL);
+ _ASSERTE(!patch->IsILMasterPatch());
+ _ASSERTE(fd != NULL);
+
+ //
+ // Translate patch to address, if it hasn't been already.
+ //
+
+ if (patch->address != NULL)
+ {
+ return true;
+ }
+
+ if (startAddr == NULL)
+ {
+ if (patch->HasDJI() && patch->GetDJI()->m_jitComplete)
+ {
+ startAddr = (CORDB_ADDRESS_TYPE *) CORDB_ADDRESS_TO_PTR(patch->GetDJI()->m_addrOfCode);
+ _ASSERTE(startAddr != NULL);
+ }
+ if (startAddr == NULL)
+ {
+ // Should not be trying to place patches on MethodDecs's for stubs.
+ // These stubs will never get jitted.
+ CONSISTENCY_CHECK_MSGF(!fd->IsWrapperStub(), ("Can't place patch at stub md %p, %s::%s",
+ fd, fd->m_pszDebugClassName, fd->m_pszDebugMethodName));
+
+ startAddr = (CORDB_ADDRESS_TYPE *)g_pEEInterface->GetFunctionAddress(fd);
+ //
+ // Code is not available yet to patch. The prestub should
+ // notify us when it is executed.
+ //
+ if (startAddr == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO10000,
+ "DC::BP:Patch at 0x%x not bindable yet.\n", patch->offset));
+
+ return false;
+ }
+ }
+ }
+
+ _ASSERTE(!g_pEEInterface->IsStub((const BYTE *)startAddr));
+
+ // If we've jitted, map to a native offset.
+ DebuggerJitInfo *info = g_pDebugger->GetJitInfo(fd, (const BYTE *)startAddr);
+
+#ifdef LOGGING
+ if (info == NULL)
+ {
+ LOG((LF_CORDB,LL_INFO10000, "DC::BindPa: For startAddr 0x%x, didn't find a DJI\n", startAddr));
+ }
+#endif //LOGGING
+ if (info != NULL)
+ {
+ // There is a strange case with prejitted code and unjitted trace patches. We can enter this function
+ // with no DebuggerJitInfo created, then have the call just above this actually create the
+ // DebuggerJitInfo, which causes JitComplete to be called, which causes all patches to be bound! If this
+ // happens, then we don't need to continue here (its already been done recursivley) and we don't need to
+ // re-active the patch, so we return false from right here. We can check this by seeing if we suddently
+ // have the address in the patch set.
+ if (patch->address != NULL)
+ {
+ LOG((LF_CORDB,LL_INFO10000, "DC::BindPa: patch bound recursivley by GetJitInfo, bailing...\n"));
+ return false;
+ }
+
+ LOG((LF_CORDB,LL_INFO10000, "DC::BindPa: For startAddr 0x%x, got DJI "
+ "0x%x, from 0x%x size: 0x%x\n", startAddr, info, info->m_addrOfCode, info->m_sizeOfCode));
+ }
+
+ LOG((LF_CORDB, LL_INFO10000, "DC::BP:Trying to bind patch in %s::%s version %d\n",
+ fd->m_pszDebugClassName, fd->m_pszDebugMethodName, info ? info->m_encVersion : (SIZE_T)-1));
+
+ _ASSERTE(g_patches != NULL);
+
+ CORDB_ADDRESS_TYPE *addr = (CORDB_ADDRESS_TYPE *)
+ CodeRegionInfo::GetCodeRegionInfo(NULL, NULL, startAddr).OffsetToAddress(patch->offset);
+ g_patches->BindPatch(patch, addr);
+
+ LOG((LF_CORDB, LL_INFO10000, "DC::BP:Binding patch at 0x%x(off:%x)\n", addr, patch->offset));
+
+ return true;
+}
+
+// bool DebuggerController::ApplyPatch() applies
+// the patch described to the code, and
+// remembers the replaced opcode. Note that the same address
+// cannot be patched twice at the same time.
+// Grabs the opcode & stores in patch, then sets a break
+// instruction for either native or IL.
+// VirtualProtect & some macros. Returns false if anything
+// went bad.
+// DebuggerControllerPatch *patch: The patch, indicates where
+// to set the INT3 instruction
+// Returns: true if the user break instruction was successfully
+// placed into the code-stream, false otherwise
+bool DebuggerController::ApplyPatch(DebuggerControllerPatch *patch)
+{
+ LOG((LF_CORDB, LL_INFO10000, "DC::ApplyPatch at addr 0x%p\n",
+ patch->address));
+
+ // If we try to apply an already applied patch, we'll overide our saved opcode
+ // with the break opcode and end up getting a break in out patch bypass buffer.
+ _ASSERTE(!patch->IsActivated() );
+ _ASSERTE(patch->IsBound());
+
+ // Note we may be patching at certain "blessed" points in mscorwks.
+ // This is very dangerous b/c we can't be sure patch->Address is blessed or not.
+
+
+ //
+ // Apply the patch.
+ //
+ _ASSERTE(!(g_pConfig->GetGCStressLevel() & (EEConfig::GCSTRESS_INSTR_JIT|EEConfig::GCSTRESS_INSTR_NGEN))
+ && "Debugger does not work with GCSTRESS 4");
+
+ if (patch->IsNativePatch())
+ {
+ if (patch->fSaveOpcode)
+ {
+ // We only used SaveOpcode for when we've moved code, so
+ // the patch should already be there.
+ patch->opcode = patch->opcodeSaved;
+ _ASSERTE( AddressIsBreakpoint(patch->address) );
+ return true;
+ }
+
+#if _DEBUG
+ VerifyExecutableAddress((BYTE*)patch->address);
+#endif
+
+ LPVOID baseAddress = (LPVOID)(patch->address);
+
+ DWORD oldProt;
+
+ if (!VirtualProtect(baseAddress,
+ CORDbg_BREAK_INSTRUCTION_SIZE,
+ PAGE_EXECUTE_READWRITE, &oldProt))
+ {
+ _ASSERTE(!"VirtualProtect of code page failed");
+ return false;
+ }
+
+ patch->opcode = CORDbgGetInstruction(patch->address);
+
+ CORDbgInsertBreakpoint((CORDB_ADDRESS_TYPE *)patch->address);
+ LOG((LF_CORDB, LL_EVERYTHING, "Breakpoint was inserted at %p for opcode %x\n", patch->address, patch->opcode));
+
+ if (!VirtualProtect(baseAddress,
+ CORDbg_BREAK_INSTRUCTION_SIZE,
+ oldProt, &oldProt))
+ {
+ _ASSERTE(!"VirtualProtect of code page failed");
+ return false;
+ }
+ }
+// TODO: : determine if this is needed for AMD64
+#if defined(_TARGET_X86_) //REVISIT_TODO what is this?!
+ else
+ {
+ DWORD oldProt;
+
+ //
+ // !!! IL patch logic assumes reference insruction encoding
+ //
+ if (!VirtualProtect((void *) patch->address, 2,
+ PAGE_EXECUTE_READWRITE, &oldProt))
+ {
+ _ASSERTE(!"VirtualProtect of code page failed");
+ return false;
+ }
+ patch->opcode =
+ (unsigned int) *(unsigned short*)(patch->address+1);
+
+ _ASSERTE(patch->opcode != CEE_BREAK);
+
+ *(unsigned short *) (patch->address+1) = CEE_BREAK;
+
+ if (!VirtualProtect((void *) patch->address, 2, oldProt, &oldProt))
+ {
+ _ASSERTE(!"VirtualProtect of code page failed");
+ return false;
+ }
+ }
+#endif //_TARGET_X86_
+
+ return true;
+}
+
+// bool DebuggerController::UnapplyPatch()
+// UnapplyPatch removes the patch described by the patch.
+// (CopyOpcodeFromAddrToPatch, in reverse.)
+// Looks a lot like CopyOpcodeFromAddrToPatch, except that we use a macro to
+// copy the instruction back to the code-stream & immediately set the
+// opcode field to 0 so ReadMemory,WriteMemory will work right.
+// Note that it's very important to zero out the opcode field, as it
+// is used by the right side to determine if a patch is
+// valid or not.
+// NO LOCKING
+// DebuggerControllerPatch * patch: Patch to remove
+// Returns: true if the patch was unapplied, false otherwise
+bool DebuggerController::UnapplyPatch(DebuggerControllerPatch *patch)
+{
+ _ASSERTE(patch->address != NULL);
+ _ASSERTE(patch->IsActivated() );
+
+ LOG((LF_CORDB,LL_INFO1000, "DC::UP unapply patch at addr 0x%p\n",
+ patch->address));
+
+ if (patch->IsNativePatch())
+ {
+ if (patch->fSaveOpcode)
+ {
+ // We're doing this for MoveCode, and we don't want to
+ // overwrite something if we don't get moved far enough.
+ patch->opcodeSaved = patch->opcode;
+ InitializePRD(&(patch->opcode));
+ _ASSERTE( !patch->IsActivated() );
+ return true;
+ }
+
+ LPVOID baseAddress = (LPVOID)(patch->address);
+
+ DWORD oldProt;
+
+ if (!VirtualProtect(baseAddress,
+ CORDbg_BREAK_INSTRUCTION_SIZE,
+ PAGE_EXECUTE_READWRITE, &oldProt))
+ {
+ //
+ // We may be trying to remove a patch from memory
+ // which has been unmapped. We can ignore the
+ // error in this case.
+ //
+ InitializePRD(&(patch->opcode));
+ return false;
+ }
+
+ CORDbgSetInstruction((CORDB_ADDRESS_TYPE *)patch->address, patch->opcode);
+
+ //VERY IMPORTANT to zero out opcode, else we might mistake
+ //this patch for an active on on ReadMem/WriteMem (see
+ //header file comment)
+ InitializePRD(&(patch->opcode));
+
+ if (!VirtualProtect(baseAddress,
+ CORDbg_BREAK_INSTRUCTION_SIZE,
+ oldProt, &oldProt))
+ {
+ _ASSERTE(!"VirtualProtect of code page failed");
+ return false;
+ }
+ }
+ else
+ {
+ DWORD oldProt;
+
+ if (!VirtualProtect((void *) patch->address, 2,
+ PAGE_EXECUTE_READWRITE, &oldProt))
+ {
+ //
+ // We may be trying to remove a patch from memory
+ // which has been unmapped. We can ignore the
+ // error in this case.
+ //
+ InitializePRD(&(patch->opcode));
+ return false;
+ }
+
+ //
+ // !!! IL patch logic assumes reference encoding
+ //
+// TODO: : determine if this is needed for AMD64
+#if defined(_TARGET_X86_)
+ _ASSERTE(*(unsigned short*)(patch->address+1) == CEE_BREAK);
+
+ *(unsigned short *) (patch->address+1)
+ = (unsigned short) patch->opcode;
+#endif //this makes no sense on anything but X86
+ //VERY IMPORTANT to zero out opcode, else we might mistake
+ //this patch for an active on on ReadMem/WriteMem (see
+ //header file comment
+ InitializePRD(&(patch->opcode));
+
+ if (!VirtualProtect((void *) patch->address, 2, oldProt, &oldProt))
+ {
+ _ASSERTE(!"VirtualProtect of code page failed");
+ return false;
+ }
+ }
+
+ _ASSERTE( !patch->IsActivated() );
+ _ASSERTE( patch->IsBound() );
+ return true;
+}
+
+// void DebuggerController::UnapplyPatchAt()
+// NO LOCKING
+// UnapplyPatchAt removes the patch from a copy of the patched code.
+// Like UnapplyPatch, except that we don't bother checking
+// memory permissions, but instead replace the breakpoint instruction
+// with the opcode at an arbitrary memory address.
+void DebuggerController::UnapplyPatchAt(DebuggerControllerPatch *patch,
+ CORDB_ADDRESS_TYPE *address)
+{
+ _ASSERTE(patch->IsBound() );
+
+ if (patch->IsNativePatch())
+ {
+ CORDbgSetInstruction((CORDB_ADDRESS_TYPE *)address, patch->opcode);
+ //note that we don't have to zero out opcode field
+ //since we're unapplying at something other than
+ //the original spot. We assert this is true:
+ _ASSERTE( patch->address != address );
+ }
+ else
+ {
+ //
+ // !!! IL patch logic assumes reference encoding
+ //
+// TODO: : determine if this is needed for AMD64
+#ifdef _TARGET_X86_
+ _ASSERTE(*(unsigned short*)(address+1) == CEE_BREAK);
+
+ *(unsigned short *) (address+1)
+ = (unsigned short) patch->opcode;
+ _ASSERTE( patch->address != address );
+#endif // this makes no sense on anything but X86
+ }
+}
+
+// bool DebuggerController::IsPatched() Is there a patch at addr?
+// How: if fNative && the instruction at addr is the break
+// instruction for this platform.
+bool DebuggerController::IsPatched(CORDB_ADDRESS_TYPE *address, BOOL native)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ if (native)
+ {
+ return AddressIsBreakpoint(address);
+ }
+ else
+ return false;
+}
+
+// DWORD DebuggerController::GetPatchedOpcode() Gets the opcode
+// at addr, 'looking underneath' any patches if needed.
+// GetPatchedInstruction is a function for the EE to call to "see through"
+// a patch to the opcodes which was patched.
+// How: Lock() grab opcode directly unless there's a patch, in
+// which case grab it out of the patch table.
+// BYTE * address: The address that we want to 'see through'
+// Returns: DWORD value, that is the opcode that should really be there,
+// if we hadn't placed a patch there. If we haven't placed a patch
+// there, then we'll see the actual opcode at that address.
+PRD_TYPE DebuggerController::GetPatchedOpcode(CORDB_ADDRESS_TYPE *address)
+{
+ _ASSERTE(g_patches != NULL);
+
+ PRD_TYPE opcode;
+ ZeroMemory(&opcode, sizeof(opcode));
+
+ ControllerLockHolder lockController;
+
+ //
+ // Look for a patch at the address
+ //
+
+ DebuggerControllerPatch *patch = g_patches->GetPatch((CORDB_ADDRESS_TYPE *)address);
+
+ if (patch != NULL)
+ {
+ // Since we got the patch at this address, is must by definition be bound to that address
+ _ASSERTE( patch->IsBound() );
+ _ASSERTE( patch->address == address );
+ // If we're going to be returning it's opcode, then the patch must also be activated
+ _ASSERTE( patch->IsActivated() );
+ opcode = patch->opcode;
+ }
+ else
+ {
+ //
+ // Patch was not found - it either is not our patch, or it has
+ // just been removed. In either case, just return the current
+ // opcode.
+ //
+
+ if (g_pEEInterface->IsManagedNativeCode((const BYTE *)address))
+ {
+ opcode = CORDbgGetInstruction((CORDB_ADDRESS_TYPE *)address);
+ }
+// <REVISIT_TODO>
+// TODO: : determine if this is needed for AMD64
+// </REVISIT_TODO>
+#ifdef _TARGET_X86_ //what is this?!
+ else
+ {
+ //
+ // !!! IL patch logic assumes reference encoding
+ //
+
+ opcode = *(unsigned short*)(address+1);
+ }
+#endif //_TARGET_X86_
+
+ }
+
+ return opcode;
+}
+
+// Holding the controller lock, this will check if an address is patched,
+// and if so will then set the PRT_TYPE out parameter to the unpatched value.
+BOOL DebuggerController::CheckGetPatchedOpcode(CORDB_ADDRESS_TYPE *address,
+ /*OUT*/ PRD_TYPE *pOpcode)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE; // take Controller lock.
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(g_patches != NULL);
+
+ BOOL res;
+
+ ControllerLockHolder lockController;
+
+ //
+ // Look for a patch at the address
+ //
+
+ if (IsAddressPatched(address))
+ {
+ *pOpcode = GetPatchedOpcode(address);
+ res = TRUE;
+ }
+ else
+ {
+ InitializePRD(pOpcode);
+ res = FALSE;
+ }
+
+
+ return res;
+}
+
+// void DebuggerController::ActivatePatch() Place a breakpoint
+// so that threads will trip over this patch.
+// If there any patches at the address already, then copy
+// their opcode into this one & return. Otherwise,
+// call ApplyPatch(patch). There is an implicit list of patches at this
+// address by virtue of the fact that we can iterate through all the
+// patches in the patch with the same address.
+// DebuggerControllerPatch *patch: The patch to activate
+/* static */ void DebuggerController::ActivatePatch(DebuggerControllerPatch *patch)
+{
+ _ASSERTE(g_patches != NULL);
+ _ASSERTE(patch != NULL);
+ _ASSERTE(patch->IsBound() );
+ _ASSERTE(!patch->IsActivated() );
+
+ bool fApply = true;
+
+ //
+ // See if we already have an active patch at this address.
+ //
+ for (DebuggerControllerPatch *p = g_patches->GetPatch(patch->address);
+ p != NULL;
+ p = g_patches->GetNextPatch(p))
+ {
+ if (p != patch)
+ {
+ // If we're going to skip activating 'patch' because 'p' already exists at the same address
+ // then 'p' must be activated. We expect that all bound patches are activated.
+ _ASSERTE( p->IsActivated() );
+ patch->opcode = p->opcode;
+ fApply = false;
+ break;
+ }
+ }
+
+ //
+ // This is the only patch at this address - apply the patch
+ // to the code.
+ //
+ if (fApply)
+ {
+ ApplyPatch(patch);
+ }
+
+ _ASSERTE(patch->IsActivated() );
+}
+
+// void DebuggerController::DeactivatePatch() Make sure that a
+// patch won't be hit.
+// How: If this patch is the last one at this address, then
+// UnapplyPatch. The caller should then invoke RemovePatch to remove the
+// patch from the patch table.
+// DebuggerControllerPatch *patch: Patch to deactivate
+void DebuggerController::DeactivatePatch(DebuggerControllerPatch *patch)
+{
+ _ASSERTE(g_patches != NULL);
+
+ if( !patch->IsBound() ) {
+ // patch is not bound, nothing to do
+ return;
+ }
+
+ // We expect that all bound patches are also activated.
+ // One exception to this is if the shutdown thread killed another thread right after
+ // if deactivated a patch but before it got to remove it.
+ _ASSERTE(patch->IsActivated() );
+
+ bool fUnapply = true;
+
+ //
+ // See if we already have an active patch at this address.
+ //
+ for (DebuggerControllerPatch *p = g_patches->GetPatch(patch->address);
+ p != NULL;
+ p = g_patches->GetNextPatch(p))
+ {
+ if (p != patch)
+ {
+ // There is another patch at this address, so don't remove it
+ // However, clear the patch data so that we no longer consider this particular patch activated
+ fUnapply = false;
+ InitializePRD(&(patch->opcode));
+ break;
+ }
+ }
+
+ if (fUnapply)
+ {
+ UnapplyPatch(patch);
+ }
+
+ _ASSERTE(!patch->IsActivated() );
+
+ //
+ // Patch must now be removed from the table.
+ //
+}
+
+// AddILMasterPatch: record a patch on IL code but do not bind it or activate it. The master b.p.
+// is associated with a module/token pair. It is used later
+// (e.g. in MapAndBindFunctionPatches) to create one or more "slave"
+// breakpoints which are associated with particular MethodDescs/JitInfos.
+//
+// Rationale: For generic code a single IL patch (e.g a breakpoint)
+// may give rise to several patches, one for each JITting of
+// the IL (i.e. generic code may be JITted multiple times for
+// different instantiations).
+//
+// So we keep one patch which describes
+// the breakpoint but which is never actually bound or activated.
+// This is then used to apply new "slave" patches to all copies of
+// JITted code associated with the method.
+//
+// <REVISIT_TODO>In theory we could bind and apply the master patch when the
+// code is known not to be generic (as used to happen to all breakpoint
+// patches in V1). However this seems like a premature
+// optimization.</REVISIT_TODO>
+DebuggerControllerPatch *DebuggerController::AddILMasterPatch(Module *module,
+ mdMethodDef md,
+ SIZE_T offset,
+ SIZE_T encVersion)
+{
+ CONTRACTL
+ {
+ THROWS;
+ MODE_ANY;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(g_patches != NULL);
+
+ ControllerLockHolder ch;
+
+
+ DebuggerControllerPatch *patch = g_patches->AddPatchForMethodDef(this,
+ module,
+ md,
+ offset,
+ PATCH_KIND_IL_MASTER,
+ LEAF_MOST_FRAME,
+ NULL,
+ encVersion,
+ NULL);
+
+ LOG((LF_CORDB, LL_INFO10000,
+ "DC::AP: Added IL master patch 0x%x for md 0x%x at offset %d encVersion %d\n", patch, md, offset, encVersion));
+
+ return patch;
+}
+
+// See notes above on AddILMasterPatch
+BOOL DebuggerController::AddBindAndActivateILSlavePatch(DebuggerControllerPatch *master,
+ DebuggerJitInfo *dji)
+{
+ _ASSERTE(g_patches != NULL);
+ _ASSERTE(master->IsILMasterPatch());
+ _ASSERTE(dji != NULL);
+
+ // Do not dereference the "master" pointer in the loop! The loop may add more patches,
+ // causing the patch table to grow and move.
+ BOOL result = FALSE;
+ SIZE_T masterILOffset = master->offset;
+
+ // Loop through all the native offsets mapped to the given IL offset. On x86 the mapping
+ // should be 1:1. On WIN64, because there are funclets, we have have an 1:N mapping.
+ DebuggerJitInfo::ILToNativeOffsetIterator it;
+ for (dji->InitILToNativeOffsetIterator(it, masterILOffset); !it.IsAtEnd(); it.Next())
+ {
+ BOOL fExact;
+ SIZE_T offsetNative = it.Current(&fExact);
+
+ // We special case offset 0, which is when a breakpoint is set
+ // at the beginning of a method that hasn't been jitted yet. In
+ // that case it's possible that offset 0 has been optimized out,
+ // but we still want to set the closest breakpoint to that.
+ if (!fExact && (masterILOffset != 0))
+ {
+ LOG((LF_CORDB, LL_INFO10000, "DC::BP:Failed to bind patch at IL offset 0x%p in %s::%s\n",
+ masterILOffset, dji->m_fd->m_pszDebugClassName, dji->m_fd->m_pszDebugMethodName));
+
+ continue;
+ }
+ else
+ {
+ result = TRUE;
+ }
+
+ INDEBUG(BOOL fOk = )
+ AddBindAndActivatePatchForMethodDesc(dji->m_fd, dji,
+ offsetNative, PATCH_KIND_IL_SLAVE,
+ LEAF_MOST_FRAME, m_pAppDomain);
+ _ASSERTE(fOk);
+ }
+
+ // As long as we have successfully bound at least one patch, we consider the operation successful.
+ return result;
+}
+
+
+
+// This routine places a patch that is conceptually a patch on the IL code.
+// The IL code may be jitted multiple times, e.g. due to generics.
+// This routine ensures that both present and subsequent JITtings of code will
+// also be patched.
+//
+// This routine will return FALSE only if we will _never_ be able to
+// place the patch in any native code corresponding to the given offset.
+// Otherwise it will:
+// (a) record a "master" patch
+// (b) apply as many slave patches as it can to existing copies of code
+// that have debugging information
+BOOL DebuggerController::AddILPatch(AppDomain * pAppDomain, Module *module,
+ mdMethodDef md,
+ SIZE_T encVersion, // what encVersion does this apply to?
+ SIZE_T offset)
+{
+ _ASSERTE(g_patches != NULL);
+ _ASSERTE(md != NULL);
+ _ASSERTE(module != NULL);
+
+ BOOL fOk = FALSE;
+
+ DebuggerMethodInfo *dmi = g_pDebugger->GetOrCreateMethodInfo(module, md); // throws
+ if (dmi == NULL)
+ {
+ return false;
+ }
+
+ EX_TRY
+ {
+ // OK, we either have (a) no code at all or (b) we have both JIT information and code
+ //.
+ // Either way, lay down the MasterPatch.
+ //
+ // MapAndBindFunctionPatches will take care of any instantiations that haven't
+ // finished JITting, by making a copy of the master breakpoint.
+ DebuggerControllerPatch *master = AddILMasterPatch(module, md, offset, encVersion);
+
+ // We have to keep the index here instead of the pointer. The loop below adds more patches,
+ // which may cause the patch table to grow and move.
+ ULONG masterIndex = g_patches->GetItemIndex((HASHENTRY*)master);
+
+ // Iterate through every existing NativeCodeBlob (with the same EnC version).
+ // This includes generics + prejitted code.
+ DebuggerMethodInfo::DJIIterator it;
+ dmi->IterateAllDJIs(pAppDomain, NULL /* module filter */, &it);
+
+ if (it.IsAtEnd())
+ {
+ // It is okay if we don't have any DJIs yet. It just means that the method hasn't been jitted.
+ fOk = TRUE;
+ }
+ else
+ {
+ // On the other hand, if the method has been jitted, then we expect to be able to bind at least
+ // one breakpoint. The exception is when we have multiple EnC versions of the method, in which
+ // case it is ok if we don't bind any breakpoint. One scenario is when a method has been updated
+ // via EnC but it's not yet jitted. We need to allow a debugger to put a breakpoint on the new
+ // version of the method, but the new version won't have a DJI yet.
+ BOOL fVersionMatch = FALSE;
+ while(!it.IsAtEnd())
+ {
+ DebuggerJitInfo *dji = it.Current();
+ _ASSERTE(dji->m_jitComplete);
+ if (dji->m_encVersion == encVersion)
+ {
+ fVersionMatch = TRUE;
+
+ master = (DebuggerControllerPatch *)g_patches->GetEntryPtr(masterIndex);
+
+ // <REVISIT_TODO> If we're missing JIT info for any then
+ // we won't have applied the bp to every instantiation. That should probably be reported
+ // as a new kind of condition to the debugger, i.e. report "bp only partially applied". It would be
+ // a shame to completely fail just because on instantiation is missing debug info: e.g. just because
+ // one component hasn't been prejitted with debugging information.</REVISIT_TODO>
+ fOk = (AddBindAndActivateILSlavePatch(master, dji) || fOk);
+ }
+ it.Next();
+ }
+
+ // This is the exceptional case referred to in the comment above. If we fail to put a breakpoint
+ // because we don't have a matching version of the method, we need to return TRUE.
+ if (fVersionMatch == FALSE)
+ {
+ fOk = TRUE;
+ }
+ }
+ }
+ EX_CATCH
+ {
+ fOk = FALSE;
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+ return fOk;
+}
+
+// Add a patch at native-offset 0 in the latest version of the method.
+// This is used by step-in.
+// Calls to new methods always go to the latest version, so EnC is not an issue here.
+// The method may be not yet jitted. Or it may be prejitted.
+void DebuggerController::AddPatchToStartOfLatestMethod(MethodDesc * fd)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ THROWS; // from GetJitInfo
+ GC_NOTRIGGER;
+ MODE_ANY; // don't really care what mode we're in.
+
+ PRECONDITION(ThisMaybeHelperThread());
+ PRECONDITION(CheckPointer(fd));
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(g_patches != NULL);
+ DebuggerController::AddBindAndActivatePatchForMethodDesc(fd, NULL, 0, PATCH_KIND_NATIVE_MANAGED, LEAF_MOST_FRAME, NULL);
+ return;
+}
+
+
+// Place patch in method at native offset.
+BOOL DebuggerController::AddBindAndActivateNativeManagedPatch(MethodDesc * fd,
+ DebuggerJitInfo *dji,
+ SIZE_T offsetNative,
+ FramePointer fp,
+ AppDomain *pAppDomain)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ THROWS; // from GetJitInfo
+ GC_NOTRIGGER;
+ MODE_ANY; // don't really care what mode we're in.
+
+ PRECONDITION(ThisMaybeHelperThread());
+ PRECONDITION(CheckPointer(fd));
+ PRECONDITION(fd->IsDynamicMethod() || (dji != NULL));
+ }
+ CONTRACTL_END;
+
+ // For non-dynamic methods, we always expect to have a DJI, but just in case, we don't want the assert to AV.
+ _ASSERTE((dji == NULL) || (fd == dji->m_fd));
+ _ASSERTE(g_patches != NULL);
+ return DebuggerController::AddBindAndActivatePatchForMethodDesc(fd, dji, offsetNative, PATCH_KIND_NATIVE_MANAGED, fp, pAppDomain);
+}
+
+
+BOOL DebuggerController::AddBindAndActivatePatchForMethodDesc(MethodDesc *fd,
+ DebuggerJitInfo *dji,
+ SIZE_T offset,
+ DebuggerPatchKind kind,
+ FramePointer fp,
+ AppDomain *pAppDomain)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ THROWS;
+ GC_NOTRIGGER;
+ MODE_ANY; // don't really care what mode we're in.
+
+ PRECONDITION(ThisMaybeHelperThread());
+ }
+ CONTRACTL_END;
+
+ BOOL ok = FALSE;
+ ControllerLockHolder ch;
+
+ LOG((LF_CORDB|LF_ENC,LL_INFO10000,"DC::AP: Add to %s::%s, at offs 0x%x "
+ "fp:0x%x AD:0x%x\n", fd->m_pszDebugClassName,
+ fd->m_pszDebugMethodName,
+ offset, fp.GetSPValue(), pAppDomain));
+
+ DebuggerControllerPatch *patch = g_patches->AddPatchForMethodDef(
+ this,
+ g_pEEInterface->MethodDescGetModule(fd),
+ fd->GetMemberDef(),
+ offset,
+ kind,
+ fp,
+ pAppDomain,
+ NULL,
+ dji);
+
+ if (DebuggerController::BindPatch(patch, fd, NULL))
+ {
+ LOG((LF_CORDB|LF_ENC,LL_INFO1000,"BindPatch went fine, doing ActivatePatch\n"));
+ DebuggerController::ActivatePatch(patch);
+ ok = TRUE;
+ }
+
+ return ok;
+}
+
+
+// This version is particularly useful b/c it doesn't assume that the
+// patch is inside a managed method.
+DebuggerControllerPatch *DebuggerController::AddAndActivateNativePatchForAddress(CORDB_ADDRESS_TYPE *address,
+ FramePointer fp,
+ bool managed,
+ TraceType traceType)
+{
+ CONTRACTL
+ {
+ THROWS;
+ MODE_ANY;
+ GC_NOTRIGGER;
+
+ PRECONDITION(g_patches != NULL);
+ }
+ CONTRACTL_END;
+
+
+ ControllerLockHolder ch;
+
+ DebuggerControllerPatch *patch
+ = g_patches->AddPatchForAddress(this,
+ NULL,
+ 0,
+ (managed? PATCH_KIND_NATIVE_MANAGED : PATCH_KIND_NATIVE_UNMANAGED),
+ address,
+ fp,
+ NULL,
+ NULL,
+ DebuggerPatchTable::DCP_PID_INVALID,
+ traceType);
+
+ ActivatePatch(patch);
+
+ return patch;
+}
+
+void DebuggerController::RemovePatchesFromModule(Module *pModule, AppDomain *pAppDomain )
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_INFO100000, "DPT::CPFM mod:0x%p (%S)\n",
+ pModule, pModule->GetDebugName()));
+
+ // First find all patches of interest
+ DebuggerController::ControllerLockHolder ch;
+ HASHFIND f;
+ for (DebuggerControllerPatch *patch = g_patches->GetFirstPatch(&f);
+ patch != NULL;
+ patch = g_patches->GetNextPatch(&f))
+ {
+ // Skip patches not in the specified domain
+ if ((pAppDomain != NULL) && (patch->pAppDomain != pAppDomain))
+ continue;
+
+ BOOL fRemovePatch = FALSE;
+
+ // Remove both native and IL patches the belong to this module
+ if (patch->HasDJI())
+ {
+ DebuggerJitInfo * dji = patch->GetDJI();
+
+ _ASSERTE(patch->key.module == dji->m_fd->GetModule());
+
+ // It is not necessary to check for m_fd->GetModule() here. It will
+ // be covered by other module unload notifications issued for the appdomain.
+ if ( dji->m_pLoaderModule == pModule )
+ fRemovePatch = TRUE;
+ }
+ else
+ if (patch->key.module == pModule)
+ {
+ fRemovePatch = TRUE;
+ }
+
+ if (fRemovePatch)
+ {
+ LOG((LF_CORDB, LL_EVERYTHING, "Removing patch 0x%p\n",
+ patch));
+ // we shouldn't be both hitting this patch AND
+ // unloading the module it belongs to.
+ _ASSERTE(!patch->IsTriggering());
+ Release( patch );
+ }
+ }
+}
+
+#ifdef _DEBUG
+bool DebuggerController::ModuleHasPatches( Module* pModule )
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ if( g_patches == NULL )
+ {
+ // Patch table hasn't been initialized
+ return false;
+ }
+
+ // First find all patches of interest
+ HASHFIND f;
+ for (DebuggerControllerPatch *patch = g_patches->GetFirstPatch(&f);
+ patch != NULL;
+ patch = g_patches->GetNextPatch(&f))
+ {
+ //
+ // This mirrors logic in code:DebuggerController::RemovePatchesFromModule
+ //
+
+ if (patch->HasDJI())
+ {
+ DebuggerJitInfo * dji = patch->GetDJI();
+
+ _ASSERTE(patch->key.module == dji->m_fd->GetModule());
+
+ // It may be sufficient to just check m_pLoaderModule here. Since this is used for debug-only
+ // check, we will check for m_fd->GetModule() as well to catch more potential problems.
+ if ( (dji->m_pLoaderModule == pModule) || (dji->m_fd->GetModule() == pModule) )
+ {
+ return true;
+ }
+ }
+
+ if (patch->key.module == pModule)
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+#endif // _DEBUG
+
+//
+// Returns true if the given address is in an internal helper
+// function, false if its not.
+//
+// This is a temporary workaround function to avoid having us stop in
+// unmanaged code belonging to the Runtime during a StepIn operation.
+//
+static bool _AddrIsJITHelper(PCODE addr)
+{
+#if !defined(_WIN64) && !defined(FEATURE_PAL)
+ // Is the address in the runtime dll (clr.dll or coreclr.dll) at all? (All helpers are in
+ // that dll)
+ if (g_runtimeLoadedBaseAddress <= addr &&
+ addr < g_runtimeLoadedBaseAddress + g_runtimeVirtualSize)
+ {
+ for (int i = 0; i < CORINFO_HELP_COUNT; i++)
+ {
+ if (hlpFuncTable[i].pfnHelper == (void*)addr)
+ {
+ LOG((LF_CORDB, LL_INFO10000,
+ "_ANIM: address of helper function found: 0x%08x\n",
+ addr));
+ return true;
+ }
+ }
+
+ for (unsigned d = 0; d < DYNAMIC_CORINFO_HELP_COUNT; d++)
+ {
+ if (hlpDynamicFuncTable[d].pfnHelper == (void*)addr)
+ {
+ LOG((LF_CORDB, LL_INFO10000,
+ "_ANIM: address of helper function found: 0x%08x\n",
+ addr));
+ return true;
+ }
+ }
+
+ LOG((LF_CORDB, LL_INFO10000,
+ "_ANIM: address within runtime dll, but not a helper function "
+ "0x%08x\n", addr));
+ }
+#else // !defined(_WIN64) && !defined(FEATURE_PAL)
+ // TODO: Figure out what we want to do here
+#endif // !defined(_WIN64) && !defined(FEATURE_PAL)
+
+ return false;
+}
+
+// bool DebuggerController::PatchTrace() What: Invoke
+// AddPatch depending on the type of the given TraceDestination.
+// How: Invokes AddPatch based on the trace type: TRACE_OTHER will
+// return false, the others will obtain args for a call to an AddPatch
+// method & return true.
+//
+// Return true if we set a patch, else false
+bool DebuggerController::PatchTrace(TraceDestination *trace,
+ FramePointer fp,
+ bool fStopInUnmanaged)
+{
+ CONTRACTL
+ {
+ THROWS; // Because AddPatch may throw on oom. We may want to convert this to nothrow and return false.
+ MODE_ANY;
+ DISABLED(GC_TRIGGERS); // @todo - what should this be?
+
+ PRECONDITION(ThisMaybeHelperThread());
+ }
+ CONTRACTL_END;
+ DebuggerControllerPatch *dcp = NULL;
+
+ switch (trace->GetTraceType())
+ {
+ case TRACE_ENTRY_STUB: // fall through
+ case TRACE_UNMANAGED:
+ LOG((LF_CORDB, LL_INFO10000,
+ "DC::PT: Setting unmanaged trace patch at 0x%p(%p)\n",
+ trace->GetAddress(), fp.GetSPValue()));
+
+ if (fStopInUnmanaged && !_AddrIsJITHelper(trace->GetAddress()))
+ {
+ AddAndActivateNativePatchForAddress((CORDB_ADDRESS_TYPE *)trace->GetAddress(),
+ fp,
+ FALSE,
+ trace->GetTraceType());
+ return true;
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO10000, "DC::PT: decided to NOT "
+ "place a patch in unmanaged code\n"));
+ return false;
+ }
+
+ case TRACE_MANAGED:
+ LOG((LF_CORDB, LL_INFO10000,
+ "Setting managed trace patch at 0x%p(%p)\n", trace->GetAddress(), fp.GetSPValue()));
+
+ MethodDesc *fd;
+ fd = g_pEEInterface->GetNativeCodeMethodDesc(trace->GetAddress());
+ _ASSERTE(fd);
+
+ DebuggerJitInfo *dji;
+ dji = g_pDebugger->GetJitInfoFromAddr(trace->GetAddress());
+ //_ASSERTE(dji); //we'd like to assert this, but attach won't work
+
+ AddBindAndActivateNativeManagedPatch(fd,
+ dji,
+ CodeRegionInfo::GetCodeRegionInfo(dji, fd).AddressToOffset((const BYTE *)trace->GetAddress()),
+ fp,
+ NULL);
+ return true;
+
+ case TRACE_UNJITTED_METHOD:
+ // trace->address is actually a MethodDesc* of the method that we'll
+ // soon JIT, so put a relative bp at offset zero in.
+ LOG((LF_CORDB, LL_INFO10000,
+ "Setting unjitted method patch in MethodDesc 0x%p %s\n", trace->GetMethodDesc(), trace->GetMethodDesc() ? trace->GetMethodDesc()->m_pszDebugMethodName : ""));
+
+ // Note: we have to make sure to bind here. If this function is prejitted, this may be our only chance to get a
+ // DebuggerJITInfo and thereby cause a JITComplete callback.
+ AddPatchToStartOfLatestMethod(trace->GetMethodDesc());
+ return true;
+
+ case TRACE_FRAME_PUSH:
+ LOG((LF_CORDB, LL_INFO10000,
+ "Setting frame patch at 0x%p(%p)\n", trace->GetAddress(), fp.GetSPValue()));
+
+ AddAndActivateNativePatchForAddress((CORDB_ADDRESS_TYPE *)trace->GetAddress(),
+ fp,
+ TRUE,
+ TRACE_FRAME_PUSH);
+ return true;
+
+ case TRACE_MGR_PUSH:
+ LOG((LF_CORDB, LL_INFO10000,
+ "Setting frame patch (TRACE_MGR_PUSH) at 0x%p(%p)\n",
+ trace->GetAddress(), fp.GetSPValue()));
+
+ dcp = AddAndActivateNativePatchForAddress((CORDB_ADDRESS_TYPE *)trace->GetAddress(),
+ LEAF_MOST_FRAME, // But Mgr_push can't have fp affinity!
+ TRUE,
+ DPT_DEFAULT_TRACE_TYPE); // TRACE_OTHER
+ // Now copy over the trace field since TriggerPatch will expect this
+ // to be set for this case.
+ if (dcp != NULL)
+ {
+ dcp->trace = *trace;
+ }
+
+ return true;
+
+ case TRACE_OTHER:
+ LOG((LF_CORDB, LL_INFO10000,
+ "Can't set a trace patch for TRACE_OTHER...\n"));
+ return false;
+
+ default:
+ _ASSERTE(0);
+ return false;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Checks if the patch matches the context + thread.
+// Multiple patches can exist at a single address, so given a patch at the
+// Context's current address, this does additional patch-affinity checks like
+// thread, AppDomain, and frame-pointer.
+// thread - thread executing the given context that hit the patch
+// context - context of the thread that hit the patch
+// patch - candidate patch that we're looking for a match.
+// Returns:
+// True if the patch matches.
+// False
+//-----------------------------------------------------------------------------
+bool DebuggerController::MatchPatch(Thread *thread,
+ CONTEXT *context,
+ DebuggerControllerPatch *patch)
+{
+ LOG((LF_CORDB, LL_INFO100000, "DC::MP: EIP:0x%p\n", GetIP(context)));
+
+ // Caller should have already matched our addresses.
+ if (patch->address != dac_cast<PTR_CORDB_ADDRESS_TYPE>(GetIP(context)))
+ {
+ return false;
+ }
+
+ // <BUGNUM>RAID 67173 -</BUGNUM> we'll make sure that intermediate patches have NULL
+ // pAppDomain so that we don't end up running to completion when
+ // the appdomain switches halfway through a step.
+ if (patch->pAppDomain != NULL)
+ {
+ AppDomain *pAppDomainCur = thread->GetDomain();
+
+ if (pAppDomainCur != patch->pAppDomain)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "DC::MP: patches didn't match b/c of "
+ "appdomains!\n"));
+ return false;
+ }
+ }
+
+ if (patch->controller->m_thread != NULL && patch->controller->m_thread != thread)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "DC::MP: patches didn't match b/c threads\n"));
+ return false;
+ }
+
+ if (patch->fp != LEAF_MOST_FRAME)
+ {
+ // If we specified a Frame-pointer, than it should have been safe to take a stack trace.
+
+ ControllerStackInfo info;
+ StackTraceTicket ticket(patch);
+ info.GetStackInfo(ticket, thread, LEAF_MOST_FRAME, context);
+
+ // !!! This check should really be != , but there is some ambiguity about which frame is the parent frame
+ // in the destination returned from Frame::TraceFrame, so this allows some slop there.
+
+ if (info.HasReturnFrame() && IsCloserToLeaf(info.m_returnFrame.fp, patch->fp))
+ {
+ LOG((LF_CORDB, LL_INFO10000, "Patch hit but frame not matched at %p (current=%p, patch=%p)\n",
+ patch->address, info.m_returnFrame.fp.GetSPValue(), patch->fp.GetSPValue()));
+
+ return false;
+ }
+ }
+
+ LOG((LF_CORDB, LL_INFO100000, "DC::MP: Returning true"));
+
+ return true;
+}
+
+DebuggerPatchSkip *DebuggerController::ActivatePatchSkip(Thread *thread,
+ const BYTE *PC,
+ BOOL fForEnC)
+{
+#ifdef _DEBUG
+ BOOL shouldBreak = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ActivatePatchSkip);
+ if (shouldBreak > 0) {
+ _ASSERTE(!"ActivatePatchSkip");
+ }
+#endif
+
+ LOG((LF_CORDB,LL_INFO10000, "DC::APS thread=0x%p pc=0x%p fForEnc=%d\n",
+ thread, PC, fForEnC));
+ _ASSERTE(g_patches != NULL);
+
+ // Previously, we assumed that if we got to this point & the patch
+ // was still there that we'd have to skip the patch. SetIP changes
+ // this like so:
+ // A breakpoint is set, and hit (but not removed), and all the
+ // EE threads come to a skreeching halt. The Debugger RC thread
+ // continues along, and is told to SetIP of the thread that hit
+ // the BP to whatever. Eventually the RC thread is told to continue,
+ // and at that point the EE thread is released, finishes DispatchPatchOrSingleStep,
+ // and shows up here.
+ // At that point, if the thread's current PC is
+ // different from the patch PC, then SetIP must have moved it elsewhere
+ // & we shouldn't do this patch skip (which will put us back to where
+ // we were, which is clearly wrong). If the PC _is_ the same, then
+ // the thread hasn't been moved, the patch is still in the code stream,
+ // and we want to do the patch skip thing in order to execute this
+ // instruction w/o removing it from the code stream.
+
+ DebuggerControllerPatch *patch = g_patches->GetPatch((CORDB_ADDRESS_TYPE *)PC);
+ DebuggerPatchSkip *skip = NULL;
+
+ if (patch != NULL && patch->IsNativePatch())
+ {
+ //
+ // We adjust the thread's PC to someplace where we write
+ // the next instruction, then
+ // we single step over that, then we set the PC back here so
+ // we don't let other threads race past here while we're stepping
+ // this one.
+ //
+ // !!! check result
+ LOG((LF_CORDB,LL_INFO10000, "DC::APS: About to skip from PC=0x%p\n", PC));
+ skip = new (interopsafe) DebuggerPatchSkip(thread, patch, thread->GetDomain());
+ TRACE_ALLOC(skip);
+ }
+
+ return skip;
+}
+
+DPOSS_ACTION DebuggerController::ScanForTriggers(CORDB_ADDRESS_TYPE *address,
+ Thread *thread,
+ CONTEXT *context,
+ DebuggerControllerQueue *pDcq,
+ SCAN_TRIGGER stWhat,
+ TP_RESULT *pTpr)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ // @todo - should this throw or not?
+ NOTHROW;
+
+ // call Triggers which may invoke GC stuff... See comment in DispatchNativeException for why it's disabled.
+ DISABLED(GC_TRIGGERS);
+ PRECONDITION(!ThisIsHelperThreadWorker());
+
+ PRECONDITION(CheckPointer(address));
+ PRECONDITION(CheckPointer(thread));
+ PRECONDITION(CheckPointer(context));
+ PRECONDITION(CheckPointer(pDcq));
+ PRECONDITION(CheckPointer(pTpr));
+
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(HasLock());
+
+ CONTRACT_VIOLATION(ThrowsViolation);
+
+ LOG((LF_CORDB, LL_INFO10000, "DC::SFT: starting scan for addr:0x%p"
+ " thread:0x%x\n", address, thread));
+
+ _ASSERTE( pTpr != NULL );
+ DebuggerControllerPatch *patch = NULL;
+
+ if (g_patches != NULL)
+ patch = g_patches->GetPatch(address);
+
+ ULONG iEvent = UINT32_MAX;
+ ULONG iEventNext = UINT32_MAX;
+ BOOL fDone = FALSE;
+
+ // This is a debugger exception if there's a patch here, or
+ // we're here for something like a single step.
+ DPOSS_ACTION used = DPOSS_INVALID;
+ if ((patch != NULL) || !IsPatched(address, TRUE))
+ {
+ // we are sure that we care for this exception but not sure
+ // if we will send event to the RS
+ used = DPOSS_USED_WITH_NO_EVENT;
+ }
+ else
+ {
+ // initialize it to don't care for now
+ used = DPOSS_DONT_CARE;
+ }
+
+ TP_RESULT tpr = TPR_IGNORE;
+
+ while (stWhat & ST_PATCH &&
+ patch != NULL &&
+ !fDone)
+ {
+ _ASSERTE(IsInUsedAction(used) == true);
+
+ DebuggerControllerPatch *patchNext
+ = g_patches->GetNextPatch(patch);
+
+ LOG((LF_CORDB, LL_INFO10000, "DC::SFT: patch 0x%x, patchNext 0x%x\n", patch, patchNext));
+
+ // Annoyingly, TriggerPatch may add patches, which may cause
+ // the patch table to move, which may, in turn, invalidate
+ // the patch (and patchNext) pointers. Store indeces, instead.
+ iEvent = g_patches->GetItemIndex( (HASHENTRY *)patch );
+
+ if (patchNext != NULL)
+ {
+ iEventNext = g_patches->GetItemIndex((HASHENTRY *)patchNext);
+ }
+
+ if (MatchPatch(thread, context, patch))
+ {
+ LOG((LF_CORDB, LL_INFO10000, "DC::SFT: patch matched\n"));
+ AddRef(patch);
+
+ // We are hitting a patch at a virtual trace call target, so let's trigger trace call here.
+ if (patch->trace.GetTraceType() == TRACE_ENTRY_STUB)
+ {
+ patch->controller->TriggerTraceCall(thread, dac_cast<PTR_CBYTE>(::GetIP(context)));
+ tpr = TPR_IGNORE;
+ }
+ else
+ {
+ // Mark if we're at an unsafe place.
+ AtSafePlaceHolder unsafePlaceHolder(thread);
+
+ tpr = patch->controller->TriggerPatch(patch,
+ thread,
+ TY_NORMAL);
+ }
+
+ // Any patch may potentially send an event.
+ // (Whereas some single-steps are "internal-only" and can
+ // never send an event- such as a single step over an exception that
+ // lands us in la-la land.)
+ used = DPOSS_USED_WITH_EVENT;
+
+ if (tpr == TPR_TRIGGER ||
+ tpr == TPR_TRIGGER_ONLY_THIS ||
+ tpr == TPR_TRIGGER_ONLY_THIS_AND_LOOP)
+ {
+ // Make sure we've still got a valid pointer.
+ patch = (DebuggerControllerPatch *)
+ DebuggerController::g_patches->GetEntryPtr( iEvent );
+
+ pDcq->dcqEnqueue(patch->controller, TRUE); // <REVISIT_TODO>@todo Return value</REVISIT_TODO>
+ }
+
+ // Make sure we've got a valid pointer in case TriggerPatch
+ // returned false but still caused the table to move.
+ patch = (DebuggerControllerPatch *)
+ g_patches->GetEntryPtr( iEvent );
+
+ // A patch can be deleted as a result of it's being triggered.
+ // The actual deletion of the patch is delayed until after the
+ // the end of the trigger.
+ // Moreover, "patchNext" could have been deleted as a result of DisableAll()
+ // being called in TriggerPatch(). Thus, we should update our patchNext
+ // pointer now. We were just lucky before, because the now-deprecated
+ // "deleted" flag didn't get set when we iterate the patches in DisableAll().
+ patchNext = g_patches->GetNextPatch(patch);
+ if (patchNext != NULL)
+ iEventNext = g_patches->GetItemIndex((HASHENTRY *)patchNext);
+
+ // Note that Release() actually removes the patch if its ref count
+ // reaches 0 after the release.
+ Release(patch);
+ }
+
+ if (tpr == TPR_IGNORE_AND_STOP ||
+ tpr == TPR_TRIGGER_ONLY_THIS ||
+ tpr == TPR_TRIGGER_ONLY_THIS_AND_LOOP)
+ {
+#ifdef _DEBUG
+ if (tpr == TPR_TRIGGER_ONLY_THIS ||
+ tpr == TPR_TRIGGER_ONLY_THIS_AND_LOOP)
+ _ASSERTE(pDcq->dcqGetCount() == 1);
+#endif //_DEBUG
+
+ fDone = TRUE;
+ }
+ else if (patchNext != NULL)
+ {
+ patch = (DebuggerControllerPatch *)
+ g_patches->GetEntryPtr(iEventNext);
+ }
+ else
+ {
+ patch = NULL;
+ }
+ }
+
+ if (stWhat & ST_SINGLE_STEP &&
+ tpr != TPR_TRIGGER_ONLY_THIS)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "DC::SFT: Trigger controllers with single step\n"));
+
+ //
+ // Now, go ahead & trigger all controllers with
+ // single step events
+ //
+
+ DebuggerController *p;
+
+ p = g_controllers;
+ while (p != NULL)
+ {
+ DebuggerController *pNext = p->m_next;
+
+ if (p->m_thread == thread && p->m_singleStep)
+ {
+ if (used == DPOSS_DONT_CARE)
+ {
+ // Debugger does care for this exception.
+ used = DPOSS_USED_WITH_NO_EVENT;
+ }
+
+ if (p->TriggerSingleStep(thread, (const BYTE *)address))
+ {
+ // by now, we should already know that we care for this exception.
+ _ASSERTE(IsInUsedAction(used) == true);
+
+ // now we are sure that we will send event to the RS
+ used = DPOSS_USED_WITH_EVENT;
+ pDcq->dcqEnqueue(p, FALSE); // <REVISIT_TODO>@todo Return value</REVISIT_TODO>
+
+ }
+ }
+
+ p = pNext;
+ }
+
+ UnapplyTraceFlag(thread);
+
+ //
+ // See if we have any steppers still active for this thread, if so
+ // re-apply the trace flag.
+ //
+
+ p = g_controllers;
+ while (p != NULL)
+ {
+ if (p->m_thread == thread && p->m_singleStep)
+ {
+ ApplyTraceFlag(thread);
+ break;
+ }
+
+ p = p->m_next;
+ }
+ }
+
+ // Significant speed increase from single dereference, I bet :)
+ (*pTpr) = tpr;
+
+ LOG((LF_CORDB, LL_INFO10000, "DC::SFT returning 0x%x as used\n",used));
+ return used;
+}
+
+#ifdef EnC_SUPPORTED
+DebuggerControllerPatch *DebuggerController::IsXXXPatched(const BYTE *PC,
+ DEBUGGER_CONTROLLER_TYPE dct)
+{
+ _ASSERTE(g_patches != NULL);
+
+ DebuggerControllerPatch *patch = g_patches->GetPatch((CORDB_ADDRESS_TYPE *)PC);
+
+ while(patch != NULL &&
+ (int)patch->controller->GetDCType() <= (int)dct)
+ {
+ if (patch->IsNativePatch() &&
+ patch->controller->GetDCType()==dct)
+ {
+ return patch;
+ }
+ patch = g_patches->GetNextPatch(patch);
+ }
+
+ return NULL;
+}
+
+// This function will check for an EnC patch at the given address and return
+// it if one is there, otherwise it will return NULL.
+DebuggerControllerPatch *DebuggerController::GetEnCPatch(const BYTE *address)
+{
+ _ASSERTE(address);
+
+ if( g_pEEInterface->IsManagedNativeCode(address) )
+ {
+ DebuggerJitInfo *dji = g_pDebugger->GetJitInfoFromAddr((TADDR) address);
+ if (dji == NULL)
+ return NULL;
+
+ // we can have two types of patches - one in code where the IL has been updated to trigger
+ // the switch and the other in the code we've switched to in order to trigger FunctionRemapComplete
+ // callback. If version == default then can't be the latter, but otherwise if haven't handled the
+ // remap for this function yet is certainly the latter.
+ if (! dji->m_encBreakpointsApplied &&
+ (dji->m_encVersion == CorDB_DEFAULT_ENC_FUNCTION_VERSION))
+ {
+ return NULL;
+ }
+ }
+ return IsXXXPatched(address, DEBUGGER_CONTROLLER_ENC);
+}
+#endif //EnC_SUPPORTED
+
+// DebuggerController::DispatchPatchOrSingleStep - Ask any patches that are active at a given
+// address if they want to do anything about the exception that's occurred there. How: For the given
+// address, go through the list of patches & see if any of them are interested (by invoking their
+// DebuggerController's TriggerPatch). Put any DCs that are interested into a queue and then calls
+// SendEvent on each.
+// Note that control will not return from this function in the case of EnC remap
+DPOSS_ACTION DebuggerController::DispatchPatchOrSingleStep(Thread *thread, CONTEXT *context, CORDB_ADDRESS_TYPE *address, SCAN_TRIGGER which)
+{
+ CONTRACT(DPOSS_ACTION)
+ {
+ // @todo - should this throw or not?
+ NOTHROW;
+ DISABLED(GC_TRIGGERS); // Only GC triggers if we send an event. See Comment in DispatchNativeException
+ PRECONDITION(!ThisIsHelperThreadWorker());
+
+ PRECONDITION(CheckPointer(thread));
+ PRECONDITION(CheckPointer(context));
+ PRECONDITION(CheckPointer(address));
+ PRECONDITION(!HasLock());
+
+ POSTCONDITION(!HasLock()); // make sure we're not leaking the controller lock
+ }
+ CONTRACT_END;
+
+ CONTRACT_VIOLATION(ThrowsViolation);
+
+ LOG((LF_CORDB|LF_ENC,LL_INFO1000,"DC:DPOSS at 0x%x trigger:0x%x\n", address, which));
+
+ // We should only have an exception if some managed thread was running.
+ // Thus we should never be here when we're stopped.
+ // @todo - this assert fires! Is that an issue, or is it invalid?
+ //_ASSERTE(!g_pDebugger->IsStopped());
+ DPOSS_ACTION used = DPOSS_DONT_CARE;
+
+ DebuggerControllerQueue dcq;
+ if (!g_patchTableValid)
+ {
+
+ LOG((LF_CORDB|LF_ENC, LL_INFO1000, "DC::DPOSS returning, no patch table.\n"));
+ RETURN (used);
+ }
+ _ASSERTE(g_patches != NULL);
+
+ CrstHolderWithState lockController(&g_criticalSection);
+
+#ifdef EnC_SUPPORTED
+ DebuggerControllerPatch *dcpEnCOriginal = NULL;
+
+ // If this sequence point has an EnC patch, we want to process it ahead of any others. If the
+ // debugger wants to remap the function at this point, then we'll call ResumeInUpdatedFunction and
+ // not return, otherwise we will just continue with regular patch-handling logic
+ dcpEnCOriginal = GetEnCPatch(dac_cast<PTR_CBYTE>(GetIP(context)));
+
+ if (dcpEnCOriginal)
+ {
+ LOG((LF_CORDB|LF_ENC,LL_INFO10000, "DC::DPOSS EnC short-circuit\n"));
+ TP_RESULT tpres =
+ dcpEnCOriginal->controller->TriggerPatch(dcpEnCOriginal,
+ thread,
+ TY_SHORT_CIRCUIT);
+
+ // We will only come back here on a RemapOppporunity that wasn't taken, or on a RemapComplete.
+ // If we processed a RemapComplete (which returns TPR_IGNORE_AND_STOP), then don't want to handle
+ // additional breakpoints on the current line because we've already effectively executed to that point
+ // and would have hit them already. If they are new, we also don't want to hit them because eg. if are
+ // sitting on line 10 and add a breakpoint at line 10 and step,
+ // don't expect to stop at line 10, expect to go to line 11.
+ //
+ // Special case is if an EnC remap breakpoint exists in the function. This could only happen if the function was
+ // updated between the RemapOpportunity and the RemapComplete. In that case we want to not skip the patches
+ // and fall through to handle the remap breakpoint.
+
+ if (tpres == TPR_IGNORE_AND_STOP)
+ {
+ // It was a RemapComplete, so fall through. Set dcpEnCOriginal to NULL to indicate that any
+ // EnC patch still there should be treated as a new patch. Any RemapComplete patch will have been
+ // already removed by patch processing.
+ dcpEnCOriginal = NULL;
+ LOG((LF_CORDB|LF_ENC,LL_INFO10000, "DC::DPOSS done EnC short-circuit, exiting\n"));
+ used = DPOSS_USED_WITH_EVENT; // indicate that we handled a patch
+ goto Exit;
+ }
+
+ _ASSERTE(tpres==TPR_IGNORE);
+ LOG((LF_CORDB|LF_ENC,LL_INFO10000, "DC::DPOSS done EnC short-circuit, ignoring\n"));
+ // if we got here, then the EnC remap opportunity was not taken, so just continue on.
+ }
+#endif // EnC_SUPPORTED
+
+ TP_RESULT tpr;
+
+ used = ScanForTriggers((CORDB_ADDRESS_TYPE *)address, thread, context, &dcq, which, &tpr);
+
+ LOG((LF_CORDB|LF_ENC, LL_EVERYTHING, "DC::DPOSS ScanForTriggers called and returned.\n"));
+
+
+ // If we setip, then that will change the address in the context.
+ // Remeber the old address so that we can compare it to the context's ip and see if it changed.
+ // If it did change, then don't dispatch our current event.
+ TADDR originalAddress = (TADDR) address;
+
+#ifdef _DEBUG
+ // If we do a SetIP after this point, the value of address will be garbage. Set it to a distictive pattern now, so
+ // we don't accidentally use what will (98% of the time) appear to be a valid value.
+ address = (CORDB_ADDRESS_TYPE *)(UINT_PTR)0xAABBCCFF;
+#endif //_DEBUG
+
+ if (dcq.dcqGetCount()> 0)
+ {
+ lockController.Release();
+
+ // Mark if we're at an unsafe place.
+ bool atSafePlace = g_pDebugger->IsThreadAtSafePlace(thread);
+ if (!atSafePlace)
+ g_pDebugger->IncThreadsAtUnsafePlaces();
+
+ DWORD dwEvent = 0xFFFFFFFF;
+ DWORD dwNumberEvents = 0;
+ BOOL reabort = FALSE;
+
+ SENDIPCEVENT_BEGIN(g_pDebugger, thread);
+
+ // Now that we've resumed from blocking, check if somebody did a SetIp on us.
+ bool fIpChanged = (originalAddress != GetIP(context));
+
+ // Send the events outside of the controller lock
+ bool anyEventsSent = false;
+
+ dwNumberEvents = dcq.dcqGetCount();
+ dwEvent = 0;
+
+ while (dwEvent < dwNumberEvents)
+ {
+ DebuggerController *event = dcq.dcqGetElement(dwEvent);
+
+ if (!event->m_deleted)
+ {
+#ifdef DEBUGGING_SUPPORTED
+ if (thread->GetDomain()->IsDebuggerAttached())
+ {
+ if (event->SendEvent(thread, fIpChanged))
+ {
+ anyEventsSent = true;
+ }
+ }
+#endif //DEBUGGING_SUPPORTED
+ }
+
+ dwEvent++;
+ }
+
+ // Trap all threads if necessary, but only if we actually sent a event up (i.e., all the queued events weren't
+ // deleted before we got a chance to get the EventSending lock.)
+ if (anyEventsSent)
+ {
+ LOG((LF_CORDB|LF_ENC, LL_EVERYTHING, "DC::DPOSS We sent an event\n"));
+ g_pDebugger->SyncAllThreads(SENDIPCEVENT_PtrDbgLockHolder);
+ LOG((LF_CORDB,LL_INFO1000, "SAT called!\n"));
+ }
+
+
+ // If we need to to a re-abort (see below), then save the current IP in the thread's context before we block and
+ // possibly let another func eval get setup.
+ reabort = thread->m_StateNC & Thread::TSNC_DebuggerReAbort;
+ SENDIPCEVENT_END;
+
+ if (!atSafePlace)
+ g_pDebugger->DecThreadsAtUnsafePlaces();
+
+ lockController.Acquire();
+
+ // Dequeue the events while we have the controller lock.
+ dwEvent = 0;
+ while (dwEvent < dwNumberEvents)
+ {
+ dcq.dcqDequeue();
+ dwEvent++;
+ }
+ // If a func eval completed with a ThreadAbortException, go ahead and setup the thread to re-abort itself now
+ // that we're continuing the thread. Note: we make sure that the thread's IP hasn't changed between now and when
+ // we blocked above. While blocked above, the debugger has a chance to setup another func eval on this
+ // thread. If that happens, we don't want to setup the reabort just yet.
+ if (reabort)
+ {
+ if ((UINT_PTR)GetEEFuncEntryPoint(::FuncEvalHijack) != (UINT_PTR)GetIP(context))
+ {
+ HRESULT hr;
+ hr = g_pDebugger->FuncEvalSetupReAbort(thread, Thread::TAR_Thread);
+ _ASSERTE(SUCCEEDED(hr));
+ }
+ }
+ }
+
+#if defined EnC_SUPPORTED
+Exit:
+#endif
+
+ // Note: if the thread filter context is NULL, then SetIP would have failed & thus we should do the
+ // patch skip thing.
+ // @todo - do we need to get the context again here?
+ CONTEXT *pCtx = GetManagedLiveCtx(thread);
+
+#ifdef EnC_SUPPORTED
+ DebuggerControllerPatch *dcpEnCCurrent = GetEnCPatch(dac_cast<PTR_CBYTE>((GetIP(context))));
+
+ // we have a new patch if the original was null and the current is non-null. Otherwise we have an old
+ // patch. We want to skip old patches, but handle new patches.
+ if (dcpEnCOriginal == NULL && dcpEnCCurrent != NULL)
+ {
+ LOG((LF_CORDB|LF_ENC,LL_INFO10000, "DC::DPOSS EnC post-processing\n"));
+ dcpEnCCurrent->controller->TriggerPatch( dcpEnCCurrent,
+ thread,
+ TY_SHORT_CIRCUIT);
+ used = DPOSS_USED_WITH_EVENT; // indicate that we handled a patch
+ }
+#endif
+
+ ActivatePatchSkip(thread, dac_cast<PTR_CBYTE>(GetIP(pCtx)), FALSE);
+
+ lockController.Release();
+
+
+ // We pulse the GC mode here too cooperate w/ a thread trying to suspend the runtime. If we didn't pulse
+ // the GC, the odds of catching this thread in interuptable code may be very small (since this filter
+ // could be very large compared to the managed code this thread is running).
+ // Only do this if the exception was actually for the debugger. (We don't want to toggle the GC mode on every
+ // random exception). We can't do this while holding any debugger locks.
+ if (used == DPOSS_USED_WITH_EVENT)
+ {
+ bool atSafePlace = g_pDebugger->IsThreadAtSafePlace(thread);
+ if (!atSafePlace)
+ {
+ g_pDebugger->IncThreadsAtUnsafePlaces();
+ }
+
+ // Always pulse the GC mode. This will allow an async break to complete even if we have a patch
+ // at an unsafe place.
+ // If we are at an unsafe place, then we can't do a GC.
+ thread->PulseGCMode();
+
+ if (!atSafePlace)
+ {
+ g_pDebugger->DecThreadsAtUnsafePlaces();
+ }
+
+ }
+
+ RETURN used;
+}
+
+bool DebuggerController::IsSingleStepEnabled()
+{
+ LIMITED_METHOD_CONTRACT;
+ return m_singleStep;
+}
+
+void DebuggerController::EnableSingleStep()
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+#ifdef _DEBUG
+ // Some controllers don't need to set the SS to do their job, and if they are setting it, it's likely an issue.
+ // So we assert here to catch them red-handed. This assert can always be updated to accomodate changes
+ // in a controller's behavior.
+
+ switch(GetDCType())
+ {
+ case DEBUGGER_CONTROLLER_THREAD_STARTER:
+ case DEBUGGER_CONTROLLER_BREAKPOINT:
+ case DEBUGGER_CONTROLLER_USER_BREAKPOINT:
+ case DEBUGGER_CONTROLLER_FUNC_EVAL_COMPLETE:
+ CONSISTENCY_CHECK_MSGF(false, ("Controller pThis=%p shouldn't be setting ss flag.", this));
+ break;
+ default: // MingW compilers require all enum cases to be handled in switch statement.
+ break;
+ }
+#endif
+
+ EnableSingleStep(m_thread);
+ m_singleStep = true;
+}
+
+#ifdef EnC_SUPPORTED
+// Note that this doesn't tell us if Single Stepping is currently enabled
+// at the hardware level (ie, for x86, if (context->EFlags & 0x100), but
+// rather, if we WANT single stepping enabled (pThread->m_State &Thread::TS_DebuggerIsStepping)
+// This gets called from exactly one place - ActivatePatchSkipForEnC
+BOOL DebuggerController::IsSingleStepEnabled(Thread *pThread)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ // This should be an atomic operation, do we
+ // don't need to lock it.
+ if(pThread->m_StateNC & Thread::TSNC_DebuggerIsStepping)
+ {
+ _ASSERTE(pThread->m_StateNC & Thread::TSNC_DebuggerIsStepping);
+
+ return TRUE;
+ }
+ else
+ return FALSE;
+}
+#endif //EnC_SUPPORTED
+
+void DebuggerController::EnableSingleStep(Thread *pThread)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB,LL_INFO1000, "DC::EnableSingleStep\n"));
+
+ _ASSERTE(pThread != NULL);
+
+ ControllerLockHolder lockController;
+
+ ApplyTraceFlag(pThread);
+}
+
+// Disable Single stepping for this controller.
+// If none of the controllers on this thread want single-stepping, then also
+// ensure that it's disabled on the hardware level.
+void DebuggerController::DisableSingleStep()
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(m_thread != NULL);
+
+ LOG((LF_CORDB,LL_INFO1000, "DC::DisableSingleStep\n"));
+
+ ControllerLockHolder lockController;
+ {
+ DebuggerController *p = g_controllers;
+
+ m_singleStep = false;
+
+ while (p != NULL)
+ {
+ if (p->m_thread == m_thread
+ && p->m_singleStep)
+ break;
+
+ p = p->m_next;
+ }
+
+ if (p == NULL)
+ {
+ UnapplyTraceFlag(m_thread);
+ }
+ }
+}
+
+
+//
+// ApplyTraceFlag sets the trace flag (i.e., turns on single-stepping)
+// for a thread.
+//
+void DebuggerController::ApplyTraceFlag(Thread *thread)
+{
+ LOG((LF_CORDB,LL_INFO1000, "DC::ApplyTraceFlag thread:0x%x [0x%0x]\n", thread, Debugger::GetThreadIdHelper(thread)));
+
+ CONTEXT *context;
+ if(thread->GetInteropDebuggingHijacked())
+ {
+ context = GetManagedLiveCtx(thread);
+ }
+ else
+ {
+ context = GetManagedStoppedCtx(thread);
+ }
+ CONSISTENCY_CHECK_MSGF(context != NULL, ("Can't apply ss flag to thread 0x%p b/c it's not in a safe place.\n", thread));
+ PREFIX_ASSUME(context != NULL);
+
+
+ g_pEEInterface->MarkThreadForDebugStepping(thread, true);
+ LOG((LF_CORDB,LL_INFO1000, "DC::ApplyTraceFlag marked thread for debug stepping\n"));
+
+ SetSSFlag(reinterpret_cast<DT_CONTEXT *>(context) ARM_ARG(thread));
+ LOG((LF_CORDB,LL_INFO1000, "DC::ApplyTraceFlag Leaving, baby!\n"));
+}
+
+//
+// UnapplyTraceFlag sets the trace flag for a thread.
+// Removes the hardware trace flag on this thread.
+//
+
+void DebuggerController::UnapplyTraceFlag(Thread *thread)
+{
+ LOG((LF_CORDB,LL_INFO1000, "DC::UnapplyTraceFlag thread:0x%x\n", thread));
+
+
+ // Either this is the helper thread, or we're manipulating our own context.
+ _ASSERTE(
+ ThisIsHelperThreadWorker() ||
+ (thread == ::GetThread())
+ );
+
+ CONTEXT *context = GetManagedStoppedCtx(thread);
+
+ // If there's no context available, then the thread shouldn't have the single-step flag
+ // enabled and there's nothing for us to do.
+ if (context == NULL)
+ {
+ // In theory, I wouldn't expect us to ever get here.
+ // Even if we are here, our single-step flag should already be deactivated,
+ // so there should be nothing to do. However, we still assert b/c we want to know how
+ // we'd actually hit this.
+ // @todo - is there a path if TriggerUnwind() calls DisableAll(). But why would
+ CONSISTENCY_CHECK_MSGF(false, ("How did we get here?. thread=%p\n", thread));
+ LOG((LF_CORDB,LL_INFO1000, "DC::UnapplyTraceFlag couldn't get context.\n"));
+ return;
+ }
+
+ // Always need to unmark for stepping
+ g_pEEInterface->MarkThreadForDebugStepping(thread, false);
+ UnsetSSFlag(reinterpret_cast<DT_CONTEXT *>(context) ARM_ARG(thread));
+}
+
+void DebuggerController::EnableExceptionHook()
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(m_thread != NULL);
+
+ ControllerLockHolder lockController;
+
+ m_exceptionHook = true;
+}
+
+void DebuggerController::DisableExceptionHook()
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(m_thread != NULL);
+
+ ControllerLockHolder lockController;
+ m_exceptionHook = false;
+}
+
+
+// void DebuggerController::DispatchExceptionHook() Called before
+// the switch statement in DispatchNativeException (therefore
+// when any exception occurs), this allows patches to do something before the
+// regular DispatchX methods.
+// How: Iterate through list of controllers. If m_exceptionHook
+// is set & m_thread is either thread or NULL, then invoke TriggerExceptionHook()
+BOOL DebuggerController::DispatchExceptionHook(Thread *thread,
+ CONTEXT *context,
+ EXCEPTION_RECORD *pException)
+{
+ // ExceptionHook has restrictive contract b/c it could come from anywhere.
+ // This can only modify controller's internal state. Can't send managed debug events.
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ GC_NOTRIGGER;
+ NOTHROW;
+ MODE_ANY;
+
+ // Filter context not set yet b/c we can only set it in COOP, and this may be in preemptive.
+ PRECONDITION(thread == ::GetThread());
+ PRECONDITION((g_pEEInterface->GetThreadFilterContext(thread) == NULL));
+ PRECONDITION(CheckPointer(pException));
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_INFO1000, "DC:: DispatchExceptionHook\n"));
+
+ if (!g_patchTableValid)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "DC::DEH returning, no patch table.\n"));
+ return (TRUE);
+ }
+
+
+ _ASSERTE(g_patches != NULL);
+
+ ControllerLockHolder lockController;
+
+ TP_RESULT tpr = TPR_IGNORE;
+ DebuggerController *p;
+
+ p = g_controllers;
+ while (p != NULL)
+ {
+ DebuggerController *pNext = p->m_next;
+
+ if (p->m_exceptionHook
+ && (p->m_thread == NULL || p->m_thread == thread) &&
+ tpr != TPR_IGNORE_AND_STOP)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "DC::DEH calling TEH...\n"));
+ tpr = p->TriggerExceptionHook(thread, context , pException);
+ LOG((LF_CORDB, LL_INFO1000, "DC::DEH ... returned.\n"));
+
+ if (tpr == TPR_IGNORE_AND_STOP)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "DC:: DEH: leaving early!\n"));
+ break;
+ }
+ }
+
+ p = pNext;
+ }
+
+ LOG((LF_CORDB, LL_INFO1000, "DC:: DEH: returning 0x%x!\n", tpr));
+
+ return (tpr != TPR_IGNORE_AND_STOP);
+}
+
+//
+// EnableUnwind enables an unwind event to be called when the stack is unwound
+// (via an exception) to or past the given pointer.
+//
+
+void DebuggerController::EnableUnwind(FramePointer fp)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ ASSERT(m_thread != NULL);
+ LOG((LF_CORDB,LL_EVERYTHING,"DC:EU EnableUnwind at 0x%x\n", fp.GetSPValue()));
+
+ ControllerLockHolder lockController;
+ m_unwindFP = fp;
+}
+
+FramePointer DebuggerController::GetUnwind()
+{
+ LIMITED_METHOD_CONTRACT;
+
+ return m_unwindFP;
+}
+
+//
+// DisableUnwind disables the unwind event for the controller.
+//
+
+void DebuggerController::DisableUnwind()
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ CAN_TAKE_LOCK;
+ }
+ CONTRACTL_END;
+
+ ASSERT(m_thread != NULL);
+
+ LOG((LF_CORDB,LL_INFO1000, "DC::DU\n"));
+
+ ControllerLockHolder lockController;
+
+ m_unwindFP = LEAF_MOST_FRAME;
+}
+
+//
+// DispatchUnwind is called when an unwind happens.
+// the event to the appropriate controllers.
+// - handlerFP is the frame pointer that the handler will be invoked at.
+// - DJI is EnC-aware method that the handler is in.
+// - newOffset is the
+//
+bool DebuggerController::DispatchUnwind(Thread *thread,
+ MethodDesc *fd, DebuggerJitInfo * pDJI,
+ SIZE_T newOffset,
+ FramePointer handlerFP,
+ CorDebugStepReason unwindReason)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER; // don't send IPC events
+ MODE_COOPERATIVE; // TriggerUnwind always is coop
+
+ PRECONDITION(!IsDbgHelperSpecialThread());
+ }
+ CONTRACTL_END;
+
+
+ CONTRACT_VIOLATION(ThrowsViolation); // trigger unwind throws
+
+ _ASSERTE(unwindReason == STEP_EXCEPTION_FILTER || unwindReason == STEP_EXCEPTION_HANDLER);
+
+ bool used = false;
+
+ LOG((LF_CORDB, LL_INFO10000, "DC: Dispatch Unwind\n"));
+
+ ControllerLockHolder lockController;
+ {
+ DebuggerController *p;
+
+ p = g_controllers;
+
+ while (p != NULL)
+ {
+ DebuggerController *pNext = p->m_next;
+
+ if (p->m_thread == thread && p->m_unwindFP != LEAF_MOST_FRAME)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "Dispatch Unwind: Found candidate\n"));
+
+
+ // Assumptions here:
+ // Function with handlers are -ALWAYS- EBP-frame based (JIT assumption)
+ //
+ // newFrame is the EBP for the handler
+ // p->m_unwindFP points to the stack slot with the return address of the function.
+ //
+ // For the interesting case: stepover, we want to know if the handler is in the same function
+ // as the stepper, if its above it (caller) o under it (callee) in order to know if we want
+ // to patch the handler or not.
+ //
+ // 3 cases:
+ //
+ // a) Handler is in a function under the function where the step happened. It therefore is
+ // a stepover. We don't want to patch this handler. The handler will have an EBP frame.
+ // So it will be at least be 2 DWORDs away from the m_unwindFP of the controller (
+ // 1 DWORD from the pushed return address and 1 DWORD for the push EBP).
+ //
+ // b) Handler is in the same function as the stepper. We want to patch the handler. In this
+ // case handlerFP will be the same as p->m_unwindFP-sizeof(void*). Why? p->m_unwindFP
+ // stores a pointer to the return address of the function. As a function with a handler
+ // is always EBP frame based it will have the following code in the prolog:
+ //
+ // push ebp <- ( sub esp, 4 ; mov [esp], ebp )
+ // mov esp, ebp
+ //
+ // Therefore EBP will be equal to &CallerReturnAddress-4.
+ //
+ // c) Handler is above the function where the stepper is. We want to patch the handler. handlerFP
+ // will be always greater than the pointer to the return address of the function where the
+ // stepper is.
+ //
+ //
+ //
+
+ if (IsEqualOrCloserToRoot(handlerFP, p->m_unwindFP))
+ {
+ used = true;
+
+ //
+ // Assume that this isn't going to block us at all --
+ // other threads may be waiting to patch or unpatch something,
+ // or to dispatch.
+ //
+ LOG((LF_CORDB, LL_INFO10000,
+ "Unwind trigger at offset 0x%p; handlerFP: 0x%p unwindReason: 0x%x.\n",
+ newOffset, handlerFP.GetSPValue(), unwindReason));
+
+ p->TriggerUnwind(thread,
+ fd, pDJI,
+ newOffset,
+ handlerFP,
+ unwindReason);
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO10000,
+ "Unwind trigger at offset 0x%p; handlerFP: 0x%p unwindReason: 0x%x.\n",
+ newOffset, handlerFP.GetSPValue(), unwindReason));
+ }
+ }
+
+ p = pNext;
+ }
+ }
+
+ return used;
+}
+
+//
+// EnableTraceCall enables a call event on the controller
+// maxFrame is the leaf-most frame that we want notifications for.
+// For step-in stuff, this will always be LEAF_MOST_FRAME.
+// for step-out, this will be the current frame because we don't
+// care if the current frame calls back into managed code when we're
+// only interested in our parent frames.
+//
+
+void DebuggerController::EnableTraceCall(FramePointer maxFrame)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ ASSERT(m_thread != NULL);
+
+ LOG((LF_CORDB,LL_INFO1000, "DC::ETC maxFrame=0x%x, thread=0x%x\n",
+ maxFrame.GetSPValue(), Debugger::GetThreadIdHelper(m_thread)));
+
+ // JMC stepper should never enabled this. (They should enable ME instead).
+ _ASSERTE((DEBUGGER_CONTROLLER_JMC_STEPPER != this->GetDCType()) || !"JMC stepper shouldn't enable trace-call");
+
+
+ ControllerLockHolder lockController;
+ {
+ if (!m_traceCall)
+ {
+ m_traceCall = true;
+ g_pEEInterface->EnableTraceCall(m_thread);
+ }
+
+ if (IsCloserToLeaf(maxFrame, m_traceCallFP))
+ m_traceCallFP = maxFrame;
+ }
+}
+
+struct PatchTargetVisitorData
+{
+ DebuggerController* controller;
+ FramePointer maxFrame;
+};
+
+VOID DebuggerController::PatchTargetVisitor(TADDR pVirtualTraceCallTarget, VOID* pUserData)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ DebuggerController* controller = ((PatchTargetVisitorData*) pUserData)->controller;
+ FramePointer maxFrame = ((PatchTargetVisitorData*) pUserData)->maxFrame;
+
+ EX_TRY
+ {
+ CONTRACT_VIOLATION(GCViolation); // PatchTrace throws, which implies GC-triggers
+ TraceDestination trace;
+ trace.InitForUnmanagedStub(pVirtualTraceCallTarget);
+ controller->PatchTrace(&trace, maxFrame, true);
+ }
+ EX_CATCH
+ {
+ // not much we can do here
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+}
+
+//
+// DisableTraceCall disables call events on the controller
+//
+
+void DebuggerController::DisableTraceCall()
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ ASSERT(m_thread != NULL);
+
+ ControllerLockHolder lockController;
+ {
+ if (m_traceCall)
+ {
+ LOG((LF_CORDB,LL_INFO1000, "DC::DTC thread=0x%x\n",
+ Debugger::GetThreadIdHelper(m_thread)));
+
+ g_pEEInterface->DisableTraceCall(m_thread);
+
+ m_traceCall = false;
+ m_traceCallFP = ROOT_MOST_FRAME;
+ }
+ }
+}
+
+// Get a FramePointer for the leafmost frame on this thread's stacktrace.
+// It's tempting to create this off the head of the Frame chain, but that may
+// include internal EE Frames (like GCRoot frames) which a FrameInfo-stackwalk may skip over.
+// Thus using the Frame chain would err on the side of returning a FramePointer that
+// closer to the leaf.
+FramePointer GetCurrentFramePointerFromStackTraceForTraceCall(Thread * thread)
+{
+ _ASSERTE(thread != NULL);
+
+ // Ensure this is really the same as CSI.
+ ControllerStackInfo info;
+
+ // It's possible this stackwalk may be done at an unsafe time.
+ // this method may trigger a GC, for example, in
+ // FramedMethodFrame::AskStubForUnmanagedCallSite
+ // which will trash the incoming argument array
+ // which is not gc-protected.
+
+ // We could probably imagine a more specialized stackwalk that
+ // avoids these calls and is thus GC_NOTRIGGER.
+ CONTRACT_VIOLATION(GCViolation);
+
+ // This is being run live, so there's no filter available.
+ CONTEXT *context;
+ context = g_pEEInterface->GetThreadFilterContext(thread);
+ _ASSERTE(context == NULL);
+ _ASSERTE(!ISREDIRECTEDTHREAD(thread));
+
+ // This is actually safe because we're coming from a TraceCall, which
+ // means we're not in the middle of a stub. We don't have some partially
+ // constructed frame, so we can safely traverse the stack.
+ // However, we may still have a problem w/ the GC-violation.
+ StackTraceTicket ticket(StackTraceTicket::SPECIAL_CASE_TICKET);
+ info.GetStackInfo(ticket, thread, LEAF_MOST_FRAME, NULL);
+
+ FramePointer fp = info.m_activeFrame.fp;
+
+ return fp;
+}
+//
+// DispatchTraceCall is called when a call is traced in the EE
+// It dispatches the event to the appropriate controllers.
+//
+
+bool DebuggerController::DispatchTraceCall(Thread *thread,
+ const BYTE *ip)
+{
+ CONTRACTL
+ {
+ GC_NOTRIGGER;
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ bool used = false;
+
+ LOG((LF_CORDB, LL_INFO10000,
+ "DC::DTC: TraceCall at 0x%x\n", ip));
+
+ ControllerLockHolder lockController;
+ {
+ DebuggerController *p;
+
+ p = g_controllers;
+ while (p != NULL)
+ {
+ DebuggerController *pNext = p->m_next;
+
+ if (p->m_thread == thread && p->m_traceCall)
+ {
+ bool trigger;
+
+ if (p->m_traceCallFP == LEAF_MOST_FRAME)
+ trigger = true;
+ else
+ {
+ // We know we don't have a filter context, so get a frame pointer from our frame chain.
+ FramePointer fpToCheck = GetCurrentFramePointerFromStackTraceForTraceCall(thread);
+
+
+ // <REVISIT_TODO>
+ //
+ // Currently, we never ever put a patch in an IL stub, and as such, if the IL stub
+ // throws an exception after returning from unmanaged code, we would not trigger
+ // a trace call when we call the constructor of the exception. The following is
+ // kind of a workaround to make that working. If we ever make the change to stop in
+ // IL stubs (for example, if we start to share security IL stub), then this can be
+ // removed.
+ //
+ // </REVISIT_TODO>
+
+
+
+ // It's possible this stackwalk may be done at an unsafe time.
+ // this method may trigger a GC, for example, in
+ // FramedMethodFrame::AskStubForUnmanagedCallSite
+ // which will trash the incoming argument array
+ // which is not gc-protected.
+ ControllerStackInfo info;
+ {
+ CONTRACT_VIOLATION(GCViolation);
+#ifdef _DEBUG
+ CONTEXT *context = g_pEEInterface->GetThreadFilterContext(thread);
+#endif // _DEBUG
+ _ASSERTE(context == NULL);
+ _ASSERTE(!ISREDIRECTEDTHREAD(thread));
+
+ // See explanation in GetCurrentFramePointerFromStackTraceForTraceCall.
+ StackTraceTicket ticket(StackTraceTicket::SPECIAL_CASE_TICKET);
+ info.GetStackInfo(ticket, thread, LEAF_MOST_FRAME, NULL);
+ }
+
+ if (info.m_activeFrame.chainReason == CHAIN_ENTER_UNMANAGED)
+ {
+ _ASSERTE(info.HasReturnFrame());
+
+ // This check makes sure that we don't do this logic for inlined frames.
+ if (info.m_returnFrame.md->IsILStub())
+ {
+ // Make sure that the frame pointer of the active frame is actually
+ // the address of an exit frame.
+ _ASSERTE( (static_cast<Frame*>(info.m_activeFrame.fp.GetSPValue()))->GetFrameType()
+ == Frame::TYPE_EXIT );
+ _ASSERTE(!info.m_returnFrame.HasChainMarker());
+ fpToCheck = info.m_returnFrame.fp;
+ }
+ }
+
+ // @todo - This comparison seems somewhat nonsensical. We don't have a filter context
+ // in place, so what frame pointer is fpToCheck actually for?
+ trigger = IsEqualOrCloserToRoot(fpToCheck, p->m_traceCallFP);
+ }
+
+ if (trigger)
+ {
+ used = true;
+
+ // This can only update controller's state, can't actually send IPC events.
+ p->TriggerTraceCall(thread, ip);
+ }
+ }
+
+ p = pNext;
+ }
+ }
+
+ return used;
+}
+
+bool DebuggerController::IsMethodEnterEnabled()
+{
+ LIMITED_METHOD_CONTRACT;
+ return m_fEnableMethodEnter;
+}
+
+
+// Notify dispatching logic that this controller wants to get TriggerMethodEnter
+// We keep a count of total controllers waiting for MethodEnter (in g_cTotalMethodEnter).
+// That way we know if any controllers want MethodEnter callbacks. If none do,
+// then we can set the JMC probe flag to false for all modules.
+void DebuggerController::EnableMethodEnter()
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ ControllerLockHolder chController;
+ Debugger::DebuggerDataLockHolder chInfo(g_pDebugger);
+
+ // Both JMC + Traditional steppers may use MethodEnter.
+ // For JMC, it's a core part of functionality. For Traditional steppers, we use it as a backstop
+ // in case the stub-managers fail.
+ _ASSERTE(g_cTotalMethodEnter >= 0);
+ if (!m_fEnableMethodEnter)
+ {
+ LOG((LF_CORDB, LL_INFO1000000, "DC::EnableME, this=%p, previously disabled\n", this));
+ m_fEnableMethodEnter = true;
+
+ g_cTotalMethodEnter++;
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO1000000, "DC::EnableME, this=%p, already set\n", this));
+ }
+ g_pDebugger->UpdateAllModuleJMCFlag(g_cTotalMethodEnter != 0); // Needs JitInfo lock
+}
+
+// Notify dispatching logic that this controller doesn't want to get
+// TriggerMethodEnter
+void DebuggerController::DisableMethodEnter()
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ ControllerLockHolder chController;
+ Debugger::DebuggerDataLockHolder chInfo(g_pDebugger);
+
+ if (m_fEnableMethodEnter)
+ {
+ LOG((LF_CORDB, LL_INFO1000000, "DC::DisableME, this=%p, previously set\n", this));
+ m_fEnableMethodEnter = false;
+
+ g_cTotalMethodEnter--;
+ _ASSERTE(g_cTotalMethodEnter >= 0);
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO1000000, "DC::DisableME, this=%p, already disabled\n", this));
+ }
+
+ g_pDebugger->UpdateAllModuleJMCFlag(g_cTotalMethodEnter != 0); // Needs JitInfo lock
+}
+
+// Loop through controllers and dispatch TriggerMethodEnter
+void DebuggerController::DispatchMethodEnter(void * pIP, FramePointer fp)
+{
+ _ASSERTE(pIP != NULL);
+
+ Thread * pThread = g_pEEInterface->GetThread();
+ _ASSERTE(pThread != NULL);
+
+ // Lookup the DJI for this method & ip.
+ // Since we create DJIs when we jit the code, and this code has been jitted
+ // (that's where the probe's coming from!), we will have a DJI.
+ DebuggerJitInfo * dji = g_pDebugger->GetJitInfoFromAddr((TADDR) pIP);
+
+ // This includes the case where we have a LightWeight codegen method.
+ if (dji == NULL)
+ {
+ return;
+ }
+
+ LOG((LF_CORDB, LL_INFO100000, "DC::DispatchMethodEnter for '%s::%s'\n",
+ dji->m_fd->m_pszDebugClassName,
+ dji->m_fd->m_pszDebugMethodName));
+
+ ControllerLockHolder lockController;
+
+ // For debug check, keep a count to make sure that g_cTotalMethodEnter
+ // is actually the number of controllers w/ MethodEnter enabled.
+ int count = 0;
+
+ DebuggerController *p = g_controllers;
+ while (p != NULL)
+ {
+ if (p->m_fEnableMethodEnter)
+ {
+ if ((p->GetThread() == NULL) || (p->GetThread() == pThread))
+ {
+ ++count;
+ p->TriggerMethodEnter(pThread, dji, (const BYTE *) pIP, fp);
+ }
+ }
+ p = p->m_next;
+ }
+
+ _ASSERTE(g_cTotalMethodEnter == count);
+
+}
+
+//
+// AddProtection adds page protection to (at least) the given range of
+// addresses
+//
+
+void DebuggerController::AddProtection(const BYTE *start, const BYTE *end,
+ bool readable)
+{
+ // !!!
+ _ASSERTE(!"Not implemented yet");
+}
+
+//
+// RemoveProtection removes page protection from the given
+// addresses. The parameters should match an earlier call to
+// AddProtection
+//
+
+void DebuggerController::RemoveProtection(const BYTE *start, const BYTE *end,
+ bool readable)
+{
+ // !!!
+ _ASSERTE(!"Not implemented yet");
+}
+
+
+// Default implementations for FuncEvalEnter & Exit notifications.
+void DebuggerController::TriggerFuncEvalEnter(Thread * thread)
+{
+ LOG((LF_CORDB, LL_INFO100000, "DC::TFEEnter, thead=%p, this=%p\n", thread, this));
+}
+
+void DebuggerController::TriggerFuncEvalExit(Thread * thread)
+{
+ LOG((LF_CORDB, LL_INFO100000, "DC::TFEExit, thead=%p, this=%p\n", thread, this));
+}
+
+// bool DebuggerController::TriggerPatch() What: Tells the
+// static DC whether this patch should be activated now.
+// Returns true if it should be, false otherwise.
+// How: Base class implementation returns false. Others may
+// return true.
+TP_RESULT DebuggerController::TriggerPatch(DebuggerControllerPatch *patch,
+ Thread *thread,
+ TRIGGER_WHY tyWhy)
+{
+ LOG((LF_CORDB, LL_INFO10000, "DC::TP: in default TriggerPatch\n"));
+ return TPR_IGNORE;
+}
+
+bool DebuggerController::TriggerSingleStep(Thread *thread,
+ const BYTE *ip)
+{
+ LOG((LF_CORDB, LL_INFO10000, "DC::TP: in default TriggerSingleStep\n"));
+ return false;
+}
+
+void DebuggerController::TriggerUnwind(Thread *thread,
+ MethodDesc *fd, DebuggerJitInfo * pDJI, SIZE_T offset,
+ FramePointer fp,
+ CorDebugStepReason unwindReason)
+{
+ LOG((LF_CORDB, LL_INFO10000, "DC::TP: in default TriggerUnwind\n"));
+}
+
+void DebuggerController::TriggerTraceCall(Thread *thread,
+ const BYTE *ip)
+{
+ LOG((LF_CORDB, LL_INFO10000, "DC::TP: in default TriggerTraceCall\n"));
+}
+
+TP_RESULT DebuggerController::TriggerExceptionHook(Thread *thread, CONTEXT * pContext,
+ EXCEPTION_RECORD *exception)
+{
+ LOG((LF_CORDB, LL_INFO10000, "DC::TP: in default TriggerExceptionHook\n"));
+ return TPR_IGNORE;
+}
+
+void DebuggerController::TriggerMethodEnter(Thread * thread,
+ DebuggerJitInfo * dji,
+ const BYTE * ip,
+ FramePointer fp)
+{
+ LOG((LF_CORDB, LL_INFO10000, "DC::TME in default impl. dji=%p, addr=%p, fp=%p\n",
+ dji, ip, fp.GetSPValue()));
+}
+
+bool DebuggerController::SendEvent(Thread *thread, bool fIpChanged)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ SENDEVENT_CONTRACT_ITEMS;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_INFO10000, "DC::TP: in default SendEvent\n"));
+
+ // If any derived class trigger SendEvent, it should also implement SendEvent.
+ _ASSERTE(false || !"Base DebuggerController sending an event?");
+ return false;
+}
+
+
+// Dispacth Func-Eval Enter & Exit notifications.
+void DebuggerController::DispatchFuncEvalEnter(Thread * thread)
+{
+ LOG((LF_CORDB, LL_INFO100000, "DC::DispatchFuncEvalEnter for thread 0x%p\n", thread));
+
+ ControllerLockHolder lockController;
+
+ DebuggerController *p = g_controllers;
+ while (p != NULL)
+ {
+ if ((p->GetThread() == NULL) || (p->GetThread() == thread))
+ {
+ p->TriggerFuncEvalEnter(thread);
+ }
+
+ p = p->m_next;
+ }
+
+
+}
+
+void DebuggerController::DispatchFuncEvalExit(Thread * thread)
+{
+ LOG((LF_CORDB, LL_INFO100000, "DC::DispatchFuncEvalExit for thread 0x%p\n", thread));
+
+ ControllerLockHolder lockController;
+
+ DebuggerController *p = g_controllers;
+ while (p != NULL)
+ {
+ if ((p->GetThread() == NULL) || (p->GetThread() == thread))
+ {
+ p->TriggerFuncEvalExit(thread);
+ }
+
+ p = p->m_next;
+ }
+
+
+}
+
+
+#ifdef _DEBUG
+// See comment in DispatchNativeException
+void ThisFunctionMayHaveTriggerAGC()
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ GC_TRIGGERS;
+ NOTHROW;
+ }
+ CONTRACTL_END;
+}
+#endif
+
+// bool DebuggerController::DispatchNativeException() Figures out
+// if any debugger controllers will handle the exception.
+// DispatchNativeException should be called by the EE when a native exception
+// occurs. If it returns true, the exception was generated by a Controller and
+// should be ignored.
+// How: Calls DispatchExceptionHook to see if anything is
+// interested in ExceptionHook, then does a switch on dwCode:
+// EXCEPTION_BREAKPOINT means invoke DispatchPatchOrSingleStep(ST_PATCH).
+// EXCEPTION_SINGLE_STEP means DispatchPatchOrSingleStep(ST_SINGLE_STEP).
+// EXCEPTION_ACCESS_VIOLATION means invoke DispatchAccessViolation.
+// Returns true if the exception was actually meant for the debugger,
+// returns false otherwise.
+bool DebuggerController::DispatchNativeException(EXCEPTION_RECORD *pException,
+ CONTEXT *pContext,
+ DWORD dwCode,
+ Thread *pCurThread)
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ NOTHROW;
+
+ // If this exception is for the debugger, then we may trigger a GC.
+ // But we'll be called on _any_ exception, including ones in a GC-no-triggers region.
+ // Our current contract system doesn't let us specify such conditions on GC_TRIGGERS.
+ // So we disable it now, and if we find out the exception is meant for the debugger,
+ // we'll call ThisFunctionMayHaveTriggerAGC() to ping that we're really a GC_TRIGGERS.
+ DISABLED(GC_TRIGGERS); // Only GC triggers if we send an event,
+ PRECONDITION(!IsDbgHelperSpecialThread());
+
+ // If we're called from preemptive mode, than our caller has protected the stack.
+ // If we're in cooperative mode, then we need to protect the stack before toggling GC modes
+ // (by setting the filter-context)
+ MODE_ANY;
+
+ PRECONDITION(CheckPointer(pException));
+ PRECONDITION(CheckPointer(pContext));
+ PRECONDITION(CheckPointer(pCurThread));
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_EVERYTHING, "DispatchNativeException was called\n"));
+ LOG((LF_CORDB, LL_INFO10000, "Native exception at 0x%p, code=0x%8x, context=0x%p, er=0x%p\n",
+ pException->ExceptionAddress, dwCode, pContext, pException));
+
+
+ bool fDebuggers;
+ BOOL fDispatch;
+ DPOSS_ACTION result = DPOSS_DONT_CARE;
+
+
+ // We have a potentially ugly locking problem here. This notification is called on any exception,
+ // but we have no idea what our locking context is at the time. Thus we may hold locks smaller
+ // than the controller lock.
+ // The debugger logic really only cares about exceptions directly in managed code (eg, hardware exceptions)
+ // or in patch-skippers (since that's a copy of managed code running in a look-aside buffer).
+ // That should exclude all C++ exceptions, which are the common case if Runtime code throws an internal ex.
+ // So we ignore those to avoid the lock violation.
+ if (pException->ExceptionCode == EXCEPTION_MSVC)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "Debugger skipping for C++ exception.\n"));
+ return FALSE;
+ }
+
+ // The debugger really only cares about exceptions in managed code. Any exception that occurs
+ // while the thread is redirected (such as EXCEPTION_HIJACK) is not of interest to the debugger.
+ // Allowing this would be problematic because when an exception occurs while the thread is
+ // redirected, we don't know which context (saved redirection context or filter context)
+ // we should be operating on (see code:GetManagedStoppedCtx).
+ if( ISREDIRECTEDTHREAD(pCurThread) )
+ {
+ LOG((LF_CORDB, LL_INFO1000, "Debugger ignoring exception 0x%x on redirected thread.\n", dwCode));
+
+ // We shouldn't be seeing debugging exceptions on a redirected thread. While a thread is
+ // redirected we only call a few internal things (see code:Thread.RedirectedHandledJITCase),
+ // and may call into the host. We can't call normal managed code or anything we'd want to debug.
+ _ASSERTE(dwCode != EXCEPTION_BREAKPOINT);
+ _ASSERTE(dwCode != EXCEPTION_SINGLE_STEP);
+
+ return FALSE;
+ }
+
+ // It's possible we're here without a debugger (since we have to call the
+ // patch skippers). The Debugger may detach anytime,
+ // so remember the attach state now.
+#ifdef _DEBUG
+ bool fWasAttached = false;
+#ifdef DEBUGGING_SUPPORTED
+ fWasAttached = (CORDebuggerAttached() != 0);
+#endif //DEBUGGING_SUPPORTED
+#endif //_DEBUG
+
+ {
+ // If we're in cooperative mode, it's unsafe to do a GC until we've put a filter context in place.
+ GCX_NOTRIGGER();
+
+ // If we know the debugger doesn't care about this exception, bail now.
+ // Usually this is just if there's a debugger attached.
+ // However, if a debugger detached but left outstanding controllers (like patch-skippers),
+ // we still may care.
+ // The only way a controller would get created outside of the helper thread is from
+ // a patch skipper, so we always handle breakpoints.
+ if (!CORDebuggerAttached() && (g_controllers == NULL) && (dwCode != EXCEPTION_BREAKPOINT))
+ {
+ return false;
+ }
+
+ FireEtwDebugExceptionProcessingStart();
+
+ // We should never be here if the debugger was never involved.
+ CONTEXT * pOldContext;
+ pOldContext = pCurThread->GetFilterContext();
+
+ // In most cases it is an error to nest, however in the patch-skipping logic we must
+ // copy an unknown amount of code into another buffer and it occasionally triggers
+ // an AV. This heuristic should filter that case out. See DDB 198093.
+ // Ensure we perform this exception nesting filtering even before the call to
+ // DebuggerController::DispatchExceptionHook, otherwise the nesting will continue when
+ // a contract check is triggered in DispatchExceptionHook and another BP exception is
+ // raised. See Dev11 66058.
+ if ((pOldContext != NULL) && pCurThread->AVInRuntimeImplOkay() &&
+ pException->ExceptionCode == STATUS_ACCESS_VIOLATION)
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO100, "DC::DNE Nested Access Violation at 0x%p is being ignored\n",
+ pException->ExceptionAddress);
+ return false;
+ }
+ // Otherwise it is an error to nest at all
+ _ASSERTE(pOldContext == NULL);
+
+ fDispatch = DebuggerController::DispatchExceptionHook(pCurThread,
+ pContext,
+ pException);
+
+ {
+ // Must be in cooperative mode to set the filter context. We know there are times we'll be in preemptive mode,
+ // (such as M2U handoff, or potentially patches in the middle of a stub, or various random exceptions)
+
+ // @todo - We need to worry about GC-protecting our stack. If we're in preemptive mode, the caller did it for us.
+ // If we're in cooperative, then we need to set the FilterContext *before* we toggle GC mode (since
+ // the FC protects the stack).
+ // If we're in preemptive, then we need to set the FilterContext *after* we toggle ourselves to Cooperative.
+ // Also note it may not be possible to toggle GC mode at these times (such as in the middle of the stub).
+ //
+ // Part of the problem is that the Filter Context is serving 2 purposes here:
+ // - GC protect the stack. (essential if we're in coop mode).
+ // - provide info to controllers (such as current IP, and a place to set the Single-Step flag).
+ //
+ // This contract violation is mitigated in that we must have had the debugger involved to get to this point.
+ CONTRACT_VIOLATION(ModeViolation);
+ g_pEEInterface->SetThreadFilterContext(pCurThread, pContext);
+ }
+ // Now that we've set the filter context, we can let the GCX_NOTRIGGER expire.
+ // It's still possible that we may be called from a No-trigger region.
+ }
+
+
+ if (fDispatch)
+ {
+ // Disable SingleStep for all controllers on this thread. This requires the filter context set.
+ // This is what would disable the ss-flag when single-stepping over an AV.
+ if (g_patchTableValid && (dwCode != EXCEPTION_SINGLE_STEP))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "DC::DNE non-single-step exception; check if any controller has ss turned on\n"));
+
+ ControllerLockHolder lockController;
+ for (DebuggerController* p = g_controllers; p != NULL; p = p->m_next)
+ {
+ if (p->m_singleStep && (p->m_thread == pCurThread))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "DC::DNE turn off ss for controller 0x%p\n", p));
+ p->DisableSingleStep();
+ }
+ }
+ // implicit controller lock release
+ }
+
+ CORDB_ADDRESS_TYPE * ip = dac_cast<PTR_CORDB_ADDRESS_TYPE>(GetIP(pContext));
+
+ switch (dwCode)
+ {
+ case EXCEPTION_BREAKPOINT:
+ // EIP should be properly set up at this point.
+ result = DebuggerController::DispatchPatchOrSingleStep(pCurThread,
+ pContext,
+ ip,
+ ST_PATCH);
+ LOG((LF_CORDB, LL_EVERYTHING, "DC::DNE DispatchPatch call returned\n"));
+
+ // If we detached, we should remove all our breakpoints. So if we try
+ // to handle this breakpoint, make sure that we're attached.
+ if (IsInUsedAction(result) == true)
+ {
+ _ASSERTE(fWasAttached);
+ }
+ break;
+
+ case EXCEPTION_SINGLE_STEP:
+ LOG((LF_CORDB, LL_EVERYTHING, "DC::DNE SINGLE_STEP Exception\n"));
+
+ result = DebuggerController::DispatchPatchOrSingleStep(pCurThread,
+ pContext,
+ ip,
+ (SCAN_TRIGGER)(ST_PATCH|ST_SINGLE_STEP));
+ // We pass patch | single step since single steps actually
+ // do both (eg, you SS onto a breakpoint).
+ break;
+
+ default:
+ break;
+ } // end switch
+
+ }
+#ifdef _DEBUG
+ else
+ {
+ LOG((LF_CORDB, LL_INFO1000, "DC:: DNE step-around fDispatch:0x%x!\n", fDispatch));
+ }
+#endif //_DEBUG
+
+ fDebuggers = (fDispatch?(IsInUsedAction(result)?true:false):true);
+
+ LOG((LF_CORDB, LL_INFO10000, "DC::DNE, returning 0x%x.\n", fDebuggers));
+
+#ifdef _DEBUG
+ if (fDebuggers && (result == DPOSS_USED_WITH_EVENT))
+ {
+ // If the exception belongs to the debugger, then we may have sent an event,
+ // and thus we may have triggered a GC.
+ ThisFunctionMayHaveTriggerAGC();
+ }
+#endif
+
+
+
+ // Must restore the filter context. After the filter context is gone, we're
+ // unprotected again and unsafe for a GC.
+ {
+ CONTRACT_VIOLATION(ModeViolation);
+ g_pEEInterface->SetThreadFilterContext(pCurThread, NULL);
+ }
+
+#ifdef _TARGET_ARM_
+ if (pCurThread->IsSingleStepEnabled())
+ pCurThread->ApplySingleStep(pContext);
+#endif
+
+ FireEtwDebugExceptionProcessingEnd();
+
+ return fDebuggers;
+}
+
+// * -------------------------------------------------------------------------
+// * DebuggerPatchSkip routines
+// * -------------------------------------------------------------------------
+
+DebuggerPatchSkip::DebuggerPatchSkip(Thread *thread,
+ DebuggerControllerPatch *patch,
+ AppDomain *pAppDomain)
+ : DebuggerController(thread, pAppDomain),
+ m_address(patch->address)
+{
+ LOG((LF_CORDB, LL_INFO10000,
+ "DPS::DPS: Patch skip 0x%p\n", patch->address));
+
+ // On ARM the single-step emulation already utilizes a per-thread execution buffer similar to the scheme
+ // below. As a result we can skip most of the instruction parsing logic that's instead internalized into
+ // the single-step emulation itself.
+#ifndef _TARGET_ARM_
+
+ // NOTE: in order to correctly single-step RIP-relative writes on multiple threads we need to set up
+ // a shared buffer with the instruction and a buffer for the RIP-relative value so that all threads
+ // are working on the same copy. as the single-steps complete the modified data in the buffer is
+ // copied back to the real address to ensure proper execution of the program.
+
+ //
+ // Create the shared instruction block. this will also create the shared RIP-relative buffer
+ //
+
+ m_pSharedPatchBypassBuffer = patch->GetOrCreateSharedPatchBypassBuffer();
+ BYTE* patchBypass = m_pSharedPatchBypassBuffer->PatchBypass;
+
+ // Copy the instruction block over to the patch skip
+ // WARNING: there used to be an issue here because CopyInstructionBlock copied the breakpoint from the
+ // jitted code stream into the patch buffer. Further below CORDbgSetInstruction would correct the
+ // first instruction. This buffer is shared by all threads so if another thread executed the buffer
+ // between this thread's execution of CopyInstructionBlock and CORDbgSetInstruction the wrong
+ // code would be executed. The bug has been fixed by changing CopyInstructionBlock to only copy
+ // the code bytes after the breakpoint.
+ // You might be tempted to stop copying the code at all, however that wouldn't work well with rejit.
+ // If we skip a breakpoint that is sitting at the beginning of a method, then the profiler rejits that
+ // method causing a jump-stamp to be placed, then we skip the breakpoint again, we need to make sure
+ // the 2nd skip executes the new jump-stamp code and not the original method prologue code. Copying
+ // the code every time ensures that we have the most up-to-date version of the code in the buffer.
+ _ASSERTE( patch->IsBound() );
+ CopyInstructionBlock(patchBypass, (const BYTE *)patch->address);
+
+ // Technically, we could create a patch skipper for an inactive patch, but we rely on the opcode being
+ // set here.
+ _ASSERTE( patch->IsActivated() );
+ CORDbgSetInstruction((CORDB_ADDRESS_TYPE *)patchBypass, patch->opcode);
+
+ LOG((LF_CORDB, LL_EVERYTHING, "SetInstruction was called\n"));
+ //
+ // Look at instruction to get some attributes
+ //
+
+ NativeWalker::DecodeInstructionForPatchSkip(patchBypass, &(m_instrAttrib));
+
+#if defined(_TARGET_AMD64_)
+
+
+ // The code below handles RIP-relative addressing on AMD64. the original implementation made the assumption that
+ // we are only using RIP-relative addressing to access read-only data (see VSW 246145 for more information). this
+ // has since been expanded to handle RIP-relative writes as well.
+ if (m_instrAttrib.m_dwOffsetToDisp != 0)
+ {
+ _ASSERTE(m_instrAttrib.m_cbInstr != 0);
+
+ //
+ // Populate the RIP-relative buffer with the current value if needed
+ //
+
+ BYTE* bufferBypass = m_pSharedPatchBypassBuffer->BypassBuffer;
+
+ // Overwrite the *signed* displacement.
+ int dwOldDisp = *(int*)(&patchBypass[m_instrAttrib.m_dwOffsetToDisp]);
+ int dwNewDisp = offsetof(SharedPatchBypassBuffer, BypassBuffer) -
+ (offsetof(SharedPatchBypassBuffer, PatchBypass) + m_instrAttrib.m_cbInstr);
+ *(int*)(&patchBypass[m_instrAttrib.m_dwOffsetToDisp]) = dwNewDisp;
+
+ // This could be an LEA, which we'll just have to change into a MOV
+ // and copy the original address
+ if (((patchBypass[0] == 0x4C) || (patchBypass[0] == 0x48)) && (patchBypass[1] == 0x8d))
+ {
+ patchBypass[1] = 0x8b; // MOV reg, mem
+ _ASSERTE((int)sizeof(void*) <= SharedPatchBypassBuffer::cbBufferBypass);
+ *(void**)bufferBypass = (void*)(patch->address + m_instrAttrib.m_cbInstr + dwOldDisp);
+ }
+ else
+ {
+ // Copy the data into our buffer.
+ memcpy(bufferBypass, patch->address + m_instrAttrib.m_cbInstr + dwOldDisp, SharedPatchBypassBuffer::cbBufferBypass);
+
+ if (m_instrAttrib.m_fIsWrite)
+ {
+ // save the actual destination address and size so when we TriggerSingleStep() we can update the value
+ m_pSharedPatchBypassBuffer->RipTargetFixup = (UINT_PTR)(patch->address + m_instrAttrib.m_cbInstr + dwOldDisp);
+ m_pSharedPatchBypassBuffer->RipTargetFixupSize = m_instrAttrib.m_cOperandSize;
+ }
+ }
+ }
+#endif // _TARGET_AMD64_
+
+#endif // !_TARGET_ARM_
+
+ // Signals our thread that the debugger will be manipulating the context
+ // during the patch skip operation. This effectively prevents other threads
+ // from suspending us until we have completed skiping the patch and restored
+ // a good context (See DDB 188816)
+ thread->BeginDebuggerPatchSkip(this);
+
+ //
+ // Set IP of context to point to patch bypass buffer
+ //
+
+ T_CONTEXT *context = g_pEEInterface->GetThreadFilterContext(thread);
+ _ASSERTE(!ISREDIRECTEDTHREAD(thread));
+ CONTEXT c;
+ if (context == NULL)
+ {
+ // We can't play with our own context!
+#if _DEBUG
+ if (g_pEEInterface->GetThread())
+ {
+ // current thread is mamaged thread
+ _ASSERTE(Debugger::GetThreadIdHelper(thread) != Debugger::GetThreadIdHelper(g_pEEInterface->GetThread()));
+ }
+#endif // _DEBUG
+
+ c.ContextFlags = CONTEXT_CONTROL;
+
+ thread->GetThreadContext(&c);
+ context =(T_CONTEXT *) &c;
+
+ ARM_ONLY(_ASSERTE(!"We should always have a filter context in DebuggerPatchSkip."));
+ }
+
+#ifdef _TARGET_ARM_
+ // Since we emulate all single-stepping on ARM using an instruction buffer and a breakpoint all we have to
+ // do here is initiate a normal single-step except that we pass the instruction to be stepped explicitly
+ // (calling EnableSingleStep() would infer this by looking at the PC in the context, which would pick up
+ // the patch we're trying to skip).
+ //
+ // Ideally we'd refactor the EnableSingleStep to support this alternative calling sequence but since this
+ // involves three levels of methods and is only applicable to ARM we've chosen to replicate the relevant
+ // implementation here instead.
+ {
+ ControllerLockHolder lockController;
+ g_pEEInterface->MarkThreadForDebugStepping(thread, true);
+ WORD opcode2 = 0;
+
+ if (Is32BitInstruction(patch->opcode))
+ {
+ opcode2 = CORDbgGetInstruction((CORDB_ADDRESS_TYPE *)(((DWORD)patch->address) + 2));
+ }
+
+ thread->BypassWithSingleStep((DWORD)patch->address, patch->opcode, opcode2);
+ m_singleStep = true;
+ }
+
+#else // _TARGET_ARM_
+
+#ifdef _TARGET_ARM64_
+ patchBypass = NativeWalker::SetupOrSimulateInstructionForPatchSkip(context, m_pSharedPatchBypassBuffer, (const BYTE *)patch->address, patch->opcode);
+#endif //_TARGET_ARM64_
+
+ //set eip to point to buffer...
+ SetIP(context, (PCODE)patchBypass);
+
+ if (context ==(T_CONTEXT*) &c)
+ thread->SetThreadContext(&c);
+
+
+ LOG((LF_CORDB, LL_INFO10000, "DPS::DPS Bypass at 0x%p for opcode %p \n", patchBypass, patch->opcode));
+
+ //
+ // Turn on single step (if the platform supports it) so we can
+ // fix up state after the instruction is executed.
+ // Also turn on exception hook so we can adjust IP in exceptions
+ //
+
+ EnableSingleStep();
+
+#endif // _TARGET_ARM_
+
+ EnableExceptionHook();
+}
+
+DebuggerPatchSkip::~DebuggerPatchSkip()
+{
+#ifndef _TARGET_ARM_
+ _ASSERTE(m_pSharedPatchBypassBuffer);
+ m_pSharedPatchBypassBuffer->Release();
+#endif
+}
+
+void DebuggerPatchSkip::DebuggerDetachClean()
+{
+// Since for ARM SharedPatchBypassBuffer isn't existed, we don't have to anything here.
+#ifndef _TARGET_ARM_
+ // Fix for Bug 1176448
+ // When a debugger is detaching from the debuggee, we need to move the IP if it is pointing
+ // somewhere in PatchBypassBuffer.All managed threads are suspended during detach, so changing
+ // the context without notifications is safe.
+ // Notice:
+ // THIS FIX IS INCOMPLETE!It attempts to update the IP in the cases we can easily detect.However,
+ // if a thread is in pre - emptive mode, and its filter context has been propagated to a VEH
+ // context, then the filter context we get will be NULL and this fix will not work.Our belief is
+ // that this scenario is rare enough that it doesn’t justify the cost and risk associated with a
+ // complete fix, in which we would have to either :
+ // 1. Change the reference counting for DebuggerController and then change the exception handling
+ // logic in the debuggee so that we can handle the debugger event after detach.
+ // 2. Create a "stack walking" implementation for native code and use it to get the current IP and
+ // set the IP to the right place.
+
+ Thread *thread = GetThread();
+ if (thread != NULL)
+ {
+ BYTE *patchBypass = m_pSharedPatchBypassBuffer->PatchBypass;
+ CONTEXT *context = thread->GetFilterContext();
+ if (patchBypass != NULL &&
+ context != NULL &&
+ (size_t)GetIP(context) >= (size_t)patchBypass &&
+ (size_t)GetIP(context) <= (size_t)(patchBypass + MAX_INSTRUCTION_LENGTH + 1))
+ {
+ SetIP(context, (PCODE)((BYTE *)GetIP(context) - (patchBypass - (BYTE *)m_address)));
+ }
+ }
+#endif
+}
+
+
+//
+// We have to have a whole seperate function for this because you
+// can't use __try in a function that requires object unwinding...
+//
+
+LONG FilterAccessViolation2(LPEXCEPTION_POINTERS ep, PVOID pv)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ return (ep->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
+ ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH;
+}
+
+// This helper is required because the AVInRuntimeImplOkayHolder can not
+// be directly placed inside the scope of a PAL_TRY
+void _CopyInstructionBlockHelper(BYTE* to, const BYTE* from)
+{
+ AVInRuntimeImplOkayHolder AVOkay;
+
+ // This function only copies the portion of the instruction that follows the
+ // breakpoint opcode, not the breakpoint itself
+ to += CORDbg_BREAK_INSTRUCTION_SIZE;
+ from += CORDbg_BREAK_INSTRUCTION_SIZE;
+
+ // If an AV occurs because we walked off a valid page then we need
+ // to be certain that all bytes on the previous page were copied.
+ // We are certain that we copied enough bytes to contain the instruction
+ // because it must have fit within the valid page.
+ for (int i = 0; i < MAX_INSTRUCTION_LENGTH - CORDbg_BREAK_INSTRUCTION_SIZE; i++)
+ {
+ *to++ = *from++;
+ }
+
+}
+
+// WARNING: this function skips copying the first CORDbg_BREAK_INSTRUCTION_SIZE bytes by design
+// See the comment at the callsite in DebuggerPatchSkip::DebuggerPatchSkip for more details on
+// this
+void DebuggerPatchSkip::CopyInstructionBlock(BYTE *to, const BYTE* from)
+{
+ // We wrap the memcpy in an exception handler to handle the
+ // extremely rare case where we're copying an instruction off the
+ // end of a method that is also at the end of a page, and the next
+ // page is unmapped.
+ struct Param
+ {
+ BYTE *to;
+ const BYTE* from;
+ } param;
+ param.to = to;
+ param.from = from;
+ PAL_TRY(Param *, pParam, &param)
+ {
+ _CopyInstructionBlockHelper(pParam->to, pParam->from);
+ }
+ PAL_EXCEPT_FILTER(FilterAccessViolation2)
+ {
+ // The whole point is that if we copy up the the AV, then
+ // that's enough to execute, otherwise we would not have been
+ // able to execute the code anyway. So we just ignore the
+ // exception.
+ LOG((LF_CORDB, LL_INFO10000,
+ "DPS::DPS: AV copying instruction block ignored.\n"));
+ }
+ PAL_ENDTRY
+
+ // We just created a new buffer of code, but the CPU caches code and may
+ // not be aware of our changes. This should force the CPU to dump any cached
+ // instructions it has in this region and load the new ones from memory
+ FlushInstructionCache(GetCurrentProcess(), to + CORDbg_BREAK_INSTRUCTION_SIZE,
+ MAX_INSTRUCTION_LENGTH - CORDbg_BREAK_INSTRUCTION_SIZE);
+}
+
+TP_RESULT DebuggerPatchSkip::TriggerPatch(DebuggerControllerPatch *patch,
+ Thread *thread,
+ TRIGGER_WHY tyWhy)
+{
+ ARM_ONLY(_ASSERTE(!"Should not have called DebuggerPatchSkip::TriggerPatch."));
+ LOG((LF_CORDB, LL_EVERYTHING, "DPS::TP called\n"));
+
+#if defined(_DEBUG) && !defined(_TARGET_ARM_)
+ CONTEXT *context = GetManagedLiveCtx(thread);
+
+ LOG((LF_CORDB, LL_INFO1000, "DPS::TP: We've patched 0x%x (byPass:0x%x) "
+ "for a skip after an EnC update!\n", GetIP(context),
+ GetBypassAddress()));
+ _ASSERTE(g_patches != NULL);
+
+ // We shouldn't have mucked with EIP, yet.
+ _ASSERTE(dac_cast<PTR_CORDB_ADDRESS_TYPE>(GetIP(context)) == GetBypassAddress());
+
+ //We should be the _only_ patch here
+ MethodDesc *md2 = dac_cast<PTR_MethodDesc>(GetIP(context));
+ DebuggerControllerPatch *patchCheck = g_patches->GetPatch(g_pEEInterface->MethodDescGetModule(md2),md2->GetMemberDef());
+ _ASSERTE(patchCheck == patch);
+ _ASSERTE(patchCheck->controller == patch->controller);
+
+ patchCheck = g_patches->GetNextPatch(patchCheck);
+ _ASSERTE(patchCheck == NULL);
+#endif // _DEBUG
+
+ DisableAll();
+ EnableExceptionHook();
+ EnableSingleStep(); //gets us back to where we want.
+ return TPR_IGNORE; // don't actually want to stop here....
+}
+
+TP_RESULT DebuggerPatchSkip::TriggerExceptionHook(Thread *thread, CONTEXT * context,
+ EXCEPTION_RECORD *exception)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ // Patch skippers only operate on patches set in managed code. But the infrastructure may have
+ // toggled the GC mode underneath us.
+ MODE_ANY;
+
+ PRECONDITION(GetThread() == thread);
+ PRECONDITION(thread != NULL);
+ PRECONDITION(CheckPointer(context));
+ }
+ CONTRACTL_END;
+
+ if (m_pAppDomain != NULL)
+ {
+ AppDomain *pAppDomainCur = thread->GetDomain();
+
+ if (pAppDomainCur != m_pAppDomain)
+ {
+ LOG((LF_CORDB,LL_INFO10000, "DPS::TEH: Appdomain mismatch - not skiiping!\n"));
+ return TPR_IGNORE;
+ }
+ }
+
+ LOG((LF_CORDB,LL_INFO10000, "DPS::TEH: doing the patch-skip thing\n"));
+
+#if defined(_TARGET_ARM64_)
+
+ if (!IsSingleStep(exception->ExceptionCode))
+ {
+ LOG((LF_CORDB, LL_INFO10000, "Exception in patched Bypass instruction .\n"));
+ return (TPR_IGNORE_AND_STOP);
+ }
+
+ _ASSERTE(m_pSharedPatchBypassBuffer);
+ BYTE* patchBypass = m_pSharedPatchBypassBuffer->PatchBypass;
+ PCODE targetIp;
+ if (m_pSharedPatchBypassBuffer->RipTargetFixup)
+ {
+ targetIp = m_pSharedPatchBypassBuffer->RipTargetFixup;
+ }
+ else
+ {
+ targetIp = (PCODE)((BYTE *)GetIP(context) - (patchBypass - (BYTE *)m_address));
+ }
+
+ SetIP(context, targetIp);
+ LOG((LF_CORDB, LL_ALWAYS, "Redirecting after Patch to 0x%p\n", GetIP(context)));
+
+#elif defined (_TARGET_ARM_)
+//Do nothing
+#else
+ _ASSERTE(m_pSharedPatchBypassBuffer);
+ BYTE* patchBypass = m_pSharedPatchBypassBuffer->PatchBypass;
+
+ if (m_instrAttrib.m_fIsCall && IsSingleStep(exception->ExceptionCode))
+ {
+ // Fixup return address on stack
+#if defined(_TARGET_X86_) || defined(_TARGET_AMD64_)
+ SIZE_T *sp = (SIZE_T *) GetSP(context);
+
+ LOG((LF_CORDB, LL_INFO10000,
+ "Bypass call return address redirected from 0x%p\n", *sp));
+
+ *sp -= patchBypass - (BYTE*)m_address;
+
+ LOG((LF_CORDB, LL_INFO10000, "to 0x%p\n", *sp));
+#else
+ PORTABILITY_ASSERT("DebuggerPatchSkip::TriggerExceptionHook -- return address fixup NYI");
+#endif
+ }
+
+ if (!m_instrAttrib.m_fIsAbsBranch || !IsSingleStep(exception->ExceptionCode))
+ {
+ // Fixup IP
+
+ LOG((LF_CORDB, LL_INFO10000, "Bypass instruction redirected from 0x%p\n", GetIP(context)));
+
+ if (IsSingleStep(exception->ExceptionCode))
+ {
+#ifndef FEATURE_PAL
+ // Check if the current IP is anywhere near the exception dispatcher logic.
+ // If it is, ignore the exception, as the real exception is coming next.
+ static FARPROC pExcepDispProc = NULL;
+
+ if (!pExcepDispProc)
+ {
+ HMODULE hNtDll = WszGetModuleHandle(W("ntdll.dll"));
+
+ if (hNtDll != NULL)
+ {
+ pExcepDispProc = GetProcAddress(hNtDll, "KiUserExceptionDispatcher");
+
+ if (!pExcepDispProc)
+ pExcepDispProc = (FARPROC)(size_t)(-1);
+ }
+ else
+ pExcepDispProc = (FARPROC)(size_t)(-1);
+ }
+
+ _ASSERTE(pExcepDispProc != NULL);
+
+ if ((size_t)pExcepDispProc != (size_t)(-1))
+ {
+ LPVOID pExcepDispEntryPoint = pExcepDispProc;
+
+ if ((size_t)GetIP(context) > (size_t)pExcepDispEntryPoint &&
+ (size_t)GetIP(context) <= ((size_t)pExcepDispEntryPoint + MAX_INSTRUCTION_LENGTH * 2 + 1))
+ {
+ LOG((LF_CORDB, LL_INFO10000,
+ "Bypass instruction not redirected. Landed in exception dispatcher.\n"));
+
+ return (TPR_IGNORE_AND_STOP);
+ }
+ }
+#endif // FEATURE_PAL
+
+ // If the IP is close to the skip patch start, or if we were skipping over a call, then assume the IP needs
+ // adjusting.
+ if (m_instrAttrib.m_fIsCall ||
+ ((size_t)GetIP(context) > (size_t)patchBypass &&
+ (size_t)GetIP(context) <= (size_t)(patchBypass + MAX_INSTRUCTION_LENGTH + 1)))
+ {
+ LOG((LF_CORDB, LL_INFO10000, "Bypass instruction redirected because still in skip area.\n"));
+ LOG((LF_CORDB, LL_INFO10000, "m_fIsCall = %d, patchBypass = 0x%x, m_address = 0x%x\n",
+ m_instrAttrib.m_fIsCall, patchBypass, m_address));
+ SetIP(context, (PCODE)((BYTE *)GetIP(context) - (patchBypass - (BYTE *)m_address)));
+ }
+ else
+ {
+ // Otherwise, need to see if the IP is something we recognize (either managed code
+ // or stub code) - if not, we ignore the exception
+ PCODE newIP = GetIP(context);
+ newIP -= PCODE(patchBypass - (BYTE *)m_address);
+ TraceDestination trace;
+
+ if (g_pEEInterface->IsManagedNativeCode(dac_cast<PTR_CBYTE>(newIP)) ||
+ (g_pEEInterface->TraceStub(LPBYTE(newIP), &trace)))
+ {
+ LOG((LF_CORDB, LL_INFO10000, "Bypass instruction redirected because we landed in managed or stub code\n"));
+ SetIP(context, newIP);
+ }
+
+ // If we have no idea where things have gone, then we assume that the IP needs no adjusting (which
+ // could happen if the instruction we were trying to patch skip caused an AV). In this case we want
+ // to claim it as ours but ignore it and continue execution.
+ else
+ {
+ LOG((LF_CORDB, LL_INFO10000, "Bypass instruction not redirected because we're not in managed or stub code.\n"));
+ return (TPR_IGNORE_AND_STOP);
+ }
+ }
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO10000, "Bypass instruction redirected because it wasn't a single step exception.\n"));
+ SetIP(context, (PCODE)((BYTE *)GetIP(context) - (patchBypass - (BYTE *)m_address)));
+ }
+
+ LOG((LF_CORDB, LL_ALWAYS, "to 0x%x\n", GetIP(context)));
+
+ }
+
+#endif
+
+
+ // Signals our thread that the debugger is done manipulating the context
+ // during the patch skip operation. This effectively prevented other threads
+ // from suspending us until we completed skiping the patch and restored
+ // a good context (See DDB 188816)
+ m_thread->EndDebuggerPatchSkip();
+
+ // Don't delete the controller yet if this is a single step exception, as the code will still want to dispatch to
+ // our single step method, and if it doesn't find something to dispatch to we won't continue from the exception.
+ //
+ // (This is kind of broken behavior but is easily worked around here
+ // by this test)
+ if (!IsSingleStep(exception->ExceptionCode))
+ {
+ Delete();
+ }
+
+ DisableExceptionHook();
+
+ return TPR_TRIGGER;
+}
+
+bool DebuggerPatchSkip::TriggerSingleStep(Thread *thread, const BYTE *ip)
+{
+ LOG((LF_CORDB,LL_INFO10000, "DPS::TSS: basically a no-op\n"));
+
+ if (m_pAppDomain != NULL)
+ {
+ AppDomain *pAppDomainCur = thread->GetDomain();
+
+ if (pAppDomainCur != m_pAppDomain)
+ {
+ LOG((LF_CORDB,LL_INFO10000, "DPS::TSS: Appdomain mismatch - "
+ "not SingSteping!!\n"));
+ return false;
+ }
+ }
+#if defined(_TARGET_AMD64_)
+ // Dev11 91932: for RIP-relative writes we need to copy the value that was written in our buffer to the actual address
+ _ASSERTE(m_pSharedPatchBypassBuffer);
+ if (m_pSharedPatchBypassBuffer->RipTargetFixup)
+ {
+ _ASSERTE(m_pSharedPatchBypassBuffer->RipTargetFixupSize);
+
+ BYTE* bufferBypass = m_pSharedPatchBypassBuffer->BypassBuffer;
+ BYTE fixupSize = m_pSharedPatchBypassBuffer->RipTargetFixupSize;
+ UINT_PTR targetFixup = m_pSharedPatchBypassBuffer->RipTargetFixup;
+
+ switch (fixupSize)
+ {
+ case 1:
+ *(reinterpret_cast<BYTE*>(targetFixup)) = *(reinterpret_cast<BYTE*>(bufferBypass));
+ break;
+
+ case 2:
+ *(reinterpret_cast<WORD*>(targetFixup)) = *(reinterpret_cast<WORD*>(bufferBypass));
+ break;
+
+ case 4:
+ *(reinterpret_cast<DWORD*>(targetFixup)) = *(reinterpret_cast<DWORD*>(bufferBypass));
+ break;
+
+ case 8:
+ *(reinterpret_cast<ULONGLONG*>(targetFixup)) = *(reinterpret_cast<ULONGLONG*>(bufferBypass));
+ break;
+
+ case 16:
+ memcpy(reinterpret_cast<void*>(targetFixup), bufferBypass, 16);
+ break;
+
+ default:
+ _ASSERTE(!"bad operand size");
+ }
+ }
+#endif
+ LOG((LF_CORDB,LL_INFO10000, "DPS::TSS: triggered, about to delete\n"));
+
+ TRACE_FREE(this);
+ Delete();
+ return false;
+}
+
+// * -------------------------------------------------------------------------
+// * DebuggerBreakpoint routines
+// * -------------------------------------------------------------------------
+// DebuggerBreakpoint::DebuggerBreakpoint() The constructor
+// invokes AddBindAndActivatePatch to set the breakpoint
+DebuggerBreakpoint::DebuggerBreakpoint(Module *module,
+ mdMethodDef md,
+ AppDomain *pAppDomain,
+ SIZE_T offset,
+ bool native,
+ SIZE_T ilEnCVersion, // must give the EnC version for non-native bps
+ MethodDesc *nativeMethodDesc, // use only when m_native
+ DebuggerJitInfo *nativeJITInfo, // optional when m_native, null otherwise
+ BOOL *pSucceed
+ )
+ : DebuggerController(NULL, pAppDomain)
+{
+ _ASSERTE(pSucceed != NULL);
+ _ASSERTE(native == (nativeMethodDesc != NULL));
+ _ASSERTE(native || nativeJITInfo == NULL);
+ _ASSERTE(!nativeJITInfo || nativeJITInfo->m_jitComplete); // this is sent by the left-side, and it couldn't have got the code if the JIT wasn't complete
+
+ if (native)
+ {
+ (*pSucceed) = AddBindAndActivateNativeManagedPatch(nativeMethodDesc, nativeJITInfo, offset, LEAF_MOST_FRAME, pAppDomain);
+ return;
+ }
+ else
+ {
+ (*pSucceed) = AddILPatch(pAppDomain, module, md, ilEnCVersion, offset);
+ }
+}
+
+// TP_RESULT DebuggerBreakpoint::TriggerPatch()
+// What: This patch will always be activated.
+// How: return true.
+TP_RESULT DebuggerBreakpoint::TriggerPatch(DebuggerControllerPatch *patch,
+ Thread *thread,
+ TRIGGER_WHY tyWhy)
+{
+ LOG((LF_CORDB, LL_INFO10000, "DB::TP\n"));
+
+ return TPR_TRIGGER;
+}
+
+// void DebuggerBreakpoint::SendEvent() What: Inform
+// the right side that the breakpoint was reached.
+// How: g_pDebugger->SendBreakpoint()
+bool DebuggerBreakpoint::SendEvent(Thread *thread, bool fIpChanged)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ SENDEVENT_CONTRACT_ITEMS;
+ }
+ CONTRACTL_END;
+
+
+ LOG((LF_CORDB, LL_INFO10000, "DB::SE: in DebuggerBreakpoint's SendEvent\n"));
+
+ CONTEXT *context = g_pEEInterface->GetThreadFilterContext(thread);
+
+ // If we got interupted by SetIp, we just don't send the IPC event. Our triggers are still
+ // active so no harm done.
+ if (!fIpChanged)
+ {
+ g_pDebugger->SendBreakpoint(thread, context, this);
+ return true;
+ }
+
+ // Controller is still alive, will fire if we hit the breakpoint again.
+ return false;
+}
+
+//* -------------------------------------------------------------------------
+// * DebuggerStepper routines
+// * -------------------------------------------------------------------------
+
+DebuggerStepper::DebuggerStepper(Thread *thread,
+ CorDebugUnmappedStop rgfMappingStop,
+ CorDebugIntercept interceptStop,
+ AppDomain *appDomain)
+ : DebuggerController(thread, appDomain),
+ m_stepIn(false),
+ m_reason(STEP_NORMAL),
+ m_fpStepInto(LEAF_MOST_FRAME),
+ m_rgfInterceptStop(interceptStop),
+ m_rgfMappingStop(rgfMappingStop),
+ m_range(NULL),
+ m_rangeCount(0),
+ m_realRangeCount(0),
+ m_fp(LEAF_MOST_FRAME),
+#if defined(WIN64EXCEPTIONS)
+ m_fpParentMethod(LEAF_MOST_FRAME),
+#endif // WIN64EXCEPTIONS
+ m_fpException(LEAF_MOST_FRAME),
+ m_fdException(0),
+ m_cFuncEvalNesting(0)
+{
+#ifdef _DEBUG
+ m_fReadyToSend = false;
+#endif
+}
+
+DebuggerStepper::~DebuggerStepper()
+{
+ if (m_range != NULL)
+ {
+ TRACE_FREE(m_range);
+ DeleteInteropSafe(m_range);
+ }
+}
+
+// bool DebuggerStepper::ShouldContinueStep() Return true if
+// the stepper should not stop at this address. The stepper should not
+// stop here if: here is in the {prolog,epilog,etc};
+// and the stepper is not interested in stopping here.
+// We assume that this is being called in the frame which the stepper steps
+// through. Unless, of course, we're returning from a call, in which
+// case we want to stop in the epilog even if the user didn't say so,
+// to prevent stepping out of multiple frames at once.
+// <REVISIT_TODO>Possible optimization: GetJitInfo, then AddPatch @ end of prolog?</REVISIT_TODO>
+bool DebuggerStepper::ShouldContinueStep( ControllerStackInfo *info,
+ SIZE_T nativeOffset)
+{
+ LOG((LF_CORDB,LL_INFO10000, "DeSt::ShContSt: nativeOffset:0x%p \n", nativeOffset));
+ if (m_rgfMappingStop != STOP_ALL && (m_reason != STEP_EXIT) )
+ {
+
+ DebuggerJitInfo *ji = info->m_activeFrame.GetJitInfoFromFrame();
+
+ if ( ji != NULL )
+ {
+ LOG((LF_CORDB,LL_INFO10000,"DeSt::ShContSt: For code 0x%p, got "
+ "DJI 0x%p, from 0x%p to 0x%p\n",
+ (const BYTE*)GetControlPC(&(info->m_activeFrame.registers)),
+ ji, ji->m_addrOfCode, ji->m_addrOfCode+ji->m_sizeOfCode));
+ }
+ else
+ {
+ LOG((LF_CORDB,LL_INFO10000,"DeSt::ShCoSt: For code 0x%p, didn't "
+ "get DJI\n",(const BYTE*)GetControlPC(&(info->m_activeFrame.registers))));
+
+ return false; // Haven't a clue if we should continue, so
+ // don't
+ }
+ CorDebugMappingResult map = MAPPING_UNMAPPED_ADDRESS;
+ DWORD whichIDontCare;
+ ji->MapNativeOffsetToIL( nativeOffset, &map, &whichIDontCare);
+ unsigned int interestingMappings =
+ (map & ~(MAPPING_APPROXIMATE | MAPPING_EXACT));
+
+ LOG((LF_CORDB,LL_INFO10000,
+ "DeSt::ShContSt: interestingMappings:0x%x m_rgfMappingStop:%x\n",
+ interestingMappings,m_rgfMappingStop));
+
+ // If we're in a prolog,epilog, then we may want to skip
+ // over it or stop
+ if ( interestingMappings )
+ {
+ if ( interestingMappings & m_rgfMappingStop )
+ return false;
+ else
+ return true;
+ }
+ }
+ return false;
+}
+
+bool DebuggerStepper::IsRangeAppropriate(ControllerStackInfo *info)
+{
+ LOG((LF_CORDB,LL_INFO10000, "DS::IRA: info:0x%x \n", info));
+ if (m_range == NULL)
+ {
+ LOG((LF_CORDB,LL_INFO10000, "DS::IRA: m_range == NULL, returning FALSE\n"));
+ return false;
+ }
+
+ FrameInfo *realFrame;
+
+#if defined(WIN64EXCEPTIONS)
+ bool fActiveFrameIsFunclet = info->m_activeFrame.IsNonFilterFuncletFrame();
+
+ if (fActiveFrameIsFunclet)
+ {
+ realFrame = &(info->m_returnFrame);
+ }
+ else
+#endif // WIN64EXCEPTIONS
+ {
+ realFrame = &(info->m_activeFrame);
+ }
+
+ LOG((LF_CORDB,LL_INFO10000, "DS::IRA: info->m_activeFrame.fp:0x%x m_fp:0x%x\n", info->m_activeFrame.fp, m_fp));
+ LOG((LF_CORDB,LL_INFO10000, "DS::IRA: m_fdException:0x%x realFrame->md:0x%x realFrame->fp:0x%x m_fpException:0x%x\n",
+ m_fdException, realFrame->md, realFrame->fp, m_fpException));
+ if ( (info->m_activeFrame.fp == m_fp) ||
+ ( (m_fdException != NULL) && (realFrame->md == m_fdException) &&
+ IsEqualOrCloserToRoot(realFrame->fp, m_fpException) ) )
+ {
+ LOG((LF_CORDB,LL_INFO10000, "DS::IRA: returning TRUE\n"));
+ return true;
+ }
+
+#if defined(WIN64EXCEPTIONS)
+ // There are two scenarios which make this function more complicated on WIN64.
+ // 1) We initiate a step in the parent method or a funclet but end up stepping into another funclet closer to the leaf.
+ // a) start in the parent method
+ // b) start in a funclet
+ // 2) We initiate a step in a funclet but end up stepping out to the parent method or a funclet closer to the root.
+ // a) end up in the parent method
+ // b) end up in a funclet
+ // In both cases the range of the stepper should still be appropriate.
+
+ bool fValidParentMethodFP = (m_fpParentMethod != LEAF_MOST_FRAME);
+
+ if (fActiveFrameIsFunclet)
+ {
+ // Scenario 1a
+ if (m_fp == info->m_returnFrame.fp)
+ {
+ LOG((LF_CORDB,LL_INFO10000, "DS::IRA: returning TRUE\n"));
+ return true;
+ }
+ // Scenario 1b & 2b have the same condition
+ else if (fValidParentMethodFP && (m_fpParentMethod == info->m_returnFrame.fp))
+ {
+ LOG((LF_CORDB,LL_INFO10000, "DS::IRA: returning TRUE\n"));
+ return true;
+ }
+ }
+ else
+ {
+ // Scenario 2a
+ if (fValidParentMethodFP && (m_fpParentMethod == info->m_activeFrame.fp))
+ {
+ LOG((LF_CORDB,LL_INFO10000, "DS::IRA: returning TRUE\n"));
+ return true;
+ }
+ }
+#endif // WIN64EXCEPTIONS
+
+ LOG((LF_CORDB,LL_INFO10000, "DS::IRA: returning FALSE\n"));
+ return false;
+}
+
+// bool DebuggerStepper::IsInRange() Given the native offset ip,
+// returns true if ip falls within any of the native offset ranges specified
+// by the array of COR_DEBUG_STEP_RANGEs.
+// Returns true if ip falls within any of the ranges. Returns false
+// if ip doesn't, or if there are no ranges (rangeCount==0). Note that a
+// COR_DEBUG_STEP_RANGE with an endOffset of zero is interpreted as extending
+// from startOffset to the end of the method.
+// SIZE_T ip: Native offset, relative to the beginning of the method.
+// COR_DEBUG_STEP_RANGE *range: An array of ranges, which are themselves
+// native offsets, to compare against ip.
+// SIZE_T rangeCount: Number of elements in range
+bool DebuggerStepper::IsInRange(SIZE_T ip, COR_DEBUG_STEP_RANGE *range, SIZE_T rangeCount,
+ ControllerStackInfo *pInfo)
+{
+ LOG((LF_CORDB,LL_INFO10000,"DS::IIR: off=0x%x\n", ip));
+
+ if (range == NULL)
+ {
+ LOG((LF_CORDB,LL_INFO10000,"DS::IIR: range == NULL -> not in range\n"));
+ return false;
+ }
+
+ if (pInfo && !IsRangeAppropriate(pInfo))
+ {
+ LOG((LF_CORDB,LL_INFO10000,"DS::IIR: no pInfo or range not appropriate -> not in range\n"));
+ return false;
+ }
+
+ COR_DEBUG_STEP_RANGE *r = range;
+ COR_DEBUG_STEP_RANGE *rEnd = r + rangeCount;
+
+ while (r < rEnd)
+ {
+ SIZE_T endOffset = r->endOffset ? r->endOffset : ~0;
+ LOG((LF_CORDB,LL_INFO100000,"DS::IIR: so=0x%x, eo=0x%x\n",
+ r->startOffset, endOffset));
+
+ if (ip >= r->startOffset && ip < endOffset)
+ {
+ LOG((LF_CORDB,LL_INFO1000,"DS::IIR:this:0x%x Found native offset "
+ "0x%x to be in the range"
+ "[0x%x, 0x%x), index 0x%x\n\n", this, ip, r->startOffset,
+ endOffset, ((r-range)/sizeof(COR_DEBUG_STEP_RANGE *)) ));
+ return true;
+ }
+
+ r++;
+ }
+
+ LOG((LF_CORDB,LL_INFO10000,"DS::IIR: not in range\n"));
+ return false;
+}
+
+// bool DebuggerStepper::DetectHandleInterceptors() Return true if
+// the current execution takes place within an interceptor (that is, either
+// the current frame, or the parent frame is a framed frame whose
+// GetInterception method returns something other than INTERCEPTION_NONE),
+// and this stepper doesn't want to stop in an interceptor, and we successfully
+// set a breakpoint after the top-most interceptor in the stack.
+bool DebuggerStepper::DetectHandleInterceptors(ControllerStackInfo *info)
+{
+ LOG((LF_CORDB,LL_INFO10000,"DS::DHI: Start DetectHandleInterceptors\n"));
+ LOG((LF_CORDB,LL_INFO10000,"DS::DHI: active frame=0x%08x, has return frame=%d, return frame=0x%08x m_reason:%d\n",
+ info->m_activeFrame.frame, info->HasReturnFrame(), info->m_returnFrame.frame, m_reason));
+
+ // If this is a normal step, then we want to continue stepping, even if we
+ // are in an interceptor.
+ if (m_reason == STEP_NORMAL || m_reason == STEP_RETURN || m_reason == STEP_EXCEPTION_HANDLER)
+ {
+ LOG((LF_CORDB,LL_INFO1000,"DS::DHI: Returning false while stepping within function, finally!\n"));
+ return false;
+ }
+
+ bool fAttemptStepOut = false;
+
+ if (m_rgfInterceptStop != INTERCEPT_ALL) // we may have to skip out of one
+ {
+ if (info->m_activeFrame.frame != NULL &&
+ info->m_activeFrame.frame != FRAME_TOP &&
+ info->m_activeFrame.frame->GetInterception() != Frame::INTERCEPTION_NONE)
+ {
+ if (!((CorDebugIntercept)info->m_activeFrame.frame->GetInterception() & Frame::Interception(m_rgfInterceptStop)))
+ {
+ LOG((LF_CORDB,LL_INFO10000,"DS::DHI: Stepping out b/c of excluded frame type:0x%x\n",
+ info->m_returnFrame. frame->GetInterception()));
+
+ fAttemptStepOut = true;
+ }
+ else
+ {
+ LOG((LF_CORDB,LL_INFO10000,"DS::DHI: 0x%x set to STEP_INTERCEPT\n", this));
+
+ m_reason = STEP_INTERCEPT; //remember why we've stopped
+ }
+ }
+
+ if ((m_reason == STEP_EXCEPTION_FILTER) ||
+ (info->HasReturnFrame() &&
+ info->m_returnFrame.frame != NULL &&
+ info->m_returnFrame.frame != FRAME_TOP &&
+ info->m_returnFrame.frame->GetInterception() != Frame::INTERCEPTION_NONE))
+ {
+ if (m_reason == STEP_EXCEPTION_FILTER)
+ {
+ // Exceptions raised inside of the EE by COMPlusThrow, FCThrow, etc will not
+ // insert an ExceptionFrame, and hence info->m_returnFrame.frame->GetInterception()
+ // will not be accurate. Hence we use m_reason instead
+
+ if (!(Frame::INTERCEPTION_EXCEPTION & Frame::Interception(m_rgfInterceptStop)))
+ {
+ LOG((LF_CORDB,LL_INFO10000,"DS::DHI: Stepping out b/c of excluded INTERCEPTION_EXCEPTION\n"));
+ fAttemptStepOut = true;
+ }
+ }
+ else if (!(info->m_returnFrame.frame->GetInterception() & Frame::Interception(m_rgfInterceptStop)))
+ {
+ LOG((LF_CORDB,LL_INFO10000,"DS::DHI: Stepping out b/c of excluded return frame type:0x%x\n",
+ info->m_returnFrame.frame->GetInterception()));
+
+ fAttemptStepOut = true;
+ }
+
+ if (!fAttemptStepOut)
+ {
+ LOG((LF_CORDB,LL_INFO10000,"DS::DHI 0x%x set to STEP_INTERCEPT\n", this));
+
+ m_reason = STEP_INTERCEPT; //remember why we've stopped
+ }
+ }
+ else if (info->m_specialChainReason != CHAIN_NONE)
+ {
+ if(!(info->m_specialChainReason & CorDebugChainReason(m_rgfInterceptStop)) )
+ {
+ LOG((LF_CORDB,LL_INFO10000, "DS::DHI: (special) Stepping out b/c of excluded return frame type:0x%x\n",
+ info->m_specialChainReason));
+
+ fAttemptStepOut = true;
+ }
+ else
+ {
+ LOG((LF_CORDB,LL_INFO10000,"DS::DHI 0x%x set to STEP_INTERCEPT\n", this));
+
+ m_reason = STEP_INTERCEPT; //remember why we've stopped
+ }
+ }
+ else if (info->m_activeFrame.frame == NULL)
+ {
+ // Make sure we are not dealing with a chain here.
+ if (info->m_activeFrame.HasMethodFrame())
+ {
+ // Check whether we are executing in a class constructor.
+ _ASSERTE(info->m_activeFrame.md != NULL);
+ if (info->m_activeFrame.md->IsClassConstructor())
+ {
+ // We are in a class constructor. Check whether we want to stop in it.
+ if (!(CHAIN_CLASS_INIT & CorDebugChainReason(m_rgfInterceptStop)))
+ {
+ LOG((LF_CORDB, LL_INFO10000, "DS::DHI: Stepping out b/c of excluded cctor:0x%x\n",
+ CHAIN_CLASS_INIT));
+
+ fAttemptStepOut = true;
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO10000,"DS::DHI 0x%x set to STEP_INTERCEPT\n", this));
+
+ m_reason = STEP_INTERCEPT; //remember why we've stopped
+ }
+ }
+ }
+ }
+ }
+
+ if (fAttemptStepOut)
+ {
+ LOG((LF_CORDB,LL_INFO1000,"DS::DHI: Doing TSO!\n"));
+
+ // TrapStepOut could alter the step reason if we're stepping out of an inteceptor and it looks like we're
+ // running off the top of the program. So hold onto it here, and if our step reason becomes STEP_EXIT, then
+ // reset it to what it was.
+ CorDebugStepReason holdReason = m_reason;
+
+ // @todo - should this be TrapStepNext??? But that may stop in a child...
+ TrapStepOut(info);
+ EnableUnwind(m_fp);
+
+ if (m_reason == STEP_EXIT)
+ {
+ m_reason = holdReason;
+ }
+
+ return true;
+ }
+
+ // We're not in a special area of code, so we don't want to continue unless some other part of the code decides that
+ // we should.
+ LOG((LF_CORDB,LL_INFO1000,"DS::DHI: Returning false, finally!\n"));
+
+ return false;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// This function checks whether the given IP is in an LCG method. If so, it enables
+// JMC and does a step out. This effectively makes sure that we never stop in an LCG method.
+//
+// There are two common scnearios here:
+// 1) We single-step into an LCG method from a managed method.
+// 2) We single-step off the end of a method called by an LCG method and end up in the calling LCG method.
+//
+// In both cases, we don't want to stop in the LCG method. If the LCG method directly or indirectly calls
+// another user method, we want to stop there. Otherwise, we just want to step out back to the caller of
+// LCG method. In other words, what we want is exactly the JMC behaviour.
+//
+// Arguments:
+// ip - the current IP where the thread is stopped at
+// pMD - This is the MethodDesc for the specified ip. This can be NULL, but if it's not,
+// then it has to match the specified IP.
+// pInfo - the ControllerStackInfo taken at the specified IP (see Notes below)
+//
+// Return Value:
+// Returns TRUE if the specified IP is indeed in an LCG method, in which case this function has already
+// enabled all the traps to catch the thread, including turning on JMC, enabling unwind callback, and
+// putting a patch in the caller.
+//
+// Notes:
+// LCG methods don't show up in stackwalks done by the ControllerStackInfo. So even if the specified IP
+// is in an LCG method, the LCG method won't show up in the call strack. That's why we need to call
+// ControllerStackInfo::SetReturnFrameWithActiveFrame() in this function before calling TrapStepOut().
+// Otherwise TrapStepOut() will put a patch in the caller's caller (if there is one).
+//
+
+BOOL DebuggerStepper::DetectHandleLCGMethods(const PCODE ip, MethodDesc * pMD, ControllerStackInfo * pInfo)
+{
+ // Look up the MethodDesc for the given IP.
+ if (pMD == NULL)
+ {
+ if (g_pEEInterface->IsManagedNativeCode((const BYTE *)ip))
+ {
+ pMD = g_pEEInterface->GetNativeCodeMethodDesc(ip);
+ _ASSERTE(pMD != NULL);
+ }
+ }
+#if defined(_DEBUG)
+ else
+ {
+ // If a MethodDesc is specified, it has to match the given IP.
+ _ASSERTE(pMD == g_pEEInterface->GetNativeCodeMethodDesc(ip));
+ }
+#endif // _DEBUG
+
+ // If the given IP is in unmanaged code, then we won't have a MethodDesc by this point.
+ if (pMD != NULL)
+ {
+ if (pMD->IsLCGMethod())
+ {
+ // Enable all the traps to catch the thread.
+ EnableUnwind(m_fp);
+ EnableJMCBackStop(pMD);
+
+ pInfo->SetReturnFrameWithActiveFrame();
+ TrapStepOut(pInfo);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+
+// Steppers override these so that they can skip func-evals. Note that steppers can
+// be created & used inside of func-evals (nested-break states).
+// On enter, we check for freezing the stepper.
+void DebuggerStepper::TriggerFuncEvalEnter(Thread * thread)
+{
+ LOG((LF_CORDB, LL_INFO10000, "DS::TFEEnter, this=0x%p, old nest=%d\n", this, m_cFuncEvalNesting));
+
+ // Since this is always called on the hijacking thread, we should be thread-safe
+ _ASSERTE(thread == this->GetThread());
+
+ if (IsDead())
+ return;
+
+ m_cFuncEvalNesting++;
+
+ if (m_cFuncEvalNesting == 1)
+ {
+ // We're entering our 1st funceval, so freeze us.
+ LOG((LF_CORDB, LL_INFO100000, "DS::TFEEnter - freezing stepper\n"));
+
+ // Freeze the stepper by disabling all triggers
+ m_bvFrozenTriggers = 0;
+
+ //
+ // We dont explicitly disable single-stepping because the OS
+ // gives us a new thread context during an exception. Since
+ // all func-evals are done inside exceptions, we should never
+ // have this problem.
+ //
+ // Note: however, that if func-evals were no longer done in
+ // exceptions, this would have to change.
+ //
+
+
+ if (IsMethodEnterEnabled())
+ {
+ m_bvFrozenTriggers |= kMethodEnter;
+ DisableMethodEnter();
+ }
+
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO100000, "DS::TFEEnter - new nest=%d\n", m_cFuncEvalNesting));
+ }
+}
+
+// On Func-EvalExit, we check if the stepper is trying to step-out of a func-eval
+// (in which case we kill it)
+// or if we previously entered this func-eval and should thaw it now.
+void DebuggerStepper::TriggerFuncEvalExit(Thread * thread)
+{
+ LOG((LF_CORDB, LL_INFO10000, "DS::TFEExit, this=0x%p, old nest=%d\n", this, m_cFuncEvalNesting));
+
+ // Since this is always called on the hijacking thread, we should be thread-safe
+ _ASSERTE(thread == this->GetThread());
+
+ if (IsDead())
+ return;
+
+ m_cFuncEvalNesting--;
+
+ if (m_cFuncEvalNesting == -1)
+ {
+ LOG((LF_CORDB, LL_INFO100000, "DS::TFEExit - disabling stepper\n"));
+
+ // we're exiting the func-eval session we were created in. So we just completely
+ // disable ourselves so that we don't fire anything anymore.
+ // The RS still has to free the stepper though.
+
+ // This prevents us from stepping-out of a func-eval. For traditional steppers,
+ // this is overkill since it won't have any outstanding triggers. (trap-step-out
+ // won't patch if it crosses a func-eval frame).
+ // But JMC-steppers have Method-Enter; and so this is the only place we have to
+ // disable that.
+ DisableAll();
+ }
+ else if (m_cFuncEvalNesting == 0)
+ {
+ // We're back to our starting Func-eval session, we should have been frozen,
+ // so now we thaw.
+ LOG((LF_CORDB, LL_INFO100000, "DS::TFEExit - thawing stepper\n"));
+
+ // Thaw the stepper (reenable triggers)
+ if ((m_bvFrozenTriggers & kMethodEnter) != 0)
+ {
+ EnableMethodEnter();
+ }
+ m_bvFrozenTriggers = 0;
+
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO100000, "DS::TFEExit - new nest=%d\n", m_cFuncEvalNesting));
+ }
+}
+
+
+// Return true iff we set a patch (which implies to caller that we should
+// let controller run free and hit that patch)
+bool DebuggerStepper::TrapStepInto(ControllerStackInfo *info,
+ const BYTE *ip,
+ TraceDestination *pTD)
+{
+ _ASSERTE( pTD != NULL );
+ _ASSERTE(this->GetDCType() == DEBUGGER_CONTROLLER_STEPPER);
+
+ EnableTraceCall(LEAF_MOST_FRAME);
+ if (IsCloserToRoot(info->m_activeFrame.fp, m_fpStepInto))
+ m_fpStepInto = info->m_activeFrame.fp;
+
+ LOG((LF_CORDB, LL_INFO1000, "Ds::TSI this:0x%x m_fpStepInto:0x%x\n",
+ this, m_fpStepInto.GetSPValue()));
+
+ TraceDestination trace;
+
+ // Trace through the stubs.
+ // If we're calling from managed code, this should either succeed
+ // or become an ecall into mscorwks.
+ // @Todo - what about stubs in mscorwks.
+ // @todo - if this fails, we want to provde as much info as possible.
+ if (!g_pEEInterface->TraceStub(ip, &trace)
+ || !g_pEEInterface->FollowTrace(&trace))
+ {
+ return false;
+ }
+
+
+ (*pTD) = trace; //bitwise copy
+
+ // Step-in always operates at the leaf-most frame. Thus the frame pointer for any
+ // patch for step-in should be LEAF_MOST_FRAME, regardless of whatever our current fp
+ // is before the step-in.
+ // Note that step-in may skip 'internal' frames (FrameInfo w/ internal=true) since
+ // such frames may really just be a marker for an internal EE Frame on the stack.
+ // However, step-out uses these frames b/c it may call frame->TraceFrame() on them.
+ return PatchTrace(&trace,
+ LEAF_MOST_FRAME, // step-in is always leaf-most frame.
+ (m_rgfMappingStop&STOP_UNMANAGED)?(true):(false));
+}
+
+// Enable the JMC backstop for stepping on Step-In.
+// This activate the JMC probes, which will provide a safety net
+// to stop a stepper if the StubManagers don't predict the call properly.
+// Ideally, this should never be necessary (because the SMs would do their job).
+void DebuggerStepper::EnableJMCBackStop(MethodDesc * pStartMethod)
+{
+ // JMC steppers should not need the JMC backstop unless a thread inadvertently stops in an LCG method.
+ //_ASSERTE(DEBUGGER_CONTROLLER_JMC_STEPPER != this->GetDCType());
+
+ // Since we should never hit the JMC backstop (since it's really a SM issue), we'll assert if we actually do.
+ // However, there's 1 corner case here. If we trace calls at the start of the method before the JMC-probe,
+ // then we'll still hit the JMC backstop in our own method.
+ // Record that starting method. That way, if we end up hitting our JMC backstop in our own method,
+ // we don't over aggressively fire the assert. (This won't work for recursive cases, but since this is just
+ // changing an assert, we don't care).
+
+#ifdef _DEBUG
+ // May be NULL if we didn't start in a method.
+ m_StepInStartMethod = pStartMethod;
+#endif
+
+ // We don't want traditional steppers to rely on MethodEnter (b/c it's not guaranteed to be correct),
+ // but it may be a useful last resort.
+ this->EnableMethodEnter();
+}
+
+// Return true if the stepper can run free.
+bool DebuggerStepper::TrapStepInHelper(
+ ControllerStackInfo * pInfo,
+ const BYTE * ipCallTarget,
+ const BYTE * ipNext,
+ bool fCallingIntoFunclet)
+{
+ TraceDestination td;
+
+#ifdef _DEBUG
+ // Begin logging the step-in activity in debug builds.
+ StubManager::DbgBeginLog((TADDR) ipNext, (TADDR) ipCallTarget);
+#endif
+
+
+ if (TrapStepInto(pInfo, ipCallTarget, &td))
+ {
+ // If we placed a patch, see if we need to update our step-reason
+ if (td.GetTraceType() == TRACE_MANAGED )
+ {
+ // Possible optimization: Roll all of g_pEEInterface calls into
+ // one function so we don't repeatedly get the CodeMan,etc
+ MethodDesc *md = NULL;
+ _ASSERTE( g_pEEInterface->IsManagedNativeCode((const BYTE *)td.GetAddress()) );
+ md = g_pEEInterface->GetNativeCodeMethodDesc(td.GetAddress());
+
+ if ( g_pEEInterface->GetFunctionAddress(md) == td.GetAddress())
+ {
+
+ LOG((LF_CORDB,LL_INFO1000,"\tDS::TS 0x%x m_reason = STEP_CALL"
+ "@ip0x%x\n", this, (BYTE*)GetControlPC(&(pInfo->m_activeFrame.registers))));
+ m_reason = STEP_CALL;
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO1000, "Didn't step: md:0x%x"
+ "td.type:%s td.address:0x%x, gfa:0x%x\n",
+ md, GetTType(td.GetTraceType()), td.GetAddress(),
+ g_pEEInterface->GetFunctionAddress(md)));
+ }
+ }
+ else
+ {
+ LOG((LF_CORDB,LL_INFO10000,"DS::TS else 0x%x m_reason = STEP_CALL\n",
+ this));
+ m_reason = STEP_CALL;
+ }
+
+
+ return true;
+ } // end TrapStepIn
+ else
+ {
+ // If we can't figure out where the stepper should call into (likely because we can't find a stub-manager),
+ // then enable the JMC backstop.
+ EnableJMCBackStop(pInfo->m_activeFrame.md);
+
+ }
+
+ // We ignore ipNext here. Instead we'll return false and let the caller (TrapStep)
+ // set the patch for us.
+ return false;
+}
+
+FORCEINLINE bool IsTailCall(const BYTE * pTargetIP)
+{
+ return TailCallStubManager::IsTailCallStubHelper(reinterpret_cast<PCODE>(pTargetIP));
+}
+
+// bool DebuggerStepper::TrapStep() TrapStep attepts to set a
+// patch at the next IL instruction to be executed. If we're stepping in &
+// the next IL instruction is a call, then this'll set a breakpoint inside
+// the code that will be called.
+// How: There are a number of cases, depending on where the IP
+// currently is:
+// Unmanaged code: EnableTraceCall() & return false - try and get
+// it when it returns.
+// In a frame: if the <p in> param is true, then do an
+// EnableTraceCall(). If the frame isn't the top frame, also do
+// g_pEEInterface->TraceFrame(), g_pEEInterface->FollowTrace, and
+// PatchTrace.
+// Normal managed frame: create a Walker and walk the instructions until either
+// leave the provided range (AddPatch there, return true), or we don't know what the
+// next instruction is (say, after a call, or return, or branch - return false).
+// Returns a boolean indicating if we were able to set a patch successfully
+// in either this method, or (if in == true & the next instruction is a call)
+// inside a callee method.
+// true: Patch successfully placed either in this method or a callee,
+// so the stepping is taken care of.
+// false: Unable to place patch in either this method or any
+// applicable callee methods, so the only option the caller has to put
+// patch to control flow is to call TrapStepOut & try and place a patch
+// on the method that called the current frame's method.
+bool DebuggerStepper::TrapStep(ControllerStackInfo *info, bool in)
+{
+ LOG((LF_CORDB,LL_INFO10000,"DS::TS: this:0x%x\n", this));
+ if (!info->m_activeFrame.managed)
+ {
+ //
+ // We're not in managed code. Patch up all paths back in.
+ //
+
+ LOG((LF_CORDB,LL_INFO10000, "DS::TS: not in managed code\n"));
+
+ if (in)
+ {
+ EnablePolyTraceCall();
+ }
+
+ return false;
+ }
+
+ if (info->m_activeFrame.frame != NULL)
+ {
+
+ //
+ // We're in some kind of weird frame. Patch further entry to the frame.
+ // or if we can't, patch return from the frame
+ //
+
+ LOG((LF_CORDB,LL_INFO10000, "DS::TS: in a weird frame\n"));
+
+ if (in)
+ {
+ EnablePolyTraceCall();
+
+ // Only traditional steppers should patch a frame. JMC steppers will
+ // just rely on TriggerMethodEnter.
+ if (DEBUGGER_CONTROLLER_STEPPER == this->GetDCType())
+ {
+ if (info->m_activeFrame.frame != FRAME_TOP)
+ {
+ TraceDestination trace;
+
+ CONTRACT_VIOLATION(GCViolation); // TraceFrame GC-triggers
+
+ // This could be anywhere, especially b/c step could be on non-leaf frame.
+ if (g_pEEInterface->TraceFrame(this->GetThread(),
+ info->m_activeFrame.frame,
+ FALSE, &trace,
+ &(info->m_activeFrame.registers))
+ && g_pEEInterface->FollowTrace(&trace)
+ && PatchTrace(&trace, info->m_activeFrame.fp,
+ (m_rgfMappingStop&STOP_UNMANAGED)?
+ (true):(false)))
+
+ {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+#ifdef _TARGET_X86_
+ LOG((LF_CORDB,LL_INFO1000, "GetJitInfo for pc = 0x%x (addr of "
+ "that value:0x%x)\n", (const BYTE*)(GetControlPC(&info->m_activeFrame.registers)),
+ info->m_activeFrame.registers.PCTAddr));
+#endif
+
+ // Note: we used to pass in the IP from the active frame to GetJitInfo, but there seems to be no value in that, and
+ // it was causing problems creating a stepper while sitting in ndirect stubs after we'd returned from the unmanaged
+ // function that had been called.
+ DebuggerJitInfo *ji = info->m_activeFrame.GetJitInfoFromFrame();
+ if( ji != NULL )
+ {
+ LOG((LF_CORDB,LL_INFO10000,"DS::TS: For code 0x%p, got DJI 0x%p, "
+ "from 0x%p to 0x%p\n",
+ (const BYTE*)(GetControlPC(&info->m_activeFrame.registers)),
+ ji, ji->m_addrOfCode, ji->m_addrOfCode+ji->m_sizeOfCode));
+ }
+ else
+ {
+ LOG((LF_CORDB,LL_INFO10000,"DS::TS: For code 0x%p, "
+ "didn't get a DJI \n",
+ (const BYTE*)(GetControlPC(&info->m_activeFrame.registers))));
+ }
+
+ //
+ // We're in a normal managed frame - walk the code
+ //
+
+ NativeWalker walker;
+
+ LOG((LF_CORDB,LL_INFO1000, "DS::TS: &info->m_activeFrame.registers 0x%p\n", &info->m_activeFrame.registers));
+
+ // !!! Eventually when using the fjit, we'll want
+ // to walk the IL to get the next location, & then map
+ // it back to native.
+ walker.Init((BYTE*)GetControlPC(&(info->m_activeFrame.registers)), &info->m_activeFrame.registers);
+
+
+ // Is the active frame really the active frame?
+ // What if the thread is stopped at a managed debug event outside of a filter ctx? Eg, stopped
+ // somewhere directly in mscorwks (like sending a LogMsg or ClsLoad event) or even at WaitForSingleObject.
+ // ActiveFrame is either the stepper's initial frame or the frame of a filterctx.
+ bool fIsActivFrameLive = (info->m_activeFrame.fp == info->m_bottomFP);
+
+ // If this thread isn't stopped in managed code, it can't be at the active frame.
+ if (GetManagedStoppedCtx(this->GetThread()) == NULL)
+ {
+ fIsActivFrameLive = false;
+ }
+
+ bool fIsJump = false;
+ bool fCallingIntoFunclet = false;
+
+ // If m_activeFrame is not the actual active frame,
+ // we should skip this first switch - never single step, and
+ // assume our context is bogus.
+ if (fIsActivFrameLive)
+ {
+ LOG((LF_CORDB,LL_INFO10000, "DC::TS: immediate?\n"));
+
+ // Note that by definition our walker must always be able to step
+ // through a single instruction, so any return
+ // of NULL IP's from those cases on the first step
+ // means that an exception is going to be generated.
+ //
+ // (On future steps, it can also mean that the destination
+ // simply can't be computed.)
+ WALK_TYPE wt = walker.GetOpcodeWalkType();
+ {
+ switch (wt)
+ {
+ case WALK_RETURN:
+ {
+ LOG((LF_CORDB,LL_INFO10000, "DC::TS:Imm:WALK_RETURN\n"));
+
+ // Normally a 'ret' opcode means we're at the end of a function and doing a step-out.
+ // But the jit is free to use a 'ret' opcode to implement various goofy constructs like
+ // managed filters, in which case we may ret to the same function or we may ret to some
+ // internal CLR stub code.
+ // So we'll just ignore this and tell the Stepper to enable every notification it has
+ // and let the thread run free. This will include TrapStepOut() and EnableUnwind()
+ // to catch any potential filters.
+
+
+ // Go ahead and enable the single-step flag too. We know it's safe.
+ // If this lands in random code, then TriggerSingleStep will just ignore it.
+ EnableSingleStep();
+
+ // Don't set step-reason yet. If another trigger gets hit, it will set the reason.
+ return false;
+ }
+
+ case WALK_BRANCH:
+ LOG((LF_CORDB,LL_INFO10000, "DC::TS:Imm:WALK_BRANCH\n"));
+ // A branch can be handled just like a call. If the branch is within the current method, then we just
+ // down to WALK_UNKNOWN, otherwise we handle it just like a call. Note: we need to force in=true
+ // because for a jmp, in or over is the same thing, we're still going there, and the in==true case is
+ // the case we want to use...
+ fIsJump = true;
+
+ // fall through...
+
+ case WALK_CALL:
+ LOG((LF_CORDB,LL_INFO10000, "DC::TS:Imm:WALK_CALL ip=%p nextip=%p\n", walker.GetIP(), walker.GetNextIP()));
+
+ // If we're doing some sort of intra-method jump (usually, to get EIP in a clever way, via the CALL
+ // instruction), then put the bp where we're going, NOT at the instruction following the call
+ if (IsAddrWithinFrame(ji, info->m_activeFrame.md, walker.GetIP(), walker.GetNextIP()))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "Walk call within method!" ));
+ goto LWALK_UNKNOWN;
+ }
+
+ if (walker.GetNextIP() != NULL)
+ {
+#ifdef WIN64EXCEPTIONS
+ // There are 4 places we could be jumping:
+ // 1) to the beginning of the same method (recursive call)
+ // 2) somewhere in the same funclet, that isn't the method start
+ // 3) somewhere in the same method but different funclet
+ // 4) somewhere in a different method
+ //
+ // IsAddrWithinFrame ruled out option 2, IsAddrWithinMethodIncludingFunclet rules out option 4,
+ // and checking the IP against the start address rules out option 1. That leaves option only what we
+ // wanted, option #3
+ fCallingIntoFunclet = IsAddrWithinMethodIncludingFunclet(ji, info->m_activeFrame.md, walker.GetNextIP()) &&
+ ((CORDB_ADDRESS)(SIZE_T)walker.GetNextIP() != ji->m_addrOfCode);
+#endif
+ // At this point, we know that the call/branch target is not in the current method.
+ // So if the current instruction is a jump, this must be a tail call or possibly a jump to the finally.
+ // So, check if the call/branch target is the JIT helper for handling tail calls if we are not calling
+ // into the funclet.
+ if ((fIsJump && !fCallingIntoFunclet) || IsTailCall(walker.GetNextIP()))
+ {
+ // A step-over becomes a step-out for a tail call.
+ if (!in)
+ {
+ TrapStepOut(info);
+ return true;
+ }
+ }
+
+ // To preserve the old behaviour, if this is not a tail call, then we assume we want to
+ // follow the call/jump.
+ if (fIsJump)
+ {
+ in = true;
+ }
+
+
+ // There are two cases where we need to perform a step-in. One, if the step operation is
+ // a step-in. Two, if the target address of the call is in a funclet of the current method.
+ // In this case, we want to step into the funclet even if the step operation is a step-over.
+ if (in || fCallingIntoFunclet)
+ {
+ if (TrapStepInHelper(info, walker.GetNextIP(), walker.GetSkipIP(), fCallingIntoFunclet))
+ {
+ return true;
+ }
+ }
+
+ }
+ if (walker.GetSkipIP() == NULL)
+ {
+ LOG((LF_CORDB,LL_INFO10000,"DS::TS 0x%x m_reason = STEP_CALL (skip)\n",
+ this));
+ m_reason = STEP_CALL;
+
+ return true;
+ }
+
+
+ LOG((LF_CORDB,LL_INFO100000, "DC::TS:Imm:WALK_CALL Skip instruction\n"));
+ walker.Skip();
+ break;
+
+ case WALK_UNKNOWN:
+ LWALK_UNKNOWN:
+ LOG((LF_CORDB,LL_INFO10000,"DS::TS:WALK_UNKNOWN - curIP:0x%x "
+ "nextIP:0x%x skipIP:0x%x 1st byte of opcode:0x%x\n", (BYTE*)GetControlPC(&(info->m_activeFrame.
+ registers)), walker.GetNextIP(),walker.GetSkipIP(),
+ *(BYTE*)GetControlPC(&(info->m_activeFrame.registers))));
+
+ EnableSingleStep();
+
+ return true;
+
+ default:
+ if (walker.GetNextIP() == NULL)
+ {
+ return true;
+ }
+
+ walker.Next();
+ }
+ }
+ } // if (fIsActivFrameLive)
+
+ //
+ // Use our range, if we're in the original
+ // frame.
+ //
+
+ COR_DEBUG_STEP_RANGE *range;
+ SIZE_T rangeCount;
+
+ if (info->m_activeFrame.fp == m_fp)
+ {
+ range = m_range;
+ rangeCount = m_rangeCount;
+ }
+ else
+ {
+ range = NULL;
+ rangeCount = 0;
+ }
+
+ //
+ // Keep walking until either we're out of range, or
+ // else we can't predict ahead any more.
+ //
+
+ while (TRUE)
+ {
+ const BYTE *ip = walker.GetIP();
+
+ SIZE_T offset = CodeRegionInfo::GetCodeRegionInfo(ji, info->m_activeFrame.md).AddressToOffset(ip);
+
+ LOG((LF_CORDB, LL_INFO1000, "Walking to ip 0x%p (natOff:0x%x)\n",ip,offset));
+
+ if (!IsInRange(offset, range, rangeCount)
+ && !ShouldContinueStep( info, offset ))
+ {
+ AddBindAndActivateNativeManagedPatch(info->m_activeFrame.md,
+ ji,
+ offset,
+ info->m_returnFrame.fp,
+ NULL);
+ return true;
+ }
+
+ switch (walker.GetOpcodeWalkType())
+ {
+ case WALK_RETURN:
+
+ LOG((LF_CORDB, LL_INFO10000, "DS::TS: WALK_RETURN Adding Patch.\n"));
+
+ // In the loop above, if we're at the return address, we'll check & see
+ // if we're returning to elsewhere within the same method, and if so,
+ // we'll single step rather than TrapStepOut. If we see a return in the
+ // code stream, then we'll set a breakpoint there, so that we can
+ // examine the return address, and decide whether to SS or TSO then
+ AddBindAndActivateNativeManagedPatch(info->m_activeFrame.md,
+ ji,
+ offset,
+ info->m_returnFrame.fp,
+ NULL);
+ return true;
+
+ case WALK_CALL:
+
+ LOG((LF_CORDB, LL_INFO10000, "DS::TS: WALK_CALL.\n"));
+
+ // If we're doing some sort of intra-method jump (usually, to get EIP in a clever way, via the CALL
+ // instruction), then put the bp where we're going, NOT at the instruction following the call
+ if (IsAddrWithinFrame(ji, info->m_activeFrame.md, walker.GetIP(), walker.GetNextIP()))
+ {
+ LOG((LF_CORDB, LL_INFO10000, "DS::TS: WALK_CALL IsAddrWithinFrame, Adding Patch.\n"));
+
+ // How else to detect this?
+ AddBindAndActivateNativeManagedPatch(info->m_activeFrame.md,
+ ji,
+ CodeRegionInfo::GetCodeRegionInfo(ji, info->m_activeFrame.md).AddressToOffset(walker.GetNextIP()),
+ info->m_returnFrame.fp,
+ NULL);
+ return true;
+ }
+
+ if (IsTailCall(walker.GetNextIP()))
+ {
+ if (!in)
+ {
+ AddBindAndActivateNativeManagedPatch(info->m_activeFrame.md,
+ ji,
+ offset,
+ info->m_returnFrame.fp,
+ NULL);
+ return true;
+ }
+ }
+
+#ifdef WIN64EXCEPTIONS
+ fCallingIntoFunclet = IsAddrWithinMethodIncludingFunclet(ji, info->m_activeFrame.md, walker.GetNextIP());
+#endif
+ if (in || fCallingIntoFunclet)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "DS::TS: WALK_CALL step in is true\n"));
+ if (walker.GetNextIP() == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "DS::TS: WALK_CALL NextIP == NULL\n"));
+ AddBindAndActivateNativeManagedPatch(info->m_activeFrame.md,
+ ji,
+ offset,
+ info->m_returnFrame.fp,
+ NULL);
+
+ LOG((LF_CORDB,LL_INFO10000,"DS0x%x m_reason=STEP_CALL 2\n",
+ this));
+ m_reason = STEP_CALL;
+
+ return true;
+ }
+
+ if (TrapStepInHelper(info, walker.GetNextIP(), walker.GetSkipIP(), fCallingIntoFunclet))
+ {
+ return true;
+ }
+
+ }
+
+ LOG((LF_CORDB, LL_INFO10000, "DS::TS: WALK_CALL Calling GetSkipIP\n"));
+ if (walker.GetSkipIP() == NULL)
+ {
+ AddBindAndActivateNativeManagedPatch(info->m_activeFrame.md,
+ ji,
+ offset,
+ info->m_returnFrame.fp,
+ NULL);
+
+ LOG((LF_CORDB,LL_INFO10000,"DS 0x%x m_reason=STEP_CALL4\n",this));
+ m_reason = STEP_CALL;
+
+ return true;
+ }
+
+ walker.Skip();
+ LOG((LF_CORDB, LL_INFO10000, "DS::TS: skipping over call.\n"));
+ break;
+
+ default:
+ if (walker.GetNextIP() == NULL)
+ {
+ AddBindAndActivateNativeManagedPatch(info->m_activeFrame.md,
+ ji,
+ offset,
+ info->m_returnFrame.fp,
+ NULL);
+ return true;
+ }
+ walker.Next();
+ break;
+ }
+ }
+ LOG((LF_CORDB,LL_INFO1000,"Ending TrapStep\n"));
+}
+
+bool DebuggerStepper::IsAddrWithinFrame(DebuggerJitInfo *dji,
+ MethodDesc* pMD,
+ const BYTE* currentAddr,
+ const BYTE* targetAddr)
+{
+ _ASSERTE(dji != NULL);
+
+ bool result = IsAddrWithinMethodIncludingFunclet(dji, pMD, targetAddr);
+
+ // We need to check if this is a recursive call. In RTM we should see if this method is really necessary,
+ // since it looks like the X86 JIT doesn't emit intra-method jumps anymore.
+ if (result)
+ {
+ if ((CORDB_ADDRESS)(SIZE_T)targetAddr == dji->m_addrOfCode)
+ {
+ result = false;
+ }
+ }
+
+#if defined(WIN64EXCEPTIONS)
+ // On WIN64, we also check whether the targetAddr and the currentAddr is in the same funclet.
+ _ASSERTE(currentAddr != NULL);
+ if (result)
+ {
+ int currentFuncletIndex = dji->GetFuncletIndex((CORDB_ADDRESS)currentAddr, DebuggerJitInfo::GFIM_BYADDRESS);
+ int targetFuncletIndex = dji->GetFuncletIndex((CORDB_ADDRESS)targetAddr, DebuggerJitInfo::GFIM_BYADDRESS);
+ result = (currentFuncletIndex == targetFuncletIndex);
+ }
+#endif // WIN64EXCEPTIONS
+
+ return result;
+}
+
+// x86 shouldn't need to call this method directly. We should call IsAddrWithinFrame() on x86 instead.
+// That's why I use a name with the word "funclet" in it to scare people off.
+bool DebuggerStepper::IsAddrWithinMethodIncludingFunclet(DebuggerJitInfo *dji,
+ MethodDesc* pMD,
+ const BYTE* targetAddr)
+{
+ _ASSERTE(dji != NULL);
+ return CodeRegionInfo::GetCodeRegionInfo(dji, pMD).IsMethodAddress(targetAddr);
+}
+
+void DebuggerStepper::TrapStepNext(ControllerStackInfo *info)
+{
+ LOG((LF_CORDB, LL_INFO10000, "DS::TrapStepNext, this=%p\n", this));
+ // StepNext for a Normal stepper is just a step-out
+ TrapStepOut(info);
+
+ // @todo -should we also EnableTraceCall??
+}
+
+// Is this frame interesting?
+// For a traditional stepper, all frames are interesting.
+bool DebuggerStepper::IsInterestingFrame(FrameInfo * pFrame)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ return true;
+}
+
+// Place a single patch somewhere up the stack to do a step-out
+void DebuggerStepper::TrapStepOut(ControllerStackInfo *info, bool fForceTraditional)
+{
+ ControllerStackInfo returnInfo;
+ DebuggerJitInfo *dji;
+
+ LOG((LF_CORDB, LL_INFO10000, "DS::TSO this:0x%p\n", this));
+
+ bool fReturningFromFinallyFunclet = false;
+
+#if defined(WIN64EXCEPTIONS)
+ // When we step out of a funclet, we should do one of two things, depending
+ // on the original stepping intention:
+ // 1) If we originally want to step out, then we should skip the parent method.
+ // 2) If we originally want to step in/over but we step off the end of the funclet,
+ // then we should resume in the parent, if possible.
+ if (info->m_activeFrame.IsNonFilterFuncletFrame())
+ {
+ // There should always be a frame for the parent method.
+ _ASSERTE(info->HasReturnFrame());
+
+#ifdef _TARGET_ARM_
+ while (info->HasReturnFrame() && info->m_activeFrame.md != info->m_returnFrame.md)
+ {
+ StackTraceTicket ticket(info);
+ returnInfo.GetStackInfo(ticket, GetThread(), info->m_returnFrame.fp, NULL);
+ info = &returnInfo;
+ }
+
+ _ASSERTE(info->HasReturnFrame());
+#endif
+
+ _ASSERTE(info->m_activeFrame.md == info->m_returnFrame.md);
+
+ if (m_eMode == cStepOut)
+ {
+ StackTraceTicket ticket(info);
+ returnInfo.GetStackInfo(ticket, GetThread(), info->m_returnFrame.fp, NULL);
+ info = &returnInfo;
+ }
+ else
+ {
+ _ASSERTE(info->m_returnFrame.managed);
+ _ASSERTE(info->m_returnFrame.frame == NULL);
+
+ MethodDesc *md = info->m_returnFrame.md;
+ dji = info->m_returnFrame.GetJitInfoFromFrame();
+
+ // The return value of a catch funclet is the control PC to resume to.
+ // The return value of a finally funclet has no meaning, so we need to check
+ // if the return value is in the main method.
+ LPVOID resumePC = GetRegdisplayReturnValue(&(info->m_activeFrame.registers));
+
+ // For finally funclet, there are two possible situations. Either the finally is
+ // called normally (i.e. no exception), in which case we simply fall through and
+ // let the normal loop do its work below, or the finally is called by the EH
+ // routines, in which case we need the unwind notification.
+ if (IsAddrWithinMethodIncludingFunclet(dji, md, (const BYTE *)resumePC))
+ {
+ SIZE_T reloffset = dji->m_codeRegionInfo.AddressToOffset((BYTE*)resumePC);
+
+ AddBindAndActivateNativeManagedPatch(info->m_returnFrame.md,
+ dji,
+ reloffset,
+ info->m_returnFrame.fp,
+ NULL);
+
+ LOG((LF_CORDB, LL_INFO10000,
+ "DS::TSO:normally managed code AddPatch"
+ " in %s::%s, offset 0x%x, m_reason=%d\n",
+ info->m_returnFrame.md->m_pszDebugClassName,
+ info->m_returnFrame.md->m_pszDebugMethodName,
+ reloffset, m_reason));
+
+ // Do not set m_reason to STEP_RETURN here. Logically, the funclet and the parent method are the
+ // same method, so we should not "return" to the parent method.
+ LOG((LF_CORDB, LL_INFO10000,"DS::TSO: done\n"));
+
+ return;
+ }
+ else
+ {
+ // This is the case where we step off the end of a finally funclet.
+ fReturningFromFinallyFunclet = true;
+ }
+ }
+ }
+#endif // WIN64EXCEPTIONS
+
+#ifdef _DEBUG
+ FramePointer dbgLastFP; // for debug, make sure we're making progress through the stack.
+#endif
+
+ while (info->HasReturnFrame())
+ {
+
+#ifdef _DEBUG
+ dbgLastFP = info->m_activeFrame.fp;
+#endif
+
+ // Continue walking up the stack & set a patch upon the next
+ // frame up. We will eventually either hit managed code
+ // (which we can set a definite patch in), or the top of the
+ // stack.
+ StackTraceTicket ticket(info);
+
+ // The last parameter here is part of a really targetted (*cough* dirty) fix to
+ // disable getting an unwanted UMChain to fix issue 650903 (See
+ // code:ControllerStackInfo::WalkStack and code:TrackUMChain for the other
+ // parts.) In the case of managed step out we know that we aren't interested in
+ // unmanaged frames, and generating that unmanaged frame causes the stackwalker
+ // not to report the managed frame that was at the same SP. However the unmanaged
+ // frame might be used in the mixed-mode step out case so I don't suppress it
+ // there.
+ returnInfo.GetStackInfo(ticket, GetThread(), info->m_returnFrame.fp, NULL, !(m_rgfMappingStop & STOP_UNMANAGED));
+ info = &returnInfo;
+
+#ifdef _DEBUG
+ // If this assert fires, then it means that we're not making progress while
+ // tracing up the towards the root of the stack. Likely an issue in the Left-Side's
+ // stackwalker.
+ _ASSERTE(IsCloserToLeaf(dbgLastFP, info->m_activeFrame.fp));
+#endif
+
+#ifdef FEATURE_STUBS_AS_IL
+ if (info->m_activeFrame.md->IsILStub() && info->m_activeFrame.md->AsDynamicMethodDesc()->IsMulticastStub())
+ {
+ LOG((LF_CORDB, LL_INFO10000,
+ "DS::TSO: multicast frame.\n"));
+
+ // User break should always be called from managed code, so it should never actually hit this codepath.
+ _ASSERTE(GetDCType() != DEBUGGER_CONTROLLER_USER_BREAKPOINT);
+
+ // JMC steppers shouldn't be patching stubs.
+ if (DEBUGGER_CONTROLLER_JMC_STEPPER == this->GetDCType())
+ {
+ LOG((LF_CORDB, LL_INFO10000, "DS::TSO: JMC stepper skipping frame.\n"));
+ continue;
+ }
+
+ TraceDestination trace;
+
+ EnableTraceCall(info->m_activeFrame.fp);
+
+ PCODE ip = GetControlPC(&(info->m_activeFrame.registers));
+ if (g_pEEInterface->TraceStub((BYTE*)ip, &trace)
+ && g_pEEInterface->FollowTrace(&trace)
+ && PatchTrace(&trace, info->m_activeFrame.fp,
+ true))
+ break;
+ }
+ else
+#endif // FEATURE_STUBS_AS_IL
+ if (info->m_activeFrame.managed)
+ {
+ LOG((LF_CORDB, LL_INFO10000,
+ "DS::TSO: return frame is managed.\n"));
+
+ if (info->m_activeFrame.frame == NULL)
+ {
+ // Returning normally to managed code.
+ _ASSERTE(info->m_activeFrame.md != NULL);
+
+ // Polymorphic check to skip over non-interesting frames.
+ if (!fForceTraditional && !this->IsInterestingFrame(&info->m_activeFrame))
+ continue;
+
+ dji = info->m_activeFrame.GetJitInfoFromFrame();
+ _ASSERTE(dji != NULL);
+
+ // Note: we used to pass in the IP from the active frame to GetJitInfo, but there seems to be no value
+ // in that, and it was causing problems creating a stepper while sitting in ndirect stubs after we'd
+ // returned from the unmanaged function that had been called.
+ ULONG reloffset = info->m_activeFrame.relOffset;
+
+ AddBindAndActivateNativeManagedPatch(info->m_activeFrame.md,
+ dji,
+ reloffset,
+ info->m_returnFrame.fp,
+ NULL);
+
+ LOG((LF_CORDB, LL_INFO10000,
+ "DS::TSO:normally managed code AddPatch"
+ " in %s::%s, offset 0x%x, m_reason=%d\n",
+ info->m_activeFrame.md->m_pszDebugClassName,
+ info->m_activeFrame.md->m_pszDebugMethodName,
+ reloffset, m_reason));
+
+
+ // Do not set m_reason to STEP_RETURN here. Logically, the funclet and the parent method are the
+ // same method, so we should not "return" to the parent method.
+ if (!fReturningFromFinallyFunclet)
+ {
+ m_reason = STEP_RETURN;
+ }
+ break;
+ }
+ else if (info->m_activeFrame.frame == FRAME_TOP)
+ {
+
+ // Trad-stepper's step-out is actually like a step-next when we go off the top.
+ // JMC-steppers do a true-step out. So for JMC-steppers, don't enable trace-call.
+ if (DEBUGGER_CONTROLLER_JMC_STEPPER == this->GetDCType())
+ {
+ LOG((LF_CORDB, LL_EVERYTHING, "DS::TSO: JMC stepper skipping exit-frame case.\n"));
+ break;
+ }
+
+ // User break should always be called from managed code, so it should never actually hit this codepath.
+ _ASSERTE(GetDCType() != DEBUGGER_CONTROLLER_USER_BREAKPOINT);
+
+
+ // We're walking off the top of the stack. Note that if we call managed code again,
+ // this trace-call will cause us our stepper-to fire. So we'll actually do a
+ // step-next; not a true-step out.
+ EnableTraceCall(info->m_activeFrame.fp);
+
+ LOG((LF_CORDB, LL_INFO1000, "DS::TSO: Off top of frame!\n"));
+
+ m_reason = STEP_EXIT; //we're on the way out..
+
+ // <REVISIT_TODO>@todo not that it matters since we don't send a
+ // stepComplete message to the right side.</REVISIT_TODO>
+ break;
+ }
+ else if (info->m_activeFrame.frame->GetFrameType() == Frame::TYPE_FUNC_EVAL)
+ {
+ // Note: we treat walking off the top of the stack and
+ // walking off the top of a func eval the same way,
+ // except that we don't enable trace call since we
+ // know exactly where were going.
+
+ LOG((LF_CORDB, LL_INFO1000,
+ "DS::TSO: Off top of func eval!\n"));
+
+ m_reason = STEP_EXIT;
+ break;
+ }
+ else if (info->m_activeFrame.frame->GetFrameType() == Frame::TYPE_SECURITY &&
+ info->m_activeFrame.frame->GetInterception() == Frame::INTERCEPTION_NONE)
+ {
+ // If we're stepping out of something that was protected by (declarative) security,
+ // the security subsystem may leave a frame on the stack to cache it's computation.
+ // HOWEVER, this isn't a real frame, and so we don't want to stop here. On the other
+ // hand, if we're in the security goop (sec. executes managed code to do stuff), then
+ // we'll want to use the "returning to stub case", below. GetInterception()==NONE
+ // indicates that the frame is just a cache frame:
+ // Skip it and keep on going
+
+ LOG((LF_CORDB, LL_INFO10000,
+ "DS::TSO: returning to a non-intercepting frame. Keep unwinding\n"));
+ continue;
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO10000,
+ "DS::TSO: returning to a stub frame.\n"));
+
+ // User break should always be called from managed code, so it should never actually hit this codepath.
+ _ASSERTE(GetDCType() != DEBUGGER_CONTROLLER_USER_BREAKPOINT);
+
+ // JMC steppers shouldn't be patching stubs.
+ if (DEBUGGER_CONTROLLER_JMC_STEPPER == this->GetDCType())
+ {
+ LOG((LF_CORDB, LL_INFO10000, "DS::TSO: JMC stepper skipping frame.\n"));
+ continue;
+ }
+
+ // We're returning to some funky frame.
+ // (E.g. a security frame has called a native method.)
+
+ // Patch the frame from entering other methods. This effectively gives the Step-out
+ // a step-next behavior. For eg, this can be useful for step-out going between multicast delegates.
+ // This step-next could actually land us leaf-more on the callstack than we currently are!
+ // If we were a true-step out, we'd skip this and keep crawling.
+ // up the callstack.
+ //
+ // !!! For now, we assume that the TraceFrame entry
+ // point is smart enough to tell where it is in the
+ // calling sequence. We'll see how this holds up.
+ TraceDestination trace;
+
+ // We don't want notifications of trace-calls leaf-more than our current frame.
+ // For eg, if our current frame calls out to unmanaged code and then back in,
+ // we'll get a TraceCall notification. But since it's leaf-more than our current frame,
+ // we don't care because we just want to step out of our current frame (and everything
+ // our current frame may call).
+ EnableTraceCall(info->m_activeFrame.fp);
+
+ CONTRACT_VIOLATION(GCViolation); // TraceFrame GC-triggers
+
+ if (g_pEEInterface->TraceFrame(GetThread(),
+ info->m_activeFrame.frame, FALSE,
+ &trace, &(info->m_activeFrame.registers))
+ && g_pEEInterface->FollowTrace(&trace)
+ && PatchTrace(&trace, info->m_activeFrame.fp,
+ true))
+ break;
+
+ // !!! Problem: we don't know which return frame to use -
+ // the TraceFrame patch may be in a frame below the return
+ // frame, or in a frame parallel with it
+ // (e.g. prestub popping itself & then calling.)
+ //
+ // For now, I've tweaked the FP comparison in the
+ // patch dispatching code to allow either case.
+ }
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO10000,
+ "DS::TSO: return frame is not managed.\n"));
+
+ // Only step out to unmanaged code if we're actually
+ // marked to stop in unamanged code. Otherwise, just loop
+ // to get us past the unmanaged frames.
+ if (m_rgfMappingStop & STOP_UNMANAGED)
+ {
+ LOG((LF_CORDB, LL_INFO10000,
+ "DS::TSO: return to unmanaged code "
+ "m_reason=STEP_RETURN\n"));
+
+ // Do not set m_reason to STEP_RETURN here. Logically, the funclet and the parent method are the
+ // same method, so we should not "return" to the parent method.
+ if (!fReturningFromFinallyFunclet)
+ {
+ m_reason = STEP_RETURN;
+ }
+
+ // We're stepping out into unmanaged code
+ LOG((LF_CORDB, LL_INFO10000,
+ "DS::TSO: Setting unmanaged trace patch at 0x%x(%x)\n",
+ GetControlPC(&(info->m_activeFrame.registers)),
+ info->m_returnFrame.fp.GetSPValue()));
+
+
+ AddAndActivateNativePatchForAddress((CORDB_ADDRESS_TYPE *)GetControlPC(&(info->m_activeFrame.registers)),
+ info->m_returnFrame.fp,
+ FALSE,
+ TRACE_UNMANAGED);
+
+ break;
+
+ }
+ }
+ }
+
+ // <REVISIT_TODO>If we get here, we may be stepping out of the last frame. Our thread
+ // exit logic should catch this case. (@todo)</REVISIT_TODO>
+ LOG((LF_CORDB, LL_INFO10000,"DS::TSO: done\n"));
+}
+
+
+// void DebuggerStepper::StepOut()
+// Called by Debugger::HandleIPCEvent to setup
+// everything so that the process will step over the range of IL
+// correctly.
+// How: Converts the provided array of ranges from IL ranges to
+// native ranges (if they're not native already), and then calls
+// TrapStep or TrapStepOut, like so:
+// Get the appropriate MethodDesc & JitInfo
+// Iterate through array of IL ranges, use
+// JitInfo::MapILRangeToMapEntryRange to translate IL to native
+// ranges.
+// Set member variables to remember that the DebuggerStepper now uses
+// the ranges: m_range, m_rangeCount, m_stepIn, m_fp
+// If (!TrapStep()) then {m_stepIn = true; TrapStepOut()}
+// EnableUnwind( m_fp );
+void DebuggerStepper::StepOut(FramePointer fp, StackTraceTicket ticket)
+{
+ LOG((LF_CORDB, LL_INFO10000, "Attempting to step out, fp:0x%x this:0x%x"
+ "\n", fp.GetSPValue(), this ));
+
+ Thread *thread = GetThread();
+
+
+ CONTEXT *context = g_pEEInterface->GetThreadFilterContext(thread);
+ ControllerStackInfo info;
+
+ // We pass in the ticket b/c this is called both when we're live (via
+ // DebuggerUserBreakpoint) and when we're stopped (via normal StepOut)
+ info.GetStackInfo(ticket, thread, fp, context);
+
+
+ ResetRange();
+
+
+ m_stepIn = FALSE;
+ m_fp = info.m_activeFrame.fp;
+#if defined(WIN64EXCEPTIONS)
+ // We need to remember the parent method frame pointer here so that we will recognize
+ // the range of the stepper as being valid when we return to the parent method.
+ if (info.m_activeFrame.IsNonFilterFuncletFrame())
+ {
+ m_fpParentMethod = info.m_returnFrame.fp;
+ }
+#endif // WIN64EXCEPTIONS
+
+ m_eMode = cStepOut;
+
+ _ASSERTE((fp == LEAF_MOST_FRAME) || (info.m_activeFrame.md != NULL) || (info.m_returnFrame.md != NULL));
+
+ TrapStepOut(&info);
+ EnableUnwind(m_fp);
+}
+
+#define GROW_RANGES_IF_NECESSARY() \
+ if (rTo == rToEnd) \
+ { \
+ ULONG NewSize, OldSize; \
+ if (!ClrSafeInt<ULONG>::multiply(sizeof(COR_DEBUG_STEP_RANGE), (ULONG)(realRangeCount*2), NewSize) || \
+ !ClrSafeInt<ULONG>::multiply(sizeof(COR_DEBUG_STEP_RANGE), (ULONG)realRangeCount, OldSize) || \
+ NewSize < OldSize) \
+ { \
+ DeleteInteropSafe(m_range); \
+ m_range = NULL; \
+ return false; \
+ } \
+ COR_DEBUG_STEP_RANGE *_pTmp = (COR_DEBUG_STEP_RANGE*) \
+ g_pDebugger->GetInteropSafeHeap()->Realloc(m_range, \
+ NewSize, \
+ OldSize); \
+ \
+ if (_pTmp == NULL) \
+ { \
+ DeleteInteropSafe(m_range); \
+ m_range = NULL; \
+ return false; \
+ } \
+ \
+ m_range = _pTmp; \
+ rTo = m_range + realRangeCount; \
+ rToEnd = m_range + (realRangeCount*2); \
+ realRangeCount *= 2; \
+ }
+
+//-----------------------------------------------------------------------------
+// Given a set of IL ranges, convert them to native and cache them.
+// Return true on success, false on error.
+//-----------------------------------------------------------------------------
+bool DebuggerStepper::SetRangesFromIL(DebuggerJitInfo *dji, COR_DEBUG_STEP_RANGE *ranges, SIZE_T rangeCount)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ WRAPPER(THROWS);
+ GC_NOTRIGGER;
+ PRECONDITION(ThisIsHelperThreadWorker()); // Only help initializes a stepper.
+ PRECONDITION(m_range == NULL); // shouldn't be set already.
+ PRECONDITION(CheckPointer(ranges));
+ PRECONDITION(CheckPointer(dji));
+ }
+ CONTRACTL_END;
+
+ // Note: we used to pass in the IP from the active frame to GetJitInfo, but there seems to be no value in that, and
+ // it was causing problems creating a stepper while sitting in ndirect stubs after we'd returned from the unmanaged
+ // function that had been called.
+ MethodDesc *fd = dji->m_fd;
+
+ // The "+1" is for internal use, when we need to
+ // set an intermediate patch in pitched code. Isn't
+ // used unless the method is pitched & a patch is set
+ // inside it. Thus we still pass cRanges as the
+ // range count.
+ m_range = new (interopsafe) COR_DEBUG_STEP_RANGE[rangeCount+1];
+
+ if (m_range == NULL)
+ return false;
+
+ TRACE_ALLOC(m_range);
+
+ SIZE_T realRangeCount = rangeCount;
+
+ if (dji != NULL)
+ {
+ LOG((LF_CORDB,LL_INFO10000,"DeSt::St: For code md=0x%x, got DJI 0x%x, from 0x%x to 0x%x\n",
+ fd,
+ dji, dji->m_addrOfCode, (ULONG)dji->m_addrOfCode
+ + (ULONG)dji->m_sizeOfCode));
+
+ //
+ // Map ranges to native offsets for jitted code
+ //
+ COR_DEBUG_STEP_RANGE *r, *rEnd, *rTo, *rToEnd;
+
+ r = ranges;
+ rEnd = r + rangeCount;
+
+ rTo = m_range;
+ rToEnd = rTo + realRangeCount;
+
+ // <NOTE>
+ // rTo may also be incremented in the middle of the loop on WIN64 platforms.
+ // </NOTE>
+ for (/**/; r < rEnd; r++, rTo++)
+ {
+ // If we are already at the end of our allocated array, but there are still
+ // more ranges to copy over, then grow the array.
+ GROW_RANGES_IF_NECESSARY();
+
+ if (r->startOffset == 0 && r->endOffset == (ULONG) ~0)
+ {
+ // {0...-1} means use the entire method as the range
+ // Code dup'd from below case.
+ LOG((LF_CORDB, LL_INFO10000, "DS:Step: Have DJI, special (0,-1) entry\n"));
+ rTo->startOffset = 0;
+ rTo->endOffset = (ULONG32)g_pEEInterface->GetFunctionSize(fd);
+ }
+ else
+ {
+ //
+ // One IL range may consist of multiple
+ // native ranges.
+ //
+
+ DebuggerILToNativeMap *mStart, *mEnd;
+
+ dji->MapILRangeToMapEntryRange(r->startOffset,
+ r->endOffset,
+ &mStart,
+ &mEnd);
+
+ // Either mStart and mEnd are both NULL (we don't have any sequence point),
+ // or they are both non-NULL.
+ _ASSERTE( ((mStart == NULL) && (mEnd == NULL)) ||
+ ((mStart != NULL) && (mEnd != NULL)) );
+
+ if (mStart == NULL)
+ {
+ // <REVISIT_TODO>@todo Won't this result in us stepping across
+ // the entire method?</REVISIT_TODO>
+ rTo->startOffset = 0;
+ rTo->endOffset = 0;
+ }
+ else if (mStart == mEnd)
+ {
+ rTo->startOffset = mStart->nativeStartOffset;
+ rTo->endOffset = mStart->nativeEndOffset;
+ }
+ else
+ {
+ // Account for more than one continuous range here.
+
+ // Move the pointer back to work with the loop increment below.
+ // Don't dereference this pointer now!
+ rTo--;
+
+ for (DebuggerILToNativeMap* pMap = mStart;
+ pMap <= mEnd;
+ pMap = pMap + 1)
+ {
+ if ((pMap == mStart) ||
+ (pMap->nativeStartOffset != (pMap-1)->nativeEndOffset))
+ {
+ rTo++;
+ GROW_RANGES_IF_NECESSARY();
+
+ rTo->startOffset = pMap->nativeStartOffset;
+ rTo->endOffset = pMap->nativeEndOffset;
+ }
+ else
+ {
+ // If we have continuous ranges, then lump them together.
+ _ASSERTE(rTo->endOffset == pMap->nativeStartOffset);
+ rTo->endOffset = pMap->nativeEndOffset;
+ }
+ }
+
+ LOG((LF_CORDB, LL_INFO10000, "DS:Step: nat off:0x%x to 0x%x\n", rTo->startOffset, rTo->endOffset));
+ }
+ }
+ }
+
+ rangeCount = (int)((BYTE*)rTo - (BYTE*)m_range) / sizeof(COR_DEBUG_STEP_RANGE);
+ }
+ else
+ {
+ // Even if we don't have debug info, we'll be able to
+ // step through the method
+ SIZE_T functionSize = g_pEEInterface->GetFunctionSize(fd);
+
+ COR_DEBUG_STEP_RANGE *r = ranges;
+ COR_DEBUG_STEP_RANGE *rEnd = r + rangeCount;
+
+ COR_DEBUG_STEP_RANGE *rTo = m_range;
+
+ for(/**/; r < rEnd; r++, rTo++)
+ {
+ if (r->startOffset == 0 && r->endOffset == (ULONG) ~0)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "DS:Step:No DJI, (0,-1) special entry\n"));
+ // Code dup'd from above case.
+ // {0...-1} means use the entire method as the range
+ rTo->startOffset = 0;
+ rTo->endOffset = (ULONG32)functionSize;
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO10000, "DS:Step:No DJI, regular entry\n"));
+ // We can't just leave ths IL entry - we have to
+ // get rid of it.
+ // This will just be ignored
+ rTo->startOffset = rTo->endOffset = (ULONG32)functionSize;
+ }
+ }
+ }
+
+
+ m_rangeCount = rangeCount;
+ m_realRangeCount = rangeCount;
+
+ return true;
+}
+
+
+// void DebuggerStepper::Step() Tells the stepper to step over
+// the provided ranges.
+// void *fp: frame pointer.
+// bool in: true if we want to step into a function within the range,
+// false if we want to step over functions within the range.
+// COR_DEBUG_STEP_RANGE *ranges: Assumed to be nonNULL, it will
+// always hold at least one element.
+// SIZE_T rangeCount: One less than the true number of elements in
+// the ranges argument.
+// bool rangeIL: true if the ranges are provided in IL (they'll be
+// converted to native before the DebuggerStepper uses them,
+// false if they already are native.
+bool DebuggerStepper::Step(FramePointer fp, bool in,
+ COR_DEBUG_STEP_RANGE *ranges, SIZE_T rangeCount,
+ bool rangeIL)
+{
+ LOG((LF_CORDB, LL_INFO1000, "DeSt:Step this:0x%x ", this));
+ if (rangeCount>0)
+ LOG((LF_CORDB,LL_INFO10000," start,end[0]:(0x%x,0x%x)\n",
+ ranges[0].startOffset, ranges[0].endOffset));
+ else
+ LOG((LF_CORDB,LL_INFO10000," single step\n"));
+
+ Thread *thread = GetThread();
+ CONTEXT *context = g_pEEInterface->GetThreadFilterContext(thread);
+
+ // ControllerStackInfo doesn't report IL stubs, so if we are in an IL stub, we need
+ // to handle the single-step specially. There are probably other problems when we stop
+ // in an IL stub. We need to revisit this later.
+ bool fIsILStub = false;
+ if ((context != NULL) &&
+ g_pEEInterface->IsManagedNativeCode(reinterpret_cast<const BYTE *>(GetIP(context))))
+ {
+ MethodDesc * pMD = g_pEEInterface->GetNativeCodeMethodDesc(GetIP(context));
+ if (pMD != NULL)
+ {
+ fIsILStub = pMD->IsILStub();
+ }
+ }
+ LOG((LF_CORDB, LL_INFO10000, "DS::S - fIsILStub = %d\n", fIsILStub));
+
+ ControllerStackInfo info;
+
+
+ StackTraceTicket ticket(thread);
+ info.GetStackInfo(ticket, thread, fp, context);
+
+ _ASSERTE((fp == LEAF_MOST_FRAME) || (info.m_activeFrame.md != NULL) ||
+ (info.m_returnFrame.md != NULL));
+
+ m_stepIn = in;
+
+ DebuggerJitInfo *dji = info.m_activeFrame.GetJitInfoFromFrame();
+
+ if (dji == NULL)
+ {
+ // !!! ERROR range step in frame with no code
+ ranges = NULL;
+ rangeCount = 0;
+ }
+
+
+ if (m_range != NULL)
+ {
+ TRACE_FREE(m_range);
+ DeleteInteropSafe(m_range);
+ m_range = NULL;
+ m_rangeCount = 0;
+ m_realRangeCount = 0;
+ }
+
+ if (rangeCount > 0)
+ {
+ if (rangeIL)
+ {
+ // IL ranges supplied, we need to convert them to native ranges.
+ bool fOk = SetRangesFromIL(dji, ranges, rangeCount);
+ if (!fOk)
+ {
+ return false;
+ }
+ }
+ else
+ {
+ // Native ranges, already supplied. Just copy them over.
+ m_range = new (interopsafe) COR_DEBUG_STEP_RANGE[rangeCount];
+
+ if (m_range == NULL)
+ {
+ return false;
+ }
+
+ memcpy(m_range, ranges, sizeof(COR_DEBUG_STEP_RANGE) * rangeCount);
+ m_realRangeCount = m_rangeCount = rangeCount;
+ }
+ _ASSERTE(m_range != NULL);
+ _ASSERTE(m_rangeCount > 0);
+ _ASSERTE(m_realRangeCount > 0);
+ }
+ else
+ {
+ // !!! ERROR cannot map IL ranges
+ ranges = NULL;
+ rangeCount = 0;
+ }
+
+ if (fIsILStub)
+ {
+ // Don't use the ControllerStackInfo if we are in an IL stub.
+ m_fp = fp;
+ }
+ else
+ {
+ m_fp = info.m_activeFrame.fp;
+#if defined(WIN64EXCEPTIONS)
+ // We need to remember the parent method frame pointer here so that we will recognize
+ // the range of the stepper as being valid when we return to the parent method.
+ if (info.m_activeFrame.IsNonFilterFuncletFrame())
+ {
+ m_fpParentMethod = info.m_returnFrame.fp;
+ }
+#endif // WIN64EXCEPTIONS
+ }
+ m_eMode = m_stepIn ? cStepIn : cStepOver;
+
+ LOG((LF_CORDB,LL_INFO10000,"DS 0x%x STep: STEP_NORMAL\n",this));
+ m_reason = STEP_NORMAL; //assume it'll be a normal step & set it to
+ //something else if we walk over it
+ if (fIsILStub)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "DS:Step: stepping in an IL stub\n"));
+
+ // Enable the right triggers if the user wants to step in.
+ if (in)
+ {
+ if (this->GetDCType() == DEBUGGER_CONTROLLER_STEPPER)
+ {
+ EnableTraceCall(info.m_activeFrame.fp);
+ }
+ else if (this->GetDCType() == DEBUGGER_CONTROLLER_JMC_STEPPER)
+ {
+ EnableMethodEnter();
+ }
+ }
+
+ // Also perform a step-out in case this IL stub is returning to managed code.
+ // However, we must fix up the ControllerStackInfo first, since it doesn't
+ // report IL stubs. The active frame reported by the ControllerStackInfo is
+ // actually the return frame in this case.
+ info.SetReturnFrameWithActiveFrame();
+ TrapStepOut(&info);
+ }
+ else if (!TrapStep(&info, in))
+ {
+ LOG((LF_CORDB,LL_INFO10000,"DS:Step: Did TS\n"));
+ m_stepIn = true;
+ TrapStepNext(&info);
+ }
+
+ LOG((LF_CORDB,LL_INFO10000,"DS:Step: Did TS,TSO\n"));
+
+ EnableUnwind(m_fp);
+
+ return true;
+}
+
+// TP_RESULT DebuggerStepper::TriggerPatch()
+// What: Triggers patch if we're not in a stub, and we're
+// outside of the stepping range. Otherwise sets another patch so as to
+// step out of the stub, or in the next instruction within the range.
+// How: If module==NULL & managed==> we're in a stub:
+// TrapStepOut() and return false. Module==NULL&!managed==> return
+// true. If m_range != NULL & execution is currently in the range,
+// attempt a TrapStep (TrapStepOut otherwise) & return false. Otherwise,
+// return true.
+TP_RESULT DebuggerStepper::TriggerPatch(DebuggerControllerPatch *patch,
+ Thread *thread,
+ TRIGGER_WHY tyWhy)
+{
+ LOG((LF_CORDB, LL_INFO10000, "DeSt::TP\n"));
+
+ // If we're frozen, we may hit a patch but we just ignore it
+ if (IsFrozen())
+ {
+ LOG((LF_CORDB, LL_INFO1000000, "DS::TP, ignoring patch at %p during frozen state\n", patch->address));
+ return TPR_IGNORE;
+ }
+
+ Module *module = patch->key.module;
+ BOOL managed = patch->IsManagedPatch();
+ mdMethodDef md = patch->key.md;
+ SIZE_T offset = patch->offset;
+
+ _ASSERTE((this->GetThread() == thread) || !"Stepper should only get patches on its thread");
+
+ // Note we can only run a stack trace if:
+ // - the context is in managed code (eg, not a stub)
+ // - OR we have a frame in place to prime the stackwalk.
+ ControllerStackInfo info;
+ CONTEXT *context = g_pEEInterface->GetThreadFilterContext(thread);
+
+ _ASSERTE(!ISREDIRECTEDTHREAD(thread));
+
+ // Context should always be from patch.
+ _ASSERTE(context != NULL);
+
+ bool fSafeToDoStackTrace = true;
+
+ // If we're in a stub (module == NULL and still in managed code), then our context is off in lala-land
+ // Then, it's only safe to do a stackwalk if the top frame is protecting us. That's only true for a
+ // frame_push. If we're here on a manager_push, then we don't have any such protection, so don't do the
+ // stackwalk.
+
+ fSafeToDoStackTrace = patch->IsSafeForStackTrace();
+
+
+ if (fSafeToDoStackTrace)
+ {
+ StackTraceTicket ticket(patch);
+ info.GetStackInfo(ticket, thread, LEAF_MOST_FRAME, context);
+
+ LOG((LF_CORDB, LL_INFO10000, "DS::TP: this:0x%p in %s::%s (fp:0x%p, "
+ "off:0x%p md:0x%p), \n\texception source:%s::%s (fp:0x%p)\n",
+ this,
+ info.m_activeFrame.md!=NULL?info.m_activeFrame.md->m_pszDebugClassName:"Unknown",
+ info.m_activeFrame.md!=NULL?info.m_activeFrame.md->m_pszDebugMethodName:"Unknown",
+ info.m_activeFrame.fp.GetSPValue(), patch->offset, patch->key.md,
+ m_fdException!=NULL?m_fdException->m_pszDebugClassName:"None",
+ m_fdException!=NULL?m_fdException->m_pszDebugMethodName:"None",
+ m_fpException.GetSPValue()));
+ }
+
+ DisableAll();
+
+ if (DetectHandleLCGMethods(dac_cast<PCODE>(patch->address), NULL, &info))
+ {
+ return TPR_IGNORE;
+ }
+
+ if (module == NULL)
+ {
+ // JMC steppers should not be patching here...
+ _ASSERTE(DEBUGGER_CONTROLLER_JMC_STEPPER != this->GetDCType());
+
+ if (managed)
+ {
+
+ LOG((LF_CORDB, LL_INFO10000,
+ "Frame (stub) patch hit at offset 0x%x\n", offset));
+
+ // This is a stub patch. If it was a TRACE_FRAME_PUSH that
+ // got us here, then the stub's frame is pushed now, so we
+ // tell the frame to apply the real patch. If we got here
+ // via a TRACE_MGR_PUSH, however, then there is no frame
+ // and we tell the stub manager that generated the
+ // TRACE_MGR_PUSH to apply the real patch.
+ TraceDestination trace;
+ bool traceOk;
+ FramePointer frameFP;
+ PTR_BYTE traceManagerRetAddr = NULL;
+
+ if (patch->trace.GetTraceType() == TRACE_MGR_PUSH)
+ {
+ _ASSERTE(context != NULL);
+ CONTRACT_VIOLATION(GCViolation);
+ traceOk = g_pEEInterface->TraceManager(
+ thread,
+ patch->trace.GetStubManager(),
+ &trace,
+ context,
+ &traceManagerRetAddr);
+
+ // We don't hae an active frame here, so patch with a
+ // FP of NULL so anything will match.
+ //
+ // <REVISIT_TODO>@todo: should we take Esp out of the context?</REVISIT_TODO>
+ frameFP = LEAF_MOST_FRAME;
+ }
+ else
+ {
+ _ASSERTE(fSafeToDoStackTrace);
+ CONTRACT_VIOLATION(GCViolation); // TraceFrame GC-triggers
+ traceOk = g_pEEInterface->TraceFrame(thread,
+ thread->GetFrame(),
+ TRUE,
+ &trace,
+ &(info.m_activeFrame.registers));
+
+ frameFP = info.m_activeFrame.fp;
+ }
+
+ // Enable the JMC backstop for traditional steppers to catch us in case
+ // we didn't predict the call target properly.
+ EnableJMCBackStop(NULL);
+
+
+ if (!traceOk
+ || !g_pEEInterface->FollowTrace(&trace)
+ || !PatchTrace(&trace, frameFP,
+ (m_rgfMappingStop&STOP_UNMANAGED)?
+ (true):(false)))
+ {
+ //
+ // We can't set a patch in the frame -- we need
+ // to trap returning from this frame instead.
+ //
+ // Note: if we're in the TRACE_MGR_PUSH case from
+ // above, then we must place a patch where the
+ // TraceManager function told us to, since we can't
+ // actually unwind from here.
+ //
+ if (patch->trace.GetTraceType() != TRACE_MGR_PUSH)
+ {
+ _ASSERTE(fSafeToDoStackTrace);
+ LOG((LF_CORDB,LL_INFO10000,"TSO for non TRACE_MGR_PUSH case\n"));
+ TrapStepOut(&info);
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO10000,
+ "TSO for TRACE_MGR_PUSH case."));
+
+ // We'd better have a valid return address.
+ _ASSERTE(traceManagerRetAddr != NULL);
+
+ if (g_pEEInterface->IsManagedNativeCode(traceManagerRetAddr))
+ {
+ // Grab the jit info for the method.
+ DebuggerJitInfo *dji;
+ dji = g_pDebugger->GetJitInfoFromAddr((TADDR) traceManagerRetAddr);
+
+ MethodDesc * mdNative = (dji == NULL) ?
+ g_pEEInterface->GetNativeCodeMethodDesc(dac_cast<PCODE>(traceManagerRetAddr)) : dji->m_fd;
+ _ASSERTE(mdNative != NULL);
+
+ // Find the method that the return is to.
+ _ASSERTE(g_pEEInterface->GetFunctionAddress(mdNative) != NULL);
+ SIZE_T offsetRet = dac_cast<TADDR>(traceManagerRetAddr -
+ g_pEEInterface->GetFunctionAddress(mdNative));
+
+ // Place the patch.
+ AddBindAndActivateNativeManagedPatch(mdNative,
+ dji,
+ offsetRet,
+ LEAF_MOST_FRAME,
+ NULL);
+
+ LOG((LF_CORDB, LL_INFO10000,
+ "DS::TP: normally managed code AddPatch"
+ " in %s::%s, offset 0x%x\n",
+ mdNative->m_pszDebugClassName,
+ mdNative->m_pszDebugMethodName,
+ offsetRet));
+ }
+ else
+ {
+ // We're hitting this code path with MC++ assemblies
+ // that have an unmanaged entry point so the stub returns to CallDescrWorker.
+ _ASSERTE(g_pEEInterface->GetNativeCodeMethodDesc(dac_cast<PCODE>(patch->address))->IsILStub());
+ }
+
+ }
+
+ m_reason = STEP_NORMAL; //we tried to do a STEP_CALL, but since it didn't
+ //work, we're doing what amounts to a normal step.
+ LOG((LF_CORDB,LL_INFO10000,"DS 0x%x m_reason = STEP_NORMAL"
+ "(attempted call thru stub manager, SM didn't know where"
+ " we're going, so did a step out to original call\n",this));
+ }
+ else
+ {
+ m_reason = STEP_CALL;
+ }
+
+ EnableTraceCall(LEAF_MOST_FRAME);
+ EnableUnwind(m_fp);
+
+ return TPR_IGNORE;
+ }
+ else
+ {
+ // @todo - when would we hit this codepath?
+ // If we're not in managed, then we should have pushed a frame onto the Thread's frame chain,
+ // and thus we should still safely be able to do a stackwalk here.
+ _ASSERTE(fSafeToDoStackTrace);
+ if (DetectHandleInterceptors(&info) )
+ {
+ return TPR_IGNORE; //don't actually want to stop
+ }
+
+ LOG((LF_CORDB, LL_INFO10000,
+ "Unmanaged step patch hit at 0x%x\n", offset));
+
+ StackTraceTicket ticket(patch);
+ PrepareForSendEvent(ticket);
+ return TPR_TRIGGER;
+ }
+ } // end (module == NULL)
+
+ // If we're inside an interceptor but don't want to be,then we'll set a
+ // patch outside the current function.
+ _ASSERTE(fSafeToDoStackTrace);
+ if (DetectHandleInterceptors(&info) )
+ {
+ return TPR_IGNORE; //don't actually want to stop
+ }
+
+ LOG((LF_CORDB,LL_INFO10000, "DS: m_fp:0x%p, activeFP:0x%p fpExc:0x%p\n",
+ m_fp.GetSPValue(), info.m_activeFrame.fp.GetSPValue(), m_fpException.GetSPValue()));
+
+ if (IsInRange(offset, m_range, m_rangeCount, &info) ||
+ ShouldContinueStep( &info, offset))
+ {
+ LOG((LF_CORDB, LL_INFO10000,
+ "Intermediate step patch hit at 0x%x\n", offset));
+
+ if (!TrapStep(&info, m_stepIn))
+ TrapStepNext(&info);
+
+ EnableUnwind(m_fp);
+ return TPR_IGNORE;
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO10000, "Step patch hit at 0x%x\n", offset));
+
+ // For a JMC stepper, we have an additional constraint:
+ // skip non-user code. So if we're still in non-user code, then
+ // we've got to keep going
+ DebuggerMethodInfo * dmi = g_pDebugger->GetOrCreateMethodInfo(module, md);
+
+ if ((dmi != NULL) && DetectHandleNonUserCode(&info, dmi))
+ {
+ return TPR_IGNORE;
+ }
+
+ StackTraceTicket ticket(patch);
+ PrepareForSendEvent(ticket);
+ return TPR_TRIGGER;
+ }
+}
+
+// Return true if this should be skipped.
+// For a non-jmc stepper, we don't care about non-user code, so we
+// don't skip it and so we always return false.
+bool DebuggerStepper::DetectHandleNonUserCode(ControllerStackInfo *info, DebuggerMethodInfo * pInfo)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ return false;
+}
+
+// For regular steppers, trace-call is just a trace-call.
+void DebuggerStepper::EnablePolyTraceCall()
+{
+ this->EnableTraceCall(LEAF_MOST_FRAME);
+}
+
+// Traditional steppers enable MethodEnter as a back-stop for step-in.
+// We hope that the stub-managers will predict the step-in for us,
+// but in case they don't the Method-Enter should catch us.
+// MethodEnter is not fully correct for traditional steppers for a few reasons:
+// - doesn't handle step-in to native
+// - stops us *after* the prolog (a traditional stepper can stop us before the prolog).
+// - only works for methods that have the JMC probe. That can exclude all optimized code.
+void DebuggerStepper::TriggerMethodEnter(Thread * thread,
+ DebuggerJitInfo *dji,
+ const BYTE * ip,
+ FramePointer fp)
+{
+ _ASSERTE(dji != NULL);
+ _ASSERTE(thread != NULL);
+ _ASSERTE(ip != NULL);
+
+
+
+ _ASSERTE(this->GetDCType() == DEBUGGER_CONTROLLER_STEPPER);
+
+ _ASSERTE(!IsFrozen());
+
+ MethodDesc * pDesc = dji->m_fd;
+ LOG((LF_CORDB, LL_INFO10000, "DJMCStepper::TME, desc=%p, addr=%p\n",
+ pDesc, ip));
+
+ // JMC steppers won't stop in Lightweight delegates. Just return & keep executing.
+ if (pDesc->IsNoMetadata())
+ {
+ LOG((LF_CORDB, LL_INFO100000, "DJMCStepper::TME, skipping b/c it's lw-codegen\n"));
+ return;
+ }
+
+ // This is really just a heuristic. We don't want to trigger a JMC probe when we are
+ // executing in an IL stub, or in one of the marshaling methods called by the IL stub.
+ // The problem is that the IL stub can call into arbitrary code, including custom marshalers.
+ // In that case the user has to put a breakpoint to stop in the code.
+ if (g_pEEInterface->DetectHandleILStubs(thread))
+ {
+ return;
+ }
+
+#ifdef _DEBUG
+ // To help trace down if a problem is related to a stubmanager,
+ // we add a knob that lets us skip the MethodEnter checks. This lets tests directly
+ // go against the Stub-managers w/o the MethodEnter check backstops.
+ int fSkip = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgSkipMEOnStep);
+ if (fSkip)
+ {
+ return;
+ }
+
+ // See EnableJMCBackStop() for details here. This check just makes sure that we don't fire
+ // the assert if we end up in the method we started in (which could happen if we trace call
+ // instructions before the JMC probe).
+ // m_StepInStartMethod may be null (if this step-in didn't start from managed code).
+ if ((m_StepInStartMethod != pDesc) &&
+ (!m_StepInStartMethod->IsLCGMethod()))
+ {
+ // Since normal step-in should stop us at the prolog, and TME is after the prolog,
+ // if a stub-manager did successfully find the address, we should get a TriggerPatch first
+ // at native offset 0 (before the prolog) and before we get the TME. That means if
+ // we do get the TME, then there was no stub-manager to find us.
+
+ SString sLog;
+ StubManager::DbgGetLog(&sLog);
+
+ // Assert b/c the Stub-manager should have caught us first.
+ // We don't want people relying on TriggerMethodEnter as the real implementation for Traditional Step-in
+ // (see above for reasons why). However, using TME will provide a bandage for the final retail product
+ // in cases where we are missing a stub-manager.
+ CONSISTENCY_CHECK_MSGF(false, (
+ "\nThe Stubmanagers failed to identify and trace a stub on step-in. The stub-managers for this code-path path need to be fixed.\n"
+ "See http://team/sites/clrdev/Devdocs/StubManagers.rtf for more information on StubManagers.\n"
+ "Stepper this=0x%p, startMethod='%s::%s'\n"
+ "---------------------------------\n"
+ "Stub manager log:\n%S"
+ "\n"
+ "The thread is now in managed method '%s::%s'.\n"
+ "---------------------------------\n",
+ this,
+ ((m_StepInStartMethod == NULL) ? "unknown" : m_StepInStartMethod->m_pszDebugClassName),
+ ((m_StepInStartMethod == NULL) ? "unknown" : m_StepInStartMethod->m_pszDebugMethodName),
+ sLog.GetUnicode(),
+ pDesc->m_pszDebugClassName, pDesc->m_pszDebugMethodName
+ ));
+ }
+#endif
+
+
+
+ // Place a patch to stopus.
+ // Don't bind to a particular AppDomain so that we can do a Cross-Appdomain step.
+ AddBindAndActivateNativeManagedPatch(pDesc,
+ dji,
+ CodeRegionInfo::GetCodeRegionInfo(dji, pDesc).AddressToOffset(ip),
+ fp,
+ NULL // AppDomain
+ );
+
+ LOG((LF_CORDB, LL_INFO10000, "DJMCStepper::TME, after setting patch to stop\n"));
+
+ // Once we resume, we'll go hit that patch (duh, we patched our return address)
+ // Furthermore, we know the step will complete with reason = call, so set that now.
+ m_reason = STEP_CALL;
+}
+
+
+// We may have single-stepped over a return statement to land us up a frame.
+// Or we may have single-stepped through a method.
+// We never single-step into calls (we place a patch at the call destination).
+bool DebuggerStepper::TriggerSingleStep(Thread *thread, const BYTE *ip)
+{
+ LOG((LF_CORDB,LL_INFO10000,"DS:TSS this:0x%x, @ ip:0x%x\n", this, ip));
+
+ _ASSERTE(!IsFrozen());
+
+ // User break should only do a step-out and never actually need a singlestep flag.
+ _ASSERTE(GetDCType() != DEBUGGER_CONTROLLER_USER_BREAKPOINT);
+
+ //
+ // there's one weird case here - if the last instruction generated
+ // a hardware exception, we may be in lala land. If so, rely on the unwind
+ // handler to figure out what happened.
+ //
+ // <REVISIT_TODO>@todo this could be wrong when we have the incremental collector going</REVISIT_TODO>
+ //
+
+ if (!g_pEEInterface->IsManagedNativeCode(ip))
+ {
+ LOG((LF_CORDB,LL_INFO10000, "DS::TSS: not in managed code, Returning false (case 0)!\n"));
+ DisableSingleStep();
+ return false;
+ }
+
+ // If we EnC the method, we'll blast the function address,
+ // and so have to get it from teh DJI that we'll have. If
+ // we haven't gotten debugger info about a regular function, then
+ // we'll have to get the info from the EE, which will be valid
+ // since we're standing in the function at this point, and
+ // EnC couldn't have happened yet.
+ MethodDesc *fd = g_pEEInterface->GetNativeCodeMethodDesc((PCODE)ip);
+
+ SIZE_T offset;
+ DebuggerJitInfo *dji = g_pDebugger->GetJitInfoFromAddr((TADDR) ip);
+ offset = CodeRegionInfo::GetCodeRegionInfo(dji, fd).AddressToOffset(ip);
+
+ ControllerStackInfo info;
+
+ // Safe to stackwalk b/c we've already checked that our IP is in crawlable code.
+ StackTraceTicket ticket(ip);
+ info.GetStackInfo(ticket, GetThread(), LEAF_MOST_FRAME, NULL);
+
+ // This is a special case where we return from a managed method back to an IL stub. This can
+ // only happen if there's no more managed method frames closer to the root and we want to perform
+ // a step out, or if we step-next off the end of a method called by an IL stub. In either case,
+ // we'll get a single step in an IL stub, which we want to ignore. We also want to enable trace
+ // call here, just in case this IL stub is about to call the managed target (in the reverse interop case).
+ if (fd->IsILStub())
+ {
+ LOG((LF_CORDB,LL_INFO10000, "DS::TSS: not in managed code, Returning false (case 0)!\n"));
+ if (this->GetDCType() == DEBUGGER_CONTROLLER_STEPPER)
+ {
+ EnableTraceCall(info.m_activeFrame.fp);
+ }
+ else if (this->GetDCType() == DEBUGGER_CONTROLLER_JMC_STEPPER)
+ {
+ EnableMethodEnter();
+ }
+ DisableSingleStep();
+ return false;
+ }
+
+ DisableAll();
+
+ LOG((LF_CORDB,LL_INFO10000, "DS::TSS m_fp:0x%x, activeFP:0x%x fpExc:0x%x\n",
+ m_fp.GetSPValue(), info.m_activeFrame.fp.GetSPValue(), m_fpException.GetSPValue()));
+
+ if (DetectHandleLCGMethods((PCODE)ip, fd, &info))
+ {
+ return false;
+ }
+
+ if (IsInRange(offset, m_range, m_rangeCount, &info) ||
+ ShouldContinueStep( &info, offset))
+ {
+ if (!TrapStep(&info, m_stepIn))
+ TrapStepNext(&info);
+
+ EnableUnwind(m_fp);
+
+ LOG((LF_CORDB,LL_INFO10000, "DS::TSS: Returning false Case 1!\n"));
+ return false;
+ }
+ else
+ {
+ LOG((LF_CORDB,LL_INFO10000, "DS::TSS: Returning true Case 2 for reason STEP_%02x!\n", m_reason));
+
+ // @todo - when would a single-step (not a patch) land us in user-code?
+ // For a JMC stepper, we have an additional constraint:
+ // skip non-user code. So if we're still in non-user code, then
+ // we've got to keep going
+ DebuggerMethodInfo * dmi = g_pDebugger->GetOrCreateMethodInfo(fd->GetModule(), fd->GetMemberDef());
+
+ if ((dmi != NULL) && DetectHandleNonUserCode(&info, dmi))
+ return false;
+
+ PrepareForSendEvent(ticket);
+ return true;
+ }
+}
+
+void DebuggerStepper::TriggerTraceCall(Thread *thread, const BYTE *ip)
+{
+ LOG((LF_CORDB,LL_INFO10000,"DS:TTC this:0x%x, @ ip:0x%x\n",this,ip));
+ TraceDestination trace;
+
+ if (IsFrozen())
+ {
+ LOG((LF_CORDB,LL_INFO10000,"DS:TTC exit b/c of Frozen\n"));
+ return;
+ }
+
+ // This is really just a heuristic. We don't want to trigger a JMC probe when we are
+ // executing in an IL stub, or in one of the marshaling methods called by the IL stub.
+ // The problem is that the IL stub can call into arbitrary code, including custom marshalers.
+ // In that case the user has to put a breakpoint to stop in the code.
+ if (g_pEEInterface->DetectHandleILStubs(thread))
+ {
+ return;
+ }
+
+ if (g_pEEInterface->TraceStub(ip, &trace)
+ && g_pEEInterface->FollowTrace(&trace)
+ && PatchTrace(&trace, LEAF_MOST_FRAME,
+ (m_rgfMappingStop&STOP_UNMANAGED)?(true):(false)))
+ {
+ // !!! We really want to know ahead of time if PatchTrace will succeed.
+ DisableAll();
+ PatchTrace(&trace, LEAF_MOST_FRAME, (m_rgfMappingStop&STOP_UNMANAGED)?
+ (true):(false));
+
+ // If we're triggering a trace call, and we're following a trace into either managed code or unjitted managed
+ // code, then we need to update our stepper's reason to STEP_CALL to reflect the fact that we're going to land
+ // into a new function because of a call.
+ if ((trace.GetTraceType() == TRACE_UNJITTED_METHOD) || (trace.GetTraceType() == TRACE_MANAGED))
+ {
+ m_reason = STEP_CALL;
+ }
+
+ EnableUnwind(m_fp);
+
+ LOG((LF_CORDB, LL_INFO10000, "DS::TTC potentially a step call!\n"));
+ }
+}
+
+void DebuggerStepper::TriggerUnwind(Thread *thread,
+ MethodDesc *fd, DebuggerJitInfo * pDJI, SIZE_T offset,
+ FramePointer fp,
+ CorDebugStepReason unwindReason)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ THROWS; // from GetJitInfo
+ GC_NOTRIGGER; // don't send IPC events
+ MODE_COOPERATIVE; // TriggerUnwind always is coop
+
+ PRECONDITION(!IsDbgHelperSpecialThread());
+ PRECONDITION(fd->IsDynamicMethod() || (pDJI != NULL));
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB,LL_INFO10000,"DS::TU this:0x%p, in %s::%s, offset 0x%p "
+ "frame:0x%p unwindReason:0x%x\n", this, fd->m_pszDebugClassName,
+ fd->m_pszDebugMethodName, offset, fp.GetSPValue(), unwindReason));
+
+ _ASSERTE(unwindReason == STEP_EXCEPTION_FILTER || unwindReason == STEP_EXCEPTION_HANDLER);
+
+ if (IsFrozen())
+ {
+ LOG((LF_CORDB,LL_INFO10000,"DS:TTC exit b/c of Frozen\n"));
+ return;
+ }
+
+ if (IsCloserToRoot(fp, GetUnwind()))
+ {
+ // Handler is in a parent frame . For all steps (in,out,over)
+ // we want to stop in the handler.
+ // This will be like a Step Out, so we don't need any range.
+ ResetRange();
+ }
+ else
+ {
+ // Handler/Filter is in the same frame as the stepper
+ // For a step-in/over, we want to patch the handler/filter.
+ // But for a step-out, we want to just continue executing (and don't change
+ // the step-reason either).
+ if (m_eMode == cStepOut)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "DS::TU Step-out, returning for same-frame case.\n"));
+ return;
+ }
+
+ }
+
+ // Remember the origin of the exception, so that if the step looks like
+ // it's going to complete in a different frame, but the code comes from the
+ // same frame as the one we're in, we won't stop twice in the "same" range
+ m_fpException = fp;
+ m_fdException = fd;
+
+ //
+ // An exception is exiting the step region. Set a patch on
+ // the filter/handler.
+ //
+
+ DisableAll();
+
+ BOOL fOk;
+ fOk = AddBindAndActivateNativeManagedPatch(fd, pDJI, offset, LEAF_MOST_FRAME, NULL);
+
+ // Since we're unwinding to an already executed method, the method should already
+ // be jitted and placing the patch should work.
+ CONSISTENCY_CHECK_MSGF(fOk, ("Failed to place patch at TriggerUnwind.\npThis=0x%p md=0x%p, native offset=0x%x\n", this, fd, offset));
+
+ LOG((LF_CORDB,LL_INFO100000,"Step reason:%s\n", unwindReason==STEP_EXCEPTION_FILTER
+ ? "STEP_EXCEPTION_FILTER":"STEP_EXCEPTION_HANDLER"));
+ m_reason = unwindReason;
+}
+
+
+// Prepare for sending an event.
+// This is called 1:1 w/ SendEvent, but this guy can be called in a GC_TRIGGERABLE context
+// whereas SendEvent is pretty strict.
+// Caller ensures that it's safe to run a stack trace.
+void DebuggerStepper::PrepareForSendEvent(StackTraceTicket ticket)
+{
+#ifdef _DEBUG
+ _ASSERTE(!m_fReadyToSend);
+ m_fReadyToSend = true;
+#endif
+
+ LOG((LF_CORDB, LL_INFO10000, "DS::SE m_fpStepInto:0x%x\n", m_fpStepInto.GetSPValue()));
+
+ if (m_fpStepInto != LEAF_MOST_FRAME)
+ {
+ ControllerStackInfo csi;
+ csi.GetStackInfo(ticket, GetThread(), LEAF_MOST_FRAME, NULL);
+
+ if (csi.m_targetFrameFound &&
+#if !defined(WIN64EXCEPTIONS)
+ IsCloserToRoot(m_fpStepInto, csi.m_activeFrame.fp)
+#else
+ IsCloserToRoot(m_fpStepInto, (csi.m_activeFrame.IsNonFilterFuncletFrame() ? csi.m_returnFrame.fp : csi.m_activeFrame.fp))
+#endif // WIN64EXCEPTIONS
+ )
+
+ {
+ m_reason = STEP_CALL;
+ LOG((LF_CORDB, LL_INFO10000, "DS::SE this:0x%x STEP_CALL!\n", this));
+ }
+#ifdef _DEBUG
+ else
+ {
+ LOG((LF_CORDB, LL_INFO10000, "DS::SE this:0x%x not a step call!\n", this));
+ }
+#endif
+ }
+
+#ifdef _DEBUG
+ // Steppers should only stop in interesting code.
+ if (this->GetDCType() == DEBUGGER_CONTROLLER_JMC_STEPPER)
+ {
+ // If we're at either a patch or SS, we'll have a context.
+ CONTEXT *context = g_pEEInterface->GetThreadFilterContext(GetThread());
+ if (context == NULL)
+ {
+ void * pIP = CORDbgGetIP(reinterpret_cast<DT_CONTEXT *>(context));
+
+ DebuggerJitInfo * dji = g_pDebugger->GetJitInfoFromAddr((TADDR) pIP);
+ DebuggerMethodInfo * dmi = NULL;
+ if (dji != NULL)
+ {
+ dmi = dji->m_methodInfo;
+
+ CONSISTENCY_CHECK_MSGF(dmi->IsJMCFunction(), ("JMC stepper %p stopping in non-jmc method, MD=%p, '%s::%s'",
+ this, dji->m_fd, dji->m_fd->m_pszDebugClassName, dji->m_fd->m_pszDebugMethodName));
+
+ }
+
+
+ }
+ }
+
+#endif
+}
+
+bool DebuggerStepper::SendEvent(Thread *thread, bool fIpChanged)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ SENDEVENT_CONTRACT_ITEMS;
+ }
+ CONTRACTL_END;
+
+ // We practically should never have a step interupted by SetIp.
+ // We'll still go ahead and send the Step-complete event because we've already
+ // deactivated our triggers by now and we haven't placed any new patches to catch us.
+ // We assert here because we don't believe we'll ever be able to hit this scenario.
+ // This is technically an issue, but we consider it benign enough to leave in.
+ _ASSERTE(!fIpChanged || !"Stepper interupted by SetIp");
+
+ LOG((LF_CORDB, LL_INFO10000, "DS::SE m_fpStepInto:0x%x\n", m_fpStepInto.GetSPValue()));
+
+ _ASSERTE(m_fReadyToSend);
+ _ASSERTE(GetThread() == thread);
+
+ CONTEXT *context = g_pEEInterface->GetThreadFilterContext(thread);
+ _ASSERTE(!ISREDIRECTEDTHREAD(thread));
+
+ // We need to send the stepper and delete the controller because our stepper
+ // no longer has any patches or other triggers that will let it send the step-complete event.
+ g_pDebugger->SendStep(thread, context, this, m_reason);
+
+ this->Delete();
+
+#ifdef _DEBUG
+ // Now that we've sent the event, we can stop recording information.
+ StubManager::DbgFinishLog();
+#endif
+
+ return true;
+}
+
+void DebuggerStepper::ResetRange()
+{
+ if (m_range)
+ {
+ TRACE_FREE(m_range);
+ DeleteInteropSafe(m_range);
+
+ m_range = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Return true if this stepper is alive, but frozen. (we freeze when the stepper
+// enters a nested func-eval).
+//-----------------------------------------------------------------------------
+bool DebuggerStepper::IsFrozen()
+{
+ return (m_cFuncEvalNesting > 0);
+}
+
+//-----------------------------------------------------------------------------
+// Returns true if this stepper is 'dead' - which happens if a non-frozen stepper
+// gets a func-eval exit.
+//-----------------------------------------------------------------------------
+bool DebuggerStepper::IsDead()
+{
+ return (m_cFuncEvalNesting < 0);
+}
+
+// * ------------------------------------------------------------------------
+// * DebuggerJMCStepper routines
+// * ------------------------------------------------------------------------
+DebuggerJMCStepper::DebuggerJMCStepper(Thread *thread,
+ CorDebugUnmappedStop rgfMappingStop,
+ CorDebugIntercept interceptStop,
+ AppDomain *appDomain) :
+ DebuggerStepper(thread, rgfMappingStop, interceptStop, appDomain)
+{
+ LOG((LF_CORDB, LL_INFO10000, "DJMCStepper ctor, this=%p\n", this));
+}
+
+DebuggerJMCStepper::~DebuggerJMCStepper()
+{
+ LOG((LF_CORDB, LL_INFO10000, "DJMCStepper dtor, this=%p\n", this));
+}
+
+// If we're a JMC stepper, then don't stop in non-user code.
+bool DebuggerJMCStepper::IsInterestingFrame(FrameInfo * pFrame)
+{
+ CONTRACTL
+ {
+ THROWS;
+ MODE_ANY;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ DebuggerMethodInfo *pInfo = pFrame->GetMethodInfoFromFrameOrThrow();
+ _ASSERTE(pInfo != NULL); // throws on failure
+
+ bool fIsUserCode = pInfo->IsJMCFunction();
+
+
+ LOG((LF_CORDB, LL_INFO1000000, "DS::TSO, frame '%s::%s' is '%s' code\n",
+ pFrame->DbgGetClassName(), pFrame->DbgGetMethodName(),
+ fIsUserCode ? "user" : "non-user"));
+
+ return fIsUserCode;
+}
+
+// A JMC stepper's step-next stops at the next thing of code run.
+// This may be a Step-Out, or any User code called before that.
+// A1 -> B1 -> { A2, B2 -> B3 -> A3}
+// So TrapStepNex at end of A2 should land us in A3.
+void DebuggerJMCStepper::TrapStepNext(ControllerStackInfo *info)
+{
+ LOG((LF_CORDB, LL_INFO10000, "DJMCStepper::TrapStepNext, this=%p\n", this));
+ EnableMethodEnter();
+
+ // This will place a patch up the stack and set m_reason = STEP_RETURN.
+ // If we end up hitting JMC before that patch, we'll hit TriggerMethodEnter
+ // and that will set our reason to STEP_CALL.
+ TrapStepOut(info);
+}
+
+// ip - target address for call instruction
+bool DebuggerJMCStepper::TrapStepInHelper(
+ ControllerStackInfo * pInfo,
+ const BYTE * ipCallTarget,
+ const BYTE * ipNext,
+ bool fCallingIntoFunclet)
+{
+#ifndef WIN64EXCEPTIONS
+ // There are no funclets on x86.
+ _ASSERTE(!fCallingIntoFunclet);
+#endif
+
+ // If we are calling into a funclet, then we can't rely on the JMC probe to stop us because there are no
+ // JMC probes in funclets. Instead, we have to perform a traditional step-in here.
+ if (fCallingIntoFunclet)
+ {
+ TraceDestination td;
+ td.InitForManaged(reinterpret_cast<PCODE>(ipCallTarget));
+ PatchTrace(&td, LEAF_MOST_FRAME, false);
+
+ // If this succeeds, then we still need to put a patch at the return address. This is done below.
+ // If this fails, then we definitely need to put a patch at the return address to trap the thread.
+ // So in either case, we have to execute the rest of this function.
+ }
+
+ MethodDesc * pDesc = pInfo->m_activeFrame.md;
+ DebuggerJitInfo *dji = NULL;
+
+ // We may not have a DJI if we're in an attach case. We should still be able to do a JMC-step in though.
+ // So NULL is ok here.
+ dji = g_pDebugger->GetJitInfo(pDesc, (const BYTE*) ipNext);
+
+
+ // Place patch after call, which is at ipNext. Note we don't need an IL->Native map here
+ // since we disassembled native code to find the ip after the call.
+ SIZE_T offset = CodeRegionInfo::GetCodeRegionInfo(dji, pDesc).AddressToOffset(ipNext);
+
+
+ LOG((LF_CORDB, LL_INFO100000, "DJMCStepper::TSIH, at '%s::%s', calling=0x%p, next=0x%p, offset=%d\n",
+ pDesc->m_pszDebugClassName,
+ pDesc->m_pszDebugMethodName,
+ ipCallTarget, ipNext,
+ offset));
+
+ // Place a patch at the native address (inside the managed method).
+ AddBindAndActivateNativeManagedPatch(pInfo->m_activeFrame.md,
+ dji,
+ offset,
+ pInfo->m_returnFrame.fp,
+ NULL);
+
+ EnableMethodEnter();
+
+ // Return true means that we want to let the stepper run free. It will either
+ // hit the patch after the call instruction or it will hit a TriggerMethodEnter.
+ return true;
+}
+
+// For JMC-steppers, we don't enable trace-call; we enable Method-Enter.
+void DebuggerJMCStepper::EnablePolyTraceCall()
+{
+ _ASSERTE(!IsFrozen());
+
+ this->EnableMethodEnter();
+}
+
+// Return true if this is non-user code. This means we've setup the proper patches &
+// triggers, etc and so we expect the controller to just run free.
+// This is called when all other stepping criteria are met and we're about to
+// send a step-complete. For JMC, this is when we see if we're in non-user code
+// and if so, continue stepping instead of send the step complete.
+// Return false if this is user-code.
+bool DebuggerJMCStepper::DetectHandleNonUserCode(ControllerStackInfo *pInfo, DebuggerMethodInfo * dmi)
+{
+ _ASSERTE(dmi != NULL);
+ bool fIsUserCode = dmi->IsJMCFunction();
+
+ if (!fIsUserCode)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "JMC stepper stopped in non-user code, continuing.\n"));
+ // Not-user code, we want to skip through this.
+
+ // We may be here while trying to step-out.
+ // Step-out just means stop at the first interesting frame above us.
+ // So JMC TrapStepOut won't patch a non-user frame.
+ // But if we're skipping over other stuff (prolog, epilog, interceptors,
+ // trace calls), then we may still be in the middle of non-user
+ //_ASSERTE(m_eMode != cStepOut);
+
+ if (m_eMode == cStepOut)
+ {
+ TrapStepOut(pInfo);
+ }
+ else if (m_stepIn)
+ {
+ EnableMethodEnter();
+ TrapStepOut(pInfo);
+ // Run until we hit the next thing of managed code.
+ } else {
+ // Do a traditional step-out since we just want to go up 1 frame.
+ TrapStepOut(pInfo, true); // force trad step out.
+
+
+ // If we're not in the original frame anymore, then
+ // If we did a Step-over at the end of a method, and that did a single-step over the return
+ // then we may already be in our parent frame. In that case, we also want to behave
+ // like a step-in and TriggerMethodEnter.
+ if (this->m_fp != pInfo->m_activeFrame.fp)
+ {
+ // If we're a step-over, then we should only be stopped in a parent frame.
+ _ASSERTE(m_stepIn || IsCloserToLeaf(this->m_fp, pInfo->m_activeFrame.fp));
+ EnableMethodEnter();
+ }
+
+ // Step-over shouldn't stop in a frame below us in the same callstack.
+ // So we do a tradional step-out of our current frame, which guarantees
+ // that. After that, we act just like a step-in.
+ m_stepIn = true;
+ }
+ EnableUnwind(m_fp);
+
+ // Must keep going...
+ return true;
+ }
+
+ return false;
+}
+
+// Dispatched right after the prolog of a JMC function.
+// We may be blocking the GC here, so let's be fast!
+void DebuggerJMCStepper::TriggerMethodEnter(Thread * thread,
+ DebuggerJitInfo *dji,
+ const BYTE * ip,
+ FramePointer fp)
+{
+ _ASSERTE(dji != NULL);
+ _ASSERTE(thread != NULL);
+ _ASSERTE(ip != NULL);
+
+ _ASSERTE(!IsFrozen());
+
+ MethodDesc * pDesc = dji->m_fd;
+ LOG((LF_CORDB, LL_INFO10000, "DJMCStepper::TME, desc=%p, addr=%p\n",
+ pDesc, ip));
+
+ // JMC steppers won't stop in Lightweight delegates. Just return & keep executing.
+ if (pDesc->IsNoMetadata())
+ {
+ LOG((LF_CORDB, LL_INFO100000, "DJMCStepper::TME, skipping b/c it's lw-codegen\n"));
+ return;
+ }
+
+ // Is this user code?
+ DebuggerMethodInfo * dmi = dji->m_methodInfo;
+ bool fIsUserCode = dmi->IsJMCFunction();
+
+
+ LOG((LF_CORDB, LL_INFO100000, "DJMCStepper::TME, '%s::%s' is '%s' code\n",
+ pDesc->m_pszDebugClassName,
+ pDesc->m_pszDebugMethodName,
+ fIsUserCode ? "user" : "non-user"
+ ));
+
+ // If this isn't user code, then just return and continue executing.
+ if (!fIsUserCode)
+ return;
+
+ // MethodEnter is only enabled when we want to stop in a JMC function.
+ // And that's where we are now. So patch the ip and resume.
+ // The stepper will hit the patch, and stop.
+
+ // It's a good thing we have the fp passed in, because we have no other
+ // way of getting it. We can't do a stack trace here (the stack trace
+ // would start at the last pushed Frame, which miss a lot of managed
+ // frames).
+
+ // Don't bind to a particular AppDomain so that we can do a Cross-Appdomain step.
+ AddBindAndActivateNativeManagedPatch(pDesc,
+ dji,
+ CodeRegionInfo::GetCodeRegionInfo(dji, pDesc).AddressToOffset(ip),
+ fp,
+ NULL // AppDomain
+ );
+
+ LOG((LF_CORDB, LL_INFO10000, "DJMCStepper::TME, after setting patch to stop\n"));
+
+ // Once we resume, we'll go hit that patch (duh, we patched our return address)
+ // Furthermore, we know the step will complete with reason = call, so set that now.
+ m_reason = STEP_CALL;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Helper to convert form an EE Frame's interception enum to a CorDebugIntercept
+// bitfield.
+// The intercept value in EE Frame's is a 0-based enumeration (not a bitfield).
+// The intercept value for ICorDebug is a bitfied.
+//-----------------------------------------------------------------------------
+CorDebugIntercept ConvertFrameBitsToDbg(Frame::Interception i)
+{
+ _ASSERTE(i >= 0 && i < Frame::INTERCEPTION_COUNT);
+
+ // Since the ee frame is a 0-based enum, we can just use a map.
+ const CorDebugIntercept map[Frame::INTERCEPTION_COUNT] =
+ {
+ // ICorDebug EE Frame
+ INTERCEPT_NONE, // INTERCEPTION_NONE,
+ INTERCEPT_CLASS_INIT, // INTERCEPTION_CLASS_INIT
+ INTERCEPT_EXCEPTION_FILTER, // INTERCEPTION_EXCEPTION
+ INTERCEPT_CONTEXT_POLICY, // INTERCEPTION_CONTEXT
+ INTERCEPT_SECURITY, // INTERCEPTION_SECURITY
+ INTERCEPT_INTERCEPTION, // INTERCEPTION_OTHER
+ };
+
+ return map[i];
+}
+
+//-----------------------------------------------------------------------------
+// This is a helper class to do a stack walk over a certain range and find all the interceptors.
+// This allows a JMC stepper to see if there are any interceptors it wants to skip over (though
+// there's nothing JMC-specific about this).
+// Note that we only want to walk the stack range that the stepper is operating in.
+// That's because we don't care about interceptors that happened _before_ the
+// stepper was created.
+//-----------------------------------------------------------------------------
+class InterceptorStackInfo
+{
+public:
+#ifdef _DEBUG
+ InterceptorStackInfo()
+ {
+ // since this ctor just nulls out fpTop (which is already done in Init), we
+ // only need it in debug.
+ m_fpTop = LEAF_MOST_FRAME;
+ }
+#endif
+
+ // Get a CorDebugIntercept bitfield that contains a bit for each type of interceptor
+ // if that interceptor is present within our stack-range.
+ // Stack range is from leaf-most up to and including fp
+ CorDebugIntercept GetInterceptorsInRange()
+ {
+ _ASSERTE(m_fpTop != LEAF_MOST_FRAME || !"Must call Init first");
+ return (CorDebugIntercept) m_bits;
+ }
+
+ // Prime the stackwalk.
+ void Init(FramePointer fpTop, Thread *thread, CONTEXT *pContext, BOOL contextValid)
+ {
+ _ASSERTE(fpTop != LEAF_MOST_FRAME);
+ _ASSERTE(thread != NULL);
+
+ m_bits = 0;
+ m_fpTop = fpTop;
+
+ LOG((LF_CORDB,LL_EVERYTHING, "ISI::Init - fpTop=%p, thread=%p, pContext=%p, contextValid=%d\n",
+ fpTop.GetSPValue(), thread, pContext, contextValid));
+
+ int result;
+ result = DebuggerWalkStack(
+ thread,
+ LEAF_MOST_FRAME,
+ pContext,
+ contextValid,
+ WalkStack,
+ (void *) this,
+ FALSE
+ );
+ }
+
+
+protected:
+ // This is a bitfield of all the interceptors we encounter in our stack-range
+ int m_bits;
+
+ // This is the top of our stack range.
+ FramePointer m_fpTop;
+
+ static StackWalkAction WalkStack(FrameInfo *pInfo, void *data)
+ {
+ _ASSERTE(pInfo != NULL);
+ _ASSERTE(data != NULL);
+ InterceptorStackInfo * pThis = (InterceptorStackInfo*) data;
+
+ // If there's an interceptor frame here, then set those
+ // bits in our bitfield.
+ Frame::Interception i = Frame::INTERCEPTION_NONE;
+ Frame * pFrame = pInfo->frame;
+ if ((pFrame != NULL) && (pFrame != FRAME_TOP))
+ {
+ i = pFrame->GetInterception();
+ if (i != Frame::INTERCEPTION_NONE)
+ {
+ pThis->m_bits |= (int) ConvertFrameBitsToDbg(i);
+ }
+ }
+ else if (pInfo->HasMethodFrame())
+ {
+ // Check whether we are executing in a class constructor.
+ _ASSERTE(pInfo->md != NULL);
+
+ // Need to be careful about an off-by-one error here! Imagine your stack looks like:
+ // Foo.DoSomething()
+ // Foo..cctor <--- step starts/ends in here
+ // Bar.Bar();
+ //
+ // and your code looks like this:
+ // Foo..cctor()
+ // {
+ // Foo.DoSomething(); <-- JMC step started here
+ // int x = 1; <-- step ends here
+ // }
+ // This stackwalk covers the inclusive range [Foo..cctor, Foo.DoSomething()] so we will see
+ // the static cctor in this walk. However executing inside a static class constructor does not
+ // count as an interceptor. You must start the step outside the static constructor and then call
+ // into it to have an interceptor. Therefore only static constructors that aren't the outermost
+ // frame should be treated as interceptors.
+ if (pInfo->md->IsClassConstructor() && (pInfo->fp != pThis->m_fpTop))
+ {
+ // We called a class constructor, add the appropriate flag
+ pThis->m_bits |= (int) INTERCEPT_CLASS_INIT;
+ }
+ }
+ LOG((LF_CORDB,LL_EVERYTHING,"ISI::WS- Frame=%p, fp=%p, Frame bits=%x, Cor bits=0x%x\n", pInfo->frame, pInfo->fp.GetSPValue(), i, pThis->m_bits));
+
+
+ // We can stop once we hit the top frame.
+ if (pInfo->fp == pThis->m_fpTop)
+ {
+ return SWA_ABORT;
+ }
+ else
+ {
+ return SWA_CONTINUE;
+ }
+ }
+};
+
+
+
+
+// Skip interceptors for JMC steppers.
+// Return true if we patch something (and thus should keep stepping)
+// Return false if we're done.
+bool DebuggerJMCStepper::DetectHandleInterceptors(ControllerStackInfo * info)
+{
+ LOG((LF_CORDB,LL_INFO10000,"DJMCStepper::DHI: Start DetectHandleInterceptors\n"));
+
+ // For JMC, we could stop very far way from an interceptor.
+ // So we have to do a stack walk to search for interceptors...
+ // If we find any in our stack range (from m_fp ... current fp), then we just do a trap-step-next.
+
+ // Note that this logic should also work for regular steppers, but we've left that in
+ // as to keep that code-path unchanged.
+
+ // ControllerStackInfo only gives us the bottom 2 frames on the stack, so we ignore it and
+ // have to do our own stack walk.
+
+ // @todo - for us to properly skip filters, we need to make sure that filters show up in our chains.
+
+
+ InterceptorStackInfo info2;
+ CONTEXT *context = g_pEEInterface->GetThreadFilterContext(this->GetThread());
+ CONTEXT tempContext;
+
+ _ASSERTE(!ISREDIRECTEDTHREAD(this->GetThread()));
+
+ if (context == NULL)
+ {
+ info2.Init(this->m_fp, this->GetThread(), &tempContext, FALSE);
+ }
+ else
+ {
+ info2.Init(this->m_fp, this->GetThread(), context, TRUE);
+ }
+
+ // The following casts are safe on WIN64 platforms.
+ int iOnStack = (int) info2.GetInterceptorsInRange();
+ int iSkip = ~((int) m_rgfInterceptStop);
+
+ LOG((LF_CORDB,LL_INFO10000,"DJMCStepper::DHI: iOnStack=%x, iSkip=%x\n", iOnStack, iSkip));
+
+ // If the bits on the stack contain any interceptors we want to skip, then we need to keep going.
+ if ((iOnStack & iSkip) != 0)
+ {
+ LOG((LF_CORDB,LL_INFO10000,"DJMCStepper::DHI: keep going!\n"));
+ TrapStepNext(info);
+ EnableUnwind(m_fp);
+ return true;
+ }
+
+ LOG((LF_CORDB,LL_INFO10000,"DJMCStepper::DHI: Done!!\n"));
+ return false;
+}
+
+
+// * ------------------------------------------------------------------------
+// * DebuggerThreadStarter routines
+// * ------------------------------------------------------------------------
+
+DebuggerThreadStarter::DebuggerThreadStarter(Thread *thread)
+ : DebuggerController(thread, NULL)
+{
+ LOG((LF_CORDB, LL_INFO1000, "DTS::DTS: this:0x%x Thread:0x%x\n",
+ this, thread));
+
+ // Check to make sure we only have 1 ThreadStarter on a given thread. (Inspired by NDPWhidbey issue 16888)
+#if defined(_DEBUG)
+ EnsureUniqueThreadStarter(this);
+#endif
+}
+
+// TP_RESULT DebuggerThreadStarter::TriggerPatch() If we're in a
+// stub (module==NULL&&managed) then do a PatchTrace up the stack &
+// return false. Otherwise DisableAll & return
+// true
+TP_RESULT DebuggerThreadStarter::TriggerPatch(DebuggerControllerPatch *patch,
+ Thread *thread,
+ TRIGGER_WHY tyWhy)
+{
+ Module *module = patch->key.module;
+ BOOL managed = patch->IsManagedPatch();
+
+ LOG((LF_CORDB,LL_INFO1000, "DebuggerThreadStarter::TriggerPatch for thread 0x%x\n", Debugger::GetThreadIdHelper(thread)));
+
+ if (module == NULL && managed)
+ {
+ // This is a stub patch. If it was a TRACE_FRAME_PUSH that got us here, then the stub's frame is pushed now, so
+ // we tell the frame to apply the real patch. If we got here via a TRACE_MGR_PUSH, however, then there is no
+ // frame and we go back to the stub manager that generated the stub for where to patch next.
+ TraceDestination trace;
+ bool traceOk;
+ if (patch->trace.GetTraceType() == TRACE_MGR_PUSH)
+ {
+ BYTE *dummy = NULL;
+ CONTEXT *context = GetManagedLiveCtx(thread);
+ CONTRACT_VIOLATION(GCViolation);
+ traceOk = g_pEEInterface->TraceManager(thread, patch->trace.GetStubManager(), &trace, context, &dummy);
+ }
+ else if ((patch->trace.GetTraceType() == TRACE_FRAME_PUSH) && (thread->GetFrame()->IsTransitionToNativeFrame()))
+ {
+ // If we've got a frame that is transitioning to native, there's no reason to try to keep tracing. So we
+ // bail early and save ourselves some effort. This also works around a problem where we deadlock trying to
+ // do too much work to determine the destination of a ComPlusMethodFrame. (See issue 87103.)
+ //
+ // Note: trace call is still enabled, so we can just ignore this patch and wait for trace call to fire
+ // again...
+ return TPR_IGNORE;
+ }
+ else
+ {
+ // It's questionable whether Trace_Frame_Push is actually safe or not.
+ ControllerStackInfo csi;
+ StackTraceTicket ticket(patch);
+ csi.GetStackInfo(ticket, thread, LEAF_MOST_FRAME, NULL);
+
+ CONTRACT_VIOLATION(GCViolation); // TraceFrame GC-triggers
+ traceOk = g_pEEInterface->TraceFrame(thread, thread->GetFrame(), TRUE, &trace, &(csi.m_activeFrame.registers));
+ }
+
+ if (traceOk && g_pEEInterface->FollowTrace(&trace))
+ {
+ PatchTrace(&trace, LEAF_MOST_FRAME, TRUE);
+ }
+
+ return TPR_IGNORE;
+ }
+ else
+ {
+ // We've hit user code; trigger our event.
+ DisableAll();
+
+
+ {
+
+ // Give the helper thread a chance to get ready. The temporary helper can't handle
+ // execution control well, and the RS won't do any execution control until it gets a
+ // create Thread event, which it won't get until here.
+ // So now's our best time to wait for the real helper thread.
+ g_pDebugger->PollWaitingForHelper();
+ }
+
+ return TPR_TRIGGER;
+ }
+}
+
+void DebuggerThreadStarter::TriggerTraceCall(Thread *thread, const BYTE *ip)
+{
+ LOG((LF_CORDB, LL_EVERYTHING, "DTS::TTC called\n"));
+#ifdef DEBUGGING_SUPPORTED
+ if (thread->GetDomain()->IsDebuggerAttached())
+ {
+ TraceDestination trace;
+
+ if (g_pEEInterface->TraceStub(ip, &trace) && g_pEEInterface->FollowTrace(&trace))
+ {
+ PatchTrace(&trace, LEAF_MOST_FRAME, true);
+ }
+ }
+#endif //DEBUGGING_SUPPORTED
+
+}
+
+bool DebuggerThreadStarter::SendEvent(Thread *thread, bool fIpChanged)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ SENDEVENT_CONTRACT_ITEMS;
+ }
+ CONTRACTL_END;
+
+ // This SendEvent can't be interupted by a SetIp because until the client
+ // gets a ThreadStarter event, it doesn't even know the thread exists, so
+ // it certainly can't change its ip.
+ _ASSERTE(!fIpChanged);
+
+ LOG((LF_CORDB, LL_INFO10000, "DTS::SE: in DebuggerThreadStarter's SendEvent\n"));
+
+ // Send the thread started event.
+ g_pDebugger->ThreadStarted(thread);
+
+ // We delete this now because its no longer needed. We can call
+ // delete here because the queued count is above 0. This object
+ // will really be deleted when its dequeued shortly after this
+ // call returns.
+ Delete();
+
+ return true;
+}
+
+// * ------------------------------------------------------------------------
+// * DebuggerUserBreakpoint routines
+// * ------------------------------------------------------------------------
+
+bool DebuggerUserBreakpoint::IsFrameInDebuggerNamespace(FrameInfo * pFrame)
+{
+ CONTRACTL
+ {
+ THROWS;
+ MODE_ANY;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ // Steppers ignore internal frames, so should only be called on real frames.
+ _ASSERTE(pFrame->HasMethodFrame());
+
+ // Now get the namespace of the active frame
+ MethodDesc *pMD = pFrame->md;
+
+ if (pMD != NULL)
+ {
+ MethodTable * pMT = pMD->GetMethodTable();
+
+ LPCUTF8 szNamespace = NULL;
+ LPCUTF8 szClassName = pMT->GetFullyQualifiedNameInfo(&szNamespace);
+
+ if (szClassName != NULL && szNamespace != NULL)
+ {
+ MAKE_WIDEPTR_FROMUTF8(wszNamespace, szNamespace); // throw
+ MAKE_WIDEPTR_FROMUTF8(wszClassName, szClassName);
+ if (wcscmp(wszClassName, W("Debugger")) == 0 &&
+ wcscmp(wszNamespace, W("System.Diagnostics")) == 0)
+ {
+ // This will continue stepping
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+// Helper check if we're directly in a dynamic method (ignoring any chain goo
+// or stuff in the Debugger namespace.
+class IsLeafFrameDynamic
+{
+protected:
+ static StackWalkAction WalkStackWrapper(FrameInfo *pInfo, void *data)
+ {
+ IsLeafFrameDynamic * pThis = reinterpret_cast<IsLeafFrameDynamic*> (data);
+ return pThis->WalkStack(pInfo);
+ }
+
+ StackWalkAction WalkStack(FrameInfo *pInfo)
+ {
+ _ASSERTE(pInfo != NULL);
+
+ // A FrameInfo may have both Method + Chain rolled into one.
+ if (!pInfo->HasMethodFrame() && !pInfo->HasStubFrame())
+ {
+ // We're a chain. Ignore it and keep looking.
+ return SWA_CONTINUE;
+ }
+
+ // So now this is the first non-chain, non-Debugger namespace frame.
+ // LW frames don't have a name, so we check if it's LW first.
+ if (pInfo->eStubFrameType == STUBFRAME_LIGHTWEIGHT_FUNCTION)
+ {
+ m_fInLightWeightMethod = true;
+ return SWA_ABORT;
+ }
+
+ // Ignore Debugger.Break() frames.
+ // All Debugger.Break calls will have this on the stack.
+ if (DebuggerUserBreakpoint::IsFrameInDebuggerNamespace(pInfo))
+ {
+ return SWA_CONTINUE;
+ }
+
+ // We've now determined leafmost thing, so stop stackwalking.
+ _ASSERTE(m_fInLightWeightMethod == false);
+ return SWA_ABORT;
+ }
+
+
+ bool m_fInLightWeightMethod;
+
+ // Need this context to do stack trace.
+ CONTEXT m_tempContext;
+
+public:
+ // On success, copies the leafmost non-chain frameinfo (including stubs) for the current thread into pInfo
+ // and returns true.
+ // On failure, returns false.
+ // Return true on success.
+ bool DoCheck(IN Thread * pThread)
+ {
+ CONTRACTL
+ {
+ GC_TRIGGERS;
+ THROWS;
+ MODE_ANY;
+
+ PRECONDITION(CheckPointer(pThread));
+ }
+ CONTRACTL_END;
+
+ m_fInLightWeightMethod = false;
+
+
+ DebuggerWalkStack(
+ pThread,
+ LEAF_MOST_FRAME,
+ &m_tempContext, false,
+ WalkStackWrapper,
+ (void *) this,
+ TRUE // includes everything
+ );
+
+ // We don't care whether the stackwalk succeeds or not because the
+ // callback sets our status via this field either way, so just return it.
+ return m_fInLightWeightMethod;
+ };
+};
+
+// Handle a Debug.Break() notification.
+// This may create a controller to step-out out the Debug.Break() call (so that
+// we appear stopped at the callsite).
+// If we can't step-out (eg, we're directly in a dynamic method), then send
+// the debug event immediately.
+void DebuggerUserBreakpoint::HandleDebugBreak(Thread * pThread)
+{
+ bool fDoStepOut = true;
+
+ // If the leaf frame is not a LW method, then step-out.
+ IsLeafFrameDynamic info;
+ fDoStepOut = !info.DoCheck(pThread);
+
+ if (fDoStepOut)
+ {
+ // Create a controller that will step out for us.
+ new (interopsafe) DebuggerUserBreakpoint(pThread);
+ }
+ else
+ {
+ // Send debug event immediately.
+ g_pDebugger->SendUserBreakpointAndSynchronize(pThread);
+ }
+}
+
+
+DebuggerUserBreakpoint::DebuggerUserBreakpoint(Thread *thread)
+ : DebuggerStepper(thread, (CorDebugUnmappedStop) (STOP_ALL & ~STOP_UNMANAGED), INTERCEPT_ALL, NULL)
+{
+ // Setup a step out from the current frame (which we know is
+ // unmanaged, actually...)
+
+
+ // This happens to be safe, but it's a very special case (so we have a special case ticket)
+ // This is called while we're live (so no filter context) and from the fcall,
+ // and we pushed a HelperMethodFrame to protect us. We also happen to know that we have
+ // done anything illegal or dangerous since then.
+
+ StackTraceTicket ticket(this);
+ StepOut(LEAF_MOST_FRAME, ticket);
+}
+
+
+// Is this frame interesting?
+// Use this to skip all code in the namespace "Debugger.Diagnostics"
+bool DebuggerUserBreakpoint::IsInterestingFrame(FrameInfo * pFrame)
+{
+ CONTRACTL
+ {
+ THROWS;
+ MODE_ANY;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ return !IsFrameInDebuggerNamespace(pFrame);
+}
+
+bool DebuggerUserBreakpoint::SendEvent(Thread *thread, bool fIpChanged)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ SENDEVENT_CONTRACT_ITEMS;
+ }
+ CONTRACTL_END;
+
+ // See DebuggerStepper::SendEvent for why we assert here.
+ // This is technically an issue, but it's too benign to fix.
+ _ASSERTE(!fIpChanged);
+
+ LOG((LF_CORDB, LL_INFO10000,
+ "DUB::SE: in DebuggerUserBreakpoint's SendEvent\n"));
+
+ // Send the user breakpoint event.
+ g_pDebugger->SendRawUserBreakpoint(thread);
+
+ // We delete this now because its no longer needed. We can call
+ // delete here because the queued count is above 0. This object
+ // will really be deleted when its dequeued shortly after this
+ // call returns.
+ Delete();
+
+ return true;
+}
+
+// * ------------------------------------------------------------------------
+// * DebuggerFuncEvalComplete routines
+// * ------------------------------------------------------------------------
+
+DebuggerFuncEvalComplete::DebuggerFuncEvalComplete(Thread *thread,
+ void *dest)
+ : DebuggerController(thread, NULL)
+{
+#ifdef _TARGET_ARM_
+ m_pDE = reinterpret_cast<DebuggerEvalBreakpointInfoSegment*>(((DWORD)dest) & ~THUMB_CODE)->m_associatedDebuggerEval;
+#else
+ m_pDE = reinterpret_cast<DebuggerEvalBreakpointInfoSegment*>(dest)->m_associatedDebuggerEval;
+#endif
+
+ // Add an unmanaged patch at the destination.
+ AddAndActivateNativePatchForAddress((CORDB_ADDRESS_TYPE*)dest, LEAF_MOST_FRAME, FALSE, TRACE_UNMANAGED);
+}
+
+TP_RESULT DebuggerFuncEvalComplete::TriggerPatch(DebuggerControllerPatch *patch,
+ Thread *thread,
+ TRIGGER_WHY tyWhy)
+{
+
+ // It had better be an unmanaged patch...
+ _ASSERTE((patch->key.module == NULL) && !patch->IsManagedPatch());
+
+ // set ThreadFilterContext back here because we need make stack crawlable! In case,
+ // GC got triggered.
+
+ // Restore the thread's context to what it was before we hijacked it for this func eval.
+ CONTEXT *pCtx = GetManagedLiveCtx(thread);
+ CORDbgCopyThreadContext(reinterpret_cast<DT_CONTEXT *>(pCtx),
+ reinterpret_cast<DT_CONTEXT *>(&(m_pDE->m_context)));
+
+ // We've hit our patch, so simply disable all (which removes the
+ // patch) and trigger the event.
+ DisableAll();
+ return TPR_TRIGGER;
+}
+
+bool DebuggerFuncEvalComplete::SendEvent(Thread *thread, bool fIpChanged)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ THROWS;
+ SENDEVENT_CONTRACT_ITEMS;
+ }
+ CONTRACTL_END;
+
+
+ // This should not ever be interupted by a SetIp.
+ // The BP will be off in random native code for which SetIp would be illegal.
+ // However, func-eval conroller will restore the context from when we're at the patch,
+ // so that will look like the IP changed on us.
+ _ASSERTE(fIpChanged);
+
+ LOG((LF_CORDB, LL_INFO10000, "DFEC::SE: in DebuggerFuncEval's SendEvent\n"));
+
+ _ASSERTE(!ISREDIRECTEDTHREAD(thread));
+
+ // The DebuggerEval is at our faulting address.
+ DebuggerEval *pDE = m_pDE;
+
+ // Send the func eval complete (or exception) event.
+ g_pDebugger->FuncEvalComplete(thread, pDE);
+
+ // We delete this now because its no longer needed. We can call
+ // delete here because the queued count is above 0. This object
+ // will really be deleted when its dequeued shortly after this
+ // call returns.
+ Delete();
+
+ return true;
+}
+
+#ifdef EnC_SUPPORTED
+
+// * ------------------------------------------------------------------------ *
+// * DebuggerEnCBreakpoint routines
+// * ------------------------------------------------------------------------ *
+
+//---------------------------------------------------------------------------------------
+//
+// DebuggerEnCBreakpoint constructor - creates and activates a new EnC breakpoint
+//
+// Arguments:
+// offset - native offset in the function to place the patch
+// jitInfo - identifies the function in which the breakpoint is being placed
+// fTriggerType - breakpoint type: either REMAP_PENDING or REMAP_COMPLETE
+// pAppDomain - the breakpoint applies to the specified AppDomain only
+//
+
+DebuggerEnCBreakpoint::DebuggerEnCBreakpoint(SIZE_T offset,
+ DebuggerJitInfo *jitInfo,
+ DebuggerEnCBreakpoint::TriggerType fTriggerType,
+ AppDomain *pAppDomain)
+ : DebuggerController(NULL, pAppDomain),
+ m_fTriggerType(fTriggerType),
+ m_jitInfo(jitInfo)
+{
+ _ASSERTE( jitInfo != NULL );
+ // Add and activate the specified patch
+ AddBindAndActivateNativeManagedPatch(jitInfo->m_fd, jitInfo, offset, LEAF_MOST_FRAME, pAppDomain);
+ LOG((LF_ENC,LL_INFO1000, "DEnCBPDEnCBP::adding %S patch!\n",
+ fTriggerType == REMAP_PENDING ? W("remap pending") : W("remap complete")));
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// DebuggerEnCBreakpoint::TriggerPatch
+// called by the debugging infrastructure when the patch is hit.
+//
+// Arguments:
+// patch - specifies the patch that was hit
+// thread - identifies the thread on which the patch was hit
+// tyWhy - TY_SHORT_CIRCUIT for normal REMAP_PENDING EnC patches
+//
+// Return value:
+// TPR_IGNORE if the debugger chooses not to take a remap opportunity
+// TPR_IGNORE_AND_STOP when a remap-complete event is sent
+// Doesn't return at all if the debugger remaps execution to the new version of the method
+//
+TP_RESULT DebuggerEnCBreakpoint::TriggerPatch(DebuggerControllerPatch *patch,
+ Thread *thread,
+ TRIGGER_WHY tyWhy)
+{
+ _ASSERTE(HasLock());
+
+ Module *module = patch->key.module;
+ mdMethodDef md = patch->key.md;
+ SIZE_T offset = patch->offset;
+
+ // Map the current native offset back to the IL offset in the old
+ // function. This will be mapped to the new native offset within
+ // ResumeInUpdatedFunction
+ CorDebugMappingResult map;
+ DWORD which;
+ SIZE_T currentIP = (SIZE_T)m_jitInfo->MapNativeOffsetToIL(offset,
+ &map, &which);
+
+ // We only lay DebuggerEnCBreakpoints at sequence points
+ _ASSERTE(map == MAPPING_EXACT);
+
+ LOG((LF_ENC, LL_ALWAYS,
+ "DEnCBP::TP: triggered E&C %S breakpoint: tid=0x%x, module=0x%08x, "
+ "method def=0x%08x, version=%d, native offset=0x%x, IL offset=0x%x\n this=0x%x\n",
+ m_fTriggerType == REMAP_PENDING ? W("ResumePending") : W("ResumeComplete"),
+ thread, module, md, m_jitInfo->m_encVersion, offset, currentIP, this));
+
+ // If this is a REMAP_COMPLETE patch, then dispatch the RemapComplete callback
+ if (m_fTriggerType == REMAP_COMPLETE)
+ {
+ return HandleRemapComplete(patch, thread, tyWhy);
+ }
+
+ // This must be a REMAP_PENDING patch
+ // unless we got here on an explicit short-circuit, don't do any work
+ if (tyWhy != TY_SHORT_CIRCUIT)
+ {
+ LOG((LF_ENC, LL_ALWAYS, "DEnCBP::TP: not short-circuit ... bailing\n"));
+ return TPR_IGNORE;
+ }
+
+ _ASSERTE(patch->IsManagedPatch());
+
+ // Grab the MethodDesc for this function.
+ _ASSERTE(module != NULL);
+
+ // GENERICS: @todo generics. This should be replaced by a similar loop
+ // over the DJIs for the DMI as in BindPatch up above.
+ MethodDesc *pFD = g_pEEInterface->FindLoadedMethodRefOrDef(module, md);
+
+ _ASSERTE(pFD != NULL);
+
+ LOG((LF_ENC, LL_ALWAYS,
+ "DEnCBP::TP: in %s::%s\n", pFD->m_pszDebugClassName,pFD->m_pszDebugMethodName));
+
+ // Grab the jit info for the original copy of the method, which is
+ // what we are executing right now.
+ DebuggerJitInfo *pJitInfo = m_jitInfo;
+ _ASSERTE(pJitInfo);
+ _ASSERTE(pJitInfo->m_fd == pFD);
+
+ // Grab the context for this thread. This is the context that was
+ // passed to COMPlusFrameHandler.
+ CONTEXT *pContext = GetManagedLiveCtx(thread);
+
+ // We use the module the current function is in.
+ _ASSERTE(module->IsEditAndContinueEnabled());
+ EditAndContinueModule *pModule = (EditAndContinueModule*)module;
+
+ // Release the controller lock for the rest of this method
+ CrstBase::UnsafeCrstInverseHolder inverseLock(&g_criticalSection);
+
+ // resumeIP is the native offset in the new version of the method the debugger wants
+ // to resume to. We'll pass the address of this variable over to the right-side
+ // and if it modifies the contents while we're stopped dispatching the RemapOpportunity,
+ // then we know it wants a remap.
+ // This form of side-channel communication seems like an error-prone workaround. Ideally the
+ // remap IP (if any) would just be returned in a response event.
+ SIZE_T resumeIP = (SIZE_T) -1;
+
+ // Debugging code to enable a break after N RemapOpportunities
+#ifdef _DEBUG
+ static int breakOnRemapOpportunity = -1;
+ if (breakOnRemapOpportunity == -1)
+ breakOnRemapOpportunity = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_EnCBreakOnRemapOpportunity);
+
+ static int remapOpportunityCount = 0;
+
+ ++remapOpportunityCount;
+ if (breakOnRemapOpportunity == 1 || breakOnRemapOpportunity == remapOpportunityCount)
+ {
+ _ASSERTE(!"BreakOnRemapOpportunity");
+ }
+#endif
+
+ // Send an event to the RS to call the RemapOpportunity callback, passing the address of resumeIP.
+ // If the debugger responds with a call to RemapFunction, the supplied IP will be copied into resumeIP
+ // and we will know to update the context and resume the function at the new IP. Otherwise we just do
+ // nothing and try again on next RemapFunction breakpoint
+ g_pDebugger->LockAndSendEnCRemapEvent(pJitInfo, currentIP, &resumeIP);
+
+ LOG((LF_ENC, LL_ALWAYS,
+ "DEnCBP::TP: resume IL offset is 0x%x\n", resumeIP));
+
+ // Has the debugger requested a remap?
+ if (resumeIP != (SIZE_T) -1)
+ {
+ // This will jit the function, update the context, and resume execution at the new location.
+ g_pEEInterface->ResumeInUpdatedFunction(pModule,
+ pFD,
+ (void*)pJitInfo,
+ resumeIP,
+ pContext);
+ _ASSERTE(!"Returned from ResumeInUpdatedFunction!");
+ }
+
+ LOG((LF_CORDB, LL_ALWAYS, "DEnCB::TP: We've returned from ResumeInUpd"
+ "atedFunction, we're going to skip the EnC patch ####\n"));
+
+ // We're returning then we'll have to re-get this lock. Be careful that we haven't kept any controller/patches
+ // in the caller. They can move when we unlock, so when we release the lock and reget it here, things might have
+ // changed underneath us.
+ // inverseLock holder will reaquire lock.
+
+ return TPR_IGNORE;
+}
+
+//
+// HandleResumeComplete is called for an EnC patch in the newly updated function
+// so that we can notify the debugger that the remap has completed and they can
+// now remap their steppers or anything else that depends on the new code actually
+// being on the stack. We return TPR_IGNORE_AND_STOP because it's possible that the
+// function was edited after we handled remap complete and want to make sure we
+// start a fresh call to TriggerPatch
+//
+TP_RESULT DebuggerEnCBreakpoint::HandleRemapComplete(DebuggerControllerPatch *patch,
+ Thread *thread,
+ TRIGGER_WHY tyWhy)
+{
+ LOG((LF_ENC, LL_ALWAYS, "DEnCBP::HRC: HandleRemapComplete\n"));
+
+ // Debugging code to enable a break after N RemapCompletes
+#ifdef _DEBUG
+ static int breakOnRemapComplete = -1;
+ if (breakOnRemapComplete == -1)
+ breakOnRemapComplete = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_EnCBreakOnRemapComplete);
+
+ static int remapCompleteCount = 0;
+ ++remapCompleteCount;
+ if (breakOnRemapComplete == 1 || breakOnRemapComplete == remapCompleteCount)
+ {
+ _ASSERTE(!"BreakOnRemapComplete");
+ }
+#endif
+ _ASSERTE(HasLock());
+
+
+ bool fApplied = m_jitInfo->m_encBreakpointsApplied;
+ // Need to delete this before unlock below so if any other thread come in after the unlock
+ // they won't handle this patch.
+ Delete();
+
+ // We just deleted ourselves. Can't access anything any instances after this point.
+
+ // if have somehow updated this function before we resume into it then just bail
+ if (fApplied)
+ {
+ LOG((LF_ENC, LL_ALWAYS, "DEnCBP::HRC: function already updated, ignoring\n"));
+ return TPR_IGNORE_AND_STOP;
+ }
+
+ // GENERICS: @todo generics. This should be replaced by a similar loop
+ // over the DJIs for the DMI as in BindPatch up above.
+ MethodDesc *pFD = g_pEEInterface->FindLoadedMethodRefOrDef(patch->key.module, patch->key.md);
+
+ LOG((LF_ENC, LL_ALWAYS, "DEnCBP::HRC: unlocking controller\n"));
+
+ // Unlock the controller lock and dispatch the remap complete event
+ CrstBase::UnsafeCrstInverseHolder inverseLock(&g_criticalSection);
+
+ LOG((LF_ENC, LL_ALWAYS, "DEnCBP::HRC: sending RemapCompleteEvent\n"));
+
+ g_pDebugger->LockAndSendEnCRemapCompleteEvent(pFD);
+
+ // We're returning then we'll have to re-get this lock. Be careful that we haven't kept any controller/patches
+ // in the caller. They can move when we unlock, so when we release the lock and reget it here, things might have
+ // changed underneath us.
+ // inverseLock holder will reacquire.
+
+ return TPR_IGNORE_AND_STOP;
+}
+#endif //EnC_SUPPORTED
+
+// continuable-exceptions
+// * ------------------------------------------------------------------------ *
+// * DebuggerContinuableExceptionBreakpoint routines
+// * ------------------------------------------------------------------------ *
+
+
+//---------------------------------------------------------------------------------------
+//
+// constructor
+//
+// Arguments:
+// pThread - the thread on which we are intercepting an exception
+// nativeOffset - This is the target native offset. It is where we are going to resume execution.
+// jitInfo - the DebuggerJitInfo of the method at which we are intercepting
+// pAppDomain - the AppDomain in which the thread is executing
+//
+
+DebuggerContinuableExceptionBreakpoint::DebuggerContinuableExceptionBreakpoint(Thread *pThread,
+ SIZE_T nativeOffset,
+ DebuggerJitInfo *jitInfo,
+ AppDomain *pAppDomain)
+ : DebuggerController(pThread, pAppDomain)
+{
+ _ASSERTE( jitInfo != NULL );
+ // Add a native patch at the specified native offset, which is where we are going to resume execution.
+ AddBindAndActivateNativeManagedPatch(jitInfo->m_fd, jitInfo, nativeOffset, LEAF_MOST_FRAME, pAppDomain);
+}
+
+//---------------------------------------------------------------------------------------
+//
+// This function is called when the patch added in the constructor is hit. At this point,
+// we have already resumed execution, and the exception is no longer in flight.
+//
+// Arguments:
+// patch - the patch added in the constructor; unused
+// thread - the thread in question; unused
+// tyWhy - a flag which is only useful for EnC; unused
+//
+// Return Value:
+// This function always returns TPR_TRIGGER, meaning that it wants to send an event to notify the RS.
+//
+
+TP_RESULT DebuggerContinuableExceptionBreakpoint::TriggerPatch(DebuggerControllerPatch *patch,
+ Thread *thread,
+ TRIGGER_WHY tyWhy)
+{
+ LOG((LF_CORDB, LL_INFO10000, "DCEBP::TP\n"));
+
+ //
+ // Disable the patch
+ //
+ DisableAll();
+
+ // We will send a notification to the RS when the patch is triggered.
+ return TPR_TRIGGER;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// This function is called when we want to notify the RS that an interception is complete.
+// At this point, we have already resumed execution, and the exception is no longer in flight.
+//
+// Arguments:
+// thread - the thread in question
+// fIpChanged - whether the IP has changed by SetIP after the patch is hit but
+// before this function is called
+//
+
+bool DebuggerContinuableExceptionBreakpoint::SendEvent(Thread *thread, bool fIpChanged)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ SENDEVENT_CONTRACT_ITEMS;
+ }
+ CONTRACTL_END;
+
+
+
+ LOG((LF_CORDB, LL_INFO10000,
+ "DCEBP::SE: in DebuggerContinuableExceptionBreakpoint's SendEvent\n"));
+
+ if (!fIpChanged)
+ {
+ g_pDebugger->SendInterceptExceptionComplete(thread);
+ }
+
+ // On WIN64, by the time we get here the DebuggerExState is gone already.
+ // ExceptionTrackers are cleaned up before we resume execution for a handled exception.
+#if !defined(WIN64EXCEPTIONS)
+ thread->GetExceptionState()->GetDebuggerState()->SetDebuggerInterceptContext(NULL);
+#endif // !WIN64EXCEPTIONS
+
+
+ //
+ // We delete this now because its no longer needed. We can call
+ // delete here because the queued count is above 0. This object
+ // will really be deleted when its dequeued shortly after this
+ // call returns.
+ //
+ Delete();
+
+ return true;
+}
+#endif // !DACCESS_COMPILE
diff --git a/src/debug/ee/controller.h b/src/debug/ee/controller.h
new file mode 100644
index 0000000000..6611e044e5
--- /dev/null
+++ b/src/debug/ee/controller.h
@@ -0,0 +1,1979 @@
+// 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.
+//*****************************************************************************
+// File: controller.h
+//
+
+//
+// Debugger control flow object
+//
+//*****************************************************************************
+
+#ifndef CONTROLLER_H_
+#define CONTROLLER_H_
+
+/* ========================================================================= */
+
+#if !defined(DACCESS_COMPILE)
+
+#include "frameinfo.h"
+
+/* ------------------------------------------------------------------------- *
+ * Forward declarations
+ * ------------------------------------------------------------------------- */
+
+class DebuggerPatchSkip;
+class DebuggerThreadStarter;
+class DebuggerController;
+class DebuggerControllerQueue;
+struct DebuggerControllerPatch;
+class DebuggerUserBreakpoint;
+class ControllerStackInfo;
+
+// Ticket for ensuring that it's safe to get a stack trace.
+class StackTraceTicket
+{
+public:
+ // Each ctor is a rule for why it's safety to run a stacktrace.
+
+ // Safe if we're at certain types of patches.
+ StackTraceTicket(DebuggerControllerPatch * patch);
+
+ // Safe if there was already another stack trace at this spot. (Grandfather clause)
+ StackTraceTicket(ControllerStackInfo * info);
+
+ // Safe it we're at a Synchronized point point.
+ StackTraceTicket(Thread * pThread);
+
+ // Safe b/c the context shows we're in native managed code
+ StackTraceTicket(const BYTE * ip);
+
+ // DebuggerUserBreakpoint has a special case of safety.
+ StackTraceTicket(DebuggerUserBreakpoint * p);
+
+ // This is like a contract violation.
+ // Unsafe tickets. Use as:
+ // StackTraceTicket ticket(StackTraceTicket::UNSAFE_TICKET);
+ enum EUNSAFE {
+ // Ticket is unsafe. Potential issue.
+ UNSAFE_TICKET = 0,
+
+ // For some wacky reason, it's safe to take a stacktrace here, but
+ // there's not an easily verifiable rule. Use this ticket very sparingly
+ // because it's much more difficult to verify.
+ SPECIAL_CASE_TICKET = 1
+ };
+ StackTraceTicket(EUNSAFE e) { };
+
+private:
+ // Tickets can't be copied around. Hide these definitions so to enforce that.
+ // We still need the Copy ctor so that it can be passed in as a parameter.
+ void operator=(StackTraceTicket & other);
+};
+
+/* ------------------------------------------------------------------------- *
+ * ControllerStackInfo utility
+ * ------------------------------------------------------------------------- *
+ * class ControllerStackInfo is a class designed
+ * to simply obtain a two-frame stack trace: it will obtain the bottommost
+ * framepointer (m_bottomFP), a given target frame (m_activeFrame), and the
+ * frame above the target frame (m_returnFrame). Note that the target frame
+ * may be the bottommost, 'active' frame, or it may be a frame higher up in
+ * the stack. ControllerStackInfo accomplishes this by starting at the
+ * bottommost frame and walking upwards until it reaches the target frame,
+ * whereupon it records the m_activeFrame info, gets called once more to
+ * fill in the m_returnFrame info, and thereafter stops the stack walk.
+ *
+ * public:
+ * void * m_bottomFP: Frame pointer for the
+ * bottommost (most active)
+ * frame. We can add more later, if we need it. Currently just used in
+ * TrapStep. NULL indicates an uninitialized value.
+ *
+ * void * m_targetFP: The frame pointer to the frame
+ * that we actually want the info of.
+ *
+ * bool m_targetFrameFound: Set to true if
+ * WalkStack finds the frame indicated by targetFP handed to GetStackInfo
+ * false otherwise.
+ *
+ * FrameInfo m_activeFrame: A FrameInfo
+ * describing the target frame. This should always be valid after a
+ * call to GetStackInfo.
+ *
+ * FrameInfo m_returnFrame: A FrameInfo
+ * describing the frame above the target frame, if target's
+ * return frame were found (call HasReturnFrame() to see if this is
+ * valid). Otherwise, this will be the same as m_activeFrame, above
+ *
+ * private:
+ * bool m_activeFound: Set to true if we found the target frame.
+ * bool m_returnFound: Set to true if we found the target's return frame.
+ */
+class ControllerStackInfo
+{
+public:
+ friend class StackTraceTicket;
+
+ ControllerStackInfo()
+ {
+ INDEBUG(m_dbgExecuted = false);
+ }
+
+ FramePointer m_bottomFP;
+ FramePointer m_targetFP;
+ bool m_targetFrameFound;
+
+ FrameInfo m_activeFrame;
+ FrameInfo m_returnFrame;
+
+ CorDebugChainReason m_specialChainReason;
+
+ // static StackWalkAction ControllerStackInfo::WalkStack() The
+ // callback that will be invoked by the DebuggerWalkStackProc.
+ // Note that the data argument is the "this" pointer to the
+ // ControllerStackInfo.
+ static StackWalkAction WalkStack(FrameInfo *pInfo, void *data);
+
+
+ //void ControllerStackInfo::GetStackInfo(): GetStackInfo
+ // is invoked by the user to trigger the stack walk. This will
+ // cause the stack walk detailed in the class description to happen.
+ // Thread* thread: The thread to do the stack walk on.
+ // void* targetFP: Can be either NULL (meaning that the bottommost
+ // frame is the target), or an frame pointer, meaning that the
+ // caller wants information about a specific frame.
+ // CONTEXT* pContext: A pointer to a CONTEXT structure. Can be null,
+ // we use our temp context.
+ // bool suppressUMChainFromComPlusMethodFrameGeneric - A ridiculous flag that is trying to narrowly
+ // target a fix for issue 650903.
+ // StackTraceTicket - ticket ensuring that we have permission to call this.
+ void GetStackInfo(
+ StackTraceTicket ticket,
+ Thread *thread,
+ FramePointer targetFP,
+ CONTEXT *pContext,
+ bool suppressUMChainFromComPlusMethodFrameGeneric = false
+ );
+
+ //bool ControllerStackInfo::HasReturnFrame() Returns
+ // true if m_returnFrame is valid. Returns false
+ // if m_returnFrame is set to m_activeFrame
+ bool HasReturnFrame() {LIMITED_METHOD_CONTRACT; return m_returnFound; }
+
+ // This function "undoes" an unwind, i.e. it takes the active frame (the current frame)
+ // and sets it to be the return frame (the caller frame). Currently it is only used by
+ // the stepper to step out of an LCG method. See DebuggerStepper::DetectHandleLCGMethods()
+ // for more information.
+ void SetReturnFrameWithActiveFrame();
+
+private:
+ // If we don't have a valid context, then use this temp cache.
+ CONTEXT m_tempContext;
+
+ bool m_activeFound;
+ bool m_returnFound;
+
+ // A ridiculous flag that is targetting a very narrow fix at issue 650903
+ // (4.5.1/Blue). This is set for the duration of a stackwalk designed to
+ // help us "Step Out" to a managed frame (i.e., managed-only debugging).
+ bool m_suppressUMChainFromComPlusMethodFrameGeneric;
+
+ // Track if this stackwalk actually happened.
+ // This is used by the StackTraceTicket(ControllerStackInfo * info) ticket.
+ INDEBUG(bool m_dbgExecuted);
+};
+
+#endif // !DACCESS_COMPILE
+
+
+/* ------------------------------------------------------------------------- *
+ * DebuggerController routines
+ * ------------------------------------------------------------------------- */
+
+// simple ref-counted buffer that's shared among DebuggerPatchSkippers for a
+// given DebuggerControllerPatch. upon creation the refcount will be 1. when
+// the last skipper and controller are cleaned up the buffer will be released.
+// note that there isn't a clear owner of this buffer since a controller can be
+// cleaned up while the final skipper is still in flight.
+class SharedPatchBypassBuffer
+{
+public:
+ SharedPatchBypassBuffer() : m_refCount(1)
+ {
+#ifdef _DEBUG
+ DWORD cbToProtect = MAX_INSTRUCTION_LENGTH;
+ _ASSERTE(DbgIsExecutable((BYTE*)PatchBypass, cbToProtect));
+#endif // _DEBUG
+
+ // sentinel value indicating uninitialized data
+ *(reinterpret_cast<DWORD*>(PatchBypass)) = SentinelValue;
+#ifdef _TARGET_AMD64_
+ *(reinterpret_cast<DWORD*>(BypassBuffer)) = SentinelValue;
+ RipTargetFixup = 0;
+ RipTargetFixupSize = 0;
+#elif _TARGET_ARM64_
+ RipTargetFixup = 0;
+
+#endif
+ }
+
+ ~SharedPatchBypassBuffer()
+ {
+ // trap deletes that don't go through Release()
+ _ASSERTE(m_refCount == 0);
+ }
+
+ LONG AddRef()
+ {
+ InterlockedIncrement(&m_refCount);
+ _ASSERTE(m_refCount > 0);
+ return m_refCount;
+ }
+
+ LONG Release()
+ {
+ LONG result = InterlockedDecrement(&m_refCount);
+ _ASSERTE(m_refCount >= 0);
+
+ if (m_refCount == 0)
+ {
+ TRACE_FREE(this);
+ DeleteInteropSafeExecutable(this);
+ }
+
+ return result;
+ }
+
+ // "PatchBypass" must be the first field of this class for alignment to be correct.
+ BYTE PatchBypass[MAX_INSTRUCTION_LENGTH];
+#if defined(_TARGET_AMD64_)
+ const static int cbBufferBypass = 0x10;
+ BYTE BypassBuffer[cbBufferBypass];
+
+ UINT_PTR RipTargetFixup;
+ BYTE RipTargetFixupSize;
+#elif defined(_TARGET_ARM64_)
+ UINT_PTR RipTargetFixup;
+#endif
+
+private:
+ const static DWORD SentinelValue = 0xffffffff;
+ LONG m_refCount;
+};
+
+// struct DebuggerFunctionKey: Provides a means of hashing unactivated
+// breakpoints, it's used mainly for the case where the function to put
+// the breakpoint in hasn't been JITted yet.
+// Module* module: Module that the method belongs to.
+// mdMethodDef md: meta data token for the method.
+struct DebuggerFunctionKey1
+{
+ PTR_Module module;
+ mdMethodDef md;
+};
+
+typedef DebuggerFunctionKey1 UNALIGNED DebuggerFunctionKey;
+
+// ILMaster: Breakpoints on IL code may need to be applied to multiple
+// copies of code, because generics mean code gets JITTed multiple times.
+// The "master" is a patch we keep to record the IL offset, and is used to
+// create new "slave"patches.
+
+//
+// ILSlave: The slaves created from ILMaster patches. The offset for
+// these is initially an IL offset and later becomes a native offset.
+//
+// NativeManaged: A patch we apply to managed code, usually for walkers etc.
+//
+// NativeUnmanaged: A patch applied to any kind of native code.
+
+enum DebuggerPatchKind { PATCH_KIND_IL_MASTER, PATCH_KIND_IL_SLAVE, PATCH_KIND_NATIVE_MANAGED, PATCH_KIND_NATIVE_UNMANAGED };
+
+// struct DebuggerControllerPatch: An entry in the patch (hash) table,
+// this should contain all the info that's needed over the course of a
+// patch's lifetime.
+//
+// FREEHASHENTRY entry: Three ULONGs, this is required
+// by the underlying hashtable implementation
+// DWORD opcode: A nonzero opcode && address field means that
+// the patch has been applied to something.
+// A patch with a zero'd opcode field means that the patch isn't
+// actually tracking a valid break opcode. See DebuggerPatchTable
+// for more details.
+// DebuggerController *controller: The controller that put this
+// patch here.
+// BOOL fSaveOpcode: If true, then unapply patch will save
+// a copy of the opcode in opcodeSaved, and apply patch will
+// copy opcodeSaved to opcode rather than grabbing the opcode
+// from the instruction. This is useful mainly when the JIT
+// has moved code, and we don't want to erroneously pick up the
+// user break instruction.
+// Full story:
+// FJIT moves the code. Once that's done, it calls Debugger->MoveCode(MethodDesc
+// *) to let us know the code moved. At that point, unbind all the breakpoints
+// in the method. Then we whip over all the patches, and re-bind all the
+// patches in the method. However, we can't guarantee that the code will exist
+// in both the old & new locations exclusively of each other (the method could
+// be 0xFF bytes big, and get moved 0x10 bytes in one direction), so instead of
+// simply re-using the unbind/rebind logic as it is, we need a special case
+// wherein the old method isn't valid. Instead, we'll copy opcode into
+// opcodeSaved, and then zero out opcode (we need to zero out opcode since that
+// tells us that the patch is invalid, if the right side sees it). Thus the run-
+// around.
+// DebuggerPatchKind: see above
+// DWORD opcodeSaved: Contains an opcode if fSaveOpcode == true
+// SIZE_T nVersion: If the patch is stored by IL offset, then we
+// must also store the version ID so that we know which version
+// this is supposed to be applied to. Note that this will only
+// be set for DebuggerBreakpoints & DebuggerEnCBreakpoints. For
+// others, it should be set to DMI_VERSION_INVALID. For constants,
+// see DebuggerJitInfo
+// DebuggerJitInfo dji: A pointer to the debuggerJitInfo that describes
+// the method (and version) that this patch is applied to. This field may
+// also have the value DebuggerJitInfo::DMI_VERSION_INVALID
+
+// SIZE_T pid: Within a given patch table, all patches have a
+// semi-unique ID. There should be one and only 1 patch for a given
+// {pid,nVersion} tuple, thus ensuring that we don't duplicate
+// patches from multiple, previous versions.
+// AppDomain * pAppDomain: Either NULL (patch applies to all appdomains
+// that the debugger is attached to)
+// or contains a pointer to an AppDomain object (patch applies only to
+// that A.D.)
+
+// NOTE: due to unkind abuse of type system you cannot add ctor/dtor to this
+// type and expect them to be automatically invoked!
+struct DebuggerControllerPatch
+{
+ friend class DebuggerPatchTable;
+ friend class DebuggerController;
+
+ FREEHASHENTRY entry;
+ DebuggerController *controller;
+ DebuggerFunctionKey key;
+ SIZE_T offset;
+ PTR_CORDB_ADDRESS_TYPE address;
+ FramePointer fp;
+ PRD_TYPE opcode; //this name will probably change because it is a misnomer
+ BOOL fSaveOpcode;
+ PRD_TYPE opcodeSaved;//also a misnomer
+ BOOL offsetIsIL;
+ TraceDestination trace;
+private:
+ int refCount;
+ union
+ {
+ SIZE_T encVersion; // used for Master patches, to record which EnC version this Master applies to
+ DebuggerJitInfo *dji; // used for Slave and native patches, though only when tracking JIT Info
+ };
+
+#ifndef _TARGET_ARM_
+ // this is shared among all the skippers for this controller. see the comments
+ // right before the definition of SharedPatchBypassBuffer for lifetime info.
+ SharedPatchBypassBuffer* m_pSharedPatchBypassBuffer;
+#endif // _TARGET_ARM_
+
+public:
+ SIZE_T pid;
+ AppDomain *pAppDomain;
+
+ BOOL IsNativePatch();
+ BOOL IsManagedPatch();
+ BOOL IsILMasterPatch();
+ BOOL IsILSlavePatch();
+ DebuggerPatchKind GetKind();
+
+ // A patch has DJI if it was created with it or if it has been mapped to a
+ // function that has been jitted while JIT tracking was on. It does not
+ // necessarily mean the patch is bound. ILMaster patches never have DJIs.
+ // Patches will never have DJIs if we are not tracking JIT information.
+ //
+ // Patches can also be unbound, e.g. in UnbindFunctionPatches. Any DJI gets cleared
+ // when the patch is unbound. This appears to be used as an indicator
+ // to Debugger::MapAndBindFunctionPatches to make sure that
+ // we don't skip the patch when we get new code.
+ BOOL HasDJI()
+ {
+ return (!IsILMasterPatch() && dji != NULL);
+ }
+
+ DebuggerJitInfo *GetDJI()
+ {
+ _ASSERTE(!IsILMasterPatch());
+ return dji;
+ }
+
+ // These tell us which EnC version a patch relates to. They are used
+ // to determine if we are mapping a patch to a new version.
+ //
+ BOOL HasEnCVersion()
+ {
+ return (IsILMasterPatch() || HasDJI());
+ }
+
+ SIZE_T GetEnCVersion()
+ {
+ _ASSERTE(HasEnCVersion());
+ return (IsILMasterPatch() ? encVersion : (HasDJI() ? GetDJI()->m_encVersion : CorDB_DEFAULT_ENC_FUNCTION_VERSION));
+ }
+
+ // We set the DJI explicitly after mapping a patch
+ // to freshly jitted code or to a new version. The Unbind/Bind/MovedCode mess
+ // for the FJIT will also set the DJI to NULL as an indicator that Debugger::MapAndBindFunctionPatches
+ // should not skip the patch.
+ void SetDJI(DebuggerJitInfo *newDJI)
+ {
+ _ASSERTE(!IsILMasterPatch());
+ dji = newDJI;
+ }
+
+ // A patch is bound if we've mapped it to a real honest-to-goodness
+ // native address.
+ // Note that we currently activate all patches immediately after binding them, and
+ // delete all patches after unactivating them. This means that the window where
+ // a patch is bound but not active is very small (and should always be protected by
+ // a lock). We rely on this correlation in a few places, and ASSERT it explicitly there.
+ BOOL IsBound()
+ {
+ if( address == NULL ) {
+ // patch is unbound, cannot be active
+ _ASSERTE( PRDIsEmpty(opcode) );
+ return FALSE;
+ }
+
+ // IL Master patches are never bound.
+ _ASSERTE( !IsILMasterPatch() );
+
+ return TRUE;
+ }
+
+ // It would be nice if we never needed IsBreakpointPatch or IsStepperPatch,
+ // but a few bits of the existing code look at which controller type is involved.
+ BOOL IsBreakpointPatch();
+ BOOL IsStepperPatch();
+
+ bool IsActivated()
+ {
+ // Patch is activate if we've stored a non-zero opcode
+ // Note: this might be a problem as opcode 0 may be a valid opcode (see issue 366221).
+ if( PRDIsEmpty(opcode) ) {
+ return FALSE;
+ }
+
+ // Patch is active, so it must also be bound
+ _ASSERTE( address != NULL );
+ return TRUE;
+ }
+
+ bool IsFree() {return (refCount == 0);}
+ bool IsTriggering() {return (refCount > 1);}
+
+ // Is this patch at a position at which it's safe to take a stack?
+ bool IsSafeForStackTrace();
+
+#ifndef _TARGET_ARM_
+ // gets a pointer to the shared buffer
+ SharedPatchBypassBuffer* GetOrCreateSharedPatchBypassBuffer();
+
+ // entry point for general initialization when the controller is being created
+ void Initialize()
+ {
+ m_pSharedPatchBypassBuffer = NULL;
+ }
+
+ // entry point for general cleanup when the controller is being removed from the patch table
+ void DoCleanup()
+ {
+ if (m_pSharedPatchBypassBuffer != NULL)
+ m_pSharedPatchBypassBuffer->Release();
+ }
+#endif // _TARGET_ARM_
+
+private:
+ DebuggerPatchKind kind;
+};
+
+typedef DPTR(DebuggerControllerPatch) PTR_DebuggerControllerPatch;
+
+/* class DebuggerPatchTable: This is the table that contains
+ * information about the patches (breakpoints) maintained by the
+ * debugger for a variety of purposes.
+ * The only tricky part is that
+ * patches can be hashed either by the address that they're applied to,
+ * or by DebuggerFunctionKey. If address is equal to zero, then the
+ * patch is hashed by DebuggerFunctionKey.
+ *
+ * Patch table inspection scheme:
+ *
+ * We have to be able to inspect memory (read/write) from the right
+ * side w/o the help of the left side. When we do unmanaged debugging,
+ * we need to be able to R/W memory out of a debuggee s.t. the debugger
+ * won't see our patches. So we have to be able to read our patch table
+ * from the left side, which is problematic since we know that the left
+ * side will be arbitrarily frozen, but we don't know where.
+ *
+ * So our scheme is this:
+ * we'll send a pointer to the g_patches table over in startup,
+ * and when we want to inspect it at runtime, we'll freeze the left side,
+ * then read-memory the "data" (m_pcEntries) array over to the right. We'll
+ * iterate through the array & assume that anything with a non-zero opcode
+ * and address field is valid. To ensure that the assumption is ok, we
+ * use the zeroing allocator which zeros out newly created space, and
+ * we'll be very careful about zeroing out the opcode field during the
+ * Unapply operation
+ *
+ * NOTE: Don't mess with the memory protections on this while the
+ * left side is frozen (ie, no threads are executing).
+ * WriteMemory depends on being able to write the patchtable back
+ * if it was read successfully.
+ */
+#define DPT_INVALID_SLOT (UINT32_MAX)
+#define DPT_DEFAULT_TRACE_TYPE TRACE_OTHER
+
+/* Although CHashTableAndData can grow, we always use a fixed number of buckets.
+ * This is problematic for tables like the patch table which are usually small, but
+ * can become huge. When the number of entries far exceeds the number of buckets,
+ * lookup and addition basically degrade into linear searches. There is a trade-off
+ * here between wasting memory for unused buckets, and performance of large tables.
+ * Also note that the number of buckets should be a prime number.
+*/
+#define DPT_HASH_BUCKETS 1103
+
+class DebuggerPatchTable : private CHashTableAndData<CNewZeroData>
+{
+ VPTR_BASE_CONCRETE_VTABLE_CLASS(DebuggerPatchTable);
+
+public:
+ virtual ~DebuggerPatchTable() = default;
+
+ friend class DebuggerRCThread;
+private:
+ //incremented so that we can get DPT-wide unique PIDs.
+ // pid = Patch ID.
+ SIZE_T m_pid;
+ // Given a patch, retrieves the correct key. The return value of this function is passed to Cmp(), Find(), etc.
+ SIZE_T Key(DebuggerControllerPatch *patch)
+ {
+ LIMITED_METHOD_DAC_CONTRACT;
+
+ // Most clients of CHashTable pass a host pointer as the key. However, the key really could be
+ // anything. In our case, the key can either be a host pointer of type DebuggerFunctionKey or
+ // the address of the patch.
+ if (patch->address == NULL)
+ {
+ return (SIZE_T)(&patch->key);
+ }
+ else
+ {
+ return (SIZE_T)(dac_cast<TADDR>(patch->address));
+ }
+ }
+
+ // Given two DebuggerControllerPatches, tells
+ // whether they are equal or not. Does this by comparing the correct
+ // key.
+ // BYTE* pc1: If pc2 is hashed by address,
+ // pc1 is an address. If
+ // pc2 is hashed by DebuggerFunctionKey,
+ // pc1 is a DebuggerFunctionKey
+ //Returns true if the two patches are equal, false otherwise
+ BOOL Cmp(SIZE_T k1, const HASHENTRY * pc2)
+ {
+ LIMITED_METHOD_DAC_CONTRACT;
+
+ DebuggerControllerPatch * pPatch2 = dac_cast<PTR_DebuggerControllerPatch>(const_cast<HASHENTRY *>(pc2));
+
+ if (pPatch2->address == NULL)
+ {
+ // k1 is a host pointer of type DebuggerFunctionKey.
+ DebuggerFunctionKey * pKey1 = reinterpret_cast<DebuggerFunctionKey *>(k1);
+
+ return ((pKey1->module != pPatch2->key.module) || (pKey1->md != pPatch2->key.md));
+ }
+ else
+ {
+ return ((SIZE_T)(dac_cast<TADDR>(pPatch2->address)) != k1);
+ }
+ }
+
+ //Computes a hash value based on an address
+ ULONG HashAddress(PTR_CORDB_ADDRESS_TYPE address)
+ {
+ LIMITED_METHOD_DAC_CONTRACT;
+ return (ULONG)(SIZE_T)(dac_cast<TADDR>(address));
+ }
+
+ //Computes a hash value based on a DebuggerFunctionKey
+ ULONG HashKey(DebuggerFunctionKey * pKey)
+ {
+ SUPPORTS_DAC;
+ return HashPtr(pKey->md, pKey->module);
+ }
+
+ //Computes a hash value from a patch, using the address field
+ // if the patch is hashed by address, using the DebuggerFunctionKey
+ // otherwise
+ ULONG Hash(DebuggerControllerPatch * pPatch)
+ {
+ SUPPORTS_DAC;
+
+ if (pPatch->address == NULL)
+ return HashKey(&(pPatch->key));
+ else
+ return HashAddress(pPatch->address);
+ }
+ //Public Members
+public:
+ enum {
+ DCP_PID_INVALID,
+ DCP_PID_FIRST_VALID,
+ };
+
+#ifndef DACCESS_COMPILE
+
+ DebuggerPatchTable() : CHashTableAndData<CNewZeroData>(DPT_HASH_BUCKETS) { }
+
+ HRESULT Init()
+ {
+ WRAPPER_NO_CONTRACT;
+
+ m_pid = DCP_PID_FIRST_VALID;
+
+ SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE;
+ return NewInit(17, sizeof(DebuggerControllerPatch), 101);
+ }
+
+ // Assuming that the chain of patches (as defined by all the
+ // GetNextPatch from this patch) are either sorted or NULL, take the given
+ // patch (which should be the first patch in the chain). This
+ // is called by AddPatch to make sure that the order of the
+ // patches is what we want for things like E&C, DePatchSkips,etc.
+ void SortPatchIntoPatchList(DebuggerControllerPatch **ppPatch);
+
+ void SpliceOutOfList(DebuggerControllerPatch *patch);
+
+ void SpliceInBackOf(DebuggerControllerPatch *patchAppend,
+ DebuggerControllerPatch *patchEnd);
+
+ //
+ // Note that patches may be reallocated - do not keep a pointer to a patch.
+ //
+ DebuggerControllerPatch *AddPatchForMethodDef(DebuggerController *controller,
+ Module *module,
+ mdMethodDef md,
+ size_t offset,
+ DebuggerPatchKind kind,
+ FramePointer fp,
+ AppDomain *pAppDomain,
+ SIZE_T masterEnCVersion,
+ DebuggerJitInfo *dji);
+
+ DebuggerControllerPatch *AddPatchForAddress(DebuggerController *controller,
+ MethodDesc *fd,
+ size_t offset,
+ DebuggerPatchKind kind,
+ CORDB_ADDRESS_TYPE *address,
+ FramePointer fp,
+ AppDomain *pAppDomain,
+ DebuggerJitInfo *dji = NULL,
+ SIZE_T pid = DCP_PID_INVALID,
+ TraceType traceType = DPT_DEFAULT_TRACE_TYPE);
+
+ // Set the native address for this patch.
+ void BindPatch(DebuggerControllerPatch *patch, CORDB_ADDRESS_TYPE *address);
+ void UnbindPatch(DebuggerControllerPatch *patch);
+ void RemovePatch(DebuggerControllerPatch *patch);
+
+ // This is a sad legacy workaround. The patch table (implemented as this
+ // class) is shared across process. We publish the runtime offsets of
+ // some key fields. Since those fields are private, we have to provide
+ // accessors here. So if you're not using these functions, don't start.
+ // We can hopefully remove them.
+ static SIZE_T GetOffsetOfEntries()
+ {
+ // assert that we the offsets of these fields in the base class is
+ // the same as the offset of this field in this class.
+ _ASSERTE((void*)(DebuggerPatchTable*)NULL == (void*)(CHashTableAndData<CNewZeroData>*)NULL);
+ return helper_GetOffsetOfEntries();
+ }
+
+ static SIZE_T GetOffsetOfCount()
+ {
+ _ASSERTE((void*)(DebuggerPatchTable*)NULL == (void*)(CHashTableAndData<CNewZeroData>*)NULL);
+ return helper_GetOffsetOfCount();
+ }
+
+ // GetPatch find the first patch in the hash table
+ // that is hashed by matching the {Module,mdMethodDef} to the
+ // patch's DebuggerFunctionKey. This will NOT find anything
+ // hashed by address, even if that address is within the
+ // method specified.
+ // You can use GetNextPatch to iterate through all the patches keyed by
+ // this Module,mdMethodDef pair
+ DebuggerControllerPatch *GetPatch(Module *module, mdToken md)
+ {
+ DebuggerFunctionKey key;
+
+ key.module = module;
+ key.md = md;
+
+ return reinterpret_cast<DebuggerControllerPatch *>(Find(HashKey(&key), (SIZE_T)&key));
+ }
+#endif // #ifndef DACCESS_COMPILE
+
+ // GetPatch will translate find the first patch in the hash
+ // table that is hashed by address. It will NOT find anything hashed
+ // by {Module,mdMethodDef}, or by MethodDesc.
+ DebuggerControllerPatch * GetPatch(PTR_CORDB_ADDRESS_TYPE address)
+ {
+ SUPPORTS_DAC;
+ ARM_ONLY(_ASSERTE(dac_cast<DWORD>(address) & THUMB_CODE));
+
+ DebuggerControllerPatch * pPatch =
+ dac_cast<PTR_DebuggerControllerPatch>(Find(HashAddress(address), (SIZE_T)(dac_cast<TADDR>(address))));
+
+ return pPatch;
+ }
+
+ DebuggerControllerPatch *GetNextPatch(DebuggerControllerPatch *prev);
+
+ // Find the first patch in the patch table, and store
+ // index info in info. Along with GetNextPatch, this can
+ // iterate through the whole patch table. Note that since the
+ // hashtable operates via iterating through all the contents
+ // of all the buckets, if you add an entry while iterating
+ // through the table, you may or may not iterate across
+ // the new entries. You will iterate through all the entries
+ // that were present at the beginning of the run. You
+ // safely delete anything you've already iterated by, anything
+ // else is kinda risky.
+ DebuggerControllerPatch * GetFirstPatch(HASHFIND * pInfo)
+ {
+ SUPPORTS_DAC;
+
+ return dac_cast<PTR_DebuggerControllerPatch>(FindFirstEntry(pInfo));
+ }
+
+ // Along with GetFirstPatch, this can iterate through
+ // the whole patch table. See GetFirstPatch for more info
+ // on the rules of iterating through the table.
+ DebuggerControllerPatch * GetNextPatch(HASHFIND * pInfo)
+ {
+ SUPPORTS_DAC;
+
+ return dac_cast<PTR_DebuggerControllerPatch>(FindNextEntry(pInfo));
+ }
+
+ // Used by DebuggerController to translate an index
+ // of a patch into a direct pointer.
+ inline HASHENTRY * GetEntryPtr(ULONG iEntry)
+ {
+ SUPPORTS_DAC;
+
+ return EntryPtr(iEntry);
+ }
+
+ // Used by DebuggerController to grab indeces of patches
+ // rather than holding direct pointers to them.
+ inline ULONG GetItemIndex(HASHENTRY * p)
+ {
+ SUPPORTS_DAC;
+
+ return ItemIndex(p);
+ }
+
+#ifdef _DEBUG_PATCH_TABLE
+public:
+ // DEBUG An internal debugging routine, it iterates
+ // through the hashtable, stopping at every
+ // single entry, no matter what it's state. For this to
+ // compile, you're going to have to add friend status
+ // of this class to CHashTableAndData in
+ // to $\Com99\Src\inc\UtilCode.h
+ void CheckPatchTable();
+#endif // _DEBUG_PATCH_TABLE
+
+ // Count how many patches are in the table.
+ // Use for asserts
+ int GetNumberOfPatches();
+
+};
+
+typedef VPTR(class DebuggerPatchTable) PTR_DebuggerPatchTable;
+
+
+#if !defined(DACCESS_COMPILE)
+
+// DebuggerControllerPage|Will eventually be used for
+// 'break when modified' behaviour'
+typedef struct DebuggerControllerPage
+{
+ DebuggerControllerPage *next;
+ const BYTE *start, *end;
+ DebuggerController *controller;
+ bool readable;
+} DebuggerControllerPage;
+
+// DEBUGGER_CONTROLLER_TYPE: Identifies the type of the controller.
+// It exists b/c we have RTTI turned off.
+// Note that the order of these is important - SortPatchIntoPatchList
+// relies on this ordering.
+//
+// DEBUGGER_CONTROLLER_STATIC|Base class response. Should never be
+// seen, since we shouldn't be asking the base class about this.
+// DEBUGGER_CONTROLLER_BREAKPOINT|DebuggerBreakpoint
+// DEBUGGER_CONTROLLER_STEPPER|DebuggerStepper
+// DEBUGGER_CONTROLLER_THREAD_STARTER|DebuggerThreadStarter
+// DEBUGGER_CONTROLLER_ENC|DebuggerEnCBreakpoint
+// DEBUGGER_CONTROLLER_PATCH_SKIP|DebuggerPatchSkip
+// DEBUGGER_CONTROLLER_JMC_STEPPER|DebuggerJMCStepper - steps through Just-My-Code
+// DEBUGGER_CONTROLLER_CONTINUABLE_EXCEPTION|DebuggerContinuableExceptionBreakpoint
+enum DEBUGGER_CONTROLLER_TYPE
+{
+ DEBUGGER_CONTROLLER_THREAD_STARTER,
+ DEBUGGER_CONTROLLER_ENC,
+ DEBUGGER_CONTROLLER_ENC_PATCH_TO_SKIP, // At any one address,
+ // There can be only one!
+ DEBUGGER_CONTROLLER_PATCH_SKIP,
+ DEBUGGER_CONTROLLER_BREAKPOINT,
+ DEBUGGER_CONTROLLER_STEPPER,
+ DEBUGGER_CONTROLLER_FUNC_EVAL_COMPLETE,
+ DEBUGGER_CONTROLLER_USER_BREAKPOINT, // UserBreakpoints are used by Runtime threads to
+ // send that they've hit a user breakpoint to the Right Side.
+ DEBUGGER_CONTROLLER_JMC_STEPPER, // Stepper that only stops in JMC-functions.
+ DEBUGGER_CONTROLLER_CONTINUABLE_EXCEPTION,
+ DEBUGGER_CONTROLLER_STATIC,
+};
+
+enum TP_RESULT
+{
+ TPR_TRIGGER, // This controller wants to SendEvent
+ TPR_IGNORE, // This controller doesn't want to SendEvent
+ TPR_TRIGGER_ONLY_THIS, // This, and only this controller, should be triggered.
+ // Right now, only the DebuggerEnCRemap controller
+ // returns this, the remap patch should be the first
+ // patch in the list.
+ TPR_TRIGGER_ONLY_THIS_AND_LOOP,
+ // This, and only this controller, should be triggered.
+ // Right now, only the DebuggerEnCRemap controller
+ // returns this, the remap patch should be the first
+ // patch in the list.
+ // After triggering this, DPOSS should skip the
+ // ActivatePatchSkip call, so we hit the other
+ // breakpoints at this location.
+ TPR_IGNORE_AND_STOP, // Don't SendEvent, and stop asking other
+ // controllers if they want to.
+ // Service any previous triggered controllers.
+};
+
+enum SCAN_TRIGGER
+{
+ ST_PATCH = 0x1, // Only look for patches
+ ST_SINGLE_STEP = 0x2, // Look for patches, and single-steps.
+} ;
+
+enum TRIGGER_WHY
+{
+ TY_NORMAL = 0x0,
+ TY_SHORT_CIRCUIT= 0x1, // EnC short circuit - see DispatchPatchOrSingleStep
+} ;
+
+// the return value for DebuggerController::DispatchPatchOrSingleStep
+enum DPOSS_ACTION
+{
+ // the following enum has been carefully ordered to optimize the helper
+ // functions below. Do not re-order them w/o changing the helper funcs.
+ DPOSS_INVALID = 0x0, // invalid action value
+ DPOSS_DONT_CARE = 0x1, // don't care about this exception
+ DPOSS_USED_WITH_NO_EVENT = 0x2, // Care about this exception but won't send event to RS
+ DPOSS_USED_WITH_EVENT = 0x3, // Care about this exception and will send event to RS
+};
+
+// helper function
+inline bool IsInUsedAction(DPOSS_ACTION action)
+{
+ _ASSERTE(action != DPOSS_INVALID);
+ return (action >= DPOSS_USED_WITH_NO_EVENT);
+}
+
+inline void VerifyExecutableAddress(const BYTE* address)
+{
+// TODO: : when can we apply this to x86?
+#if defined(_WIN64)
+#if defined(_DEBUG)
+#ifndef FEATURE_PAL
+ MEMORY_BASIC_INFORMATION mbi;
+
+ if (sizeof(mbi) == ClrVirtualQuery(address, &mbi, sizeof(mbi)))
+ {
+ if (!(mbi.State & MEM_COMMIT))
+ {
+ STRESS_LOG1(LF_GCROOTS, LL_ERROR, "VerifyExecutableAddress: address is uncommited memory, address=0x%p", address);
+ CONSISTENCY_CHECK_MSGF((mbi.State & MEM_COMMIT), ("VEA: address (0x%p) is uncommited memory.", address));
+ }
+
+ if (!(mbi.Protect & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY)))
+ {
+ STRESS_LOG1(LF_GCROOTS, LL_ERROR, "VerifyExecutableAddress: address is not executable, address=0x%p", address);
+ CONSISTENCY_CHECK_MSGF((mbi.Protect & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY)),
+ ("VEA: address (0x%p) is not on an executable page.", address));
+ }
+ }
+#endif // !FEATURE_PAL
+#endif // _DEBUG
+#endif // _WIN64
+}
+
+#endif // !DACCESS_COMPILE
+
+
+// DebuggerController: DebuggerController serves
+// both as a static class that dispatches exceptions coming from the
+// EE, and as an abstract base class for the five classes that derrive
+// from it.
+class DebuggerController
+{
+ VPTR_BASE_CONCRETE_VTABLE_CLASS(DebuggerController);
+
+#if !defined(DACCESS_COMPILE)
+
+ // Needs friendship for lock because of EnC locking workarounds.
+ friend class DebuggerEnCBreakpoint;
+
+ friend class DebuggerPatchSkip;
+ friend class DebuggerRCThread; //so we can get offsets of fields the
+ //right side needs to read
+ friend class Debugger; // So Debugger can lock, use, unlock the patch
+ // table in MapAndBindFunctionBreakpoints
+ friend void Debugger::UnloadModule(Module* pRuntimeModule, AppDomain *pAppDomain);
+
+ //
+ // Static functionality
+ //
+
+ public:
+ // Once we support debugging + fibermode (which was cut in V2.0), we may need some Thread::BeginThreadAffinity() calls
+ // associated with the controller lock because this lock wraps context operations.
+ class ControllerLockHolder : public CrstHolder
+ {
+ public:
+ ControllerLockHolder() : CrstHolder(&g_criticalSection) { WRAPPER_NO_CONTRACT; }
+ };
+
+ static HRESULT Initialize();
+
+ // Remove and cleanup all DebuggerControllers for detach
+ static void DeleteAllControllers();
+
+ //
+ // global event dispatching functionality
+ //
+
+
+ // Controllers are notified when they enter/exit func-evals (on their same thread,
+ // on any any thread if the controller doesn't have a thread).
+ // The original use for this was to allow steppers to skip through func-evals.
+ // thread - the thread doing the funceval.
+ static void DispatchFuncEvalEnter(Thread * thread);
+ static void DispatchFuncEvalExit(Thread * thread);
+
+ static bool DispatchNativeException(EXCEPTION_RECORD *exception,
+ CONTEXT *context,
+ DWORD code,
+ Thread *thread);
+
+ static bool DispatchUnwind(Thread *thread,
+ MethodDesc *fd, DebuggerJitInfo * pDJI, SIZE_T offset,
+ FramePointer handlerFP,
+ CorDebugStepReason unwindReason);
+
+ static bool DispatchTraceCall(Thread *thread,
+ const BYTE *address);
+
+ static PRD_TYPE GetPatchedOpcode(CORDB_ADDRESS_TYPE *address);
+
+ static BOOL CheckGetPatchedOpcode(CORDB_ADDRESS_TYPE *address, /*OUT*/ PRD_TYPE *pOpcode);
+
+ // pIP is the ip right after the prolog of the method we've entered.
+ // fp is the frame pointer for that method.
+ static void DispatchMethodEnter(void * pIP, FramePointer fp);
+
+
+ // Delete any patches that exist for a specific module and optionally a specific AppDomain.
+ // If pAppDomain is specified, then only patches tied to the specified AppDomain are
+ // removed. If pAppDomain is null, then all patches for the module are removed.
+ static void RemovePatchesFromModule( Module* pModule, AppDomain* pAppdomain );
+
+ // Check whether there are any pathces in the patch table for the specified module.
+ static bool ModuleHasPatches( Module* pModule );
+
+#if EnC_SUPPORTED
+ static DebuggerControllerPatch *IsXXXPatched(const BYTE *eip,
+ DEBUGGER_CONTROLLER_TYPE dct);
+
+ static DebuggerControllerPatch *GetEnCPatch(const BYTE *address);
+#endif //EnC_SUPPORTED
+
+ static DPOSS_ACTION ScanForTriggers(CORDB_ADDRESS_TYPE *address,
+ Thread *thread,
+ CONTEXT *context,
+ DebuggerControllerQueue *pDcq,
+ SCAN_TRIGGER stWhat,
+ TP_RESULT *pTpr);
+
+
+ static DebuggerPatchSkip *ActivatePatchSkip(Thread *thread,
+ const BYTE *eip,
+ BOOL fForEnC);
+
+
+ static DPOSS_ACTION DispatchPatchOrSingleStep(Thread *thread,
+ CONTEXT *context,
+ CORDB_ADDRESS_TYPE *ip,
+ SCAN_TRIGGER which);
+
+
+ static int GetNumberOfPatches()
+ {
+ if (g_patches == NULL)
+ return 0;
+
+ return g_patches->GetNumberOfPatches();
+ }
+
+ static int GetTotalMethodEnter() {LIMITED_METHOD_CONTRACT; return g_cTotalMethodEnter; }
+
+#if defined(_DEBUG)
+ // Debug check that we only have 1 thread-starter per thread.
+ // Check this new one against all existing ones.
+ static void EnsureUniqueThreadStarter(DebuggerThreadStarter * pNew);
+#endif
+ // If we have a thread-starter on the given EE thread, make sure it's cancel.
+ // Thread-Starters normally delete themselves when they fire. But if the EE
+ // destroys the thread before it fires, then we'd still have an active DTS.
+ static void CancelOutstandingThreadStarter(Thread * pThread);
+
+ static void AddRef(DebuggerControllerPatch *patch);
+ static void Release(DebuggerControllerPatch *patch);
+
+ private:
+
+ static bool MatchPatch(Thread *thread, CONTEXT *context,
+ DebuggerControllerPatch *patch);
+
+ // Returns TRUE if we should continue to dispatch after this exception
+ // hook.
+ static BOOL DispatchExceptionHook(Thread *thread, CONTEXT *context,
+ EXCEPTION_RECORD *exception);
+
+protected:
+#ifdef _DEBUG
+ static bool HasLock()
+ {
+ return g_criticalSection.OwnedByCurrentThread() != 0;
+ }
+#endif
+
+#endif // !DACCESS_COMPILE
+
+private:
+ SPTR_DECL(DebuggerPatchTable, g_patches);
+ SVAL_DECL(BOOL, g_patchTableValid);
+
+#if !defined(DACCESS_COMPILE)
+
+private:
+ static DebuggerControllerPage *g_protections;
+ static DebuggerController *g_controllers;
+
+ // This is the "Controller" lock. It synchronizes the controller infrastructure.
+ // It is smaller than the debugger lock, but larger than the debugger-data lock.
+ // It needs to be taken in execution-control related callbacks; and will also call
+ // back into the EE when held (most notably for the stub-managers; but also for various
+ // query operations).
+ static CrstStatic g_criticalSection;
+
+ // Write is protected by both Debugger + Controller Lock
+ static int g_cTotalMethodEnter;
+
+ static bool BindPatch(DebuggerControllerPatch *patch,
+ MethodDesc *fd,
+ CORDB_ADDRESS_TYPE *startAddr);
+ static bool ApplyPatch(DebuggerControllerPatch *patch);
+ static bool UnapplyPatch(DebuggerControllerPatch *patch);
+ static void UnapplyPatchAt(DebuggerControllerPatch *patch, CORDB_ADDRESS_TYPE *address);
+ static bool IsPatched(CORDB_ADDRESS_TYPE *address, BOOL native);
+
+ static void ActivatePatch(DebuggerControllerPatch *patch);
+ static void DeactivatePatch(DebuggerControllerPatch *patch);
+
+ static void ApplyTraceFlag(Thread *thread);
+ static void UnapplyTraceFlag(Thread *thread);
+
+ virtual void DebuggerDetachClean();
+
+ public:
+ static const BYTE *g_pMSCorEEStart, *g_pMSCorEEEnd;
+
+ static const BYTE *GetILPrestubDestination(const BYTE *prestub);
+ static const BYTE *GetILFunctionCode(MethodDesc *fd);
+
+ //
+ // Non-static functionality
+ //
+
+ public:
+
+ DebuggerController(Thread * pThread, AppDomain * pAppDomain);
+ virtual ~DebuggerController();
+ void Delete();
+ bool IsDeleted() { return m_deleted; }
+
+#endif // !DACCESS_COMPILE
+
+
+ // Return the pointer g_patches.
+ // Access to patch table for the RC threads (EE,DI)
+ // Why: The right side needs to know the address of the patch
+ // table (which never changes after it gets created) so that ReadMemory,
+ // WriteMemory can work from out-of-process. This should only be used in
+ // when the Runtime Controller is starting up, and not thereafter.
+ // How:return g_patches;
+public:
+ static DebuggerPatchTable * GetPatchTable() {LIMITED_METHOD_DAC_CONTRACT; return g_patches; }
+ static BOOL GetPatchTableValid() {LIMITED_METHOD_DAC_CONTRACT; return g_patchTableValid; }
+
+#if !defined(DACCESS_COMPILE)
+ static BOOL *GetPatchTableValidAddr() {LIMITED_METHOD_CONTRACT; return &g_patchTableValid; }
+
+ // Is there a patch at addr?
+ // We sometimes want to use this version of the method
+ // (as opposed to IsPatched) because there is
+ // a race condition wherein a patch can be added to the table, we can
+ // ask about it, and then we can actually apply the patch.
+ // How: If the patch table contains a patch at that address, there
+ // is.
+ static bool IsAddressPatched(CORDB_ADDRESS_TYPE *address)
+ {
+ return (g_patches->GetPatch(address) != NULL);
+ }
+
+ //
+ // Event setup
+ //
+
+ Thread *GetThread() { return m_thread; }
+
+ // This one should be made private
+ BOOL AddBindAndActivateILSlavePatch(DebuggerControllerPatch *master,
+ DebuggerJitInfo *dji);
+
+ BOOL AddILPatch(AppDomain * pAppDomain, Module *module,
+ mdMethodDef md,
+ SIZE_T encVersion, // what encVersion does this apply to?
+ SIZE_T offset);
+
+ // The next two are very similar. Both work on offsets,
+ // but one takes a "patch id". I don't think these are really needed: the
+ // controller itself can act as the id of the patch.
+ BOOL AddBindAndActivateNativeManagedPatch(
+ MethodDesc * fd,
+ DebuggerJitInfo *dji,
+ SIZE_T offset,
+ FramePointer fp,
+ AppDomain *pAppDomain);
+
+ // Add a patch at the start of a not-yet-jitted method.
+ void AddPatchToStartOfLatestMethod(MethodDesc * fd);
+
+
+ // This version is particularly useful b/c it doesn't assume that the
+ // patch is inside a managed method.
+ DebuggerControllerPatch *AddAndActivateNativePatchForAddress(CORDB_ADDRESS_TYPE *address,
+ FramePointer fp,
+ bool managed,
+ TraceType traceType);
+
+
+
+ bool PatchTrace(TraceDestination *trace, FramePointer fp, bool fStopInUnmanaged);
+
+ void AddProtection(const BYTE *start, const BYTE *end, bool readable);
+ void RemoveProtection(const BYTE *start, const BYTE *end, bool readable);
+
+ static BOOL IsSingleStepEnabled(Thread *pThread);
+ bool IsSingleStepEnabled();
+ void EnableSingleStep();
+ static void EnableSingleStep(Thread *pThread);
+
+ void DisableSingleStep();
+
+ void EnableExceptionHook();
+ void DisableExceptionHook();
+
+ void EnableUnwind(FramePointer frame);
+ void DisableUnwind();
+ FramePointer GetUnwind();
+
+ void EnableTraceCall(FramePointer fp);
+ void DisableTraceCall();
+
+ bool IsMethodEnterEnabled();
+ void EnableMethodEnter();
+ void DisableMethodEnter();
+
+ void DisableAll();
+
+ virtual DEBUGGER_CONTROLLER_TYPE GetDCType( void )
+ { return DEBUGGER_CONTROLLER_STATIC; }
+
+ // Return true iff this is one of the stepper types.
+ // if true, we can safely cast this controller to a DebuggerStepper*.
+ inline bool IsStepperDCType()
+ {
+ DEBUGGER_CONTROLLER_TYPE e = this->GetDCType();
+ return (e == DEBUGGER_CONTROLLER_STEPPER) || (e == DEBUGGER_CONTROLLER_JMC_STEPPER);
+ }
+
+ void Enqueue();
+ void Dequeue();
+
+ private:
+ // Helper function that is called on each virtual trace call target to set a trace patch
+ static void PatchTargetVisitor(TADDR pVirtualTraceCallTarget, VOID* pUserData);
+
+ DebuggerControllerPatch *AddILMasterPatch(Module *module,
+ mdMethodDef md,
+ SIZE_T offset,
+ SIZE_T encVersion);
+
+ BOOL AddBindAndActivatePatchForMethodDesc(MethodDesc *fd,
+ DebuggerJitInfo *dji,
+ SIZE_T offset,
+ DebuggerPatchKind kind,
+ FramePointer fp,
+ AppDomain *pAppDomain);
+
+
+ protected:
+
+ //
+ // Target event handlers
+ //
+
+
+ // Notify a controller that a func-eval is starting/ending on the given thread.
+ // If a controller's m_thread!=NULL, then it is only notified of func-evals on
+ // its thread.
+ // Controllers don't need to Enable anything to get this, and most controllers
+ // can ignore it.
+ virtual void TriggerFuncEvalEnter(Thread * thread);
+ virtual void TriggerFuncEvalExit(Thread * thread);
+
+ virtual TP_RESULT TriggerPatch(DebuggerControllerPatch *patch,
+ Thread *thread,
+ TRIGGER_WHY tyWhy);
+
+ // Dispatched when we get a SingleStep exception on this thread.
+ // Return true if we want SendEvent to get called.
+
+ virtual bool TriggerSingleStep(Thread *thread, const BYTE *ip);
+
+
+ // Dispatched to notify the controller when we are going to a filter/handler
+ // that's in the stepper's current frame or above (a caller frame).
+ // 'desc' & 'offset' are the location of the filter/handler (ie, this is where
+ // execution will continue)
+ // 'frame' points into the stack at the return address for the function w/ the handler.
+ // If (frame > m_unwindFP) then the filter/handler is in a caller, else
+ // it's in the same function as the current stepper (It's not in a child because
+ // we don't dispatch in that case).
+ virtual void TriggerUnwind(Thread *thread, MethodDesc *fd, DebuggerJitInfo * pDJI,
+ SIZE_T offset, FramePointer fp,
+ CorDebugStepReason unwindReason);
+
+ virtual void TriggerTraceCall(Thread *thread, const BYTE *ip);
+ virtual TP_RESULT TriggerExceptionHook(Thread *thread, CONTEXT * pContext,
+ EXCEPTION_RECORD *exception);
+
+ // Trigger when we've entered a method
+ // thread - current thread
+ // desc - the method that we've entered
+ // ip - the address after the prolog. A controller can patch this address.
+ // To stop in this method.
+ // Returns true if the trigger will disable itself from further method entry
+ // triggers else returns false (passing through a cctor can cause this).
+ // A controller can't block in this trigger! It can only update state / set patches
+ // and then return.
+ virtual void TriggerMethodEnter(Thread * thread,
+ DebuggerJitInfo *dji,
+ const BYTE * ip,
+ FramePointer fp);
+
+
+ // Send the managed debug event.
+ // This is called after TriggerPatch/TriggerSingleStep actually trigger.
+ // Note this can have a strange interaction with SetIp. Specifically this thread:
+ // 1) may call TriggerXYZ which queues the controller for send event.
+ // 2) blocks on a the debugger lock (in which case SetIp may get invoked on it)
+ // 3) then sends the event
+ // If SetIp gets invoked at step 2, the thread's IP may have changed such that it should no
+ // longer trigger. Eg, perhaps we were about to send a breakpoint, and then SetIp moved us off
+ // the bp. So we pass in an extra flag, fInteruptedBySetIp, to let the controller decide how to handle this.
+ // Since SetIP only works within a single function, this can only be an issue if a thread's current stopping
+ // location and the patch it set are in the same function. (So this could happen for step-over, but never
+ // setp-out).
+ // This flag will almost always be false.
+ //
+ // Once we actually send the event, we're under the debugger lock, and so the world is stable underneath us.
+ // But the world may change underneath a thread between when SendEvent gets queued and by the time it's actually called.
+ // So SendIPCEvent may need to do some last-minute sanity checking (like the SetIP case) to ensure it should
+ // still send.
+ //
+ // Returns true if send an event, false elsewise.
+ virtual bool SendEvent(Thread *thread, bool fInteruptedBySetIp);
+
+ AppDomain *m_pAppDomain;
+
+ private:
+
+ Thread *m_thread;
+ DebuggerController *m_next;
+ bool m_singleStep;
+ bool m_exceptionHook;
+ bool m_traceCall;
+protected:
+ FramePointer m_traceCallFP;
+private:
+ FramePointer m_unwindFP;
+ int m_eventQueuedCount;
+ bool m_deleted;
+ bool m_fEnableMethodEnter;
+
+#endif // !DACCESS_COMPILE
+};
+
+
+#if !defined(DACCESS_COMPILE)
+
+/* ------------------------------------------------------------------------- *
+ * DebuggerPatchSkip routines
+ * ------------------------------------------------------------------------- */
+
+class DebuggerPatchSkip : public DebuggerController
+{
+ friend class DebuggerController;
+
+ DebuggerPatchSkip(Thread *thread,
+ DebuggerControllerPatch *patch,
+ AppDomain *pAppDomain);
+
+ ~DebuggerPatchSkip();
+
+ bool TriggerSingleStep(Thread *thread,
+ const BYTE *ip);
+
+ TP_RESULT TriggerExceptionHook(Thread *thread, CONTEXT * pContext,
+ EXCEPTION_RECORD *exception);
+
+ TP_RESULT TriggerPatch(DebuggerControllerPatch *patch,
+ Thread *thread,
+ TRIGGER_WHY tyWhy);
+
+ virtual DEBUGGER_CONTROLLER_TYPE GetDCType(void)
+ { return DEBUGGER_CONTROLLER_PATCH_SKIP; }
+
+ void CopyInstructionBlock(BYTE *to, const BYTE* from);
+
+ void DecodeInstruction(CORDB_ADDRESS_TYPE *code);
+
+ void DebuggerDetachClean();
+
+ CORDB_ADDRESS_TYPE *m_address;
+ int m_iOrigDisp; // the original displacement of a relative call or jump
+ InstructionAttribute m_instrAttrib; // info about the instruction being skipped over
+#ifndef _TARGET_ARM_
+ // this is shared among all the skippers and the controller. see the comments
+ // right before the definition of SharedPatchBypassBuffer for lifetime info.
+ SharedPatchBypassBuffer *m_pSharedPatchBypassBuffer;
+
+public:
+ CORDB_ADDRESS_TYPE *GetBypassAddress()
+ {
+ _ASSERTE(m_pSharedPatchBypassBuffer);
+ BYTE* patchBypass = m_pSharedPatchBypassBuffer->PatchBypass;
+ return (CORDB_ADDRESS_TYPE *)patchBypass;
+ }
+#endif // _TARGET_ARM_
+};
+
+/* ------------------------------------------------------------------------- *
+ * DebuggerBreakpoint routines
+ * ------------------------------------------------------------------------- */
+
+// DebuggerBreakpoint:
+// DBp represents a user-placed breakpoint, and when Triggered, will
+// always want to be activated, whereupon it will inform the right side of
+// being hit.
+class DebuggerBreakpoint : public DebuggerController
+{
+public:
+ DebuggerBreakpoint(Module *module,
+ mdMethodDef md,
+ AppDomain *pAppDomain,
+ SIZE_T m_offset,
+ bool m_native,
+ SIZE_T ilEnCVersion, // must give the EnC version for non-native bps
+ MethodDesc *nativeMethodDesc, // must be non-null when m_native, null otherwise
+ DebuggerJitInfo *nativeJITInfo, // optional when m_native, null otherwise
+ BOOL *pSucceed
+ );
+
+ virtual DEBUGGER_CONTROLLER_TYPE GetDCType( void )
+ { return DEBUGGER_CONTROLLER_BREAKPOINT; }
+
+private:
+
+ TP_RESULT TriggerPatch(DebuggerControllerPatch *patch,
+ Thread *thread,
+ TRIGGER_WHY tyWhy);
+ bool SendEvent(Thread *thread, bool fInteruptedBySetIp);
+};
+
+// * ------------------------------------------------------------------------ *
+// * DebuggerStepper routines
+// * ------------------------------------------------------------------------ *
+//
+
+// DebuggerStepper: This subclass of DebuggerController will
+// be instantiated to create a "Step" operation, meaning that execution
+// should continue until a range of IL code is exited.
+class DebuggerStepper : public DebuggerController
+{
+public:
+ DebuggerStepper(Thread *thread,
+ CorDebugUnmappedStop rgfMappingStop,
+ CorDebugIntercept interceptStop,
+ AppDomain *appDomain);
+ ~DebuggerStepper();
+
+ bool Step(FramePointer fp, bool in,
+ COR_DEBUG_STEP_RANGE *range, SIZE_T cRange, bool rangeIL);
+ void StepOut(FramePointer fp, StackTraceTicket ticket);
+
+ virtual DEBUGGER_CONTROLLER_TYPE GetDCType( void )
+ { return DEBUGGER_CONTROLLER_STEPPER; }
+
+ //MoveToCurrentVersion makes sure that the stepper is prepared to
+ // operate within the version of the code specified by djiNew.
+ // Currently, this means to map the ranges into the ranges of the djiNew.
+ // Idempotent.
+ void MoveToCurrentVersion( DebuggerJitInfo *djiNew);
+
+ // Public & Polymorphic on flavor (traditional vs. JMC).
+
+ // Regular steppers want to EnableTraceCall; and JMC-steppers want to EnableMethodEnter.
+ // (They're very related - they both stop at the next "interesting" managed code run).
+ // So we just gloss over the difference w/ some polymorphism.
+ virtual void EnablePolyTraceCall();
+
+protected:
+ // Steppers override these so that they can skip func-evals.
+ void TriggerFuncEvalEnter(Thread * thread);
+ void TriggerFuncEvalExit(Thread * thread);
+
+ bool TrapStepInto(ControllerStackInfo *info,
+ const BYTE *ip,
+ TraceDestination *pTD);
+
+ bool TrapStep(ControllerStackInfo *info, bool in);
+
+ // @todo - must remove that fForceTraditional flag. Need a way for a JMC stepper
+ // to do a Trad step out.
+ void TrapStepOut(ControllerStackInfo *info, bool fForceTraditional = false);
+
+ // Polymorphic on flavor (Traditional vs. Just-My-Code)
+ virtual void TrapStepNext(ControllerStackInfo *info);
+ virtual bool TrapStepInHelper(ControllerStackInfo * pInfo,
+ const BYTE * ipCallTarget,
+ const BYTE * ipNext,
+ bool fCallingIntoFunclet);
+ virtual bool IsInterestingFrame(FrameInfo * pFrame);
+ virtual bool DetectHandleNonUserCode(ControllerStackInfo *info, DebuggerMethodInfo * pInfo);
+
+
+ //DetectHandleInterceptors will figure out if the current
+ // frame is inside an interceptor, and if we're not interested in that
+ // interceptor, it will set a breakpoint outside it so that we can
+ // run to after the interceptor.
+ virtual bool DetectHandleInterceptors(ControllerStackInfo *info);
+
+ // This function checks whether the given IP is in an LCG method. If so, it enables
+ // JMC and does a step out. This effectively makes sure that we never stop in an LCG method.
+ BOOL DetectHandleLCGMethods(const PCODE ip, MethodDesc * pMD, ControllerStackInfo * pInfo);
+
+ bool IsAddrWithinFrame(DebuggerJitInfo *dji,
+ MethodDesc* pMD,
+ const BYTE* currentAddr,
+ const BYTE* targetAddr);
+
+ // x86 shouldn't need to call this method directly.
+ // We should call IsAddrWithinFrame() on x86 instead.
+ // That's why I use a name with the word "funclet" in it to scare people off.
+ bool IsAddrWithinMethodIncludingFunclet(DebuggerJitInfo *dji,
+ MethodDesc* pMD,
+ const BYTE* targetAddr);
+
+ //ShouldContinue returns false if the DebuggerStepper should stop
+ // execution and inform the right side. Returns true if the next
+ // breakpointexecution should be set, and execution allowed to continue
+ bool ShouldContinueStep( ControllerStackInfo *info, SIZE_T nativeOffset );
+
+ //IsInRange returns true if the given IL offset is inside of
+ // any of the COR_DEBUG_STEP_RANGE structures given by range.
+ bool IsInRange(SIZE_T offset, COR_DEBUG_STEP_RANGE *range, SIZE_T rangeCount,
+ ControllerStackInfo *pInfo = NULL);
+ bool IsRangeAppropriate(ControllerStackInfo *info);
+
+
+
+ TP_RESULT TriggerPatch(DebuggerControllerPatch *patch,
+ Thread *thread,
+ TRIGGER_WHY tyWhy);
+ bool TriggerSingleStep(Thread *thread, const BYTE *ip);
+ void TriggerUnwind(Thread *thread, MethodDesc *fd, DebuggerJitInfo * pDJI,
+ SIZE_T offset, FramePointer fp,
+ CorDebugStepReason unwindReason);
+ void TriggerTraceCall(Thread *thread, const BYTE *ip);
+ bool SendEvent(Thread *thread, bool fInteruptedBySetIp);
+
+
+ virtual void TriggerMethodEnter(Thread * thread, DebuggerJitInfo * dji, const BYTE * ip, FramePointer fp);
+
+
+ void ResetRange();
+
+ // Given a set of IL ranges, convert them to native and cache them.
+ bool SetRangesFromIL(DebuggerJitInfo * dji, COR_DEBUG_STEP_RANGE *ranges, SIZE_T rangeCount);
+
+ // Return true if this stepper is alive, but frozen. (we freeze when the stepper
+ // enters a nested func-eval).
+ bool IsFrozen();
+
+ // Returns true if this stepper is 'dead' - which happens if a non-frozen stepper
+ // gets a func-eval exit.
+ bool IsDead();
+
+ // Prepare for sending an event.
+ void PrepareForSendEvent(StackTraceTicket ticket);
+
+protected:
+ bool m_stepIn;
+ CorDebugStepReason m_reason; // Why did we stop?
+ FramePointer m_fpStepInto; // if we get a trace call
+ //callback, we may end up completing
+ // a step into. If fp is less than th is
+ // when we stop,
+ // then we're actually in a STEP_CALL
+
+ CorDebugIntercept m_rgfInterceptStop; // If we hit a
+ // frame that's an interceptor (internal or otherwise), should we stop?
+
+ CorDebugUnmappedStop m_rgfMappingStop; // If we hit a frame
+ // that's at an interesting mapping point (prolog, epilog,etc), should
+ // we stop?
+
+ COR_DEBUG_STEP_RANGE * m_range; // Ranges for active steppers are always in native offsets.
+
+ SIZE_T m_rangeCount;
+ SIZE_T m_realRangeCount; // @todo - delete b/c only used for CodePitching & Old-Enc
+
+ // The original step intention.
+ // As the stepper moves through code, it may change its other members.
+ // ranges may get deleted, m_stepIn may get toggled, etc.
+ // So we can't recover the original step direction from our other fields.
+ // We need to know the original direction (as well as m_fp) so we know
+ // if the frame we want to stop in is valid.
+ //
+ // Note that we can't really tell this by looking at our other state variables.
+ // For example, a single-instruction step looks like a step-over.
+ enum EStepMode
+ {
+ cStepOver, // Stop in level above or at m_fp.
+ cStepIn, // Stop in level above, below, or at m_fp.
+ cStepOut // Only stop in level above m_fp
+ } m_eMode;
+
+ // The frame that the stepper was originally created in.
+ // This is the only frame that the ranges are valid in.
+ FramePointer m_fp;
+
+#if defined(WIN64EXCEPTIONS)
+ // This frame pointer is used for funclet stepping.
+ // See IsRangeAppropriate() for more information.
+ FramePointer m_fpParentMethod;
+#endif // WIN64EXCEPTIONS
+
+ //m_fpException is 0 if we haven't stepped into an exception,
+ // and is ignored. If we get a TriggerUnwind while mid-step, we note
+ // the value of frame here, and use that to figure out if we should stop.
+ FramePointer m_fpException;
+ MethodDesc * m_fdException;
+
+ // Counter of FuncEvalEnter/Exits - used to determine if we're entering / exiting
+ // a func-eval.
+ int m_cFuncEvalNesting;
+
+ // To freeze a stepper, we disable all triggers. We have to remember that so that
+ // we can reenable them on Thaw.
+ DWORD m_bvFrozenTriggers;
+
+ // Values to use in m_bvFrozenTriggers.
+ enum ETriggers
+ {
+ kSingleStep = 0x1,
+ kMethodEnter = 0x2,
+ };
+
+
+ void EnableJMCBackStop(MethodDesc * pStartMethod);
+
+#ifdef _DEBUG
+ // MethodDesc that the Stepin started in.
+ // This is used for the JMC-backstop.
+ MethodDesc * m_StepInStartMethod;
+
+ // This flag is to ensure that PrepareForSendEvent is called before SendEvent.
+ bool m_fReadyToSend;
+#endif
+};
+
+
+
+/* ------------------------------------------------------------------------- *
+ * DebuggerJMCStepper routines
+ * ------------------------------------------------------------------------- */
+class DebuggerJMCStepper : public DebuggerStepper
+{
+public:
+ DebuggerJMCStepper(Thread *thread,
+ CorDebugUnmappedStop rgfMappingStop,
+ CorDebugIntercept interceptStop,
+ AppDomain *appDomain);
+ ~DebuggerJMCStepper();
+
+ virtual DEBUGGER_CONTROLLER_TYPE GetDCType( void )
+ { return DEBUGGER_CONTROLLER_JMC_STEPPER; }
+
+ virtual void EnablePolyTraceCall();
+protected:
+ virtual void TrapStepNext(ControllerStackInfo *info);
+ virtual bool TrapStepInHelper(ControllerStackInfo * pInfo,
+ const BYTE * ipCallTarget,
+ const BYTE * ipNext,
+ bool fCallingIntoFunclet);
+ virtual bool IsInterestingFrame(FrameInfo * pFrame);
+ virtual void TriggerMethodEnter(Thread * thread, DebuggerJitInfo * dji, const BYTE * ip, FramePointer fp);
+ virtual bool DetectHandleNonUserCode(ControllerStackInfo *info, DebuggerMethodInfo * pInfo);
+ virtual bool DetectHandleInterceptors(ControllerStackInfo *info);
+
+
+private:
+
+};
+
+
+/* ------------------------------------------------------------------------- *
+ * DebuggerThreadStarter routines
+ * ------------------------------------------------------------------------- */
+// DebuggerThreadStarter: Once triggered, it sends the thread attach
+// message to the right side (where the CreateThread managed callback
+// gets called). It then promptly disappears, as it's only purpose is to
+// alert the right side that a new thread has begun execution.
+class DebuggerThreadStarter : public DebuggerController
+{
+public:
+ DebuggerThreadStarter(Thread *thread);
+
+ virtual DEBUGGER_CONTROLLER_TYPE GetDCType( void )
+ { return DEBUGGER_CONTROLLER_THREAD_STARTER; }
+
+private:
+ TP_RESULT TriggerPatch(DebuggerControllerPatch *patch,
+ Thread *thread,
+ TRIGGER_WHY tyWhy);
+ void TriggerTraceCall(Thread *thread, const BYTE *ip);
+ bool SendEvent(Thread *thread, bool fInteruptedBySetIp);
+};
+
+/* ------------------------------------------------------------------------- *
+ * DebuggerUserBreakpoint routines. UserBreakpoints are used
+ * by Runtime threads to send that they've hit a user breakpoint to the
+ * Right Side.
+ * ------------------------------------------------------------------------- */
+class DebuggerUserBreakpoint : public DebuggerStepper
+{
+public:
+ static void HandleDebugBreak(Thread * pThread);
+
+ static bool IsFrameInDebuggerNamespace(FrameInfo * pFrame);
+
+ virtual DEBUGGER_CONTROLLER_TYPE GetDCType( void )
+ { return DEBUGGER_CONTROLLER_USER_BREAKPOINT; }
+private:
+ // Don't construct these directly. Use HandleDebugBreak().
+ DebuggerUserBreakpoint(Thread *thread);
+
+
+ virtual bool IsInterestingFrame(FrameInfo * pFrame);
+
+ bool SendEvent(Thread *thread, bool fInteruptedBySetIp);
+};
+
+/* ------------------------------------------------------------------------- *
+ * DebuggerFuncEvalComplete routines
+ * ------------------------------------------------------------------------- */
+class DebuggerFuncEvalComplete : public DebuggerController
+{
+public:
+ DebuggerFuncEvalComplete(Thread *thread,
+ void *dest);
+
+ virtual DEBUGGER_CONTROLLER_TYPE GetDCType( void )
+ { return DEBUGGER_CONTROLLER_FUNC_EVAL_COMPLETE; }
+
+private:
+ TP_RESULT TriggerPatch(DebuggerControllerPatch *patch,
+ Thread *thread,
+ TRIGGER_WHY tyWhy);
+ bool SendEvent(Thread *thread, bool fInteruptedBySetIp);
+ DebuggerEval* m_pDE;
+};
+
+// continuable-exceptions
+/* ------------------------------------------------------------------------- *
+ * DebuggerContinuableExceptionBreakpoint routines
+ * ------------------------------------------------------------------------- *
+ *
+ * DebuggerContinuableExceptionBreakpoint: Implementation of Continuable Exception support uses this.
+ */
+class DebuggerContinuableExceptionBreakpoint : public DebuggerController
+{
+public:
+ DebuggerContinuableExceptionBreakpoint(Thread *pThread,
+ SIZE_T m_offset,
+ DebuggerJitInfo *jitInfo,
+ AppDomain *pAppDomain);
+
+ virtual DEBUGGER_CONTROLLER_TYPE GetDCType( void )
+ { return DEBUGGER_CONTROLLER_CONTINUABLE_EXCEPTION; }
+
+private:
+ TP_RESULT TriggerPatch(DebuggerControllerPatch *patch,
+ Thread *thread,
+ TRIGGER_WHY tyWhy);
+
+ bool SendEvent(Thread *thread, bool fInteruptedBySetIp);
+};
+
+#ifdef EnC_SUPPORTED
+//---------------------------------------------------------------------------------------
+//
+// DebuggerEnCBreakpoint - used by edit and continue to support remapping
+//
+// When a method is updated, we make no immediate attempt to remap any existing execution
+// of the old method. Instead we mine the old method with EnC breakpoints, and prompt the
+// debugger whenever one is hit, giving it the opportunity to request a remap to the
+// latest version of the method.
+//
+// Over long debugging sessions which make many edits to large methods, we can create
+// a large number of these breakpoints. We currently make no attempt to reclaim the
+// code or patch overhead for old methods. Ideally we'd be able to detect when there are
+// no outstanding references to the old method version and clean up after it. At the
+// very least, we could remove all but the first patch when there are no outstanding
+// frames for a specific version of an edited method.
+//
+class DebuggerEnCBreakpoint : public DebuggerController
+{
+public:
+ // We have two types of EnC breakpoints. The first is the one we
+ // sprinkle through old code to let us know when execution is occuring
+ // in a function that now has a new version. The second is when we've
+ // actually resumed excecution into a remapped function and we need
+ // to then notify the debugger.
+ enum TriggerType {REMAP_PENDING, REMAP_COMPLETE};
+
+ // Create and activate an EnC breakpoint at the specified native offset
+ DebuggerEnCBreakpoint(SIZE_T m_offset,
+ DebuggerJitInfo *jitInfo,
+ TriggerType fTriggerType,
+ AppDomain *pAppDomain);
+
+ virtual DEBUGGER_CONTROLLER_TYPE GetDCType( void )
+ { return DEBUGGER_CONTROLLER_ENC; }
+
+private:
+ TP_RESULT TriggerPatch(DebuggerControllerPatch *patch,
+ Thread *thread,
+ TRIGGER_WHY tyWhy);
+
+ TP_RESULT HandleRemapComplete(DebuggerControllerPatch *patch,
+ Thread *thread,
+ TRIGGER_WHY tyWhy);
+
+ DebuggerJitInfo *m_jitInfo;
+ TriggerType m_fTriggerType;
+};
+#endif //EnC_SUPPORTED
+
+/* ========================================================================= */
+
+enum
+{
+ EVENTS_INIT_ALLOC = 5
+};
+
+class DebuggerControllerQueue
+{
+ DebuggerController **m_events;
+ DWORD m_dwEventsCount;
+ DWORD m_dwEventsAlloc;
+ DWORD m_dwNewEventsAlloc;
+
+public:
+ DebuggerControllerQueue()
+ : m_events(NULL),
+ m_dwEventsCount(0),
+ m_dwEventsAlloc(0),
+ m_dwNewEventsAlloc(0)
+ {
+ }
+
+
+ ~DebuggerControllerQueue()
+ {
+ if (m_events != NULL)
+ delete [] m_events;
+ }
+
+ BOOL dcqEnqueue(DebuggerController *dc, BOOL fSort)
+ {
+ LOG((LF_CORDB, LL_INFO100000,"DCQ::dcqE\n"));
+
+ _ASSERTE( dc != NULL );
+
+ if (m_dwEventsCount == m_dwEventsAlloc)
+ {
+ if (m_events == NULL)
+ m_dwNewEventsAlloc = EVENTS_INIT_ALLOC;
+ else
+ m_dwNewEventsAlloc = m_dwEventsAlloc<<1;
+
+ DebuggerController **newEvents = new (nothrow) DebuggerController * [m_dwNewEventsAlloc];
+
+ if (newEvents == NULL)
+ return FALSE;
+
+ if (m_events != NULL)
+ // The final argument to CopyMemory cannot over/underflow.
+ // The amount of memory copied has a strict upper bound of the size of the array,
+ // which cannot exceed the pointer size for the platform.
+ CopyMemory(newEvents, m_events, (SIZE_T)sizeof(*m_events) * (SIZE_T)m_dwEventsAlloc);
+
+ m_events = newEvents;
+ m_dwEventsAlloc = m_dwNewEventsAlloc;
+ }
+
+ dc->Enqueue();
+
+ // Make sure to place high priority patches into
+ // the event list first. This ensures, for
+ // example, that thread starts fire before
+ // breakpoints.
+ if (fSort && (m_dwEventsCount > 0))
+ {
+ DWORD i;
+ for (i = 0; i < m_dwEventsCount; i++)
+ {
+ _ASSERTE(m_events[i] != NULL);
+
+ if (m_events[i]->GetDCType() > dc->GetDCType())
+ {
+ // The final argument to CopyMemory cannot over/underflow.
+ // The amount of memory copied has a strict upper bound of the size of the array,
+ // which cannot exceed the pointer size for the platform.
+ MoveMemory(&m_events[i+1], &m_events[i], (SIZE_T)sizeof(DebuggerController*) * (SIZE_T)(m_dwEventsCount - i));
+ m_events[i] = dc;
+ break;
+ }
+ }
+
+ if (i == m_dwEventsCount)
+ m_events[m_dwEventsCount] = dc;
+
+ m_dwEventsCount++;
+ }
+ else
+ m_events[m_dwEventsCount++] = dc;
+
+ return TRUE;
+ }
+
+ DWORD dcqGetCount(void)
+ {
+ return m_dwEventsCount;
+ }
+
+ DebuggerController *dcqGetElement(DWORD dwElement)
+ {
+ LOG((LF_CORDB, LL_INFO100000,"DCQ::dcqGE\n"));
+
+ DebuggerController *dcp = NULL;
+
+ _ASSERTE(dwElement < m_dwEventsCount);
+ if (dwElement < m_dwEventsCount)
+ {
+ dcp = m_events[dwElement];
+ }
+
+ _ASSERTE(dcp != NULL);
+ return dcp;
+ }
+
+ // Kinda wacked, but this actually releases stuff in FILO order, not
+ // FIFO order. If we do this in an extra loop, then the perf
+ // is better than sliding everything down one each time.
+ void dcqDequeue(DWORD dw = 0xFFffFFff)
+ {
+ if (dw == 0xFFffFFff)
+ {
+ dw = (m_dwEventsCount - 1);
+ }
+
+ LOG((LF_CORDB, LL_INFO100000,"DCQ::dcqD element index "
+ "0x%x of 0x%x\n", dw, m_dwEventsCount));
+
+ _ASSERTE(dw < m_dwEventsCount);
+
+ m_events[dw]->Dequeue();
+
+ // Note that if we're taking the element off the end (m_dwEventsCount-1),
+ // the following will no-op.
+ // The final argument to MoveMemory cannot over/underflow.
+ // The amount of memory copied has a strict upper bound of the size of the array,
+ // which cannot exceed the pointer size for the platform.
+ MoveMemory(&(m_events[dw]),
+ &(m_events[dw + 1]),
+ (SIZE_T)sizeof(DebuggerController *) * (SIZE_T)(m_dwEventsCount - dw - 1));
+ m_dwEventsCount--;
+ }
+};
+
+// Include all of the inline stuff now.
+#include "controller.inl"
+
+#endif // !DACCESS_COMPILE
+
+#endif /* CONTROLLER_H_ */
diff --git a/src/debug/ee/controller.inl b/src/debug/ee/controller.inl
new file mode 100644
index 0000000000..fd02c55f0d
--- /dev/null
+++ b/src/debug/ee/controller.inl
@@ -0,0 +1,56 @@
+// 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.
+//*****************************************************************************
+// File: controller.inl
+//
+
+//
+// Inline definitions for the Left-Side of the CLR debugging services
+// This is logically part of the header file.
+//
+//*****************************************************************************
+
+#ifndef CONTROLLER_INL_
+#define CONTROLLER_INL_
+
+inline BOOL DebuggerControllerPatch::IsBreakpointPatch()
+{
+ return (controller->GetDCType() == DEBUGGER_CONTROLLER_BREAKPOINT);
+}
+
+inline BOOL DebuggerControllerPatch::IsStepperPatch()
+{
+ return (controller->IsStepperDCType());
+}
+
+inline DebuggerPatchKind DebuggerControllerPatch::GetKind()
+{
+ return kind;
+}
+inline BOOL DebuggerControllerPatch::IsILMasterPatch()
+{
+ LIMITED_METHOD_CONTRACT;
+
+ return (kind == PATCH_KIND_IL_MASTER);
+}
+
+inline BOOL DebuggerControllerPatch::IsILSlavePatch()
+{
+ LIMITED_METHOD_CONTRACT;
+
+ return (kind == PATCH_KIND_IL_SLAVE);
+}
+
+inline BOOL DebuggerControllerPatch::IsManagedPatch()
+{
+ return (IsILMasterPatch() || IsILSlavePatch() || kind == PATCH_KIND_NATIVE_MANAGED);
+
+}
+inline BOOL DebuggerControllerPatch::IsNativePatch()
+{
+ return (kind == PATCH_KIND_NATIVE_MANAGED || kind == PATCH_KIND_NATIVE_UNMANAGED || (IsILSlavePatch() && !offsetIsIL));
+
+}
+
+#endif // CONTROLLER_INL_
diff --git a/src/debug/ee/dac/.gitmirror b/src/debug/ee/dac/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/debug/ee/dac/.gitmirror
@@ -0,0 +1 @@
+Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror. \ No newline at end of file
diff --git a/src/debug/ee/dac/CMakeLists.txt b/src/debug/ee/dac/CMakeLists.txt
new file mode 100644
index 0000000000..a65bc07b39
--- /dev/null
+++ b/src/debug/ee/dac/CMakeLists.txt
@@ -0,0 +1,6 @@
+
+include(${CLR_DIR}/dac.cmake)
+
+add_precompiled_header(stdafx.h ../stdafx.cpp CORDBEE_SOURCES_DAC)
+
+add_library_clr(cordbee_dac ${CORDBEE_SOURCES_DAC})
diff --git a/src/debug/ee/dac/dirs.proj b/src/debug/ee/dac/dirs.proj
new file mode 100644
index 0000000000..8b766561f5
--- /dev/null
+++ b/src/debug/ee/dac/dirs.proj
@@ -0,0 +1,19 @@
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <!--Import the settings-->
+ <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\clr.props" />
+
+ <!--The following projects will build during PHASE 1-->
+ <PropertyGroup>
+ <BuildInPhase1>true</BuildInPhase1>
+ <BuildInPhaseDefault>false</BuildInPhaseDefault>
+ <BuildCoreBinaries>true</BuildCoreBinaries>
+ <BuildSysBinaries>true</BuildSysBinaries>
+ </PropertyGroup>
+
+ <ItemGroup Condition="'$(BuildExePhase)' == '1'">
+ <ProjectFile Include="HostLocal\dacwks.nativeproj" />
+ </ItemGroup>
+
+ <!--Import the targets-->
+ <Import Project="$(_NTDRIVE)$(_NTROOT)\tools\Microsoft.DevDiv.Traversal.targets" />
+</Project>
diff --git a/src/debug/ee/dactable.cpp b/src/debug/ee/dactable.cpp
new file mode 100644
index 0000000000..c37bbed744
--- /dev/null
+++ b/src/debug/ee/dactable.cpp
@@ -0,0 +1,87 @@
+// 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.
+//*****************************************************************************
+// File: dacglobals.cpp
+//
+
+//
+// The DAC global pointer table
+//
+//*****************************************************************************
+
+#include "stdafx.h"
+#include <daccess.h>
+#include "../../vm/virtualcallstub.h"
+#include "../../vm/win32threadpool.h"
+#include "../../vm/hillclimbing.h"
+#include "../../vm/codeman.h"
+#include "../../vm/eedbginterfaceimpl.h"
+#include "../../vm/common.h"
+#include "../../vm/gcenv.h"
+#include "../../vm/ecall.h"
+#include "../../vm/rcwwalker.h"
+#include "../../gc/gc.h"
+#include "../../gc/gcscan.h"
+
+#undef SERVER_GC
+namespace WKS {
+#include "../../gc/gcimpl.h"
+#include "../../gc/gcpriv.h"
+}
+
+#ifdef DEBUGGING_SUPPORTED
+
+extern PTR_ECHash gFCallMethods;
+extern TADDR gLowestFCall;
+extern TADDR gHighestFCall;
+extern PCODE g_FCDynamicallyAssignedImplementations;
+extern DWORD gThreadTLSIndex;
+extern DWORD gAppDomainTLSIndex;
+
+#ifdef FEATURE_APPX
+#if defined(FEATURE_CORECLR)
+extern BOOL g_fAppX;
+#else
+extern PTR_AppXRTInfo g_pAppXRTInfo;
+#endif
+#endif // FEATURE_APPX
+
+DacGlobals g_dacTable;
+
+// DAC global pointer table initialization
+void DacGlobals::Initialize()
+{
+ TADDR baseAddress = PTR_TO_TADDR(PAL_GetSymbolModuleBase((void *)DacGlobals::Initialize));
+ g_dacTable.InitializeEntries(baseAddress);
+#ifdef FEATURE_SVR_GC
+ g_dacTable.InitializeSVREntries(baseAddress);
+#endif
+}
+
+// Initializes the non-SVR table entries
+void DacGlobals::InitializeEntries(TADDR baseAddress)
+{
+#define DEFINE_DACVAR(id_type, size, id, var) id = PTR_TO_TADDR(&var) - baseAddress;
+#define DEFINE_DACVAR_SVR(id_type, size, id, var)
+#define DEFINE_DACVAR_NO_DUMP(id_type, size, id, var) id = PTR_TO_TADDR(&var) - baseAddress;
+#include "dacvars.h"
+
+#define VPTR_CLASS(name) \
+ { \
+ void *pBuf = _alloca(sizeof(name)); \
+ name *dummy = new (pBuf) name(0); \
+ name##__vtAddr = PTR_TO_TADDR(*((PVOID*)dummy)) - baseAddress; \
+ }
+#define VPTR_MULTI_CLASS(name, keyBase) \
+ { \
+ void *pBuf = _alloca(sizeof(name)); \
+ name *dummy = new (pBuf) name(0); \
+ name##__##keyBase##__mvtAddr = PTR_TO_TADDR(*((PVOID*)dummy)) - baseAddress; \
+ }
+#include <vptr_list.h>
+#undef VPTR_CLASS
+#undef VPTR_MULTI_CLASS
+}
+
+#endif // DEBUGGER_SUPPORTED
diff --git a/src/debug/ee/datatest.h b/src/debug/ee/datatest.h
new file mode 100644
index 0000000000..7715ba5621
--- /dev/null
+++ b/src/debug/ee/datatest.h
@@ -0,0 +1,58 @@
+// 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.
+//*****************************************************************************
+// File: DataTest.h
+//
+
+//
+// Implement a self-test for the correct detection of when the target holds a
+// lock we encounter in the DAC.
+//
+//*****************************************************************************
+
+#ifndef DATA_TEST_H
+#define DATA_TEST_H
+
+// This class is used to test our ability to detect from the RS when the target has taken a lock.
+// When the DAC executes a code path that takes a lock, we need to know if the target is holding it.
+// If it is, then we assume that the locked data is in an inconsistent state. In that case, we don't
+// want to report the data; we just want to throw an exception.
+// This functionality in this class lets us take a lock on the LS and then signal the RS to try to
+// detect whether the lock is held. The main function in this class is TestDataSafety. It deterministically
+// signals the RS at key points to execute a code path that takes a lock and also passes a flag to indicate
+// whether the LS actually holds the lock. With this information, we can ascertain that our lock detection
+// code is working correctly. Without this special test function, it would be nearly impossible to test this
+// in any kind of deterministic way.
+//
+// The test will run in either debug or retail builds, as long as the environment variable TestDataConsistency
+// is turned on. It runs once in code:Debugger::Startup. The RS part of the test is in the cases
+// DB_IPCE_TEST_CRST and DB_IPCE_TEST_RWLOCK in code:CordbProcess::RawDispatchEvent.
+class DataTest
+{
+public:
+ // constructor
+ DataTest():
+ m_crst1(CrstDataTest1),
+ m_crst2(CrstDataTest2),
+ m_rwLock(COOPERATIVE_OR_PREEMPTIVE, LOCK_TYPE_DEFAULT) {};
+
+ // Takes a series of locks in various ways and signals the RS to test the locks at interesting
+ // points to ensure we reliably detect when the LS holds a lock.
+ void TestDataSafety();
+private:
+ // Send an event to the RS to signal that it should test to determine if a crst is held.
+ // This is for testing purposes only.
+ void SendDbgCrstEvent(Crst * pCrst, bool okToTake);
+
+ // Send an event to the RS to signal that it should test to determine if a SimpleRWLock is held.
+ // This is for testing purposes only.
+ void SendDbgRWLockEvent(SimpleRWLock * pRWLock, bool okToTake);
+
+private:
+ // The locks must be data members (rather than locals in TestDataSafety) so we can ensure that
+ // they are target instances.
+ Crst m_crst1, m_crst2; // crsts to be taken for testing
+ SimpleRWLock m_rwLock; // SimpleRWLock to be taken for testing
+};
+#endif // DATA_TEST_H
diff --git a/src/debug/ee/debugger.cpp b/src/debug/ee/debugger.cpp
new file mode 100644
index 0000000000..a06811c817
--- /dev/null
+++ b/src/debug/ee/debugger.cpp
@@ -0,0 +1,17073 @@
+// 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.
+//*****************************************************************************
+// File: debugger.cpp
+//
+
+//
+// Debugger runtime controller routines.
+//
+//*****************************************************************************
+
+#include "stdafx.h"
+#include "debugdebugger.h"
+#include "ipcmanagerinterface.h"
+#include "../inc/common.h"
+#include "perflog.h"
+#include "eeconfig.h" // This is here even for retail & free builds...
+#include "../../dlls/mscorrc/resource.h"
+
+#ifdef FEATURE_REMOTING
+#include "remoting.h"
+#endif
+
+#include "context.h"
+#include "vars.hpp"
+#include <limits.h>
+#include "ilformatter.h"
+#include "typeparse.h"
+#include "debuginfostore.h"
+#include "generics.h"
+#include "../../vm/security.h"
+#include "../../vm/methoditer.h"
+#include "../../vm/encee.h"
+#include "../../vm/dwreport.h"
+#include "../../vm/eepolicy.h"
+#include "../../vm/excep.h"
+#if defined(FEATURE_DBGIPC_TRANSPORT_VM)
+#include "dbgtransportsession.h"
+#endif // FEATURE_DBGIPC_TRANSPORT_VM
+
+#ifdef TEST_DATA_CONSISTENCY
+#include "datatest.h"
+#endif // TEST_DATA_CONSISTENCY
+
+#if defined(FEATURE_CORECLR)
+#include "dbgenginemetrics.h"
+#endif // FEATURE_CORECLR
+
+#include "../../vm/rejit.h"
+
+#include "threadsuspend.h"
+
+class CCLRSecurityAttributeManager;
+extern CCLRSecurityAttributeManager s_CLRSecurityAttributeManager;
+
+
+#ifdef DEBUGGING_SUPPORTED
+
+#ifdef _DEBUG
+// Reg key. We can set this and then any debugger-lazy-init code will assert.
+// This helps track down places where we're caching in debugger stuff in a
+// non-debugger scenario.
+bool g_DbgShouldntUseDebugger = false;
+#endif
+
+
+/* ------------------------------------------------------------------------ *
+ * Global variables
+ * ------------------------------------------------------------------------ */
+
+GPTR_IMPL(Debugger, g_pDebugger);
+GPTR_IMPL(EEDebugInterface, g_pEEInterface);
+SVAL_IMPL_INIT(BOOL, Debugger, s_fCanChangeNgenFlags, TRUE);
+
+bool g_EnableSIS = false;
+
+
+#ifndef DACCESS_COMPILE
+
+DebuggerRCThread *g_pRCThread = NULL;
+
+#ifndef _PREFAST_
+// Do some compile time checking on the events in DbgIpcEventTypes.h
+// No one ever calls this. But the compiler should still compile it,
+// and that should be sufficient.
+void DoCompileTimeCheckOnDbgIpcEventTypes()
+{
+ _ASSERTE(!"Don't call this function. It just does compile time checking\n");
+
+ // We use the C_ASSERT macro here to get a compile-time assert.
+
+ // Make sure we don't have any duplicate numbers.
+ // The switch statements in the main loops won't always catch this
+ // since we may not switch on all events.
+
+ // store Type-0 in const local vars, so we can use them for bounds checking
+ // Create local vars with the val from Type1 & Type2. If there are any
+ // collisions, then the variables' names will collide at compile time.
+ #define IPC_EVENT_TYPE0(type, val) const int e_##type = val;
+ #define IPC_EVENT_TYPE1(type, val) int T_##val; T_##val = 0;
+ #define IPC_EVENT_TYPE2(type, val) int T_##val; T_##val = 0;
+ #include "dbgipceventtypes.h"
+ #undef IPC_EVENT_TYPE2
+ #undef IPC_EVENT_TYPE1
+ #undef IPC_EVENT_TYPE0
+
+ // Ensure that all identifiers are unique and are matched with
+ // integer values.
+ #define IPC_EVENT_TYPE0(type, val) int T2_##type; T2_##type = val;
+ #define IPC_EVENT_TYPE1(type, val) int T2_##type; T2_##type = val;
+ #define IPC_EVENT_TYPE2(type, val) int T2_##type; T2_##type = val;
+ #include "dbgipceventtypes.h"
+ #undef IPC_EVENT_TYPE2
+ #undef IPC_EVENT_TYPE1
+ #undef IPC_EVENT_TYPE0
+
+ // Make sure all values are subset of the bits specified by DB_IPCE_TYPE_MASK
+ #define IPC_EVENT_TYPE0(type, val)
+ #define IPC_EVENT_TYPE1(type, val) C_ASSERT((val & e_DB_IPCE_TYPE_MASK) == val);
+ #define IPC_EVENT_TYPE2(type, val) C_ASSERT((val & e_DB_IPCE_TYPE_MASK) == val);
+ #include "dbgipceventtypes.h"
+ #undef IPC_EVENT_TYPE2
+ #undef IPC_EVENT_TYPE1
+ #undef IPC_EVENT_TYPE0
+
+ // Make sure that no value is DB_IPCE_INVALID_EVENT
+ #define IPC_EVENT_TYPE0(type, val)
+ #define IPC_EVENT_TYPE1(type, val) C_ASSERT(val != e_DB_IPCE_INVALID_EVENT);
+ #define IPC_EVENT_TYPE2(type, val) C_ASSERT(val != e_DB_IPCE_INVALID_EVENT);
+ #include "dbgipceventtypes.h"
+ #undef IPC_EVENT_TYPE2
+ #undef IPC_EVENT_TYPE1
+ #undef IPC_EVENT_TYPE0
+
+ // Make sure first-last values are well structured.
+ static_assert_no_msg(e_DB_IPCE_RUNTIME_FIRST < e_DB_IPCE_RUNTIME_LAST);
+ static_assert_no_msg(e_DB_IPCE_DEBUGGER_FIRST < e_DB_IPCE_DEBUGGER_LAST);
+
+ // Make sure that event ranges don't overlap.
+ // This check is simplified because L->R events come before R<-L
+ static_assert_no_msg(e_DB_IPCE_RUNTIME_LAST < e_DB_IPCE_DEBUGGER_FIRST);
+
+
+ // Make sure values are in the proper ranges
+ // Type1 should be in the Runtime range, Type2 in the Debugger range.
+ #define IPC_EVENT_TYPE0(type, val)
+ #define IPC_EVENT_TYPE1(type, val) C_ASSERT((e_DB_IPCE_RUNTIME_FIRST <= val) && (val < e_DB_IPCE_RUNTIME_LAST));
+ #define IPC_EVENT_TYPE2(type, val) C_ASSERT((e_DB_IPCE_DEBUGGER_FIRST <= val) && (val < e_DB_IPCE_DEBUGGER_LAST));
+ #include "dbgipceventtypes.h"
+ #undef IPC_EVENT_TYPE2
+ #undef IPC_EVENT_TYPE1
+ #undef IPC_EVENT_TYPE0
+
+ // Make sure that events are in increasing order
+ // It's ok if the events skip numbers.
+ // This is a more specific check than the range check above.
+
+ /* Expands to look like this:
+ const bool f = (
+ first <=
+ 10) && (10 <
+ 11) && (11 <
+ 12) && (12 <
+ last)
+ static_assert_no_msg(f);
+ */
+
+ const bool f1 = (
+ (e_DB_IPCE_RUNTIME_FIRST <=
+ #define IPC_EVENT_TYPE0(type, val)
+ #define IPC_EVENT_TYPE1(type, val) val) && (val <
+ #define IPC_EVENT_TYPE2(type, val)
+ #include "dbgipceventtypes.h"
+ #undef IPC_EVENT_TYPE2
+ #undef IPC_EVENT_TYPE1
+ #undef IPC_EVENT_TYPE0
+ e_DB_IPCE_RUNTIME_LAST)
+ );
+ static_assert_no_msg(f1);
+
+ const bool f2 = (
+ (e_DB_IPCE_DEBUGGER_FIRST <=
+ #define IPC_EVENT_TYPE0(type, val)
+ #define IPC_EVENT_TYPE1(type, val)
+ #define IPC_EVENT_TYPE2(type, val) val) && (val <
+ #include "dbgipceventtypes.h"
+ #undef IPC_EVENT_TYPE2
+ #undef IPC_EVENT_TYPE1
+ #undef IPC_EVENT_TYPE0
+ e_DB_IPCE_DEBUGGER_LAST)
+ );
+ static_assert_no_msg(f2);
+
+} // end checks
+#endif // _PREFAST_
+
+//-----------------------------------------------------------------------------
+// Ctor for AtSafePlaceHolder
+AtSafePlaceHolder::AtSafePlaceHolder(Thread * pThread)
+{
+ _ASSERTE(pThread != NULL);
+ if (!g_pDebugger->IsThreadAtSafePlace(pThread))
+ {
+ m_pThreadAtUnsafePlace = pThread;
+ g_pDebugger->IncThreadsAtUnsafePlaces();
+ }
+ else
+ {
+ m_pThreadAtUnsafePlace = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Dtor for AtSafePlaceHolder
+AtSafePlaceHolder::~AtSafePlaceHolder()
+{
+ Clear();
+}
+
+//-----------------------------------------------------------------------------
+// Returns true if this adjusted the unsafe counter
+bool AtSafePlaceHolder::IsAtUnsafePlace()
+{
+ return m_pThreadAtUnsafePlace != NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Clear the holder.
+// Notes:
+// This can be called multiple times.
+// Calling this makes the dtor a nop.
+void AtSafePlaceHolder::Clear()
+{
+ if (m_pThreadAtUnsafePlace != NULL)
+ {
+ // The thread is still at an unsafe place.
+ // We're clearing the flag to avoid the Dtor() calling DecThreads again.
+ m_pThreadAtUnsafePlace = NULL;
+ g_pDebugger->DecThreadsAtUnsafePlaces();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Is the guard page missing on this thread?
+// Should only be called for managed threads handling a managed exception.
+// If we're handling a stack overflow (ie, missing guard page), then another
+// stack overflow will instantly terminate the process. In that case, do stack
+// intensive stuff on the helper thread (which has lots of stack space). Only
+// problem is that if the faulting thread has a lock, the helper thread may
+// get stuck.
+// Serves as a hint whether we want to do a favor on the
+// faulting thread (preferred) or the helper thread (if low stack).
+// See whidbey issue 127436.
+//-----------------------------------------------------------------------------
+bool IsGuardPageGone()
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ Thread * pThread = g_pEEInterface->GetThread();
+
+ // We're not going to be called for a unmanaged exception.
+ // Should always have a managed thread, but just in case something really
+ // crazy happens, it's not worth an AV. (since this is just being used as a hint)
+ if (pThread == NULL)
+ {
+ return false;
+ }
+
+ // Don't use pThread->IsGuardPageGone(), it's not accurate here.
+ bool fGuardPageGone = (pThread->DetermineIfGuardPagePresent() == FALSE);
+ LOG((LF_CORDB, LL_INFO1000000, "D::IsGuardPageGone=%d\n", fGuardPageGone));
+ return fGuardPageGone;
+}
+
+
+// This is called from AppDomainEnumerationIPCBlock::Lock and Unlock
+void BeginThreadAffinityHelper()
+{
+ WRAPPER_NO_CONTRACT;
+
+ Thread::BeginThreadAffinity();
+}
+void EndThreadAffinityHelper()
+{
+ WRAPPER_NO_CONTRACT;
+ Thread::EndThreadAffinity();
+}
+
+//-----------------------------------------------------------------------------
+// LSPTR_XYZ is a type-safe wrapper around an opaque reference type XYZ in the left-side.
+// But TypeHandles are value-types that can't be directly converted into a pointer.
+// Thus converting between LSPTR_XYZ and TypeHandles requires some extra glue.
+// The following conversions are valid:
+// LSPTR_XYZ <--> XYZ* (via Set/UnWrap methods)
+// TypeHandle <--> void* (via AsPtr() and FromPtr()).
+// so we can't directly convert between LSPTR_TYPEHANDLE and TypeHandle.
+// We must do: TypeHandle <--> void* <--> XYZ <--> LSPTR_XYZ
+// So LSPTR_TYPEHANDLE is actually for TypeHandleDummyPtr, and then we unsafe cast
+// that to a void* to use w/ AsPtr() and FromPtr() to convert to TypeHandles.
+// @todo- it would be nice to have these happen automatically w/ Set & UnWrap.
+//-----------------------------------------------------------------------------
+
+// helper class to do conversion above.
+class TypeHandleDummyPtr
+{
+private:
+ TypeHandleDummyPtr() { }; // should never actually create this.
+ void * data;
+};
+
+// Convert: VMPTR_TYPEHANDLE --> TypeHandle
+TypeHandle GetTypeHandle(VMPTR_TypeHandle ptr)
+{
+ return TypeHandle::FromPtr(ptr.GetRawPtr());
+}
+
+// Convert: TypeHandle --> LSPTR_TYPEHANDLE
+VMPTR_TypeHandle WrapTypeHandle(TypeHandle th)
+{
+ return VMPTR_TypeHandle::MakePtr(reinterpret_cast<TypeHandle *> (th.AsPtr()));
+}
+
+extern void WaitForEndOfShutdown();
+
+
+// Get the Canary structure which can sniff if the helper thread is safe to run.
+HelperCanary * Debugger::GetCanary()
+{
+ return g_pRCThread->GetCanary();
+}
+
+// IMPORTANT!!!!!
+// Do not call Lock and Unlock directly. Because you might not unlock
+// if exception takes place. Use DebuggerLockHolder instead!!!
+// Only AcquireDebuggerLock can call directly.
+//
+void Debugger::DoNotCallDirectlyPrivateLock(void)
+{
+ WRAPPER_NO_CONTRACT;
+
+ LOG((LF_CORDB,LL_INFO10000, "D::Lock aquire attempt by 0x%x\n",
+ GetCurrentThreadId()));
+
+ // Debugger lock is larger than both Controller & debugger-data locks.
+ // So we should never try to take the D lock if we hold either of the others.
+
+
+ // Lock becomes no-op in late shutdown.
+ if (g_fProcessDetach)
+ {
+ return;
+ }
+
+
+ //
+ // If the debugger has been disabled by the runtime, this means that it should block
+ // all threads that are trying to travel thru the debugger. We do this by blocking
+ // threads as they try and take the debugger lock.
+ //
+ if (m_fDisabled)
+ {
+ __SwitchToThread(INFINITE, CALLER_LIMITS_SPINNING);
+ _ASSERTE (!"Can not reach here");
+ }
+
+ m_mutex.Enter();
+
+ //
+ // If we were blocked on the lock and the debugging facilities got disabled
+ // while we were waiting, release the lock and park this thread.
+ //
+ if (m_fDisabled)
+ {
+ m_mutex.Leave();
+ __SwitchToThread(INFINITE, CALLER_LIMITS_SPINNING);
+ _ASSERTE (!"Can not reach here");
+ }
+
+ //
+ // Now check if we are in a shutdown case...
+ //
+ Thread * pThread;
+ bool fIsCooperative;
+ BEGIN_GETTHREAD_ALLOWED;
+ pThread = g_pEEInterface->GetThread();
+ fIsCooperative = (pThread != NULL) && (pThread->PreemptiveGCDisabled());
+ END_GETTHREAD_ALLOWED;
+ if (m_fShutdownMode && !fIsCooperative)
+ {
+ // The big fear is that some other random thread will take the debugger-lock and then block on something else,
+ // and thus prevent the helper/finalizer threads from taking the debugger-lock in shutdown scenarios.
+ //
+ // If we're in shutdown mode, then some locks (like the Thread-Store-Lock) get special semantics.
+ // Only helper / finalizer / shutdown threads can actually take these locks.
+ // Other threads that try to take them will just get parked and block forever.
+ // This is ok b/c the only threads that need to run at this point are the Finalizer and Helper threads.
+ //
+ // We need to be in preemptive to block for shutdown, so we don't do this block in Coop mode.
+ // Fortunately, it's safe to take this lock in coop mode because we know the thread can't block
+ // on anything interesting because we're in a GC-forbid region (see crst flags).
+ m_mutex.ReleaseAndBlockForShutdownIfNotSpecialThread();
+ }
+
+
+
+#ifdef _DEBUG
+ _ASSERTE(m_mutexCount >= 0);
+
+ if (m_mutexCount>0)
+ {
+ if (pThread)
+ {
+ // mamaged thread
+ _ASSERTE(m_mutexOwner == GetThreadIdHelper(pThread));
+ }
+ else
+ {
+ // unmanaged thread
+ _ASSERTE(m_mutexOwner == GetCurrentThreadId());
+ }
+ }
+
+ m_mutexCount++;
+ if (pThread)
+ {
+ m_mutexOwner = GetThreadIdHelper(pThread);
+ }
+ else
+ {
+ // unmanaged thread
+ m_mutexOwner = GetCurrentThreadId();
+ }
+
+ if (m_mutexCount == 1)
+ {
+ LOG((LF_CORDB,LL_INFO10000, "D::Lock aquired by 0x%x\n", m_mutexOwner));
+ }
+#endif
+
+}
+
+// See comment above.
+// Only ReleaseDebuggerLock can call directly.
+void Debugger::DoNotCallDirectlyPrivateUnlock(void)
+{
+ WRAPPER_NO_CONTRACT;
+
+ // Controller lock is "smaller" than debugger lock.
+
+
+ if (!g_fProcessDetach)
+ {
+#ifdef _DEBUG
+ if (m_mutexCount == 1)
+ LOG((LF_CORDB,LL_INFO10000, "D::Unlock released by 0x%x\n",
+ m_mutexOwner));
+
+ if(0 == --m_mutexCount)
+ m_mutexOwner = 0;
+
+ _ASSERTE( m_mutexCount >= 0);
+#endif
+ m_mutex.Leave();
+
+ //
+ // If the debugger has been disabled by the runtime, this means that it should block
+ // all threads that are trying to travel thru the debugger. We do this by blocking
+ // threads also as they leave the debugger lock.
+ //
+ if (m_fDisabled)
+ {
+ __SwitchToThread(INFINITE, CALLER_LIMITS_SPINNING);
+ _ASSERTE (!"Can not reach here");
+ }
+
+ }
+}
+
+#ifdef TEST_DATA_CONSISTENCY
+
+// ---------------------------------------------------------------------------------
+// Implementations for DataTest member functions
+// ---------------------------------------------------------------------------------
+
+// Send an event to the RS to signal that it should test to determine if a crst is held.
+// This is for testing purposes only.
+// Arguments:
+// input: pCrst - the lock to test
+// fOkToTake - true iff the LS does NOT currently hold the lock
+// output: none
+// Notes: The RS will throw if the lock is held. The code that tests the lock will catch the
+// exception and assert if throwing was not the correct thing to do (determined via the
+// boolean). See the case for DB_IPCE_TEST_CRST in code:CordbProcess::RawDispatchEvent.
+//
+void DataTest::SendDbgCrstEvent(Crst * pCrst, bool fOkToTake)
+{
+ DebuggerIPCEvent * pLockEvent = g_pDebugger->m_pRCThread->GetIPCEventSendBuffer();
+
+ g_pDebugger->InitIPCEvent(pLockEvent, DB_IPCE_TEST_CRST);
+
+ pLockEvent->TestCrstData.vmCrst.SetRawPtr(pCrst);
+ pLockEvent->TestCrstData.fOkToTake = fOkToTake;
+
+ g_pDebugger->SendRawEvent(pLockEvent);
+
+} // DataTest::SendDbgCrstEvent
+
+// Send an event to the RS to signal that it should test to determine if a SimpleRWLock is held.
+// This is for testing purposes only.
+// Arguments:
+// input: pRWLock - the lock to test
+// fOkToTake - true iff the LS does NOT currently hold the lock
+// output: none
+// Note: The RS will throw if the lock is held. The code that tests the lock will catch the
+// exception and assert if throwing was not the correct thing to do (determined via the
+// boolean). See the case for DB_IPCE_TEST_RWLOCK in code:CordbProcess::RawDispatchEvent.
+//
+void DataTest::SendDbgRWLockEvent(SimpleRWLock * pRWLock, bool okToTake)
+{
+ DebuggerIPCEvent * pLockEvent = g_pDebugger->m_pRCThread->GetIPCEventSendBuffer();
+
+ g_pDebugger->InitIPCEvent(pLockEvent, DB_IPCE_TEST_RWLOCK);
+
+ pLockEvent->TestRWLockData.vmRWLock.SetRawPtr(pRWLock);
+ pLockEvent->TestRWLockData.fOkToTake = okToTake;
+
+ g_pDebugger->SendRawEvent(pLockEvent);
+} // DataTest::SendDbgRWLockEvent
+
+// Takes a series of locks in various ways and signals the RS to test the locks at interesting
+// points to ensure we reliably detect when the LS holds a lock. If in the course of inspection, the
+// DAC needs to execute a code path where the LS holds a lock, we assume that the locked data is in
+// an inconsistent state. In this situation, we don't want to report information about this data, so
+// we throw an exception.
+// This is for testing purposes only.
+//
+// Arguments: none
+// Return Value: none
+// Notes: See code:CordbProcess::RawDispatchEvent for the RS part of this test and code:Debugger::Startup
+// for the LS invocation of the test.
+// The environment variable TestDataConsistency must be set to 1 to make this test run.
+void DataTest::TestDataSafety()
+{
+ const bool okToTake = true;
+
+ SendDbgCrstEvent(&m_crst1, okToTake);
+ {
+ CrstHolder ch1(&m_crst1);
+ SendDbgCrstEvent(&m_crst1, !okToTake);
+ {
+ CrstHolder ch2(&m_crst2);
+ SendDbgCrstEvent(&m_crst2, !okToTake);
+ SendDbgCrstEvent(&m_crst1, !okToTake);
+ }
+ SendDbgCrstEvent(&m_crst2, okToTake);
+ SendDbgCrstEvent(&m_crst1, !okToTake);
+ }
+ SendDbgCrstEvent(&m_crst1, okToTake);
+
+ {
+ SendDbgRWLockEvent(&m_rwLock, okToTake);
+ SimpleReadLockHolder readLock(&m_rwLock);
+ SendDbgRWLockEvent(&m_rwLock, okToTake);
+ }
+ SendDbgRWLockEvent(&m_rwLock, okToTake);
+ {
+ SimpleWriteLockHolder readLock(&m_rwLock);
+ SendDbgRWLockEvent(&m_rwLock, !okToTake);
+ }
+
+} // DataTest::TestDataSafety
+
+#endif // TEST_DATA_CONSISTENCY
+
+#if _DEBUG
+static DebugEventCounter g_debugEventCounter;
+static int g_iDbgRuntimeCounter[DBG_RUNTIME_MAX];
+static int g_iDbgDebuggerCounter[DBG_DEBUGGER_MAX];
+
+void DoAssertOnType(DebuggerIPCEventType event, int count)
+{
+ WRAPPER_NO_CONTRACT;
+
+ // check to see if we need fire the assertion or not.
+ if ((event & 0x0300) == 0x0100)
+ {
+ // use the Runtime array
+ if (g_iDbgRuntimeCounter[event & 0x00ff] == count)
+ {
+ char tmpStr[256];
+ sprintf(tmpStr, "%s == %d, break now!",
+ IPCENames::GetName(event), count);
+
+ // fire the assertion
+ DbgAssertDialog(__FILE__, __LINE__, tmpStr);
+ }
+ }
+ // check to see if we need fire the assertion or not.
+ else if ((event & 0x0300) == 0x0200)
+ {
+ // use the Runtime array
+ if (g_iDbgDebuggerCounter[event & 0x00ff] == count)
+ {
+ char tmpStr[256];
+ sprintf(tmpStr, "%s == %d, break now!",
+ IPCENames::GetName(event), count);
+
+ // fire the assertion
+ DbgAssertDialog(__FILE__, __LINE__, tmpStr);
+ }
+ }
+
+}
+void DbgLogHelper(DebuggerIPCEventType event)
+{
+ WRAPPER_NO_CONTRACT;
+
+ switch (event)
+ {
+// we don't need to handle event type 0
+#define IPC_EVENT_TYPE0(type, val)
+#define IPC_EVENT_TYPE1(type, val) case type: {\
+ g_debugEventCounter.m_iDebugCount_##type++; \
+ DoAssertOnType(type, g_debugEventCounter.m_iDebugCount_##type); \
+ break; \
+ }
+#define IPC_EVENT_TYPE2(type, val) case type: { \
+ g_debugEventCounter.m_iDebugCount_##type++; \
+ DoAssertOnType(type, g_debugEventCounter.m_iDebugCount_##type); \
+ break; \
+ }
+#include "dbgipceventtypes.h"
+#undef IPC_EVENT_TYPE2
+#undef IPC_EVENT_TYPE1
+#undef IPC_EVENT_TYPE0
+ default:
+ break;
+ }
+}
+#endif // _DEBUG
+
+
+
+
+
+
+
+
+
+/* ------------------------------------------------------------------------ *
+ * DLL export routine
+ * ------------------------------------------------------------------------ */
+
+Debugger *CreateDebugger(void)
+{
+ Debugger *pDebugger = NULL;
+
+ EX_TRY
+ {
+ pDebugger = new (nothrow) Debugger();
+ }
+ EX_CATCH
+ {
+ if (pDebugger != NULL)
+ {
+ delete pDebugger;
+ pDebugger = NULL;
+ }
+ }
+ EX_END_CATCH(RethrowTerminalExceptions);
+
+ return pDebugger;
+}
+
+//
+// CorDBGetInterface is exported to the Runtime so that it can call
+// the Runtime Controller.
+//
+extern "C"{
+HRESULT __cdecl CorDBGetInterface(DebugInterface** rcInterface)
+{
+ CONTRACT(HRESULT)
+ {
+ NOTHROW; // use HRESULTS instead
+ GC_NOTRIGGER;
+ POSTCONDITION(FAILED(RETVAL) || (rcInterface == NULL) || (*rcInterface != NULL));
+ }
+ CONTRACT_END;
+
+ HRESULT hr = S_OK;
+
+ if (rcInterface != NULL)
+ {
+ if (g_pDebugger == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO10,
+ "CorDBGetInterface: initializing debugger.\n"));
+
+ g_pDebugger = CreateDebugger();
+ TRACE_ALLOC(g_pDebugger);
+
+ if (g_pDebugger == NULL)
+ hr = E_OUTOFMEMORY;
+ }
+
+ *rcInterface = g_pDebugger;
+ }
+
+ RETURN hr;
+}
+}
+
+//-----------------------------------------------------------------------------
+// Send a pre-init IPC event and block.
+// We assume the IPC event has already been initialized. There's nothing special
+// here; it just used the standard formula for sending an IPC event to the RS.
+// This should match up w/ the description in SENDIPCEVENT_BEGIN.
+//-----------------------------------------------------------------------------
+void Debugger::SendSimpleIPCEventAndBlock()
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT;
+ MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT;
+ }
+ CONTRACTL_END;
+
+ // BEGIN will acquire the lock (END will release it). While blocking, the
+ // debugger may have detached though, so we need to check for that.
+ _ASSERTE(ThreadHoldsLock());
+
+ if (CORDebuggerAttached())
+ {
+ m_pRCThread->SendIPCEvent();
+
+ // Stop all Runtime threads
+ this->TrapAllRuntimeThreads();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Get context from a thread in managed code.
+// See header for exact semantics.
+//-----------------------------------------------------------------------------
+CONTEXT * GetManagedStoppedCtx(Thread * pThread)
+{
+ WRAPPER_NO_CONTRACT;
+
+ _ASSERTE(pThread != NULL);
+
+ // We may be stopped or live.
+
+ // If we're stopped at an interop-hijack, we'll have a filter context,
+ // but we'd better not be redirected for a managed-suspension hijack.
+ if (pThread->GetInteropDebuggingHijacked())
+ {
+ _ASSERTE(!ISREDIRECTEDTHREAD(pThread));
+ return NULL;
+ }
+
+ // Check if we have a filter ctx. This should only be for managed-code.
+ // We're stopped at some exception (likely an int3 or single-step).
+ // Can't have both filter ctx + redirected ctx.
+ CONTEXT *pCtx = g_pEEInterface->GetThreadFilterContext(pThread);
+ if (pCtx != NULL)
+ {
+ _ASSERTE(!ISREDIRECTEDTHREAD(pThread));
+ return pCtx;
+ }
+
+ if (ISREDIRECTEDTHREAD(pThread))
+ {
+ pCtx = GETREDIRECTEDCONTEXT(pThread);
+ _ASSERTE(pCtx != NULL);
+ return pCtx;
+ }
+
+ // Not stopped somewhere in managed code.
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// See header for exact semantics.
+// Never NULL. (Caller guarantees this is active.)
+//-----------------------------------------------------------------------------
+CONTEXT * GetManagedLiveCtx(Thread * pThread)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ _ASSERTE(pThread != NULL);
+
+ // We should never be on the helper thread, we should only be inspecting our own thread.
+ // We're in some Controller's Filter after hitting an exception.
+ // We're not stopped.
+ //_ASSERTE(!g_pDebugger->IsStopped()); <-- @todo - this fires, need to find out why.
+ _ASSERTE(GetThread() == pThread);
+
+ CONTEXT *pCtx = g_pEEInterface->GetThreadFilterContext(pThread);
+
+ // Note that we may be in a M2U hijack. So we can't assert !pThread->GetInteropDebuggingHijacked()
+ _ASSERTE(!ISREDIRECTEDTHREAD(pThread));
+ _ASSERTE(pCtx);
+
+ return pCtx;
+}
+
+// Attempt to validate a GC handle.
+HRESULT ValidateGCHandle(OBJECTHANDLE oh)
+{
+ // The only real way to do this is to Enumerate all GC handles in the handle table.
+ // That's too expensive. So we'll use a similar workaround that we use in ValidateObject.
+ // This will err on the side off returning True for invalid handles.
+
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+ // Use AVInRuntimeImplOkHolder.
+ AVInRuntimeImplOkayHolder AVOkay;
+
+ // This may throw if the Object Handle is invalid.
+ Object * objPtr = *((Object**) oh);
+
+ // NULL is certinally valid...
+ if (objPtr != NULL)
+ {
+ if (!objPtr->ValidateObjectWithPossibleAV())
+ {
+ LOG((LF_CORDB, LL_INFO10000, "GAV: object methodtable-class invariant doesn't hold.\n"));
+ hr = E_INVALIDARG;
+ goto LExit;
+ }
+ }
+
+ LExit: ;
+ }
+ EX_CATCH
+ {
+ LOG((LF_CORDB, LL_INFO10000, "GAV: exception indicated ref is bad.\n"));
+ hr = E_INVALIDARG;
+ }
+ EX_END_CATCH(SwallowAllExceptions);
+
+ return hr;
+}
+
+
+// Validate an object. Returns E_INVALIDARG or S_OK.
+HRESULT ValidateObject(Object *objPtr)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+ // Use AVInRuntimeImplOkHolder.
+ AVInRuntimeImplOkayHolder AVOkay;
+
+ // NULL is certinally valid...
+ if (objPtr != NULL)
+ {
+ if (!objPtr->ValidateObjectWithPossibleAV())
+ {
+ LOG((LF_CORDB, LL_INFO10000, "GAV: object methodtable-class invariant doesn't hold.\n"));
+ hr = E_INVALIDARG;
+ goto LExit;
+ }
+ }
+
+ LExit: ;
+ }
+ EX_CATCH
+ {
+ LOG((LF_CORDB, LL_INFO10000, "GAV: exception indicated ref is bad.\n"));
+ hr = E_INVALIDARG;
+ }
+ EX_END_CATCH(SwallowAllExceptions);
+
+ return hr;
+} // ValidateObject
+
+
+#ifdef FEATURE_DBGIPC_TRANSPORT_VM
+void
+ShutdownTransport()
+{
+ if (g_pDbgTransport != NULL)
+ {
+ g_pDbgTransport->Shutdown();
+ g_pDbgTransport = NULL;
+ }
+}
+
+void
+AbortTransport()
+{
+ if (g_pDbgTransport != NULL)
+ {
+ g_pDbgTransport->AbortConnection();
+ }
+}
+#endif // FEATURE_DBGIPC_TRANSPORT_VM
+
+
+/* ------------------------------------------------------------------------ *
+ * Debugger routines
+ * ------------------------------------------------------------------------ */
+
+//
+// a Debugger object represents the global state of the debugger program.
+//
+
+//
+// Constructor & Destructor
+//
+
+/******************************************************************************
+ *
+ ******************************************************************************/
+Debugger::Debugger()
+ :
+ m_fLeftSideInitialized(FALSE),
+#ifdef _DEBUG
+ m_mutexCount(0),
+#endif //_DEBUG
+ m_pRCThread(NULL),
+ m_trappingRuntimeThreads(FALSE),
+ m_stopped(FALSE),
+ m_unrecoverableError(FALSE),
+ m_ignoreThreadDetach(FALSE),
+ m_pMethodInfos(NULL),
+ m_mutex(CrstDebuggerMutex, (CrstFlags)(CRST_UNSAFE_ANYMODE | CRST_REENTRANCY | CRST_DEBUGGER_THREAD)),
+#ifdef _DEBUG
+ m_mutexOwner(0),
+ m_tidLockedForEventSending(0),
+#endif //_DEBUG
+ m_threadsAtUnsafePlaces(0),
+ m_jitAttachInProgress(FALSE),
+ m_attachingForManagedEvent(FALSE),
+ m_launchingDebugger(FALSE),
+ m_userRequestedDebuggerLaunch(FALSE),
+ m_LoggingEnabled(TRUE),
+ m_pAppDomainCB(NULL),
+ m_dClassLoadCallbackCount(0),
+ m_pModules(NULL),
+ m_RSRequestedSync(FALSE),
+ m_sendExceptionsOutsideOfJMC(TRUE),
+ m_pIDbgThreadControl(NULL),
+ m_forceNonInterceptable(FALSE),
+ m_pLazyData(NULL),
+ m_defines(_defines)
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ WRAPPER(THROWS);
+ WRAPPER(GC_TRIGGERS);
+ CONSTRUCTOR_CHECK;
+ }
+ CONTRACTL_END;
+
+ m_fShutdownMode = false;
+ m_fDisabled = false;
+ m_rgHijackFunction = NULL;
+
+#ifdef _DEBUG
+ InitDebugEventCounting();
+#endif
+
+ m_processId = GetCurrentProcessId();
+
+ // Initialize these in ctor because we free them in dtor.
+ // And we can't set them to some safe uninited value (like NULL).
+
+
+
+ //------------------------------------------------------------------------------
+ // Metadata data structure version numbers
+ //
+ // 1 - initial state of the layouts ( .Net 4.5.2 )
+ //
+ // as data structure layouts change, add a new version number
+ // and comment the changes
+ m_mdDataStructureVersion = 1;
+
+}
+
+/******************************************************************************
+ *
+ ******************************************************************************/
+Debugger::~Debugger()
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ DESTRUCTOR_CHECK;
+ SO_INTOLERANT;
+ }
+ CONTRACTL_END;
+
+ // We explicitly leak the debugger object on shutdown. See Debugger::StopDebugger for details.
+ _ASSERTE(!"Debugger dtor should not be called.");
+}
+
+#if defined(FEATURE_HIJACK) && !defined(PLATFORM_UNIX)
+typedef void (*PFN_HIJACK_FUNCTION) (void);
+
+// Given the start address and the end address of a function, return a MemoryRange for the function.
+inline MemoryRange GetMemoryRangeForFunction(PFN_HIJACK_FUNCTION pfnStart, PFN_HIJACK_FUNCTION pfnEnd)
+{
+ PCODE pfnStartAddress = (PCODE)GetEEFuncEntryPoint(pfnStart);
+ PCODE pfnEndAddress = (PCODE)GetEEFuncEntryPoint(pfnEnd);
+ return MemoryRange(dac_cast<PTR_VOID>(pfnStartAddress), (pfnEndAddress - pfnStartAddress));
+}
+
+// static
+MemoryRange Debugger::s_hijackFunction[kMaxHijackFunctions] =
+ {GetMemoryRangeForFunction(ExceptionHijack, ExceptionHijackEnd),
+ GetMemoryRangeForFunction(RedirectedHandledJITCaseForGCThreadControl_Stub,
+ RedirectedHandledJITCaseForGCThreadControl_StubEnd),
+ GetMemoryRangeForFunction(RedirectedHandledJITCaseForDbgThreadControl_Stub,
+ RedirectedHandledJITCaseForDbgThreadControl_StubEnd),
+ GetMemoryRangeForFunction(RedirectedHandledJITCaseForUserSuspend_Stub,
+ RedirectedHandledJITCaseForUserSuspend_StubEnd),
+ GetMemoryRangeForFunction(RedirectedHandledJITCaseForYieldTask_Stub,
+ RedirectedHandledJITCaseForYieldTask_StubEnd)
+#if defined(HAVE_GCCOVER) && defined(_TARGET_AMD64_)
+ ,
+ GetMemoryRangeForFunction(RedirectedHandledJITCaseForGCStress_Stub,
+ RedirectedHandledJITCaseForGCStress_StubEnd)
+#endif // HAVE_GCCOVER && _TARGET_AMD64_
+ };
+#endif // FEATURE_HIJACK && !PLATFORM_UNIX
+
+// Save the necessary information for the debugger to recognize an IP in one of the thread redirection
+// functions.
+void Debugger::InitializeHijackFunctionAddress()
+{
+#if defined(FEATURE_HIJACK) && !defined(PLATFORM_UNIX)
+ // Advertise hijack address for the DD Hijack primitive
+ m_rgHijackFunction = Debugger::s_hijackFunction;
+#endif // FEATURE_HIJACK && !PLATFORM_UNIX
+}
+
+// For debug-only builds, we'll have a debugging feature to count
+// the number of ipc events and break on a specific number.
+// Initialize the stuff to do that.
+void Debugger::InitDebugEventCounting()
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+#ifdef _DEBUG
+ // initialize the debug event counter structure to zero
+ memset(&g_debugEventCounter, 0, sizeof(DebugEventCounter));
+ memset(&g_iDbgRuntimeCounter, 0, DBG_RUNTIME_MAX*sizeof(int));
+ memset(&g_iDbgDebuggerCounter, 0, DBG_DEBUGGER_MAX*sizeof(int));
+
+ // retrieve the possible counter for break point
+ LPWSTR wstrValue = NULL;
+ // The string value is of the following format
+ // <Event Name>=Count;<Event Name>=Count;....;
+ // The string must end with ;
+ if ((wstrValue = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DebuggerBreakPoint)) != NULL)
+ {
+ LPSTR strValue;
+ int cbReq;
+ cbReq = WszWideCharToMultiByte(CP_UTF8, 0, wstrValue,-1, 0,0, 0,0);
+
+ strValue = new (nothrow) char[cbReq+1];
+ // This is a debug only thingy, if it fails, not worth taking
+ // down the process.
+ if (strValue == NULL)
+ return;
+
+
+ // now translate the unicode to ansi string
+ WszWideCharToMultiByte(CP_UTF8, 0, wstrValue, -1, strValue, cbReq+1, 0,0);
+ char *szEnd = (char *)strchr(strValue, ';');
+ char *szStart = strValue;
+ while (szEnd != NULL)
+ {
+ // Found a key value
+ char *szNameEnd = strchr(szStart, '=');
+ int iCount;
+ DebuggerIPCEventType eventType;
+ if (szNameEnd != NULL)
+ {
+ // This is a well form key
+ *szNameEnd = '\0';
+ *szEnd = '\0';
+
+ // now szStart is the key name null terminated. Translate the counter into integer.
+ iCount = atoi(szNameEnd+1);
+ if (iCount != 0)
+ {
+ eventType = IPCENames::GetEventType(szStart);
+
+ if (eventType < DB_IPCE_DEBUGGER_FIRST)
+ {
+ // use the runtime one
+ g_iDbgRuntimeCounter[eventType & 0x00ff] = iCount;
+ }
+ else if (eventType < DB_IPCE_DEBUGGER_LAST)
+ {
+ // use the debugger one
+ g_iDbgDebuggerCounter[eventType & 0x00ff] = iCount;
+ }
+ else
+ _ASSERTE(!"Unknown Event Type");
+ }
+ }
+ szStart = szEnd + 1;
+ // try to find next key value
+ szEnd = (char *)strchr(szStart, ';');
+ }
+
+ // free the ansi buffer
+ delete [] strValue;
+ REGUTIL::FreeConfigString(wstrValue);
+ }
+#endif // _DEBUG
+}
+
+
+// This is a notification from the EE it's about to go to fiber mode.
+// This is given *before* it actually goes to fiber mode.
+HRESULT Debugger::SetFiberMode(bool isFiberMode)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+
+ // Notifications from EE never come on helper worker.
+ PRECONDITION(!ThisIsHelperThreadWorker());
+ }
+ CONTRACTL_END;
+
+
+ Thread * pThread = ::GetThread();
+
+ m_pRCThread->m_pDCB->m_bHostingInFiber = isFiberMode;
+
+ // If there is a debugger already attached, then we have a big problem. As of V2.0, the debugger
+ // does not support debugging processes with fibers in them. We set the unrecoverable state to
+ // indicate that we're in a bad state now. The debugger will notice this, and take appropiate action.
+ if (isFiberMode && CORDebuggerAttached())
+ {
+ LOG((LF_CORDB, LL_INFO10, "Thread has entered fiber mode while debugger attached.\n"));
+
+ EX_TRY
+ {
+ // We send up a MDA for two reasons: 1) we want to give the user some chance to see what went wrong,
+ // and 2) we want to get the Right Side to notice that we're in an unrecoverable error state now.
+
+ SString szName(W("DebuggerFiberModeNotSupported"));
+ SString szDescription;
+ szDescription.LoadResource(CCompRC::Debugging, MDARC_DEBUGGER_FIBER_MODE_NOT_SUPPORTED);
+ SString szXML(W(""));
+
+ // Sending any debug event will be a GC violation.
+ // However, if we're enabling fiber-mode while a debugger is attached, we're already doomed.
+ // Deadlocks and AVs are just around the corner. A Gc-violation is the least of our worries.
+ // We want to at least notify the debugger at all costs.
+ CONTRACT_VIOLATION(GCViolation);
+
+ // As soon as we set unrecoverable error in the LS, the RS will pick it up and basically shut down.
+ // It won't dispatch any events. So we fire the MDA first, and then set unrecoverable error.
+ SendMDANotification(pThread, &szName, &szDescription, &szXML, (CorDebugMDAFlags) 0, FALSE);
+
+ CORDBDebuggerSetUnrecoverableError(this, CORDBG_E_CANNOT_DEBUG_FIBER_PROCESS, false);
+
+ // Fire the MDA again just to force the RS to sniff the LS and pick up that we're in an unrecoverable error.
+ // No harm done from dispatching an MDA twice. And
+ SendMDANotification(pThread, &szName, &szDescription, &szXML, (CorDebugMDAFlags) 0, FALSE);
+
+ }
+ EX_CATCH
+ {
+ LOG((LF_CORDB, LL_INFO10, "Error sending MDA regarding fiber mode.\n"));
+ }
+ EX_END_CATCH(SwallowAllExceptions);
+ }
+
+ return S_OK;
+}
+
+// Checks if the MethodInfos table has been allocated, and if not does so.
+// Throw on failure, so we always return
+HRESULT Debugger::CheckInitMethodInfoTable()
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ if (m_pMethodInfos == NULL)
+ {
+ DebuggerMethodInfoTable *pMethodInfos = NULL;
+
+ EX_TRY
+ {
+ pMethodInfos = new (interopsafe) DebuggerMethodInfoTable();
+ }
+ EX_CATCH
+ {
+ pMethodInfos = NULL;
+ }
+ EX_END_CATCH(RethrowTerminalExceptions);
+
+
+ if (pMethodInfos == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ if (InterlockedCompareExchangeT(&m_pMethodInfos, pMethodInfos, NULL) != NULL)
+ {
+ DeleteInteropSafe(pMethodInfos);
+ }
+ }
+
+ return S_OK;
+}
+
+// Checks if the m_pModules table has been allocated, and if not does so.
+HRESULT Debugger::CheckInitModuleTable()
+{
+ CONTRACT(HRESULT)
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ POSTCONDITION(m_pModules != NULL);
+ }
+ CONTRACT_END;
+
+ if (m_pModules == NULL)
+ {
+ DebuggerModuleTable *pModules = new (interopsafe, nothrow) DebuggerModuleTable();
+
+ if (pModules == NULL)
+ {
+ RETURN (E_OUTOFMEMORY);
+ }
+
+ if (InterlockedCompareExchangeT(&m_pModules, pModules, NULL) != NULL)
+ {
+ DeleteInteropSafe(pModules);
+ }
+ }
+
+ RETURN (S_OK);
+}
+
+// Checks if the m_pModules table has been allocated, and if not does so.
+HRESULT Debugger::CheckInitPendingFuncEvalTable()
+{
+ CONTRACT(HRESULT)
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ POSTCONDITION(GetPendingEvals() != NULL);
+ }
+ CONTRACT_END;
+
+#ifndef DACCESS_COMPILE
+
+ if (GetPendingEvals() == NULL)
+ {
+ DebuggerPendingFuncEvalTable *pPendingEvals = new (interopsafe, nothrow) DebuggerPendingFuncEvalTable();
+
+ if (pPendingEvals == NULL)
+ {
+ RETURN(E_OUTOFMEMORY);
+ }
+
+ // Since we're setting, we need an LValue and not just an accessor.
+ if (InterlockedCompareExchangeT(&(GetLazyData()->m_pPendingEvals), pPendingEvals, NULL) != NULL)
+ {
+ DeleteInteropSafe(pPendingEvals);
+ }
+ }
+#endif
+
+ RETURN (S_OK);
+}
+
+
+#ifdef _DEBUG_DMI_TABLE
+// Returns the number of (official) entries in the table
+ULONG DebuggerMethodInfoTable::CheckDmiTable(void)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ ULONG cApparant = 0;
+ ULONG cOfficial = 0;
+
+ if (NULL != m_pcEntries)
+ {
+ DebuggerMethodInfoEntry *dcp;
+ int i = 0;
+ while (i++ <m_iEntries)
+ {
+ dcp = (DebuggerMethodInfoEntry*)&(((DebuggerMethodInfoEntry *)m_pcEntries)[i]);
+ if(dcp->pFD != 0 &&
+ dcp->pFD != (MethodDesc*)0xcdcdcdcd &&
+ dcp->mi != NULL)
+ {
+ cApparant++;
+
+ _ASSERTE( dcp->pFD == dcp->mi->m_fd );
+ LOG((LF_CORDB, LL_INFO1000, "DMIT::CDT:Entry:0x%p mi:0x%p\nPrevs:\n",
+ dcp, dcp->mi));
+ DebuggerMethodInfo *dmi = dcp->mi->m_prevMethodInfo;
+
+ while(dmi != NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "\t0x%p\n", dmi));
+ dmi = dmi->m_prevMethodInfo;
+ }
+ dmi = dcp->mi->m_nextMethodInfo;
+
+ LOG((LF_CORDB, LL_INFO1000, "Nexts:\n", dmi));
+ while(dmi != NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "\t0x%p\n", dmi));
+ dmi = dmi->m_nextMethodInfo;
+ }
+
+ LOG((LF_CORDB, LL_INFO1000, "DMIT::CDT:DONE\n",
+ dcp, dcp->mi));
+ }
+ }
+
+ if (m_piBuckets == 0)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "DMIT::CDT: The table is officially empty!\n"));
+ return cOfficial;
+ }
+
+ LOG((LF_CORDB, LL_INFO1000, "DMIT::CDT:Looking for official entries:\n"));
+
+ ULONG iNext = m_piBuckets[0];
+ ULONG iBucket = 1;
+ HASHENTRY *psEntry = NULL;
+ while (TRUE)
+ {
+ while (iNext != UINT32_MAX)
+ {
+ cOfficial++;
+
+ psEntry = EntryPtr(iNext);
+ dcp = ((DebuggerMethodInfoEntry *)psEntry);
+
+ LOG((LF_CORDB, LL_INFO1000, "\tEntry:0x%p mi:0x%p @idx:0x%x @bucket:0x%x\n",
+ dcp, dcp->mi, iNext, iBucket));
+
+ iNext = psEntry->iNext;
+ }
+
+ // Advance to the next bucket.
+ if (iBucket < m_iBuckets)
+ iNext = m_piBuckets[iBucket++];
+ else
+ break;
+ }
+
+ LOG((LF_CORDB, LL_INFO1000, "DMIT::CDT:Finished official entries: ****************"));
+ }
+
+ return cOfficial;
+}
+#endif // _DEBUG_DMI_TABLE
+
+
+//---------------------------------------------------------------------------------------
+//
+// Class constructor for DebuggerEval. This is the supporting data structure for
+// func-eval tracking.
+//
+// Arguments:
+// pContext - The context to return to when done with this eval.
+// pEvalInfo - Contains all the important information, such as parameters, type args, method.
+// fInException - TRUE if the thread for the eval is currently in an exception notification.
+//
+DebuggerEval::DebuggerEval(CONTEXT * pContext, DebuggerIPCE_FuncEvalInfo * pEvalInfo, bool fInException)
+{
+ WRAPPER_NO_CONTRACT;
+
+ // Allocate the breakpoint instruction info in executable memory.
+ m_bpInfoSegment = new (interopsafeEXEC, nothrow) DebuggerEvalBreakpointInfoSegment(this);
+
+ // This must be non-zero so that the saved opcode is non-zero, and on IA64 we want it to be 0x16
+ // so that we can have a breakpoint instruction in any slot in the bundle.
+ m_bpInfoSegment->m_breakpointInstruction[0] = 0x16;
+#if defined(_TARGET_ARM_)
+ USHORT *bp = (USHORT*)&m_bpInfoSegment->m_breakpointInstruction;
+ *bp = CORDbg_BREAK_INSTRUCTION;
+#endif // _TARGET_ARM_
+ m_thread = pEvalInfo->vmThreadToken.GetRawPtr();
+ m_evalType = pEvalInfo->funcEvalType;
+ m_methodToken = pEvalInfo->funcMetadataToken;
+ m_classToken = pEvalInfo->funcClassMetadataToken;
+
+ // Note: we can't rely on just the DebuggerModule* or AppDomain* because the AppDomain
+ // could get unloaded between now and when the funceval actually starts. So we stash an
+ // AppDomain ID which is safe to use after the AD is unloaded. It's only safe to
+ // use the DebuggerModule* after we've verified the ADID is still valid (i.e. by entering that domain).
+ m_debuggerModule = g_pDebugger->LookupOrCreateModule(pEvalInfo->vmDomainFile);
+
+ if (m_debuggerModule == NULL)
+ {
+ // We have no associated code.
+ _ASSERTE((m_evalType == DB_IPCE_FET_NEW_STRING) || (m_evalType == DB_IPCE_FET_NEW_ARRAY));
+
+ // We'll just do the creation in whatever domain the thread is already in.
+ // It's conceivable that we might want to allow the caller to specify a specific domain, but
+ // ICorDebug provides the debugger with no was to specify the domain.
+ m_appDomainId = m_thread->GetDomain()->GetId();
+ }
+ else
+ {
+ m_appDomainId = m_debuggerModule->GetAppDomain()->GetId();
+ }
+
+ m_funcEvalKey = pEvalInfo->funcEvalKey;
+ m_argCount = pEvalInfo->argCount;
+ m_targetCodeAddr = NULL;
+ m_stringSize = pEvalInfo->stringSize;
+ m_arrayRank = pEvalInfo->arrayRank;
+ m_genericArgsCount = pEvalInfo->genericArgsCount;
+ m_genericArgsNodeCount = pEvalInfo->genericArgsNodeCount;
+ m_successful = false;
+ m_argData = NULL;
+ memset(m_result, 0, sizeof(m_result));
+ m_md = NULL;
+ m_resultType = TypeHandle();
+ m_aborting = FE_ABORT_NONE;
+ m_aborted = false;
+ m_completed = false;
+ m_evalDuringException = fInException;
+ m_rethrowAbortException = false;
+ m_retValueBoxing = Debugger::NoValueTypeBoxing;
+ m_requester = (Thread::ThreadAbortRequester)0;
+ m_vmObjectHandle = VMPTR_OBJECTHANDLE::NullPtr();
+
+ // Copy the thread's context.
+ if (pContext == NULL)
+ {
+ memset(&m_context, 0, sizeof(m_context));
+ }
+ else
+ {
+ memcpy(&m_context, pContext, sizeof(m_context));
+ }
+}
+
+//---------------------------------------------------------------------------------------
+//
+// This constructor is only used when setting up an eval to re-abort a thread.
+//
+// Arguments:
+// pContext - The context to return to when done with this eval.
+// pThread - The thread to re-abort.
+// requester - The type of abort to throw.
+//
+DebuggerEval::DebuggerEval(CONTEXT * pContext, Thread * pThread, Thread::ThreadAbortRequester requester)
+{
+ WRAPPER_NO_CONTRACT;
+
+ // Allocate the breakpoint instruction info in executable memory.
+ m_bpInfoSegment = new (interopsafeEXEC, nothrow) DebuggerEvalBreakpointInfoSegment(this);
+
+ // This must be non-zero so that the saved opcode is non-zero, and on IA64 we want it to be 0x16
+ // so that we can have a breakpoint instruction in any slot in the bundle.
+ m_bpInfoSegment->m_breakpointInstruction[0] = 0x16;
+ m_thread = pThread;
+ m_evalType = DB_IPCE_FET_RE_ABORT;
+ m_methodToken = mdMethodDefNil;
+ m_classToken = mdTypeDefNil;
+ m_debuggerModule = NULL;
+ m_funcEvalKey = RSPTR_CORDBEVAL::NullPtr();
+ m_argCount = 0;
+ m_stringSize = 0;
+ m_arrayRank = 0;
+ m_genericArgsCount = 0;
+ m_genericArgsNodeCount = 0;
+ m_successful = false;
+ m_argData = NULL;
+ m_targetCodeAddr = NULL;
+ memset(m_result, 0, sizeof(m_result));
+ m_md = NULL;
+ m_resultType = TypeHandle();
+ m_aborting = FE_ABORT_NONE;
+ m_aborted = false;
+ m_completed = false;
+ m_evalDuringException = false;
+ m_rethrowAbortException = false;
+ m_retValueBoxing = Debugger::NoValueTypeBoxing;
+ m_requester = requester;
+
+ if (pContext == NULL)
+ {
+ memset(&m_context, 0, sizeof(m_context));
+ }
+ else
+ {
+ memcpy(&m_context, pContext, sizeof(m_context));
+ }
+}
+
+
+#ifdef _DEBUG
+// Thread proc for interop stress coverage. Have an unmanaged thread
+// that just loops throwing native exceptions. This can test corner cases
+// such as getting an native exception while the runtime is synced.
+DWORD WINAPI DbgInteropStressProc(void * lpParameter)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ int i = 0;
+ int zero = 0;
+
+
+ // This will ensure that the compiler doesn't flag our 1/0 exception below at compile-time.
+ if (lpParameter != NULL)
+ {
+ zero = 1;
+ }
+
+ // Note that this thread is a non-runtime thread. So it can't take any CLR locks
+ // or do anything else that may block the helper thread.
+ // (Log statements take CLR locks).
+ while(true)
+ {
+ i++;
+
+ if ((i % 10) != 0)
+ {
+ // Generate an in-band event.
+ PAL_CPP_TRY
+ {
+ // Throw a handled exception. Don't use an AV since that's pretty special.
+ *(int*)lpParameter = 1 / zero;
+ }
+ PAL_CPP_CATCH_ALL
+ {
+ }
+ PAL_CPP_ENDTRY
+ }
+ else
+ {
+ // Generate the occasional oob-event.
+ WszOutputDebugString(W("Ping from DbgInteropStressProc"));
+ }
+
+ // This helps parallelize if we have a lot of threads, and keeps us from
+ // chewing too much CPU time.
+ ClrSleepEx(2000,FALSE);
+ ClrSleepEx(GetRandomInt(1000), FALSE);
+ }
+
+ return 0;
+}
+
+// ThreadProc that does everything in a can't stop region.
+DWORD WINAPI DbgInteropCantStopStressProc(void * lpParameter)
+{
+ WRAPPER_NO_CONTRACT;
+
+ // This will mark us as a can't stop region.
+ ClrFlsSetThreadType (ThreadType_DbgHelper);
+
+ return DbgInteropStressProc(lpParameter);
+}
+
+// Generate lots of OOB events.
+DWORD WINAPI DbgInteropDummyStressProc(void * lpParameter)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ ClrSleepEx(1,FALSE);
+ return 0;
+}
+
+DWORD WINAPI DbgInteropOOBStressProc(void * lpParameter)
+{
+ WRAPPER_NO_CONTRACT;
+
+ int i = 0;
+ while(true)
+ {
+ i++;
+ if (i % 10 == 1)
+ {
+ // Create a dummy thread. That generates 2 oob events
+ // (1 for create, 1 for destroy)
+ DWORD id;
+ ::CreateThread(NULL, 0, DbgInteropDummyStressProc, NULL, 0, &id);
+ }
+ else
+ {
+ // Generate the occasional oob-event.
+ WszOutputDebugString(W("OOB ping from "));
+ }
+
+ ClrSleepEx(3000, FALSE);
+ }
+
+ return 0;
+}
+
+// List of the different possible stress procs.
+LPTHREAD_START_ROUTINE g_pStressProcs[] =
+{
+ DbgInteropOOBStressProc,
+ DbgInteropCantStopStressProc,
+ DbgInteropStressProc
+};
+#endif
+
+
+DebuggerHeap * Debugger::GetInteropSafeHeap()
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ THROWS;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ // Lazily initialize our heap.
+ if (!m_heap.IsInit())
+ {
+ _ASSERTE(!"InteropSafe Heap should have already been initialized in LazyInit");
+
+ // Just in case we miss it in retail, convert to OOM here:
+ ThrowOutOfMemory();
+ }
+
+ return &m_heap;
+}
+
+DebuggerHeap * Debugger::GetInteropSafeHeap_NoThrow()
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ // Lazily initialize our heap.
+ if (!m_heap.IsInit())
+ {
+ _ASSERTE(!"InteropSafe Heap should have already been initialized in LazyInit");
+
+ // Just in case we miss it in retail, convert to OOM here:
+ return NULL;
+ }
+ return &m_heap;
+}
+
+DebuggerHeap * Debugger::GetInteropSafeExecutableHeap()
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ THROWS;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ // Lazily initialize our heap.
+ if (!m_executableHeap.IsInit())
+ {
+ _ASSERTE(!"InteropSafe Executable Heap should have already been initialized in LazyInit");
+
+ // Just in case we miss it in retail, convert to OOM here:
+ ThrowOutOfMemory();
+ }
+
+ return &m_executableHeap;
+}
+
+DebuggerHeap * Debugger::GetInteropSafeExecutableHeap_NoThrow()
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ // Lazily initialize our heap.
+ if (!m_executableHeap.IsInit())
+ {
+ _ASSERTE(!"InteropSafe Executable Heap should have already been initialized in LazyInit");
+
+ // Just in case we miss it in retail, convert to OOM here:
+ return NULL;
+ }
+ return &m_executableHeap;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Notify potential debugger that the runtime has started up
+//
+//
+// Assumptions:
+// Called during startup path
+//
+// Notes:
+// If no debugger is attached, this does nothing.
+//
+//---------------------------------------------------------------------------------------
+void Debugger::RaiseStartupNotification()
+{
+ // Right-side will read this field from OOP via DAC-primitive to determine attach or launch case.
+ // We do an interlocked increment to gaurantee this is an atomic memory write, and to ensure
+ // that it's flushed from any CPU cache into memory.
+ InterlockedIncrement(&m_fLeftSideInitialized);
+
+#ifndef FEATURE_DBGIPC_TRANSPORT_VM
+ // If we are remote debugging, don't send the event now if a debugger is not attached. No one will be
+ // listening, and we will fail. However, we still want to initialize the variable above.
+ DebuggerIPCEvent startupEvent;
+ InitIPCEvent(&startupEvent, DB_IPCE_LEFTSIDE_STARTUP, NULL, VMPTR_AppDomain::NullPtr());
+
+ SendRawEvent(&startupEvent);
+
+ // RS will set flags from OOP while we're stopped at the event if it wants to attach.
+#endif // FEATURE_DBGIPC_TRANSPORT_VM
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Sends a raw managed debug event to the debugger.
+//
+// Arguments:
+// pManagedEvent - managed debug event
+//
+//
+// Notes:
+// This can be called even if a debugger is not attached.
+// The entire process will get frozen by the debugger once we send. The debugger
+// needs to resume the process. It may detach as well.
+// See code:IsEventDebuggerNotification for decoding this event. These methods must stay in sync.
+// The debugger process reads the events via code:CordbProcess.CopyManagedEventFromTarget.
+//
+//---------------------------------------------------------------------------------------
+void Debugger::SendRawEvent(const DebuggerIPCEvent * pManagedEvent)
+{
+#if defined(FEATURE_DBGIPC_TRANSPORT_VM)
+ HRESULT hr = g_pDbgTransport->SendDebugEvent(const_cast<DebuggerIPCEvent *>(pManagedEvent));
+
+ if (FAILED(hr))
+ {
+ _ASSERTE(!"Failed to send debugger event");
+
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "D::SendIPCEvent Error on Send with 0x%x\n", hr);
+ UnrecoverableError(hr,
+ 0,
+ FILE_DEBUG,
+ LINE_DEBUG,
+ false);
+
+ // @dbgtodo Mac - what can we do here?
+ }
+#else
+ // We get to send an array of ULONG_PTRs as data with the notification.
+ // The debugger can then use ReadProcessMemory to read through this array.
+ ULONG_PTR rgData [] = {
+ CLRDBG_EXCEPTION_DATA_CHECKSUM,
+ (ULONG_PTR) g_pMSCorEE,
+ (ULONG_PTR) pManagedEvent
+ };
+
+ // If no debugger attached, then don't bother raising a 1st-chance exception because nobody will sniff it.
+ // @dbgtodo iDNA: in iDNA case, the recorder may sniff it.
+ if (!IsDebuggerPresent())
+ {
+ return;
+ }
+
+ //
+ // Physically send the event via an OS Exception. We're using exceptions as a notification
+ // mechanism on top of the OS native debugging pipeline.
+ // @dbgtodo cross-plat - this needs to be cross-plat.
+ //
+ EX_TRY
+ {
+ const DWORD dwFlags = 0; // continuable (eg, Debugger can continue GH)
+ RaiseException(CLRDBG_NOTIFICATION_EXCEPTION_CODE, dwFlags, NumItems(rgData), rgData);
+
+ // If debugger continues "GH" (DBG_CONTINUE), then we land here.
+ // This is the expected path for a well-behaved ICorDebug debugger.
+ }
+ EX_CATCH
+ {
+ // If no debugger is attached, or if the debugger continues "GN" (DBG_EXCEPTION_NOT_HANDLED), then we land here.
+ // A naive (not-ICorDebug aware) native-debugger won't handle the exception and so land us here.
+ // We may also get here if a debugger detaches at the Exception notification
+ // (and thus implicitly continues GN).
+ }
+ EX_END_CATCH(SwallowAllExceptions);
+#endif // FEATURE_DBGIPC_TRANSPORT_VM
+}
+
+//---------------------------------------------------------------------------------------
+// Send a createProcess event to give the RS a chance to do SetDesiredNGENFlags
+//
+// Arguments:
+// pDbgLockHolder - lock holder.
+//
+// Assumptions:
+// Lock is initially held. This will toggle the lock to send an IPC event.
+// This will start a synchronization.
+//
+// Notes:
+// In V2, this also gives the RS a chance to intialize the IPC protocol.
+// Spefically, this needs to be sent before the LS can send a sync-complete.
+//---------------------------------------------------------------------------------------
+void Debugger::SendCreateProcess(DebuggerLockHolder * pDbgLockHolder)
+{
+ pDbgLockHolder->Release();
+
+ // Encourage helper thread to spin up so that we're in a consistent state.
+ PollWaitingForHelper();
+
+ // we don't need to use SENDIPCEVENT_BEGIN/END macros that perform the debug-suspend aware checks,
+ // as this code executes on the startup path...
+ SENDIPCEVENT_RAW_BEGIN(pDbgLockHolder);
+
+ // Send a CreateProcess event.
+ // @dbgtodo pipeline - eliminate these reasons for needing a CreateProcess event (part of pipeline feature crew)
+ // This will let the RS know that the IPC block is up + ready, and then the RS can read it.
+ // The RS will then update the DCB with enough information so that we can send the sync-complete.
+ // (such as letting us know whether we're interop-debugging or not).
+ DebuggerIPCEvent event;
+ InitIPCEvent(&event, DB_IPCE_CREATE_PROCESS, NULL, VMPTR_AppDomain::NullPtr());
+ SendRawEvent(&event);
+
+ // @dbgtodo inspection- it doesn't really make sense to sync on a CreateProcess. We only have 1 thread
+ // in the CLR and we know exactly what state we're in and we can ensure that we're synchronized.
+ // For V3,RS should be able to treat a CreateProcess like a synchronized.
+ // Remove this in V3 as we make SetDesiredNgenFlags operate OOP.
+ TrapAllRuntimeThreads();
+
+ // Must have a thread object so that we ensure that we will actually block here.
+ // This ensures the debuggee is actually stopped at startup, and
+ // this gives the debugger a chance to call SetDesiredNGENFlags before we
+ // set s_fCanChangeNgenFlags to FALSE.
+ _ASSERTE(GetThread() != NULL);
+ SENDIPCEVENT_RAW_END;
+
+ pDbgLockHolder->Acquire();
+}
+
+#if defined(FEATURE_CORECLR) && !defined(FEATURE_PAL)
+
+HANDLE g_hContinueStartupEvent = NULL;
+
+CLR_ENGINE_METRICS g_CLREngineMetrics = {
+ sizeof(CLR_ENGINE_METRICS),
+ CorDebugVersion_4_0,
+ &g_hContinueStartupEvent};
+
+
+bool IsTelestoDebugPackInstalled()
+{
+ RegKeyHolder hKey;
+ if (ERROR_SUCCESS != WszRegOpenKeyEx(HKEY_LOCAL_MACHINE, FRAMEWORK_REGISTRY_KEY_W, 0, KEY_READ, &hKey))
+ return false;
+
+ bool debugPackInstalled = false;
+
+ DWORD cbValue = 0;
+
+ if (ERROR_SUCCESS == WszRegQueryValueEx(hKey, CLRConfig::EXTERNAL_DbgPackShimPath, NULL, NULL, NULL, &cbValue))
+ {
+ if (cbValue != 0)
+ {
+ debugPackInstalled = true;
+ }
+ }
+
+ // RegCloseKey called by holder
+ return debugPackInstalled;
+}
+
+#define StartupNotifyEventNamePrefix W("TelestoStartupEvent_")
+const int cchEventNameBufferSize = sizeof(StartupNotifyEventNamePrefix)/sizeof(WCHAR) + 8; // + hex DWORD (8). NULL terminator is included in sizeof(StartupNotifyEventNamePrefix)
+HANDLE OpenStartupNotificationEvent()
+{
+ DWORD debuggeePID = GetCurrentProcessId();
+ WCHAR szEventName[cchEventNameBufferSize];
+ swprintf_s(szEventName, cchEventNameBufferSize, StartupNotifyEventNamePrefix W("%08x"), debuggeePID);
+
+ return WszOpenEvent(EVENT_ALL_ACCESS, FALSE, szEventName);
+}
+
+void NotifyDebuggerOfTelestoStartup()
+{
+ // Create the continue event first so that we guarantee that any
+ // enumeration of this process will get back a valid continue event
+ // the instant we signal the startup notification event.
+
+ CONSISTENCY_CHECK(NULL == g_hContinueStartupEvent);
+ g_hContinueStartupEvent = WszCreateEvent(NULL, TRUE, FALSE, NULL);
+ CONSISTENCY_CHECK(INVALID_HANDLE_VALUE != g_hContinueStartupEvent); // we reserve this value for error conditions in EnumerateCLRs
+
+ HANDLE startupEvent = OpenStartupNotificationEvent();
+ if (startupEvent != NULL)
+ {
+ // signal notification event
+ SetEvent(startupEvent);
+ CloseHandle(startupEvent);
+ startupEvent = NULL;
+
+ // wait on continue startup event
+ // The debugger may attach to us while we're blocked here.
+ WaitForSingleObject(g_hContinueStartupEvent, INFINITE);
+ }
+
+ CloseHandle(g_hContinueStartupEvent);
+ g_hContinueStartupEvent = NULL;
+}
+
+#endif // FEATURE_CORECLR && !FEATURE_PAL
+
+//---------------------------------------------------------------------------------------
+//
+// Initialize Left-Side debugger object
+//
+// Return Value:
+// S_OK on successs. May also throw.
+//
+// Assumptions:
+// This is called in the startup path.
+//
+// Notes:
+// Startup initializes any necessary debugger objects, including creating
+// and starting the Runtime Controller thread. Once the RC thread is started
+// and we return successfully, the Debugger object can expect to have its
+// event handlers called.
+//
+//---------------------------------------------------------------------------------------
+HRESULT Debugger::Startup(void)
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr = S_OK;
+
+ _ASSERTE(g_pEEInterface != NULL);
+
+#if defined(FEATURE_CORECLR) && !defined(FEATURE_PAL)
+ if (IsWatsonEnabled() || IsTelestoDebugPackInstalled())
+ {
+ // Iff the debug pack is installed, then go through the telesto debugging pipeline.
+ LOG((LF_CORDB, LL_INFO10, "Debugging service is enabled because debug pack is installed or Watson support is enabled)\n"));
+
+ // This may block while an attach occurs.
+ NotifyDebuggerOfTelestoStartup();
+ }
+ else
+ {
+ // On Windows, it's actually safe to finish the initialization here even without the debug pack.
+ // However, doing so causes a perf regression because we used to bail out early if the debug
+ // pack is not installed.
+ //
+ // Unlike Windows, we can't continue executing this function if the debug pack is not installed.
+ // The transport requires the debug pack to be present. Otherwise it'll raise a fatal error.
+ return S_FALSE;
+ }
+#endif // FEATURE_CORECLR && !FEATURE_PAL
+
+ {
+ DebuggerLockHolder dbgLockHolder(this);
+
+ // Stubs in Stacktraces are always enabled.
+ g_EnableSIS = true;
+
+ // We can get extra Interop-debugging test coverage by having some auxillary unmanaged
+ // threads running and throwing debug events. Keep these stress procs separate so that
+ // we can focus on certain problem areas.
+ #ifdef _DEBUG
+
+ g_DbgShouldntUseDebugger = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgNoDebugger) != 0;
+
+
+ // Creates random thread procs.
+ DWORD dwRegVal = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgExtraThreads);
+ DWORD dwId;
+ DWORD i;
+
+ if (dwRegVal > 0)
+ {
+ for (i = 0; i < dwRegVal; i++)
+ {
+ int iProc = GetRandomInt(NumItems(g_pStressProcs));
+ LPTHREAD_START_ROUTINE pStartRoutine = g_pStressProcs[iProc];
+ ::CreateThread(NULL, 0, pStartRoutine, NULL, 0, &dwId);
+ LOG((LF_CORDB, LL_INFO1000, "Created random thread (%d) with tid=0x%x\n", i, dwId));
+ }
+ }
+
+ dwRegVal = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgExtraThreadsIB);
+ if (dwRegVal > 0)
+ {
+ for (i = 0; i < dwRegVal; i++)
+ {
+ ::CreateThread(NULL, 0, DbgInteropStressProc, NULL, 0, &dwId);
+ LOG((LF_CORDB, LL_INFO1000, "Created extra thread (%d) with tid=0x%x\n", i, dwId));
+ }
+ }
+
+ dwRegVal = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgExtraThreadsCantStop);
+ if (dwRegVal > 0)
+ {
+ for (i = 0; i < dwRegVal; i++)
+ {
+ ::CreateThread(NULL, 0, DbgInteropCantStopStressProc, NULL, 0, &dwId);
+ LOG((LF_CORDB, LL_INFO1000, "Created extra thread 'can't-stop' (%d) with tid=0x%x\n", i, dwId));
+ }
+ }
+
+ dwRegVal = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgExtraThreadsOOB);
+ if (dwRegVal > 0)
+ {
+ for (i = 0; i < dwRegVal; i++)
+ {
+ ::CreateThread(NULL, 0, DbgInteropOOBStressProc, NULL, 0, &dwId);
+ LOG((LF_CORDB, LL_INFO1000, "Created extra thread OOB (%d) with tid=0x%x\n", i, dwId));
+ }
+ }
+ #endif
+
+ #ifdef FEATURE_PAL
+ PAL_InitializeDebug();
+ #endif // FEATURE_PAL
+
+ // Lazily initialize the interop-safe heap
+
+ // Must be done before the RC thread is initialized.
+ // @dbgtodo - In V2, LS was lazily initialized; but was eagerly pre-initialized if launched by debugger.
+ // (This was for perf reasons). But we don't want Launch vs. Attach checks in the LS, so we now always
+ // init. As we move more to OOP, this init will become cheaper.
+ {
+ LazyInit();
+ DebuggerController::Initialize();
+ }
+
+ InitializeHijackFunctionAddress();
+
+ // Create the runtime controller thread, a.k.a, the debug helper thread.
+ // Don't use the interop-safe heap b/c we don't want to lazily create it.
+ m_pRCThread = new DebuggerRCThread(this);
+ _ASSERTE(m_pRCThread != NULL); // throws on oom
+ TRACE_ALLOC(m_pRCThread);
+
+ hr = m_pRCThread->Init();
+ _ASSERTE(SUCCEEDED(hr)); // throws on error
+
+ #if defined(FEATURE_DBGIPC_TRANSPORT_VM)
+ // Create transport session and initialize it.
+ g_pDbgTransport = new DbgTransportSession();
+ hr = g_pDbgTransport->Init(m_pRCThread->GetDCB(), m_pAppDomainCB);
+ if (FAILED(hr))
+ {
+ ShutdownTransport();
+ ThrowHR(hr);
+ }
+ #ifdef FEATURE_PAL
+ PAL_SetShutdownCallback(AbortTransport);
+ #endif // FEATURE_PAL
+ #endif // FEATURE_DBGIPC_TRANSPORT_VM
+
+ RaiseStartupNotification();
+
+ // Also initialize the AppDomainEnumerationIPCBlock
+ #if !defined(FEATURE_IPCMAN) || defined(FEATURE_DBGIPC_TRANSPORT_VM)
+ m_pAppDomainCB = new (nothrow) AppDomainEnumerationIPCBlock();
+ #else
+ m_pAppDomainCB = g_pIPCManagerInterface->GetAppDomainBlock();
+ #endif
+
+ if (m_pAppDomainCB == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO100, "D::S: Failed to get AppDomain IPC block from IPCManager.\n"));
+ ThrowHR(E_FAIL);
+ }
+
+ hr = InitAppDomainIPC();
+ _ASSERTE(SUCCEEDED(hr)); // throws on error.
+
+ // See if we need to spin up the helper thread now, rather than later.
+ DebuggerIPCControlBlock* pIPCControlBlock = m_pRCThread->GetDCB();
+ (void)pIPCControlBlock; //prevent "unused variable" error from GCC
+
+ _ASSERTE(pIPCControlBlock != NULL);
+ _ASSERTE(!pIPCControlBlock->m_rightSideShouldCreateHelperThread);
+ {
+ // Create the win32 thread for the helper and let it run free.
+ hr = m_pRCThread->Start();
+
+ // convert failure to exception as with old contract
+ if (FAILED(hr))
+ {
+ ThrowHR(hr);
+ }
+
+ LOG((LF_CORDB, LL_EVERYTHING, "Start was successful\n"));
+ }
+
+ #ifdef TEST_DATA_CONSISTENCY
+ // if we have set the environment variable TestDataConsistency, run the data consistency test.
+ // See code:DataTest::TestDataSafety for more information
+ if ((g_pConfig != NULL) && (g_pConfig->TestDataConsistency() == true))
+ {
+ DataTest dt;
+ dt.TestDataSafety();
+ }
+ #endif
+ }
+
+#ifdef FEATURE_PAL
+ // Signal the debugger (via dbgshim) and wait until it is ready for us to
+ // continue. This needs to be outside the lock and after the transport is
+ // initialized.
+ PAL_NotifyRuntimeStarted();
+#endif // FEATURE_PAL
+
+ // We don't bother changing this process's permission.
+ // A managed debugger will have the SE_DEBUG permission which will allow it to open our process handle,
+ // even if we're a guest account.
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+// Finishes startup once we have a Thread object.
+//
+// Arguments:
+// pThread - the current thread. Must be non-null
+//
+// Notes:
+// Most debugger initialization is done in code:Debugger.Startup,
+// However, debugger can't block on synchronization without a Thread object,
+// so sending IPC events must wait until after we have a thread object.
+//---------------------------------------------------------------------------------------
+HRESULT Debugger::StartupPhase2(Thread * pThread)
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr = S_OK;
+
+ // Must have a thread so that we can block
+ _ASSERTE(pThread != NULL);
+
+ DebuggerLockHolder dbgLockHolder(this);
+
+ // @dbgtodo - This may need to change when we remove SetupSyncEvent...
+ // If we're launching, then sync now so that the RS gets an early chance to dispatch the CreateProcess event.
+ // This is especially important b/c certain portions of the ICorDebugAPI (like setting ngen flags) are only
+ // valid during the CreateProcess callback in the launch case.
+ // We need to send the callback early enough so those APIs can set the flags before they're actually used.
+ // We also ensure the debugger is actually attached.
+ if (SUCCEEDED(hr) && CORDebuggerAttached())
+ {
+ StartCanaryThread();
+ SendCreateProcess(&dbgLockHolder); // toggles lock
+ }
+
+ // After returning from debugger startup we assume that the runtime might start using the NGEN flags to make
+ // binding decisions. From now on the debugger can not influence NGEN binding policy
+ // Use volatile store to guarantee make the value visible to the DAC (the store can be optimized out otherwise)
+ VolatileStoreWithoutBarrier(&s_fCanChangeNgenFlags, FALSE);
+
+ // Must release the lock (which would be done at the end of this method anyways) so that
+ // the helper thread can do the jit-attach.
+ dbgLockHolder.Release();
+
+
+#ifdef _DEBUG
+ // Give chance for stress harnesses to launch a managed debugger when a managed app starts up.
+ // This lets us run a set of managed apps under a debugger.
+ if (!CORDebuggerAttached())
+ {
+ #define DBG_ATTACH_ON_STARTUP_ENV_VAR W("COMPlus_DbgAttachOnStartup")
+ PathString temp;
+ // We explicitly just check the env because we don't want a switch this invasive to be global.
+ DWORD fAttach = WszGetEnvironmentVariable(DBG_ATTACH_ON_STARTUP_ENV_VAR, temp) > 0;
+
+ if (fAttach)
+ {
+ // Remove the env var from our process so that the debugger we spin up won't inherit it.
+ // Else, if the debugger is managed, we'll have an infinite recursion.
+ BOOL fOk = WszSetEnvironmentVariable(DBG_ATTACH_ON_STARTUP_ENV_VAR, NULL);
+
+ if (fOk)
+ {
+ // We've already created the helper thread (which can service the attach request)
+ // So just do a normal jit-attach now.
+
+ SString szName(W("DebuggerStressStartup"));
+ SString szDescription(W("MDA used for debugger-stress scenario. This is fired to trigger a jit-attach")
+ W("to allow us to attach a debugger to any managed app that starts up.")
+ W("This MDA is only fired when the 'DbgAttachOnStartup' COM+ knob/reg-key is set on checked builds."));
+ SString szXML(W("<xml>See the description</xml>"));
+
+ SendMDANotification(
+ NULL, // NULL b/c we don't have a thread yet
+ &szName,
+ &szDescription,
+ &szXML,
+ ((CorDebugMDAFlags) 0 ),
+ TRUE // this will force the jit-attach
+ );
+ }
+ }
+ }
+#endif
+
+
+ return hr;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Public entrypoint into the debugger to force the lazy data to be initialized at a
+// controlled point in time. This is useful for those callers into the debugger (e.g.,
+// ETW rundown) that know they will need the lazy data initialized but cannot afford to
+// have it initialized unpredictably or inside a lock.
+//
+// This may be called more than once, and will know to initialize the lazy data only
+// once.
+//
+
+void Debugger::InitializeLazyDataIfNecessary()
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ THROWS;
+ GC_TRIGGERS;
+ }
+ CONTRACTL_END;
+
+ if (!HasLazyData())
+ {
+ DebuggerLockHolder lockHolder(this);
+ LazyInit(); // throws
+ }
+}
+
+
+/******************************************************************************
+Lazy initialize stuff once we know we are debugging.
+This reduces the startup cost in the non-debugging case.
+
+We can do this at a bunch of random strategic places.
+ ******************************************************************************/
+
+HRESULT Debugger::LazyInitWrapper()
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ NOTHROW;
+ GC_NOTRIGGER;
+ PRECONDITION(ThisMaybeHelperThread());
+ }
+ CONTRACTL_END;
+
+ HRESULT hr = S_OK;
+
+ // Do lazy initialization now.
+ EX_TRY
+ {
+ LazyInit(); // throws on errors.
+ }
+ EX_CATCH
+ {
+ Exception *_ex = GET_EXCEPTION();
+ hr = _ex->GetHR();
+ STRESS_LOG1(LF_CORDB, LL_ALWAYS, "LazyInit failed w/ hr:0x%08x\n", hr);
+ }
+ EX_END_CATCH(SwallowAllExceptions);
+
+ return hr;
+}
+
+void Debugger::LazyInit()
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ THROWS;
+ GC_NOTRIGGER;
+ PRECONDITION(ThreadHoldsLock()); // ensure we're serialized, requires GC_NOTRIGGER
+
+ PRECONDITION(ThisMaybeHelperThread());
+ }
+ CONTRACTL_END;
+
+ // Have knob that catches places where we lazy init.
+ _ASSERTE(!g_DbgShouldntUseDebugger);
+
+ // If we're already init, then bail.
+ if (m_pLazyData != NULL)
+ {
+ return;
+ }
+
+
+
+
+ // Lazily create our heap.
+ HRESULT hr = m_heap.Init(FALSE);
+ IfFailThrow(hr);
+
+ hr = m_executableHeap.Init(TRUE);
+ IfFailThrow(hr);
+
+ m_pLazyData = new (interopsafe) DebuggerLazyInit();
+ _ASSERTE(m_pLazyData != NULL); // throws on oom.
+
+ m_pLazyData->Init();
+
+}
+
+HelperThreadFavor::HelperThreadFavor() :
+ m_fpFavor(NULL),
+ m_pFavorData(NULL),
+ m_FavorReadEvent(NULL),
+ m_FavorLock(CrstDebuggerFavorLock, CRST_DEFAULT),
+ m_FavorAvailableEvent(NULL)
+{
+}
+
+void HelperThreadFavor::Init()
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ THROWS;
+ GC_NOTRIGGER;
+ PRECONDITION(ThisMaybeHelperThread());
+ }
+ CONTRACTL_END;
+
+ // Create events for managing favors.
+ m_FavorReadEvent = CreateWin32EventOrThrow(NULL, kAutoResetEvent, FALSE);
+ m_FavorAvailableEvent = CreateWin32EventOrThrow(NULL, kAutoResetEvent, FALSE);
+}
+
+
+
+DebuggerLazyInit::DebuggerLazyInit() :
+ m_pPendingEvals(NULL),
+ // @TODO: a-meicht
+ // Major clean up needed for giving the right flag
+ // There are cases where DebuggerDataLock is taken by managed thread and unmanaged trhead is also trying to take it.
+ // It could cause deadlock if we toggle GC upon taking lock.
+ // Unfortunately UNSAFE_COOPGC is not enough. There is a code path in Jit comipling that we are in GC Preemptive
+ // enabled. workaround by orring the unsafe_anymode flag. But we really need to do proper clean up.
+ //
+ // NOTE: If this ever gets fixed, you should replace CALLED_IN_DEBUGGERDATALOCK_HOLDER_SCOPE_MAY_GC_TRIGGERS_CONTRACT
+ // with appropriate contracts at each site.
+ //
+ m_DebuggerDataLock(CrstDebuggerJitInfo, (CrstFlags)(CRST_UNSAFE_ANYMODE | CRST_REENTRANCY | CRST_DEBUGGER_THREAD)),
+ m_CtrlCMutex(NULL),
+ m_exAttachEvent(NULL),
+ m_exUnmanagedAttachEvent(NULL),
+ m_DebuggerHandlingCtrlC(NULL)
+{
+}
+
+void DebuggerLazyInit::Init()
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ THROWS;
+ GC_NOTRIGGER;
+ PRECONDITION(ThisMaybeHelperThread());
+ }
+ CONTRACTL_END;
+
+ // Caller ensures this isn't double-called.
+
+ // This event is only used in the unmanaged attach case. We must mark this event handle as inheritable.
+ // Otherwise, the unmanaged debugger won't be able to notify us.
+ //
+ // Note that PAL currently doesn't support specifying the security attributes when creating an event, so
+ // unmanaged attach for unhandled exceptions is broken on PAL.
+ SECURITY_ATTRIBUTES* pSA = NULL;
+ SECURITY_ATTRIBUTES secAttrib;
+ secAttrib.nLength = sizeof(secAttrib);
+ secAttrib.lpSecurityDescriptor = NULL;
+ secAttrib.bInheritHandle = TRUE;
+
+ pSA = &secAttrib;
+
+ // Create some synchronization events...
+ // these events stay signaled all the time except when an attach is in progress
+ m_exAttachEvent = CreateWin32EventOrThrow(NULL, kManualResetEvent, TRUE);
+ m_exUnmanagedAttachEvent = CreateWin32EventOrThrow(pSA, kManualResetEvent, TRUE);
+
+ m_CtrlCMutex = CreateWin32EventOrThrow(NULL, kAutoResetEvent, FALSE);
+ m_DebuggerHandlingCtrlC = FALSE;
+
+ // Let the helper thread lazy init stuff too.
+ m_RCThread.Init();
+}
+
+
+DebuggerLazyInit::~DebuggerLazyInit()
+{
+ {
+ USHORT cBlobs = m_pMemBlobs.Count();
+ void **rgpBlobs = m_pMemBlobs.Table();
+
+ for (int i = 0; i < cBlobs; i++)
+ {
+ g_pDebugger->ReleaseRemoteBuffer(rgpBlobs[i], false);
+ }
+ }
+
+ if (m_pPendingEvals)
+ {
+ DeleteInteropSafe(m_pPendingEvals);
+ m_pPendingEvals = NULL;
+ }
+
+ if (m_CtrlCMutex != NULL)
+ {
+ CloseHandle(m_CtrlCMutex);
+ }
+
+ if (m_exAttachEvent != NULL)
+ {
+ CloseHandle(m_exAttachEvent);
+ }
+
+ if (m_exUnmanagedAttachEvent != NULL)
+ {
+ CloseHandle(m_exUnmanagedAttachEvent);
+ }
+}
+
+
+//
+// RequestFavor gets the debugger helper thread to call a function. It's
+// typically called when the current thread can't call the function directly,
+// e.g, there isn't enough stack space.
+//
+// RequestFavor can be called in stack-overflow scenarios and thus explicitly
+// avoids any lazy initialization.
+// It blocks until the favor callback completes.
+//
+// Parameters:
+// fp - a non-null Favour callback function
+// pData - the parameter passed to the favor callback function. This can be any value.
+//
+// Return values:
+// S_OK if the function succeeds, else a failure HRESULT
+//
+
+HRESULT Debugger::RequestFavor(FAVORCALLBACK fp, void * pData)
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ NOTHROW;
+ GC_TRIGGERS;
+ PRECONDITION(fp != NULL);
+ }
+ CONTRACTL_END;
+
+ if (m_pRCThread == NULL ||
+ m_pRCThread->GetRCThreadId() == GetCurrentThreadId())
+ {
+ // Since favors are only used internally, we know that the helper should alway be up and ready
+ // to handle them. Also, since favors can be used in low-stack scenarios, there's not any
+ // extra initialization needed for them.
+ _ASSERTE(!"Helper not initialized for favors.");
+ return E_UNEXPECTED;
+ }
+
+ m_pRCThread->DoFavor(fp, pData);
+ return S_OK;
+}
+
+/******************************************************************************
+// Called to set the interface that the Runtime exposes to us.
+ ******************************************************************************/
+void Debugger::SetEEInterface(EEDebugInterface* i)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ // @@@
+
+ // Implements DebugInterface API
+
+ g_pEEInterface = i;
+
+}
+
+
+/******************************************************************************
+// Called to shut down the debugger. This stops the RC thread and cleans
+// the object up.
+ ******************************************************************************/
+void Debugger::StopDebugger(void)
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ // Leak almost everything on process exit. The OS will clean it up anyways and trying to
+ // clean it up ourselves is just one more place we may AV / deadlock.
+
+#if defined(FEATURE_DBGIPC_TRANSPORT_VM)
+ ShutdownTransport();
+#endif // FEATURE_DBGIPC_TRANSPORT_VM
+
+ // Ping the helper thread to exit. This will also prevent the helper from servicing new requests.
+ if (m_pRCThread != NULL)
+ {
+ m_pRCThread->AsyncStop();
+ }
+
+ // Also clean up the AppDomain stuff since this is cross-process.
+ TerminateAppDomainIPC ();
+
+ //
+ // Tell the VM to clear out all references to the debugger before we start cleaning up,
+ // so that nothing will reference (accidentally) through the partially cleaned up debugger.
+ //
+ // NOTE: we cannot clear out g_pDebugger before the delete call because the
+ // stuff in delete (particularly deleteinteropsafe) needs to look at it.
+ //
+ g_pEEInterface->ClearAllDebugInterfaceReferences();
+ g_pDebugger = NULL;
+}
+
+
+/* ------------------------------------------------------------------------ *
+ * JIT Interface routines
+ * ------------------------------------------------------------------------ */
+
+
+/******************************************************************************
+ *
+ ******************************************************************************/
+DebuggerMethodInfo *Debugger::CreateMethodInfo(Module *module, mdMethodDef md)
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ THROWS;
+ GC_NOTRIGGER;
+
+ PRECONDITION(HasDebuggerDataLock());
+ }
+ CONTRACTL_END;
+
+
+ // <TODO>@todo perf: creating these on the heap is slow. We should use a
+ // pool and create them out of there since we never free them
+ // until the AD is unloaded.</TODO>
+ //
+ DebuggerMethodInfo *mi = new (interopsafe) DebuggerMethodInfo(module, md);
+ _ASSERTE(mi != NULL); // throws on oom error
+
+ TRACE_ALLOC(mi);
+
+ LOG((LF_CORDB, LL_INFO100000, "D::CreateMethodInfo module=%p, token=0x%08x, info=%p\n",
+ module, md, mi));
+
+ //
+ // Lock a mutex when changing the table.
+ //
+ //@TODO : _ASSERTE(EnC);
+ HRESULT hr;
+ hr =InsertToMethodInfoList(mi);
+
+ if (FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_EVERYTHING, "IAHOL Failed!!\n"));
+ DeleteInteropSafe(mi);
+ return NULL;
+ }
+ return mi;
+
+}
+
+
+
+
+
+/******************************************************************************
+// void Debugger::JITComplete(): JITComplete is called by
+// the jit interface when the JIT completes, successfully or not.
+//
+// MethodDesc* fd: MethodDesc of the code that's been JITted
+// BYTE* newAddress: The address of that the method begins at.
+// If newAddress is NULL then the JIT failed. Remember that this
+// gets called before the start address of the MethodDesc gets set,
+// and so methods like GetFunctionAddress & GetFunctionSize won't work.
+//
+// <TODO>@Todo If we're passed 0 for the newAddress param, the jit has been
+// cancelled & should be undone.</TODO>
+ ******************************************************************************/
+void Debugger::JITComplete(MethodDesc* fd, TADDR newAddress)
+{
+
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ THROWS;
+ PRECONDITION(!HasDebuggerDataLock());
+ PRECONDITION(newAddress != NULL);
+ CALLED_IN_DEBUGGERDATALOCK_HOLDER_SCOPE_MAY_GC_TRIGGERS_CONTRACT;
+ }
+ CONTRACTL_END;
+
+#ifdef _TARGET_ARM_
+ newAddress = newAddress|THUMB_CODE;
+#endif
+
+ // @@@
+ // Can be called on managed thread only
+ // This API Implements DebugInterface
+
+ if (CORDebuggerAttached())
+ {
+ // Populate the debugger's cache of DJIs. Normally we can do this lazily,
+ // the only reason we do it here is b/c the MethodDesc is not yet officially marked as "jitted",
+ // and so we can't lazily create it yet. Furthermore, the binding operations may need the DJIs.
+ //
+ // This also gives the debugger a chance to know if new JMC methods are coming.
+ DebuggerMethodInfo * dmi = GetOrCreateMethodInfo(fd->GetModule(), fd->GetMemberDef());
+ if (dmi == NULL)
+ {
+ goto Exit;
+ }
+ DebuggerJitInfo * ji = dmi->CreateInitAndAddJitInfo(fd, newAddress);
+
+ // Bind any IL patches to the newly jitted native code.
+ HRESULT hr;
+ hr = MapAndBindFunctionPatches(ji, fd, (CORDB_ADDRESS_TYPE *)newAddress);
+ _ASSERTE(SUCCEEDED(hr));
+ }
+
+ LOG((LF_CORDB, LL_EVERYTHING, "JitComplete completed successfully\n"));
+
+Exit:
+ ;
+}
+
+/******************************************************************************
+// Get the number of fixed arguments to a function, i.e., the explicit args and the "this" pointer.
+// This does not include other implicit arguments or varargs. This is used to compute a variable ID
+// (see comment in CordbJITILFrame::ILVariableToNative for more detail)
+// fVarArg is not used when this is called by Debugger::GetAndSendJITInfo, thus it has a default value.
+// The return value is not used when this is called by Debugger::getVars.
+ ******************************************************************************/
+SIZE_T Debugger::GetArgCount(MethodDesc *fd,BOOL *fVarArg /* = NULL */)
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ THROWS;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ // Create a MetaSig for the given method's sig. (Easier than
+ // picking the sig apart ourselves.)
+ PCCOR_SIGNATURE pCallSig;
+ DWORD cbCallSigSize;
+
+ fd->GetSig(&pCallSig, &cbCallSigSize);
+
+ if (pCallSig == NULL)
+ {
+ // Sig should only be null if the image is corrupted. (Even for lightweight-codegen)
+ // We expect the jit+verifier to catch this, so that we never land here.
+ // But just in case ...
+ CONSISTENCY_CHECK_MSGF(false, ("Corrupted image, null sig.(%s::%s)", fd->m_pszDebugClassName, fd->m_pszDebugMethodName));
+ return 0;
+ }
+
+ MetaSig msig(pCallSig, cbCallSigSize, g_pEEInterface->MethodDescGetModule(fd), NULL, MetaSig::sigMember);
+
+ // Get the arg count.
+ UINT32 NumArguments = msig.NumFixedArgs();
+
+ // Account for the 'this' argument.
+ if (!(g_pEEInterface->MethodDescIsStatic(fd)))
+ NumArguments++;
+
+ // Is this a VarArg's function?
+ if (msig.IsVarArg() && fVarArg != NULL)
+ {
+ *fVarArg = true;
+ }
+
+ return NumArguments;
+}
+
+#endif // #ifndef DACCESS_COMPILE
+
+
+
+
+
+/******************************************************************************
+ DebuggerJitInfo * Debugger::GetJitInfo(): GetJitInfo
+ will return a pointer to a DebuggerJitInfo. If the DJI
+ doesn't exist, or it does exist, but the method has actually
+ been pitched (and the caller wants pitched methods filtered out),
+ then we'll return NULL.
+
+ Note: This will also create a DMI for if one does not exist for this DJI.
+
+ MethodDesc* fd: MethodDesc for the method we're interested in.
+ CORDB_ADDRESS_TYPE * pbAddr: Address within the code, to indicate which
+ version we want. If this is NULL, then we want the
+ head of the DebuggerJitInfo list, whether it's been
+ JITted or not.
+ ******************************************************************************/
+
+
+// Get a DJI from an address.
+DebuggerJitInfo *Debugger::GetJitInfoFromAddr(TADDR addr)
+{
+ WRAPPER_NO_CONTRACT;
+
+ MethodDesc *fd;
+ fd = g_pEEInterface->GetNativeCodeMethodDesc(addr);
+ _ASSERTE(fd);
+
+ return GetJitInfo(fd, (const BYTE*) addr, NULL);
+}
+
+// Get a DJI for a Native MD (MD for a native function).
+// In the EnC scenario, the MethodDesc refers to the most recent method.
+// This is very dangerous since there may be multiple versions alive at the same time.
+// This will give back the wrong DJI if we're lookikng for a stale method desc.
+// @todo - can a caller possibly use this correctly?
+DebuggerJitInfo *Debugger::GetLatestJitInfoFromMethodDesc(MethodDesc * pMethodDesc)
+{
+ WRAPPER_NO_CONTRACT;
+
+ _ASSERTE(pMethodDesc != NULL);
+ // We'd love to assert that we're jitted; but since this may be in the JitComplete
+ // callback path, we can't be sure.
+
+ return GetJitInfoWorker(pMethodDesc, NULL, NULL);
+}
+
+
+DebuggerJitInfo *Debugger::GetJitInfo(MethodDesc *fd, const BYTE *pbAddr, DebuggerMethodInfo **pMethInfo )
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ THROWS;
+ GC_NOTRIGGER;
+ PRECONDITION(!g_pDebugger->HasDebuggerDataLock());
+ }
+ CONTRACTL_END;
+
+ // Address should be non-null and in range of MethodDesc. This lets us tell which EnC version.
+ _ASSERTE(pbAddr != NULL);
+
+ return GetJitInfoWorker(fd, pbAddr, pMethInfo);
+
+}
+
+// Internal worker to GetJitInfo. Doesn't validate parameters.
+DebuggerJitInfo *Debugger::GetJitInfoWorker(MethodDesc *fd, const BYTE *pbAddr, DebuggerMethodInfo **pMethInfo)
+{
+
+ DebuggerMethodInfo *dmi = NULL;
+ DebuggerJitInfo *dji = NULL;
+
+ // If we have a null MethodDesc - we're not going to get a jit-info. Do this check once at the top
+ // rather than littered throughout the rest of this function.
+ if (fd == NULL)
+ {
+ LOG((LF_CORDB, LL_EVERYTHING, "Debugger::GetJitInfo, addr=0x%p - null fd - returning null\n", pbAddr));
+ return NULL;
+ }
+ else
+ {
+ CONSISTENCY_CHECK_MSGF(!fd->IsWrapperStub(), ("Can't get Jit-info for wrapper MDesc,'%s'", fd->m_pszDebugMethodName));
+ }
+
+ // The debugger doesn't track Lightweight-codegen methods b/c they have no metadata.
+ if (fd->IsDynamicMethod())
+ {
+ return NULL;
+ }
+
+
+ // initialize our out param
+ if (pMethInfo)
+ {
+ *pMethInfo = NULL;
+ }
+
+ LOG((LF_CORDB, LL_EVERYTHING, "Debugger::GetJitInfo called\n"));
+ // CHECK_DJI_TABLE_DEBUGGER;
+
+ // Find the DJI via the DMI
+ //
+ // One way to improve the perf, both in terms of memory usage, number of allocations
+ // and lookup speeds would be to have the first JitInfo inline in the MethodInfo
+ // struct. After all, we never want to have a MethodInfo in the table without an
+ // associated JitInfo, and this should bring us back very close to the old situation
+ // in terms of perf. But correctness comes first, and perf later...
+ // CHECK_DMI_TABLE;
+ dmi = GetOrCreateMethodInfo(fd->GetModule(), fd->GetMemberDef());
+
+ if (dmi == NULL)
+ {
+ // If we can't create the DMI, we won't be able to create the DJI.
+ return NULL;
+ }
+
+
+ // This may take the lock and lazily create an entry, so we do it up front.
+ dji = dmi->GetLatestJitInfo(fd);
+
+
+ DebuggerDataLockHolder debuggerDataLockHolder(this);
+
+ // Note the call to GetLatestJitInfo() will lazily create the first DJI if we don't already have one.
+ for (; dji != NULL; dji = dji->m_prevJitInfo)
+ {
+ if (PTR_TO_TADDR(dji->m_fd) == PTR_HOST_TO_TADDR(fd))
+ {
+ break;
+ }
+ }
+ LOG((LF_CORDB, LL_INFO1000, "D::GJI: for md:0x%x (%s::%s), got dmi:0x%x.\n",
+ fd, fd->m_pszDebugClassName, fd->m_pszDebugMethodName,
+ dmi));
+
+
+
+
+ // Log stuff - fd may be null; so we don't want to AV in the log.
+
+ LOG((LF_CORDB, LL_INFO1000, "D::GJI: for md:0x%x (%s::%s), got dmi:0x%x, dji:0x%x, latest dji:0x%x, latest fd:0x%x, prev dji:0x%x\n",
+ fd, fd->m_pszDebugClassName, fd->m_pszDebugMethodName,
+ dmi, dji, (dmi ? dmi->GetLatestJitInfo_NoCreate() : 0),
+ ((dmi && dmi->GetLatestJitInfo_NoCreate()) ? dmi->GetLatestJitInfo_NoCreate()->m_fd:0),
+ (dji?dji->m_prevJitInfo:0)));
+
+ if ((dji != NULL) && (pbAddr != NULL))
+ {
+ dji = dji->GetJitInfoByAddress(pbAddr);
+
+ // XXX Microsoft - dac doesn't support stub tracing
+ // so this just results in not-impl exceptions.
+#ifndef DACCESS_COMPILE
+ if (dji == NULL) //may have been given address of a thunk
+ {
+ LOG((LF_CORDB,LL_INFO1000,"Couldn't find a DJI by address 0x%p, "
+ "so it might be a stub or thunk\n", pbAddr));
+ TraceDestination trace;
+
+ g_pEEInterface->TraceStub((const BYTE *)pbAddr, &trace);
+
+ if ((trace.GetTraceType() == TRACE_MANAGED) && (pbAddr != (const BYTE *)trace.GetAddress()))
+ {
+ LOG((LF_CORDB,LL_INFO1000,"Address thru thunk"
+ ": 0x%p\n", trace.GetAddress()));
+ dji = GetJitInfo(fd, dac_cast<PTR_CBYTE>(trace.GetAddress()));
+ }
+#ifdef LOGGING
+ else
+ {
+ _ASSERTE(trace.GetTraceType() != TRACE_UNJITTED_METHOD ||
+ (fd == trace.GetMethodDesc()));
+ LOG((LF_CORDB,LL_INFO1000,"Address not thunked - "
+ "must be to unJITted method, or normal managed "
+ "method lacking a DJI!\n"));
+ }
+#endif //LOGGING
+ }
+#endif // #ifndef DACCESS_COMPILE
+ }
+
+ if (pMethInfo)
+ {
+ *pMethInfo = dmi;
+ }
+
+ // DebuggerDataLockHolder out of scope - release implied
+
+ return dji;
+}
+
+DebuggerMethodInfo *Debugger::GetOrCreateMethodInfo(Module *pModule, mdMethodDef token)
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ SUPPORTS_DAC;
+ THROWS;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ DebuggerMethodInfo *info = NULL;
+
+ // When dump debugging, we don't expect to have a lock,
+ // nor would it be useful for anything.
+ ALLOW_DATATARGET_MISSING_MEMORY(
+ // In case we don't have already, take it now.
+ DebuggerDataLockHolder debuggerDataLockHolder(this);
+ );
+
+ if (m_pMethodInfos != NULL)
+ {
+ info = m_pMethodInfos->GetMethodInfo(pModule, token);
+ }
+
+ // dac checks ngen'ed image content first, so
+ // if we didn't find information it doesn't exist.
+#ifndef DACCESS_COMPILE
+ if (info == NULL)
+ {
+ info = CreateMethodInfo(pModule, token);
+
+ LOG((LF_CORDB, LL_INFO1000, "D::GOCMI: created DMI for mdToken:0x%x, dmi:0x%x\n",
+ token, info));
+ }
+#endif // #ifndef DACCESS_COMPILE
+
+
+ if (info == NULL)
+ {
+ // This should only happen in an oom scenario. It would be nice to throw here.
+ STRESS_LOG2(LF_CORDB, LL_EVERYTHING, "OOM - Failed to allocate DJI (0x%p, 0x%x)\n", pModule, token);
+ }
+
+ // DebuggerDataLockHolder out of scope - release implied
+ return info;
+}
+
+
+#ifndef DACCESS_COMPILE
+
+/******************************************************************************
+ * GetILToNativeMapping returns a map from IL offsets to native
+ * offsets for this code. An array of COR_PROF_IL_TO_NATIVE_MAP
+ * structs will be returned, and some of the ilOffsets in this array
+ * may be the values specified in CorDebugIlToNativeMappingTypes.
+ ******************************************************************************/
+HRESULT Debugger::GetILToNativeMapping(MethodDesc *pMD, ULONG32 cMap,
+ ULONG32 *pcMap, COR_DEBUG_IL_TO_NATIVE_MAP map[])
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ THROWS;
+ GC_TRIGGERS_FROM_GETJITINFO;
+ }
+ CONTRACTL_END;
+
+#ifdef PROFILING_SUPPORTED
+ // At this point, we're pulling in the debugger.
+ if (!HasLazyData())
+ {
+ DebuggerLockHolder lockHolder(this);
+ LazyInit(); // throws
+ }
+
+ // Get the JIT info by functionId.
+
+ // This function is unsafe to use during EnC because the MethodDesc doesn't tell
+ // us which version is being requested.
+ // However, this function is only used by the profiler, and you can't profile with EnC,
+ // which means that getting the latest jit-info is still correct.
+#if defined(PROFILING_SUPPORTED)
+ _ASSERTE(CORProfilerPresent());
+#endif // PROFILING_SUPPORTED
+
+ DebuggerJitInfo *pDJI = GetLatestJitInfoFromMethodDesc(pMD);
+
+ // Dunno what went wrong
+ if (pDJI == NULL)
+ return (E_FAIL);
+
+ // If they gave us space to copy into...
+ if (map != NULL)
+ {
+ // Only copy as much as either they gave us or we have to copy.
+ ULONG32 cpyCount = min(cMap, pDJI->GetSequenceMapCount());
+
+ // Read the map right out of the Left Side.
+ if (cpyCount > 0)
+ ExportILToNativeMap(cpyCount,
+ map,
+ pDJI->GetSequenceMap(),
+ pDJI->m_sizeOfCode);
+ }
+
+ // Return the true count of entries
+ if (pcMap)
+ {
+ *pcMap = pDJI->GetSequenceMapCount();
+ }
+
+ return (S_OK);
+#else
+ return E_NOTIMPL;
+#endif
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// This is morally the same as GetILToNativeMapping, except the output is in a different
+// format, to better facilitate sending the ETW ILToNativeMap events.
+//
+// Arguments:
+// pMD - MethodDesc whose IL-to-native map will be returned
+// cMapMax - Max number of map entries to return. Although
+// this function handles the allocation of the returned
+// array, the caller still wants to limit how big this
+// can get, since ETW itself has limits on how big
+// events can get
+// pcMap - [out] Number of entries returned in each output parallel array (next
+// two parameters).
+// prguiILOffset - [out] Array of IL offsets. This function allocates, caller must free.
+// prguiNativeOffset - [out] Array of the starting native offsets that correspond
+// to each (*prguiILOffset)[i]. This function allocates,
+// caller must free.
+//
+// Return Value:
+// HRESULT indicating success or failure.
+//
+// Notes:
+// * This function assumes lazy data has already been initialized (in order to
+// ensure that this doesn't trigger or take the large debugger mutex). So
+// callers must guarantee they call InitializeLazyDataIfNecessary() first.
+// * Either this function fails, and (*prguiILOffset) & (*prguiNativeOffset) will be
+// untouched OR this function succeeds and (*prguiILOffset) & (*prguiNativeOffset)
+// will both be non-NULL, set to the parallel arrays this function allocated.
+// * If this function returns success, then the caller must free (*prguiILOffset) and
+// (*prguiNativeOffset)
+// * (*prguiILOffset) and (*prguiNativeOffset) are parallel arrays, such that
+// (*prguiILOffset)[i] corresponds to (*prguiNativeOffset)[i] for each 0 <= i < *pcMap
+// * If EnC is enabled, this function will return the IL-to-native mapping for the latest
+// EnC version of the function. This may not be what the profiler wants, but EnC
+// + ETW-map events is not a typical combination, and this is consistent with
+// other ETW events like JittingStarted or MethodLoad, which also fire multiple
+// events for the same MethodDesc (each time it's EnC'd), with each event
+// corresponding to the most recent EnC version at the time.
+//
+
+HRESULT Debugger::GetILToNativeMappingIntoArrays(
+ MethodDesc * pMD,
+ USHORT cMapMax,
+ USHORT * pcMap,
+ UINT ** prguiILOffset,
+ UINT ** prguiNativeOffset)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ THROWS;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(pcMap != NULL);
+ _ASSERTE(prguiILOffset != NULL);
+ _ASSERTE(prguiNativeOffset != NULL);
+
+ // Any caller of GetILToNativeMappingIntoArrays had better call
+ // InitializeLazyDataIfNecessary first!
+ _ASSERTE(HasLazyData());
+
+ // Get the JIT info by functionId.
+
+ DebuggerJitInfo * pDJI = GetLatestJitInfoFromMethodDesc(pMD);
+
+ // Dunno what went wrong
+ if (pDJI == NULL)
+ return E_FAIL;
+
+ ULONG32 cMap = min(cMapMax, pDJI->GetSequenceMapCount());
+ DebuggerILToNativeMap * rgMapInt = pDJI->GetSequenceMap();
+
+ NewArrayHolder<UINT> rguiILOffsetTemp = new (nothrow) UINT[cMap];
+ if (rguiILOffsetTemp == NULL)
+ return E_OUTOFMEMORY;
+
+ NewArrayHolder<UINT> rguiNativeOffsetTemp = new (nothrow) UINT[cMap];
+ if (rguiNativeOffsetTemp == NULL)
+ return E_OUTOFMEMORY;
+
+ for (ULONG32 iMap=0; iMap < cMap; iMap++)
+ {
+ rguiILOffsetTemp[iMap] = rgMapInt[iMap].ilOffset;
+ rguiNativeOffsetTemp[iMap] = rgMapInt[iMap].nativeStartOffset;
+ }
+
+ // Since cMap is the min of cMapMax (and something else) and cMapMax is a USHORT,
+ // then cMap must fit in a USHORT as well
+ _ASSERTE(FitsIn<USHORT>(cMap));
+ *pcMap = (USHORT) cMap;
+ *prguiILOffset = rguiILOffsetTemp.Extract();
+ *prguiNativeOffset = rguiNativeOffsetTemp.Extract();
+
+ return S_OK;
+}
+
+
+
+
+#endif // #ifndef DACCESS_COMPILE
+
+/******************************************************************************
+ *
+ ******************************************************************************/
+CodeRegionInfo CodeRegionInfo::GetCodeRegionInfo(DebuggerJitInfo *dji, MethodDesc *md, PTR_CORDB_ADDRESS_TYPE addr)
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ NOTHROW;
+ GC_NOTRIGGER;
+ SUPPORTS_DAC;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ if (dji && dji->m_addrOfCode)
+ {
+ LOG((LF_CORDB, LL_EVERYTHING, "CRI::GCRI: simple case\n"));
+ return dji->m_codeRegionInfo;
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_EVERYTHING, "CRI::GCRI: more complex case\n"));
+ CodeRegionInfo codeRegionInfo;
+
+ // Use method desc from dji if present
+ if (dji && dji->m_fd)
+ {
+ _ASSERTE(!md || md == dji->m_fd);
+ md = dji->m_fd;
+ }
+
+ if (!addr)
+ {
+ _ASSERTE(md);
+ addr = dac_cast<PTR_CORDB_ADDRESS_TYPE>(g_pEEInterface->GetFunctionAddress(md));
+ }
+ else
+ {
+ _ASSERTE(!md ||
+ (addr == dac_cast<PTR_CORDB_ADDRESS_TYPE>(g_pEEInterface->GetFunctionAddress(md))));
+ }
+
+ if (addr)
+ {
+ PCODE pCode = (PCODE)dac_cast<TADDR>(addr);
+#ifdef _TARGET_ARM_
+ pCode |= THUMB_CODE;
+#endif
+ codeRegionInfo.InitializeFromStartAddress(pCode);
+ }
+
+ return codeRegionInfo;
+ }
+}
+
+
+#ifndef DACCESS_COMPILE
+/******************************************************************************
+// Helper function for getBoundaries to get around AMD64 compiler and
+// contract holders with PAL_TRY in the same function.
+ ******************************************************************************/
+void Debugger::getBoundariesHelper(MethodDesc * md,
+ unsigned int *cILOffsets,
+ DWORD **pILOffsets)
+{
+ //
+ // CANNOT ADD A CONTRACT HERE. Contract is in getBoundaries
+ //
+
+ //
+ // Grab the JIT info struct for this method. Create if needed, as this
+ // may be called before JITComplete.
+ //
+ DebuggerMethodInfo *dmi = NULL;
+ dmi = GetOrCreateMethodInfo(md->GetModule(), md->GetMemberDef());
+
+ if (dmi != NULL)
+ {
+ LOG((LF_CORDB,LL_INFO10000,"De::NGB: Got dmi 0x%x\n",dmi));
+
+#if defined(FEATURE_ISYM_READER)
+ // Note: we need to make sure to enable preemptive GC here just in case we block in the symbol reader.
+ GCX_PREEMP_EEINTERFACE();
+
+ Module *pModule = md->GetModule();
+ (void)pModule; //prevent "unused variable" error from GCC
+ _ASSERTE(pModule != NULL);
+
+ SafeComHolder<ISymUnmanagedReader> pReader(pModule->GetISymUnmanagedReader());
+
+ // If we got a reader, use it.
+ if (pReader != NULL)
+ {
+ // Grab the sym reader's method.
+ ISymUnmanagedMethod *pISymMethod;
+
+ HRESULT hr = pReader->GetMethod(md->GetMemberDef(),
+ &pISymMethod);
+
+ ULONG32 n = 0;
+
+ if (SUCCEEDED(hr))
+ {
+ // Get the count of sequence points.
+ hr = pISymMethod->GetSequencePointCount(&n);
+ _ASSERTE(SUCCEEDED(hr));
+
+
+ LOG((LF_CORDB, LL_INFO100000,
+ "D::NGB: Reader seq pt count is %d\n", n));
+
+ ULONG32 *p;
+
+ if (n > 0)
+ {
+ ULONG32 dummy;
+
+ p = new ULONG32[n];
+ _ASSERTE(p != NULL); // throws on oom errror
+
+ hr = pISymMethod->GetSequencePoints(n, &dummy,
+ p, NULL, NULL, NULL,
+ NULL, NULL);
+ _ASSERTE(SUCCEEDED(hr));
+ _ASSERTE(dummy == n);
+
+ *pILOffsets = (DWORD*)p;
+
+ // Translate the IL offets based on an
+ // instrumented IL map if one exists.
+ if (dmi->HasInstrumentedILMap())
+ {
+ InstrumentedILOffsetMapping mapping =
+ dmi->GetRuntimeModule()->GetInstrumentedILOffsetMapping(dmi->m_token);
+
+ for (SIZE_T i = 0; i < n; i++)
+ {
+ int origOffset = *p;
+
+ *p = dmi->TranslateToInstIL(
+ &mapping,
+ origOffset,
+ bOriginalToInstrumented);
+
+ LOG((LF_CORDB, LL_INFO100000,
+ "D::NGB: 0x%04x (Real IL:0x%x)\n",
+ origOffset, *p));
+
+ p++;
+ }
+ }
+#ifdef LOGGING
+ else
+ {
+ for (SIZE_T i = 0; i < n; i++)
+ {
+ LOG((LF_CORDB, LL_INFO100000,
+ "D::NGB: 0x%04x \n", *p));
+ p++;
+ }
+ }
+#endif
+ }
+ else
+ *pILOffsets = NULL;
+
+ pISymMethod->Release();
+ }
+ else
+ {
+
+ *pILOffsets = NULL;
+
+ LOG((LF_CORDB, LL_INFO10000,
+ "De::NGB: failed to find method 0x%x in sym reader.\n",
+ md->GetMemberDef()));
+ }
+
+ *cILOffsets = n;
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO100000, "D::NGB: no reader.\n"));
+ }
+
+#else // FEATURE_ISYM_READER
+ // We don't have ISymUnmanagedReader. Pretend there are no sequence points.
+ *cILOffsets = 0;
+#endif // FEATURE_ISYM_READER
+ }
+
+ LOG((LF_CORDB, LL_INFO100000, "D::NGB: cILOffsets=%d\n", *cILOffsets));
+ return;
+}
+#endif
+
+/******************************************************************************
+// Use an ISymUnmanagedReader to get method sequence points.
+ ******************************************************************************/
+void Debugger::getBoundaries(MethodDesc * md,
+ unsigned int *cILOffsets,
+ DWORD **pILOffsets,
+ ICorDebugInfo::BoundaryTypes *implicitBoundaries)
+{
+#ifndef DACCESS_COMPILE
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ }
+ CONTRACTL_END;
+
+ // May be here even when a debugger is not attached.
+
+ // @@@
+ // Implements DebugInterface API
+
+ *cILOffsets = 0;
+ *pILOffsets = NULL;
+ *implicitBoundaries = ICorDebugInfo::DEFAULT_BOUNDARIES;
+ // If there has been an unrecoverable Left Side error, then we
+ // just pretend that there are no boundaries.
+ if (CORDBUnrecoverableError(this))
+ {
+ return;
+ }
+
+ // LCG methods have their own resolution scope that is seperate from a module
+ // so they shouldn't have their symbols looked up in the module PDB. Right now
+ // LCG methods have no symbols so we can just early out, but if they ever
+ // had some symbols attached we would need a different way of getting to them.
+ // See Dev10 issue 728519
+ if(md->IsLCGMethod())
+ {
+ return;
+ }
+
+ // If JIT optimizations are allowed for the module this function
+ // lives in, then don't grab specific boundaries from the symbol
+ // store since any boundaries we give the JIT will be pretty much
+ // ignored anyway.
+ if (!CORDisableJITOptimizations(md->GetModule()->GetDebuggerInfoBits()))
+ {
+ *implicitBoundaries = ICorDebugInfo::BoundaryTypes(ICorDebugInfo::STACK_EMPTY_BOUNDARIES |
+ ICorDebugInfo::CALL_SITE_BOUNDARIES);
+
+ return;
+ }
+
+ Module* pModule = md->GetModule();
+ DWORD dwBits = pModule->GetDebuggerInfoBits();
+ if ((dwBits & DACF_IGNORE_PDBS) != 0)
+ {
+ //
+ // If told to explicitly ignore PDBs for this function, then bail now.
+ //
+ return;
+ }
+
+ if( !pModule->IsSymbolReadingEnabled() )
+ {
+ // Symbol reading is disabled for this module, so bail out early (for efficiency only)
+ return;
+ }
+
+ if (pModule == SystemDomain::SystemModule())
+ {
+ // We don't look up PDBs for mscorlib. This is not quite right, but avoids
+ // a bootstrapping problem. When an EXE loads, it has the option of setting
+ // the COM appartment model to STA if we need to. It is important that no
+ // other Coinitialize happens before this. Since loading the PDB reader uses
+ // com we can not come first. However managed code IS run before the COM
+ // appartment model is set, and thus we have a problem since this code is
+ // called for when JITTing managed code. We avoid the problem by just
+ // bailing for mscorlib.
+ return;
+ }
+
+ // At this point, we're pulling in the debugger.
+ if (!HasLazyData())
+ {
+ DebuggerLockHolder lockHolder(this);
+ LazyInit(); // throws
+ }
+
+ getBoundariesHelper(md, cILOffsets, pILOffsets);
+
+#else
+ DacNotImpl();
+#endif // #ifndef DACCESS_COMPILE
+}
+
+
+/******************************************************************************
+ *
+ ******************************************************************************/
+void Debugger::getVars(MethodDesc * md, ULONG32 *cVars, ICorDebugInfo::ILVarInfo **vars,
+ bool *extendOthers)
+{
+#ifndef DACCESS_COMPILE
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ THROWS;
+ GC_TRIGGERS_FROM_GETJITINFO;
+ PRECONDITION(!ThisIsHelperThreadWorker());
+ }
+ CONTRACTL_END;
+
+
+
+ // At worst return no information
+ *cVars = 0;
+ *vars = NULL;
+
+ // Just tell the JIT to extend everything.
+ // Note that if optimizations are enabled, the native compilers are
+ // free to ingore *extendOthers
+ *extendOthers = true;
+
+ DWORD bits = md->GetModule()->GetDebuggerInfoBits();
+
+ if (CORDBUnrecoverableError(this))
+ goto Exit;
+
+ if (CORDisableJITOptimizations(bits))
+// if (!CORDebuggerAllowJITOpts(bits))
+ {
+ //
+ // @TODO: Do we really need this code since *extendOthers==true?
+ //
+
+ // Is this a vararg function?
+ BOOL fVarArg = false;
+ GetArgCount(md, &fVarArg);
+
+ if (fVarArg)
+ {
+ COR_ILMETHOD *ilMethod = g_pEEInterface->MethodDescGetILHeader(md);
+
+ if (ilMethod)
+ {
+ // It is, so we need to tell the JIT to give us the
+ // varags handle.
+ ICorDebugInfo::ILVarInfo *p = new ICorDebugInfo::ILVarInfo[1];
+ _ASSERTE(p != NULL); // throws on oom error
+
+ COR_ILMETHOD_DECODER header(ilMethod);
+ unsigned int ilCodeSize = header.GetCodeSize();
+
+ p->startOffset = 0;
+ p->endOffset = ilCodeSize;
+ p->varNumber = (DWORD) ICorDebugInfo::VARARGS_HND_ILNUM;
+
+ *cVars = 1;
+ *vars = p;
+ }
+ }
+ }
+
+ LOG((LF_CORDB, LL_INFO100000, "D::gV: cVars=%d, extendOthers=%d\n",
+ *cVars, *extendOthers));
+
+Exit:
+ ;
+#else
+ DacNotImpl();
+#endif // #ifndef DACCESS_COMPILE
+}
+
+
+#ifndef DACCESS_COMPILE
+
+// If we have a varargs function, we can't set the IP (we don't know how to pack/unpack the arguments), so if we
+// call SetIP with fCanSetIPOnly = true, we need to check for that.
+// Arguments:
+// input: nEntries - number of entries in varNativeInfo
+// varNativeInfo - array of entries describing the args and locals for the function
+// output: true iff the function has varargs
+BOOL Debugger::IsVarArgsFunction(unsigned int nEntries, PTR_NativeVarInfo varNativeInfo)
+{
+ for (unsigned int i = 0; i < nEntries; ++i)
+ {
+ if (varNativeInfo[i].loc.vlType == ICorDebugInfo::VLT_FIXED_VA)
+ {
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+// We want to keep the 'worst' HRESULT - if one has failed (..._E_...) & the
+// other hasn't, take the failing one. If they've both/neither failed, then
+// it doesn't matter which we take.
+// Note that this macro favors retaining the first argument
+#define WORST_HR(hr1,hr2) (FAILED(hr1)?hr1:hr2)
+/******************************************************************************
+ *
+ ******************************************************************************/
+HRESULT Debugger::SetIP( bool fCanSetIPOnly, Thread *thread,Module *module,
+ mdMethodDef mdMeth, DebuggerJitInfo* dji,
+ SIZE_T offsetILTo, BOOL fIsIL)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ PRECONDITION(CheckPointer(thread));
+ PRECONDITION(CheckPointer(module));
+ PRECONDITION(mdMeth != mdMethodDefNil);
+ }
+ CONTRACTL_END;
+
+#ifdef _DEBUG
+ static ConfigDWORD breakOnSetIP;
+ if (breakOnSetIP.val(CLRConfig::INTERNAL_DbgBreakOnSetIP)) _ASSERTE(!"DbgBreakOnSetIP");
+#endif
+
+ HRESULT hr = S_OK;
+ HRESULT hrAdvise = S_OK;
+
+ DWORD offsetILFrom;
+ CorDebugMappingResult map;
+ DWORD whichIgnore;
+
+ ControllerStackInfo csi;
+
+ BOOL exact;
+ SIZE_T offsetNatTo;
+
+ PCODE pbDest = NULL;
+ BYTE *pbBase = NULL;
+ CONTEXT *pCtx = NULL;
+ DWORD dwSize = 0;
+ SIZE_T *rgVal1 = NULL;
+ SIZE_T *rgVal2 = NULL;
+ BYTE **pVCs = NULL;
+
+ LOG((LF_CORDB, LL_INFO1000, "D::SIP: In SetIP ==> fCanSetIPOnly:0x%x <==!\n", fCanSetIPOnly));
+
+ if (ReJitManager::IsReJITEnabled())
+ {
+ return CORDBG_E_SET_IP_IMPOSSIBLE;
+ }
+
+ pCtx = GetManagedStoppedCtx(thread);
+
+ // If we can't get a context, then we can't possibly be a in a good place
+ // to do a setip.
+ if (pCtx == NULL)
+ {
+ return CORDBG_S_BAD_START_SEQUENCE_POINT;
+ }
+
+ // Implicit Caveat: We need to be the active frame.
+ // We can safely take a stack trace because the thread is synchronized.
+ StackTraceTicket ticket(thread);
+ csi.GetStackInfo(ticket, thread, LEAF_MOST_FRAME, NULL);
+
+ ULONG offsetNatFrom = csi.m_activeFrame.relOffset;
+#if defined(WIN64EXCEPTIONS)
+ if (csi.m_activeFrame.IsFuncletFrame())
+ {
+ offsetNatFrom = (ULONG)((SIZE_T)GetControlPC(&(csi.m_activeFrame.registers)) -
+ (SIZE_T)(dji->m_addrOfCode));
+ }
+#endif // WIN64EXCEPTIONS
+
+ _ASSERTE(dji != NULL);
+
+ // On WIN64 platforms, it's important to use the total size of the
+ // parent method and the funclets below (i.e. m_sizeOfCode). Don't use
+ // the size of the individual funclets or the parent method.
+ pbBase = (BYTE*)CORDB_ADDRESS_TO_PTR(dji->m_addrOfCode);
+ dwSize = (DWORD)dji->m_sizeOfCode;
+#if defined(WIN64EXCEPTIONS)
+ // Currently, method offsets are not bigger than 4 bytes even on WIN64.
+ // Assert that it is so here.
+ _ASSERTE((SIZE_T)dwSize == dji->m_sizeOfCode);
+#endif // WIN64EXCEPTIONS
+
+
+ // Create our structure for analyzing this.
+ // <TODO>@PERF: optimize - hold on to this so we don't rebuild it for both
+ // CanSetIP & SetIP.</TODO>
+ int cFunclet = 0;
+ const DWORD * rgFunclet = NULL;
+#if defined(WIN64EXCEPTIONS)
+ cFunclet = dji->GetFuncletCount();
+ rgFunclet = dji->m_rgFunclet;
+#endif // WIN64EXCEPTIONS
+
+ EHRangeTree* pEHRT = new (nothrow) EHRangeTree(csi.m_activeFrame.pIJM,
+ csi.m_activeFrame.MethodToken,
+ dwSize,
+ cFunclet,
+ rgFunclet);
+
+ // To maintain the current semantics, we will check the following right before SetIPFromSrcToDst() is called
+ // (instead of checking them now):
+ // 1) pEHRT == NULL
+ // 2) FAILED(pEHRT->m_hrInit)
+
+
+ {
+ LOG((LF_CORDB, LL_INFO1000, "D::SIP:Got version info fine\n"));
+
+ // Caveat: we need to start from a sequence point
+ offsetILFrom = dji->MapNativeOffsetToIL(offsetNatFrom,
+ &map, &whichIgnore);
+ if ( !(map & MAPPING_EXACT) )
+ {
+ LOG((LF_CORDB, LL_INFO1000, "D::SIP:Starting native offset is bad!\n"));
+ hrAdvise = WORST_HR(hrAdvise, CORDBG_S_BAD_START_SEQUENCE_POINT);
+ }
+ else
+ { // exact IL mapping
+
+ if (!(dji->GetSrcTypeFromILOffset(offsetILFrom) & ICorDebugInfo::STACK_EMPTY))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "D::SIP:Starting offset isn't stack empty!\n"));
+ hrAdvise = WORST_HR(hrAdvise, CORDBG_S_BAD_START_SEQUENCE_POINT);
+ }
+ }
+
+ // Caveat: we need to go to a sequence point
+ if (fIsIL )
+ {
+#if defined(WIN64EXCEPTIONS)
+ int funcletIndexFrom = dji->GetFuncletIndex((CORDB_ADDRESS)offsetNatFrom, DebuggerJitInfo::GFIM_BYOFFSET);
+ offsetNatTo = dji->MapILOffsetToNativeForSetIP(offsetILTo, funcletIndexFrom, pEHRT, &exact);
+#else // WIN64EXCEPTIONS
+ DebuggerJitInfo::ILToNativeOffsetIterator it;
+ dji->InitILToNativeOffsetIterator(it, offsetILTo);
+ offsetNatTo = it.CurrentAssertOnlyOne(&exact);
+#endif // WIN64EXCEPTIONS
+
+ if (!exact)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "D::SIP:Dest (via IL offset) is bad!\n"));
+ hrAdvise = WORST_HR(hrAdvise, CORDBG_S_BAD_END_SEQUENCE_POINT);
+ }
+ }
+ else
+ {
+ offsetNatTo = offsetILTo;
+ LOG((LF_CORDB, LL_INFO1000, "D::SIP:Dest of 0x%p (via native "
+ "offset) is fine!\n", offsetNatTo));
+ }
+
+ CorDebugMappingResult mapping;
+ DWORD which;
+ offsetILTo = dji->MapNativeOffsetToIL(offsetNatTo, &mapping, &which);
+
+ // We only want to perhaps return CORDBG_S_BAD_END_SEQUENCE_POINT if
+ // we're not already returning CORDBG_S_BAD_START_SEQUENCE_POINT.
+ if (hr != CORDBG_S_BAD_START_SEQUENCE_POINT)
+ {
+ if ( !(mapping & MAPPING_EXACT) )
+ {
+ LOG((LF_CORDB, LL_INFO1000, "D::SIP:Ending native offset is bad!\n"));
+ hrAdvise = WORST_HR(hrAdvise, CORDBG_S_BAD_END_SEQUENCE_POINT);
+ }
+ else
+ {
+ // <NOTE WIN64>
+ // All duplicate sequence points (ones with the same IL offset) should have the same SourceTypes.
+ // </NOTE WIN64>
+ if (!(dji->GetSrcTypeFromILOffset(offsetILTo) & ICorDebugInfo::STACK_EMPTY))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "D::SIP:Ending offset isn't a sequence"
+ " point, or not stack empty!\n"));
+ hrAdvise = WORST_HR(hrAdvise, CORDBG_S_BAD_END_SEQUENCE_POINT);
+ }
+ }
+ }
+
+ // Once we finally have a native offset, it had better be in range.
+ if (offsetNatTo >= dwSize)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "D::SIP:Code out of range! offsetNatTo = 0x%x, dwSize=0x%x\n", offsetNatTo, dwSize));
+ hrAdvise = E_INVALIDARG;
+ goto LExit;
+ }
+
+ pbDest = CodeRegionInfo::GetCodeRegionInfo(dji).OffsetToAddress(offsetNatTo);
+ LOG((LF_CORDB, LL_INFO1000, "D::SIP:Dest is 0x%p\n", pbDest));
+
+ // Don't allow SetIP if the source or target is cold (SetIPFromSrcToDst does not
+ // correctly handle this case).
+ if (!CodeRegionInfo::GetCodeRegionInfo(dji).IsOffsetHot(offsetNatTo) ||
+ !CodeRegionInfo::GetCodeRegionInfo(dji).IsOffsetHot(offsetNatFrom))
+ {
+ hrAdvise = WORST_HR(hrAdvise, CORDBG_E_SET_IP_IMPOSSIBLE);
+ goto LExit;
+ }
+ }
+
+ if (!fCanSetIPOnly)
+ {
+ hr = ShuffleVariablesGet(dji,
+ offsetNatFrom,
+ pCtx,
+ &rgVal1,
+ &rgVal2,
+ &pVCs);
+ LOG((LF_CORDB|LF_ENC,
+ LL_INFO10000,
+ "D::SIP: rgVal1 0x%X, rgVal2 0x%X\n",
+ rgVal1,
+ rgVal2));
+
+ if (FAILED(hr))
+ {
+ // This will only fail fatally, so exit.
+ hrAdvise = WORST_HR(hrAdvise, hr);
+ goto LExit;
+ }
+ }
+ else // fCanSetIPOnly
+ {
+ if (IsVarArgsFunction(dji->GetVarNativeInfoCount(), dji->GetVarNativeInfo()))
+ {
+ hrAdvise = E_INVALIDARG;
+ goto LExit;
+ }
+ }
+
+
+ if (pEHRT == NULL)
+ {
+ hr = E_OUTOFMEMORY;
+ }
+ else if (FAILED(pEHRT->m_hrInit))
+ {
+ hr = pEHRT->m_hrInit;
+ }
+ else
+ {
+ //
+ // This is a known, ok, violation. END_EXCEPTION_GLUE has a call to GetThrowable in it, but
+ // we will never hit it because we are passing in NULL below. This is to satisfy the static
+ // contract analyzer.
+ //
+ CONTRACT_VIOLATION(GCViolation);
+
+ EX_TRY
+ {
+ hr =g_pEEInterface->SetIPFromSrcToDst(thread,
+ pbBase,
+ offsetNatFrom,
+ (DWORD)offsetNatTo,
+ fCanSetIPOnly,
+ &(csi.m_activeFrame.registers),
+ pCtx,
+ (void *)dji,
+ pEHRT);
+ }
+ EX_CATCH
+ {
+ }
+ EX_END_CATCH(SwallowAllExceptions);
+
+ }
+
+ // Get the return code, if any
+ if (hr != S_OK)
+ {
+ hrAdvise = WORST_HR(hrAdvise, hr);
+ goto LExit;
+ }
+
+ // If we really want to do this, we'll have to put the
+ // variables into their new locations.
+ if (!fCanSetIPOnly && !FAILED(hrAdvise))
+ {
+ // TODO: We should zero out any registers which have now become live GC roots,
+ // but which aren't tracked variables (i.e. they are JIT temporaries). Such registers may
+ // have garbage left over in them, and we don't want the GC to try and dereference them
+ // as object references. However, we can't easily tell here which of the callee-saved regs
+ // are used in this method and therefore safe to clear.
+ //
+
+ hr = ShuffleVariablesSet(dji,
+ offsetNatTo,
+ pCtx,
+ &rgVal1,
+ &rgVal2,
+ pVCs);
+
+
+ if (hr != S_OK)
+ {
+ hrAdvise = WORST_HR(hrAdvise, hr);
+ goto LExit;
+ }
+
+ _ASSERTE(pbDest != NULL);
+
+ ::SetIP(pCtx, pbDest);
+
+ LOG((LF_CORDB, LL_INFO1000, "D::SIP:Set IP to be 0x%p\n", GetIP(pCtx)));
+ }
+
+
+LExit:
+ if (rgVal1 != NULL)
+ {
+ DeleteInteropSafe(rgVal1);
+ }
+
+ if (rgVal2 != NULL)
+ {
+ DeleteInteropSafe(rgVal2);
+ }
+
+ if (pEHRT != NULL)
+ {
+ delete pEHRT;
+ }
+
+ LOG((LF_CORDB, LL_INFO1000, "D::SIP:Returning 0x%x\n", hr));
+ return hrAdvise;
+}
+
+#include "nativevaraccessors.h"
+
+/******************************************************************************
+ *
+ ******************************************************************************/
+
+HRESULT Debugger::ShuffleVariablesGet(DebuggerJitInfo *dji,
+ SIZE_T offsetFrom,
+ CONTEXT *pCtx,
+ SIZE_T **prgVal1,
+ SIZE_T **prgVal2,
+ BYTE ***prgpVCs)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ PRECONDITION(CheckPointer(dji));
+ PRECONDITION(CheckPointer(pCtx));
+ PRECONDITION(CheckPointer(prgVal1));
+ PRECONDITION(CheckPointer(prgVal2));
+ PRECONDITION(dji->m_sizeOfCode >= offsetFrom);
+ }
+ CONTRACTL_END;
+
+ LONG cVariables = 0;
+ DWORD i;
+
+ //
+ // Find the largest variable number
+ //
+ for (i = 0; i < dji->GetVarNativeInfoCount(); i++)
+ {
+ if ((LONG)(dji->GetVarNativeInfo()[i].varNumber) > cVariables)
+ {
+ cVariables = (LONG)(dji->GetVarNativeInfo()[i].varNumber);
+ }
+ }
+
+ HRESULT hr = S_OK;
+
+ //
+ // cVariables is a zero-based count of the number of variables. Increment it.
+ //
+ cVariables++;
+
+ SIZE_T *rgVal1 = new (interopsafe, nothrow) SIZE_T[cVariables + unsigned(-ICorDebugInfo::UNKNOWN_ILNUM)];
+
+ SIZE_T *rgVal2 = NULL;
+
+ if (rgVal1 == NULL)
+ {
+ hr = E_OUTOFMEMORY;
+ goto LExit;
+ }
+
+ rgVal2 = new (interopsafe, nothrow) SIZE_T[cVariables + unsigned(-ICorDebugInfo::UNKNOWN_ILNUM)];
+
+ if (rgVal2 == NULL)
+ {
+ hr = E_OUTOFMEMORY;
+ goto LExit;
+ }
+
+ memset(rgVal1, 0, sizeof(SIZE_T) * (cVariables + unsigned(-ICorDebugInfo::UNKNOWN_ILNUM)));
+ memset(rgVal2, 0, sizeof(SIZE_T) * (cVariables + unsigned(-ICorDebugInfo::UNKNOWN_ILNUM)));
+
+ LOG((LF_CORDB|LF_ENC,
+ LL_INFO10000,
+ "D::SVG cVariables %d, hiddens %d, rgVal1 0x%X, rgVal2 0x%X\n",
+ cVariables,
+ unsigned(-ICorDebugInfo::UNKNOWN_ILNUM),
+ rgVal1,
+ rgVal2));
+
+ GetVariablesFromOffset(dji->m_fd,
+ dji->GetVarNativeInfoCount(),
+ dji->GetVarNativeInfo(),
+ offsetFrom,
+ pCtx,
+ rgVal1,
+ rgVal2,
+ cVariables + unsigned(-ICorDebugInfo::UNKNOWN_ILNUM),
+ prgpVCs);
+
+
+LExit:
+ if (!FAILED(hr))
+ {
+ (*prgVal1) = rgVal1;
+ (*prgVal2) = rgVal2;
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO100, "D::SVG: something went wrong hr=0x%x!", hr));
+
+ (*prgVal1) = NULL;
+ (*prgVal2) = NULL;
+
+ if (rgVal1 != NULL)
+ delete[] rgVal1;
+
+ if (rgVal2 != NULL)
+ delete[] rgVal2;
+ }
+
+ return hr;
+}
+
+/******************************************************************************
+ *
+ ******************************************************************************/
+HRESULT Debugger::ShuffleVariablesSet(DebuggerJitInfo *dji,
+ SIZE_T offsetTo,
+ CONTEXT *pCtx,
+ SIZE_T **prgVal1,
+ SIZE_T **prgVal2,
+ BYTE **rgpVCs)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ PRECONDITION(CheckPointer(dji));
+ PRECONDITION(CheckPointer(pCtx));
+ PRECONDITION(CheckPointer(prgVal1));
+ PRECONDITION(CheckPointer(prgVal2));
+ PRECONDITION(dji->m_sizeOfCode >= offsetTo);
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB|LF_ENC,
+ LL_INFO10000,
+ "D::SVS: rgVal1 0x%X, rgVal2 0x%X\n",
+ (*prgVal1),
+ (*prgVal2)));
+
+ HRESULT hr = SetVariablesAtOffset(dji->m_fd,
+ dji->GetVarNativeInfoCount(),
+ dji->GetVarNativeInfo(),
+ offsetTo,
+ pCtx,
+ *prgVal1,
+ *prgVal2,
+ rgpVCs);
+
+ LOG((LF_CORDB|LF_ENC,
+ LL_INFO100000,
+ "D::SVS deleting rgVal1 0x%X, rgVal2 0x%X\n",
+ (*prgVal1),
+ (*prgVal2)));
+
+ DeleteInteropSafe(*prgVal1);
+ (*prgVal1) = NULL;
+ DeleteInteropSafe(*prgVal2);
+ (*prgVal2) = NULL;
+ return hr;
+}
+
+//
+// This class is used by Get and SetVariablesFromOffsets to manage a frameHelper
+// list for the arguments and locals corresponding to each varNativeInfo. The first
+// four are hidden args, but the remainder will all have a corresponding entry
+// in the argument or local signature list.
+//
+// The structure of the array varNativeInfo contains home information for each variable
+// at various points in the function. Thus, you have to search for the proper native offset
+// (IP) in the varNativeInfo, and then find the correct varNumber in that native offset to
+// find the correct home information.
+//
+// Important to note is that the JIT has hidden args that have varNumbers that are negative.
+// Thus we cannot use varNumber as a strict index into our holder arrays, and instead shift
+// indexes before indexing into our holder arrays.
+//
+// The hidden args are a fixed-sized array given by the value of 0-UNKNOWN_ILNUM. These are used
+// to pass cookies about the arguments (var args, generics, retarg buffer etc.) to the function.
+// The real arguments and locals are as one would expect.
+//
+
+class GetSetFrameHelper
+{
+public:
+ GetSetFrameHelper();
+ ~GetSetFrameHelper();
+
+ HRESULT Init(MethodDesc* pMD);
+
+ bool GetValueClassSizeOfVar(int varNum, ICorDebugInfo::VarLocType varType, SIZE_T* pSize);
+ int ShiftIndexForHiddens(int varNum);
+
+private:
+ MethodDesc* m_pMD;
+ SIZE_T* m_rgSize;
+ CorElementType* m_rgElemType;
+ ULONG m_numArgs;
+ ULONG m_numTotalVars;
+
+ SIZE_T GetValueClassSize(MetaSig* pSig);
+
+ static SIZE_T GetSizeOfElement(CorElementType cet);
+};
+
+//
+// GetSetFrameHelper::GetSetFrameHelper()
+//
+// This is the constructor. It just initailizes all member variables.
+//
+// parameters: none
+//
+// return value: none
+//
+GetSetFrameHelper::GetSetFrameHelper() : m_pMD(NULL), m_rgSize(NULL), m_rgElemType(NULL),
+ m_numArgs(0), m_numTotalVars(0)
+{
+ LIMITED_METHOD_CONTRACT;
+}
+
+//
+// GetSetFrameHelper::Init()
+//
+// This method extracts the element type and the size of the arguments and locals of the method we are doing
+// the SetIP on and stores this information in instance variables.
+//
+// parameters: pMD - MethodDesc of the method we are doing the SetIP on
+//
+// return value: S_OK or E_OUTOFMEMORY
+//
+HRESULT
+GetSetFrameHelper::Init(MethodDesc *pMD)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ PRECONDITION(CheckPointer(pMD));
+ }
+ CONTRACTL_END;
+
+ HRESULT hr = S_OK;
+ COR_ILMETHOD* pILHeader = NULL;
+ m_pMD = pMD;
+ MetaSig *pLocSig = NULL;
+ MetaSig *pArgSig = NULL;
+
+ m_rgSize = NULL;
+ m_rgElemType = NULL;
+
+ // Initialize decoderOldIL before checking the method argument signature.
+ EX_TRY
+ {
+ pILHeader = pMD->GetILHeader();
+ }
+ EX_CATCH_HRESULT(hr);
+ if (FAILED(hr))
+ return hr;
+
+ COR_ILMETHOD_DECODER decoderOldIL(pILHeader);
+ mdSignature mdLocalSig = (decoderOldIL.GetLocalVarSigTok()) ? (decoderOldIL.GetLocalVarSigTok()):
+ (mdSignatureNil);
+
+ PCCOR_SIGNATURE pCallSig;
+ DWORD cbCallSigSize;
+
+ pMD->GetSig(&pCallSig, &cbCallSigSize);
+
+ if (pCallSig != NULL)
+ {
+ // Yes, we do need to pass in the text because this might be generic function!
+ SigTypeContext tmpContext(pMD);
+
+ pArgSig = new (interopsafe, nothrow) MetaSig(pCallSig,
+ cbCallSigSize,
+ pMD->GetModule(),
+ &tmpContext,
+ MetaSig::sigMember);
+
+ if (pArgSig == NULL)
+ {
+ IfFailGo(E_OUTOFMEMORY);
+ }
+
+ m_numArgs = pArgSig->NumFixedArgs();
+
+ if (pArgSig->HasThis())
+ {
+ m_numArgs++;
+ }
+
+ // <TODO>
+ // What should we do in this case?
+ // </TODO>
+ /*
+ if (argSig.IsVarArg())
+ m_numArgs++;
+ */
+ }
+
+ // allocation of pArgSig succeeded
+ ULONG cbSig;
+ PCCOR_SIGNATURE pLocalSig;
+ pLocalSig = NULL;
+ if (mdLocalSig != mdSignatureNil)
+ {
+ IfFailGo(pMD->GetModule()->GetMDImport()->GetSigFromToken(mdLocalSig, &cbSig, &pLocalSig));
+ }
+ if (pLocalSig != NULL)
+ {
+ SigTypeContext tmpContext(pMD);
+ pLocSig = new (interopsafe, nothrow) MetaSig(pLocalSig,
+ cbSig,
+ pMD->GetModule(),
+ &tmpContext,
+ MetaSig::sigLocalVars);
+
+ if (pLocSig == NULL)
+ {
+ IfFailGo(E_OUTOFMEMORY);
+ }
+ }
+
+ // allocation of pLocalSig succeeded
+ m_numTotalVars = m_numArgs + (pLocSig != NULL ? pLocSig->NumFixedArgs() : 0);
+
+ if (m_numTotalVars > 0)
+ {
+ m_rgSize = new (interopsafe, nothrow) SIZE_T[m_numTotalVars];
+ m_rgElemType = new (interopsafe, nothrow) CorElementType[m_numTotalVars];
+
+ if ((m_rgSize == NULL) || (m_rgElemType == NULL))
+ {
+ IfFailGo(E_OUTOFMEMORY);
+ }
+ else
+ {
+ // allocation of m_rgSize and m_rgElemType succeeded
+ for (ULONG i = 0; i < m_numTotalVars; i++)
+ {
+ // Choose the correct signature to walk.
+ MetaSig *pCur = NULL;
+ if (i < m_numArgs)
+ {
+ pCur = pArgSig;
+ }
+ else
+ {
+ pCur = pLocSig;
+ }
+
+ // The "this" argument isn't stored in the signature, so we have to
+ // check for it manually.
+ if (i == 0 && pCur->HasThis())
+ {
+ _ASSERTE(pCur == pArgSig);
+
+ m_rgElemType[i] = ELEMENT_TYPE_CLASS;
+ m_rgSize[i] = sizeof(SIZE_T);
+ }
+ else
+ {
+ m_rgElemType[i] = pCur->NextArg();
+
+ if (m_rgElemType[i] == ELEMENT_TYPE_VALUETYPE)
+ {
+ m_rgSize[i] = GetValueClassSize(pCur);
+ }
+ else
+ {
+ m_rgSize[i] = GetSetFrameHelper::GetSizeOfElement(m_rgElemType[i]);
+ }
+
+ LOG((LF_CORDB, LL_INFO10000, "GSFH::I: var 0x%x is of type %x, size:0x%x\n",
+ i, m_rgElemType[i], m_rgSize[i]));
+ }
+ }
+ } // allocation of m_rgSize and m_rgElemType succeeded
+ } // if there are variables to take care of
+
+ErrExit:
+ // clean up
+ if (pArgSig != NULL)
+ {
+ DeleteInteropSafe(pArgSig);
+ }
+
+ if (pLocSig != NULL)
+ {
+ DeleteInteropSafe(pLocSig);
+ }
+
+ if (FAILED(hr))
+ {
+ if (m_rgSize != NULL)
+ {
+ DeleteInteropSafe(m_rgSize);
+ }
+
+ if (m_rgElemType != NULL)
+ {
+ DeleteInteropSafe((int*)m_rgElemType);
+ }
+ }
+
+ return hr;
+} // GetSetFrameHelper::Init
+
+//
+// GetSetFrameHelper::~GetSetFrameHelper()
+//
+// This is the destructor. It checks the two arrays we have allocated and frees the memory accordingly.
+//
+// parameters: none
+//
+// return value: none
+//
+GetSetFrameHelper::~GetSetFrameHelper()
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ if (m_rgSize)
+ {
+ DeleteInteropSafe(m_rgSize);
+ }
+
+ if (m_rgElemType)
+ {
+ DeleteInteropSafe((int*)m_rgElemType);
+ }
+}
+
+//
+// GetSetFrameHelper::GetSizeOfElement()
+//
+// Given a CorElementType, this function returns the size of this type.
+// Note that this function doesn't handle ELEMENT_TYPE_VALUETYPE. Use GetValueClassSize() instead.
+//
+// parameters: cet - the CorElementType of the argument/local we are dealing with
+//
+// return value: the size of the argument/local
+//
+// static
+SIZE_T GetSetFrameHelper::GetSizeOfElement(CorElementType cet)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ PRECONDITION(cet != ELEMENT_TYPE_VALUETYPE);
+ }
+ CONTRACTL_END;
+
+ if (!CorIsPrimitiveType(cet))
+ {
+ return sizeof(SIZE_T);
+ }
+ else
+ {
+ switch (cet)
+ {
+ case ELEMENT_TYPE_I8:
+ case ELEMENT_TYPE_U8:
+#if defined(_WIN64)
+ case ELEMENT_TYPE_I:
+ case ELEMENT_TYPE_U:
+#endif // _WIN64
+ case ELEMENT_TYPE_R8:
+ return 8;
+
+ case ELEMENT_TYPE_I4:
+ case ELEMENT_TYPE_U4:
+#if !defined(_WIN64)
+ case ELEMENT_TYPE_I:
+ case ELEMENT_TYPE_U:
+#endif // !_WIN64
+ case ELEMENT_TYPE_R4:
+ return 4;
+
+ case ELEMENT_TYPE_I2:
+ case ELEMENT_TYPE_U2:
+ case ELEMENT_TYPE_CHAR:
+ return 2;
+
+ case ELEMENT_TYPE_I1:
+ case ELEMENT_TYPE_U1:
+ case ELEMENT_TYPE_BOOLEAN:
+ return 1;
+
+ case ELEMENT_TYPE_VOID:
+ case ELEMENT_TYPE_END:
+ _ASSERTE(!"debugger.cpp - Check this code path\n");
+ return 0;
+
+ case ELEMENT_TYPE_STRING:
+ return sizeof(SIZE_T);
+
+ default:
+ _ASSERTE(!"debugger.cpp - Check this code path\n");
+ return sizeof(SIZE_T);
+ }
+ }
+}
+
+//
+// GetSetFrameHelper::GetValueClassSize()
+//
+// Given a MetaSig pointer to the signature of a value type, this function returns its size.
+//
+// parameters: pSig - MetaSig pointer to the signature of a value type
+//
+// return value: the size of this value type
+//
+SIZE_T GetSetFrameHelper::GetValueClassSize(MetaSig* pSig)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ PRECONDITION(CheckPointer(pSig));
+ }
+ CONTRACTL_END;
+
+ // We need to determine the number of bytes for this value-type.
+ SigPointer sp = pSig->GetArgProps();
+
+ TypeHandle vcType = TypeHandle();
+ {
+ // Lookup operations run the class loader in non-load mode.
+ ENABLE_FORBID_GC_LOADER_USE_IN_THIS_SCOPE();
+
+ // This will return Null if type is not restored
+ // @todo : is this what we want?
+ SigTypeContext typeContext(m_pMD);
+ vcType = sp.GetTypeHandleThrowing(m_pMD->GetModule(),
+ &typeContext,
+ // == FailIfNotLoaded
+ ClassLoader::DontLoadTypes);
+ }
+ // We need to know the size of the class in bytes. This means:
+ // - we need a specific instantiation (since that affects size)
+ // - but we don't care if it's shared (since it will be the same size either way)
+ _ASSERTE(!vcType.IsNull() && vcType.IsValueType());
+
+ return (vcType.GetMethodTable()->GetAlignedNumInstanceFieldBytes());
+}
+
+//
+// GetSetFrameHelper::GetValueClassSizeOfVar()
+//
+// This method retrieves the size of the variable saved in the array m_rgSize. Also, it returns true
+// if the variable is a value type.
+//
+// parameters: varNum - the variable number (arguments come before locals)
+// varType - the type of variable home
+// pSize - [out] the size
+//
+// return value: whether this variable is a value type
+//
+bool GetSetFrameHelper::GetValueClassSizeOfVar(int varNum, ICorDebugInfo::VarLocType varType, SIZE_T* pSize)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ PRECONDITION(varType != ICorDebugInfo::VLT_FIXED_VA);
+ PRECONDITION(pSize != NULL);
+ }
+ CONTRACTL_END;
+
+ // preliminary checking
+ if (varNum < 0)
+ {
+ // Make sure this is one of the secret parameters (e.g. VASigCookie, generics context, etc.).
+ _ASSERTE(varNum > (int)ICorDebugInfo::MAX_ILNUM);
+
+ *pSize = sizeof(LPVOID);
+ return false;
+ }
+
+ // This check is only safe after we make sure that varNum is not negative.
+ if ((UINT)varNum >= m_numTotalVars)
+ {
+ _ASSERTE(!"invalid variable index encountered during setip");
+ *pSize = 0;
+ return false;
+ }
+
+ CorElementType cet = m_rgElemType[varNum];
+ *pSize = m_rgSize[varNum];
+
+ if ((cet != ELEMENT_TYPE_VALUETYPE) ||
+ (varType == ICorDebugInfo::VLT_REG) ||
+ (varType == ICorDebugInfo::VLT_REG_REG) ||
+ (varType == ICorDebugInfo::VLT_REG_STK) ||
+ (varType == ICorDebugInfo::VLT_STK_REG))
+ {
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+}
+
+int GetSetFrameHelper::ShiftIndexForHiddens(int varNum)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ //
+ // Need to shift them up so are appropriate index for rgVal arrays
+ //
+ return varNum - ICorDebugInfo::UNKNOWN_ILNUM;
+}
+
+// Helper method pair to grab all, then set all, variables at a given
+// point in a routine.
+// NOTE: GetVariablesFromOffset and SetVariablesAtOffset are
+// very similar - modifying one will probably need to be reflected in the other...
+// rgVal1 and rgVal2 are preallocated by callers with estimated size.
+// We pass in the size of the allocation in rRgValeSize. The safe index will be rgVal1[0..uRgValSize - 1]
+//
+HRESULT Debugger::GetVariablesFromOffset(MethodDesc *pMD,
+ UINT varNativeInfoCount,
+ ICorDebugInfo::NativeVarInfo *varNativeInfo,
+ SIZE_T offsetFrom,
+ CONTEXT *pCtx,
+ SIZE_T *rgVal1,
+ SIZE_T *rgVal2,
+ UINT uRgValSize, // number of elements of the preallocated rgVal1 and rgVal2
+ BYTE ***rgpVCs)
+{
+ // @todo - convert this to throwing w/ holders. It will be cleaner.
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ PRECONDITION(CheckPointer(rgpVCs));
+ PRECONDITION(CheckPointer(pCtx));
+ PRECONDITION(varNativeInfoCount == 0 || CheckPointer(varNativeInfo));
+ PRECONDITION(varNativeInfoCount == 0 || CheckPointer(rgVal1));
+ PRECONDITION(varNativeInfoCount == 0 || CheckPointer(rgVal2));
+ // This may or may not be called on the helper thread.
+ }
+ CONTRACTL_END;
+
+ *rgpVCs = NULL;
+ // if there are no locals, well, we are done!
+
+ if (varNativeInfoCount == 0)
+ {
+ return S_OK;
+ }
+
+ memset( rgVal1, 0, sizeof(SIZE_T)*uRgValSize);
+ memset( rgVal2, 0, sizeof(SIZE_T)*uRgValSize);
+
+ LOG((LF_CORDB|LF_ENC, LL_INFO10000, "D::GVFO: %s::%s, infoCount:0x%x, from:0x%p\n",
+ pMD->m_pszDebugClassName,
+ pMD->m_pszDebugMethodName,
+ varNativeInfoCount,
+ offsetFrom));
+
+ GetSetFrameHelper frameHelper;
+ HRESULT hr = frameHelper.Init(pMD);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+ // preallocate enough to hold all possible valueclass args & locals
+ // sure this is more than we need, but not a big deal and better
+ // than having to crawl through the frameHelper and count
+ ULONG cValueClasses = 0;
+ BYTE **rgpValueClasses = new (interopsafe, nothrow) BYTE *[varNativeInfoCount];
+ if (rgpValueClasses == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+ memset(rgpValueClasses, 0, sizeof(BYTE *)*varNativeInfoCount);
+
+ hr = S_OK;
+
+ LOG((LF_CORDB|LF_ENC,
+ LL_INFO10000,
+ "D::GVFO rgVal1 0x%X, rgVal2 0x%X\n",
+ rgVal1,
+ rgVal2));
+
+ // Now go through the full array and save off each arg and local
+ for (UINT i = 0; i< varNativeInfoCount;i++)
+ {
+ // Ignore variables not live at offsetFrom
+ //
+ // #VarLife
+ //
+ // The condition below is a little strange. If a var is alive when this is true:
+ //
+ // startOffset <= offsetFrom < endOffset
+ //
+ // Then you'd expect the negated expression below to be:
+ //
+ // startOffset > offsetFrom || endOffset <= offsetFrom
+ //
+ // instead of what we're doing ("<" instead of "<="):
+ //
+ // startOffset > offsetFrom || endOffset < offsetFrom
+ //
+ // I'm not sure if the condition below is a mistake, or if it's intentionally
+ // mirroring a workaround from FindNativeInfoInILVariableArray() (Debug\DI\module.cpp)
+ // to deal with optimized code. So I'm leaving it alone for now. See
+ // code:FindNativeInfoInILVariableArray for more info on this workaround.
+ if ((varNativeInfo[i].startOffset > offsetFrom) ||
+ (varNativeInfo[i].endOffset < offsetFrom) ||
+ (varNativeInfo[i].loc.vlType == ICorDebugInfo::VLT_INVALID))
+ {
+ LOG((LF_CORDB|LF_ENC,LL_INFO10000, "D::GVFO [%2d] invalid\n", i));
+ continue;
+ }
+
+ SIZE_T cbClass;
+ bool isVC = frameHelper.GetValueClassSizeOfVar(varNativeInfo[i].varNumber,
+ varNativeInfo[i].loc.vlType,
+ &cbClass);
+
+ if (!isVC)
+ {
+ int rgValIndex = frameHelper.ShiftIndexForHiddens(varNativeInfo[i].varNumber);
+
+ _ASSERTE(rgValIndex >= 0 && rgValIndex < (int)uRgValSize);
+
+ BOOL res = GetNativeVarVal(varNativeInfo[i].loc,
+ pCtx,
+ rgVal1 + rgValIndex,
+ rgVal2 + rgValIndex
+ WIN64_ARG(cbClass));
+
+ LOG((LF_CORDB|LF_ENC,LL_INFO10000,
+ "D::GVFO [%2d] varnum %d, nonVC type %x, addr %8.8x: %8.8x;%8.8x\n",
+ i,
+ varNativeInfo[i].varNumber,
+ varNativeInfo[i].loc.vlType,
+ NativeVarStackAddr(varNativeInfo[i].loc, pCtx),
+ rgVal1[rgValIndex],
+ rgVal2[rgValIndex]));
+
+ if (res == TRUE)
+ {
+ continue;
+ }
+
+ _ASSERTE(res == TRUE);
+ hr = E_FAIL;
+ break;
+ }
+
+ // it's definately a value class
+ // Make space for it - note that it uses the VC index, NOT the variable index
+ _ASSERTE(cbClass != 0);
+ rgpValueClasses[cValueClasses] = new (interopsafe, nothrow) BYTE[cbClass];
+ if (rgpValueClasses[cValueClasses] == NULL)
+ {
+ hr = E_OUTOFMEMORY;
+ break;
+ }
+ memcpy(rgpValueClasses[cValueClasses],
+ NativeVarStackAddr(varNativeInfo[i].loc, pCtx),
+ cbClass);
+
+ // Move index up.
+ cValueClasses++;
+#ifdef _DEBUG
+ LOG((LF_CORDB|LF_ENC,LL_INFO10000,
+ "D::GVFO [%2d] varnum %d, VC len %d, addr %8.8x, sample: %8.8x%8.8x\n",
+ i,
+ varNativeInfo[i].varNumber,
+ cbClass,
+ NativeVarStackAddr(varNativeInfo[i].loc, pCtx),
+ (rgpValueClasses[cValueClasses-1])[0], (rgpValueClasses[cValueClasses-1])[1]));
+#endif
+ }
+
+ LOG((LF_CORDB|LF_ENC, LL_INFO10000, "D::GVFO: returning %8.8x\n", hr));
+ if (SUCCEEDED(hr))
+ {
+ (*rgpVCs) = rgpValueClasses;
+ return hr;
+ }
+
+ // We failed for some reason
+ if (rgpValueClasses != NULL)
+ { // free any memory we allocated for VCs here
+ while(cValueClasses > 0)
+ {
+ --cValueClasses;
+ DeleteInteropSafe(rgpValueClasses[cValueClasses]); // OK to delete NULL
+ }
+ DeleteInteropSafe(rgpValueClasses);
+ rgpValueClasses = NULL;
+ }
+ return hr;
+}
+
+// NOTE: GetVariablesFromOffset and SetVariablesAtOffset are
+// very similar - modifying one will probably need to be reflected in the other...
+HRESULT Debugger::SetVariablesAtOffset(MethodDesc *pMD,
+ UINT varNativeInfoCount,
+ ICorDebugInfo::NativeVarInfo *varNativeInfo,
+ SIZE_T offsetTo,
+ CONTEXT *pCtx,
+ SIZE_T *rgVal1,
+ SIZE_T *rgVal2,
+ BYTE **rgpVCs)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ PRECONDITION(CheckPointer(pCtx));
+ PRECONDITION(varNativeInfoCount == 0 || CheckPointer(rgpVCs));
+ PRECONDITION(varNativeInfoCount == 0 || CheckPointer(varNativeInfo));
+ PRECONDITION(varNativeInfoCount == 0 || CheckPointer(rgVal1));
+ PRECONDITION(varNativeInfoCount == 0 || CheckPointer(rgVal2));
+ // This may or may not be called on the helper thread.
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB|LF_ENC, LL_INFO10000, "D::SVAO: %s::%s, infoCount:0x%x, to:0x%p\n",
+ pMD->m_pszDebugClassName,
+ pMD->m_pszDebugMethodName,
+ varNativeInfoCount,
+ offsetTo));
+
+ if (varNativeInfoCount == 0)
+ {
+ return S_OK;
+ }
+
+ GetSetFrameHelper frameHelper;
+ HRESULT hr = frameHelper.Init(pMD);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ ULONG iVC = 0;
+ hr = S_OK;
+
+ // Note that since we obtain all the variables in the first loop, we
+ // can now splatter those variables into their new locations
+ // willy-nilly, without the fear that variable locations that have
+ // been swapped might accidentally overwrite a variable value.
+ for (UINT i = 0;i< varNativeInfoCount;i++)
+ {
+ // Ignore variables not live at offsetTo
+ //
+ // If this IF condition looks wrong to you, see
+ // code:Debugger::GetVariablesFromOffset#VarLife for more info
+ if ((varNativeInfo[i].startOffset > offsetTo) ||
+ (varNativeInfo[i].endOffset < offsetTo) ||
+ (varNativeInfo[i].loc.vlType == ICorDebugInfo::VLT_INVALID))
+ {
+ LOG((LF_CORDB|LF_ENC,LL_INFO10000, "D::SVAO [%2d] invalid\n", i));
+ continue;
+ }
+
+ SIZE_T cbClass;
+ bool isVC = frameHelper.GetValueClassSizeOfVar(varNativeInfo[i].varNumber,
+ varNativeInfo[i].loc.vlType,
+ &cbClass);
+
+ if (!isVC)
+ {
+ int rgValIndex = frameHelper.ShiftIndexForHiddens(varNativeInfo[i].varNumber);
+
+ _ASSERTE(rgValIndex >= 0);
+
+ BOOL res = SetNativeVarVal(varNativeInfo[i].loc,
+ pCtx,
+ rgVal1[rgValIndex],
+ rgVal2[rgValIndex]
+ WIN64_ARG(cbClass));
+
+ LOG((LF_CORDB|LF_ENC,LL_INFO10000,
+ "D::SVAO [%2d] varnum %d, nonVC type %x, addr %8.8x: %8.8x;%8.8x\n",
+ i,
+ varNativeInfo[i].varNumber,
+ varNativeInfo[i].loc.vlType,
+ NativeVarStackAddr(varNativeInfo[i].loc, pCtx),
+ rgVal1[rgValIndex],
+ rgVal2[rgValIndex]));
+
+ if (res == TRUE)
+ {
+ continue;
+ }
+ _ASSERTE(res == TRUE);
+ hr = E_FAIL;
+ break;
+ }
+
+ // It's definately a value class.
+ _ASSERTE(cbClass != 0);
+ if (rgpVCs[iVC] == NULL)
+ {
+ // it's new in scope, so just clear it
+ memset(NativeVarStackAddr(varNativeInfo[i].loc, pCtx), 0, cbClass);
+ LOG((LF_CORDB|LF_ENC,LL_INFO10000, "D::SVAO [%2d] varnum %d, new VC len %d, addr %8.8x\n",
+ i,
+ varNativeInfo[i].varNumber,
+ cbClass,
+ NativeVarStackAddr(varNativeInfo[i].loc, pCtx)));
+ continue;
+ }
+ // it's a pre-existing VC, so copy it
+ memmove(NativeVarStackAddr(varNativeInfo[i].loc, pCtx), rgpVCs[iVC], cbClass);
+#ifdef _DEBUG
+ LOG((LF_CORDB|LF_ENC,LL_INFO10000,
+ "D::SVAO [%2d] varnum %d, VC len %d, addr: %8.8x sample: %8.8x%8.8x\n",
+ i,
+ varNativeInfo[i].varNumber,
+ cbClass,
+ NativeVarStackAddr(varNativeInfo[i].loc, pCtx),
+ rgpVCs[iVC][0],
+ rgpVCs[iVC][1]));
+#endif
+ // Now get rid of the memory
+ DeleteInteropSafe(rgpVCs[iVC]);
+ rgpVCs[iVC] = NULL;
+ iVC++;
+ }
+
+ LOG((LF_CORDB|LF_ENC, LL_INFO10000, "D::SVAO: returning %8.8x\n", hr));
+
+ if (rgpVCs != NULL)
+ {
+ DeleteInteropSafe(rgpVCs);
+ }
+
+ return hr;
+}
+
+BOOL IsDuplicatePatch(SIZE_T *rgEntries,
+ ULONG cEntries,
+ SIZE_T Entry )
+{
+ LIMITED_METHOD_CONTRACT;
+
+ for( ULONG i = 0; i < cEntries;i++)
+ {
+ if (rgEntries[i] == Entry)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+/******************************************************************************
+// HRESULT Debugger::MapAndBindFunctionBreakpoints(): For each breakpoint
+// that we've set in any version of the existing function,
+// set a correponding breakpoint in the new function if we haven't moved
+// the patch to the new version already.
+//
+// This must be done _AFTER_ the MethodDesc has been udpated
+// with the new address (ie, when GetFunctionAddress pFD returns
+// the address of the new EnC code)
+//
+// Parameters:
+// djiNew - this is the DJI created in D::JitComplete.
+// If djiNew == NULL iff we aren't tracking debug-info.
+// fd - the method desc that we're binding too.
+// addrOfCode - address of the native blob of code we just jitted
+//
+// <TODO>@todo Replace array with hashtable for improved efficiency</TODO>
+// <TODO>@todo Need to factor code,so that we can selectively map forward DFK(ilOFfset) BPs</TODO>
+ ******************************************************************************/
+HRESULT Debugger::MapAndBindFunctionPatches(DebuggerJitInfo *djiNew,
+ MethodDesc * fd,
+ CORDB_ADDRESS_TYPE *addrOfCode)
+{
+ // @@@
+ // Internal helper API. Can be called from Debugger or Controller.
+ //
+
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ THROWS;
+ CALLED_IN_DEBUGGERDATALOCK_HOLDER_SCOPE_MAY_GC_TRIGGERS_CONTRACT;
+ PRECONDITION(!djiNew || djiNew->m_fd == fd);
+ }
+ CONTRACTL_END;
+
+ HRESULT hr = S_OK;
+ HASHFIND hf;
+ SIZE_T *pidTableEntry = NULL;
+ SIZE_T pidInCaseTableMoves;
+ Module *pModule = g_pEEInterface->MethodDescGetModule(fd);
+ mdMethodDef md = fd->GetMemberDef();
+
+ LOG((LF_CORDB,LL_INFO10000,"D::MABFP: All BPs will be mapped to "
+ "Ver:0x%04x (DJI:0x%08x)\n", djiNew?djiNew->m_methodInfo->GetCurrentEnCVersion():0, djiNew));
+
+ // We need to traverse the patch list while under the controller lock (small lock).
+ // But we can only send BreakpointSetErros while under the debugger lock (big lock).
+ // So to avoid a lock violation, we queue any errors we find under the small lock,
+ // and then send the whole list when under the big lock.
+ PATCH_UNORDERED_ARRAY listUnbindablePatches;
+
+
+ // First lock the patch table so it doesn't move while we're
+ // examining it.
+ LOG((LF_CORDB,LL_INFO10000, "D::MABFP: About to lock patch table\n"));
+ {
+ DebuggerController::ControllerLockHolder ch;
+
+ // Manipulate tables AFTER lock's been acquired.
+ DebuggerPatchTable *pPatchTable = DebuggerController::GetPatchTable();
+ GetBPMappingDuplicates()->Clear(); //dups are tracked per-version
+
+ for (DebuggerControllerPatch *dcp = pPatchTable->GetFirstPatch(&hf);
+ dcp != NULL;
+ dcp = pPatchTable->GetNextPatch( &hf ))
+ {
+
+ LOG((LF_CORDB, LL_INFO10000, "D::MABFP: got patch 0x%p\n", dcp));
+
+ // Only copy over breakpoints that are in this method
+ // Ideally we'd have a per-method index since there can be a lot of patches
+ // when the EnCBreakpoint patches are included.
+ if (dcp->key.module != pModule || dcp->key.md != md)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "Patch not in this method\n"));
+ continue;
+ }
+
+ // Do not copy over slave breakpoint patches. Instead place a new slave
+ // based off the master.
+ if (dcp->IsILSlavePatch())
+ {
+ LOG((LF_CORDB, LL_INFO10000, "Not copying over slave breakpoint patch\n"));
+ continue;
+ }
+
+ // If the patch is already bound, then we don't want to try to rebind it.
+ // Eg. It may be bound to a different generic method instantiation.
+ if (dcp->IsBound())
+ {
+ LOG((LF_CORDB, LL_INFO10000, "Skipping already bound patch\n"));
+ continue;
+ }
+
+ // Only apply breakpoint patches that are for this version.
+ // If the patch doesn't have a particular EnCVersion available from its data then
+ // we're (probably) not tracking JIT info.
+ if (dcp->IsBreakpointPatch() && dcp->HasEnCVersion() && djiNew && dcp->GetEnCVersion() != djiNew->m_encVersion)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "Not applying breakpoint patch to new version\n"));
+ continue;
+ }
+
+ // Only apply breakpoint and stepper patches
+ //
+ // The DJI gets deleted as part of the Unbind/Rebind process in MovedCode.
+ // This is to signal that we should not skip here.
+ // <NICE> under exactly what scenarios (EnC, code pitching etc.) will this apply?... </NICE>
+ // <NICE> can't we be a little clearer about why we don't want to bind the patch in this arcance situation?</NICE>
+ if (dcp->HasDJI() && !dcp->IsBreakpointPatch() && !dcp->IsStepperPatch())
+ {
+ LOG((LF_CORDB, LL_INFO10000, "Neither stepper nor BP but we have valid a DJI (i.e. the DJI hasn't been deleted as part of the Unbind/MovedCode/Rebind mess)! - getting next patch!\n"));
+ continue;
+ }
+
+ // Now check if we're tracking JIT info or not
+ if (djiNew == NULL)
+ {
+ // This means we put a patch in a method w/ no debug info.
+ _ASSERTE(dcp->IsBreakpointPatch() ||
+ dcp->IsStepperPatch() ||
+ dcp->controller->GetDCType() == DEBUGGER_CONTROLLER_THREAD_STARTER);
+
+ // W/o Debug-info, We can only patch native offsets, and only at the start of the method (native offset 0).
+ // <TODO> Why can't we patch other native offsets??
+ // Maybe b/c we don't know if we're patching
+ // in the middle of an instruction. Though that's not a
+ // strict requirement.</TODO>
+ // We can't even do a IL-offset 0 because that's after the prolog and w/o the debug-info,
+ // we don't know where the prolog ends.
+ // Failing this assert is arguably an API misusage - the debugger should have enabled
+ // jit-tracking if they wanted to put bps at offsets other than native:0.
+ if (dcp->IsNativePatch() && (dcp->offset == 0))
+ {
+ DebuggerController::g_patches->BindPatch(dcp, addrOfCode);
+ DebuggerController::ActivatePatch(dcp);
+ }
+ else
+ {
+ // IF a debugger calls EnableJitDebugging(true, ...) in the module-load callback,
+ // we should never get here.
+ *(listUnbindablePatches.AppendThrowing()) = dcp;
+ }
+
+ }
+ else
+ {
+ pidInCaseTableMoves = dcp->pid;
+
+ // If we've already mapped this one to the current version,
+ // don't map it again.
+ LOG((LF_CORDB,LL_INFO10000,"D::MABFP: Checking if 0x%x is a dup...",
+ pidInCaseTableMoves));
+
+ if ( IsDuplicatePatch(GetBPMappingDuplicates()->Table(),
+ GetBPMappingDuplicates()->Count(),
+ pidInCaseTableMoves) )
+ {
+ LOG((LF_CORDB,LL_INFO10000,"it is!\n"));
+ continue;
+ }
+ LOG((LF_CORDB,LL_INFO10000,"nope!\n"));
+
+ // Attempt mapping from patch to new version of code, and
+ // we don't care if it turns out that there isn't a mapping.
+ // <TODO>@todo-postponed: EnC: Make sure that this doesn't cause
+ // the patch-table to shift.</TODO>
+ hr = MapPatchToDJI( dcp, djiNew );
+ if (CORDBG_E_CODE_NOT_AVAILABLE == hr )
+ {
+ *(listUnbindablePatches.AppendThrowing()) = dcp;
+ hr = S_OK;
+ }
+
+ if (FAILED(hr))
+ break;
+
+ //Remember the patch id to prevent duplication later
+ pidTableEntry = GetBPMappingDuplicates()->Append();
+ if (NULL == pidTableEntry)
+ {
+ hr = E_OUTOFMEMORY;
+ break;
+ }
+
+ *pidTableEntry = pidInCaseTableMoves;
+ LOG((LF_CORDB,LL_INFO10000,"D::MABFP Adding 0x%x to list of "
+ "already mapped patches\n", pidInCaseTableMoves));
+ }
+ }
+
+ // unlock controller lock before sending events.
+ }
+ LOG((LF_CORDB,LL_INFO10000, "D::MABFP: Unlocked patch table\n"));
+
+
+ // Now send any Breakpoint bind error events.
+ if (listUnbindablePatches.Count() > 0)
+ {
+ LockAndSendBreakpointSetError(&listUnbindablePatches);
+ }
+
+ return hr;
+}
+
+/******************************************************************************
+// HRESULT Debugger::MapPatchToDJI(): Maps the given
+// patch to the corresponding location at the new address.
+// We assume that the new code has been JITTed.
+// Returns: CORDBG_E_CODE_NOT_AVAILABLE - Indicates that a mapping wasn't
+// available, and thus no patch was placed. The caller may or may
+// not care.
+ ******************************************************************************/
+HRESULT Debugger::MapPatchToDJI( DebuggerControllerPatch *dcp,DebuggerJitInfo *djiTo)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ THROWS;
+ CALLED_IN_DEBUGGERDATALOCK_HOLDER_SCOPE_MAY_GC_TRIGGERS_CONTRACT;
+ PRECONDITION(djiTo != NULL);
+ PRECONDITION(djiTo->m_jitComplete == true);
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(DebuggerController::HasLock());
+#ifdef _DEBUG
+ static BOOL shouldBreak = -1;
+ if (shouldBreak == -1)
+ shouldBreak = UnsafeGetConfigDWORD(CLRConfig::INTERNAL_DbgBreakOnMapPatchToDJI);
+
+ if (shouldBreak > 0) {
+ _ASSERTE(!"DbgBreakOnMatchPatchToDJI");
+ }
+#endif
+
+ LOG((LF_CORDB, LL_EVERYTHING, "Calling MapPatchToDJI\n"));
+
+ // We shouldn't have been asked to map an already bound patch
+ _ASSERTE( !dcp->IsBound() );
+ if ( dcp->IsBound() )
+ {
+ return S_OK;
+ }
+
+ // If the patch has no DJI then we're doing a UnbindFunctionPatches/RebindFunctionPatches. Either
+ // way, we simply want the most recent version. In the absence of EnC we should have djiCur == djiTo.
+ DebuggerJitInfo *djiCur = dcp->HasDJI() ? dcp->GetDJI() : djiTo;
+ PREFIX_ASSUME(djiCur != NULL);
+
+ // If the source and destination are the same version, then this method
+ // decays into BindFunctionPatch's BindPatch function
+ if (djiCur->m_encVersion == djiTo->m_encVersion)
+ {
+ // If the patch is a "master" then make a new "slave" patch instead of
+ // binding the old one. This is to stop us mucking with the master breakpoint patch
+ // which we may need to bind several times for generic code.
+ if (dcp->IsILMasterPatch())
+ {
+ LOG((LF_CORDB, LL_EVERYTHING, "Add, Bind, Activate new patch from master patch\n"));
+ if (dcp->controller->AddBindAndActivateILSlavePatch(dcp, djiTo))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "Add, Bind Activate went fine!\n" ));
+ return S_OK;
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO1000, "Didn't work for some reason!\n"));
+
+ // Caller can track this HR and send error.
+ return CORDBG_E_CODE_NOT_AVAILABLE;
+ }
+ }
+ else
+ {
+ // <TODO>
+ // We could actually have a native managed patch here. This patch is probably added
+ // as a result of tracing a patch. See if we can eliminate the need for this code path
+ // </TODO>
+ _ASSERTE( dcp->GetKind() == PATCH_KIND_NATIVE_MANAGED );
+
+ // We have an unbound native patch (eg. for PatchTrace), lets try to bind and activate it
+ dcp->SetDJI(djiTo);
+ LOG((LF_CORDB, LL_EVERYTHING, "trying to bind patch... could be problem\n"));
+ if (DebuggerController::BindPatch(dcp, djiTo->m_fd, NULL))
+ {
+ DebuggerController::ActivatePatch(dcp);
+ LOG((LF_CORDB, LL_INFO1000, "Application went fine!\n" ));
+ return S_OK;
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO1000, "Didn't apply for some reason!\n"));
+
+ // Caller can track this HR and send error.
+ return CORDBG_E_CODE_NOT_AVAILABLE;
+ }
+ }
+ }
+
+ // Breakpoint patches never get mapped over
+ _ASSERTE(!dcp->IsBreakpointPatch());
+
+ return S_OK;
+}
+
+//
+// Wrapper function for debugger to WaitForSingleObject. If CLR is hosted,
+// notify host before we leave runtime.
+//
+DWORD Debugger::WaitForSingleObjectHelper(HANDLE handle, DWORD dwMilliseconds)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ DWORD dw = 0;
+ EX_TRY
+ {
+
+ // make sure that we let host know that we are leaving runtime.
+ LeaveRuntimeHolder holder((size_t)(::WaitForSingleObject));
+ dw = ::WaitForSingleObject(handle,dwMilliseconds);
+ }
+ EX_CATCH
+ {
+ // Only possibility to enter here is when Thread::LeaveRuntime
+ // throws exception.
+ dw = WAIT_ABANDONED;
+ }
+ EX_END_CATCH(SwallowAllExceptions);
+ return dw;
+
+}
+
+
+/* ------------------------------------------------------------------------ *
+ * EE Interface routines
+ * ------------------------------------------------------------------------ */
+
+//
+// SendSyncCompleteIPCEvent sends a Sync Complete event to the Right Side.
+//
+void Debugger::SendSyncCompleteIPCEvent()
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ PRECONDITION(ThreadHoldsLock());
+
+ // Anyone sending the synccomplete must hold the TSL.
+ PRECONDITION(ThreadStore::HoldingThreadStore() || g_fProcessDetach);
+
+ // The sync complete is now only sent on a helper thread.
+ PRECONDITION(ThisIsHelperThreadWorker());
+ MODE_COOPERATIVE;
+
+ // We had better be trapping Runtime threads and not stopped yet.
+ PRECONDITION(m_stopped && m_trappingRuntimeThreads);
+ }
+ CONTRACTL_END;
+
+ // @@@
+ // Internal helper API.
+ // This is to send Sync Complete event to RightSide.
+ // We should have hold the debugger lock
+ //
+
+ STRESS_LOG0(LF_CORDB, LL_INFO10000, "D::SSCIPCE: sync complete.\n");
+
+ // Synchronizing while in in rude shutdown should be extremely rare b/c we don't
+ // TART in rude shutdown. Shutdown must have started after we started to sync.
+ // We know we're not on the shutdown thread here.
+ // And we also know we can't block the shutdown thread (b/c it has the TSL and will
+ // get a free pass through the GC toggles that normally block threads for debugging).
+ if (g_fProcessDetach)
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO10000, "D::SSCIPCE: Skipping for shutdown.\n");
+ return;
+ }
+
+ // If we're not marked as attached yet, then do that now.
+ // This can be safely called multiple times.
+ // This can happen in the normal attach case. The Right-side sends an async-break,
+ // but we don't want to be considered attach until we've actually gotten our first synchronization.
+ // Else threads may slip forward during attach and send debug events while we're tyring to attach.
+ MarkDebuggerAttachedInternal();
+
+ DebuggerIPCControlBlock * pDCB;
+ pDCB = m_pRCThread->GetDCB();
+ (void)pDCB; //prevent "unused variable" error from GCC
+
+ PREFIX_ASSUME(pDCB != NULL); // must have DCB by the time we're sending IPC events.
+#ifdef FEATURE_INTEROP_DEBUGGING
+ // The synccomplete can't be the first IPC event over. That's b/c the LS needs to know
+ // if we're interop-debugging and the RS needs to know special addresses for interop-debugging
+ // (like flares). All of this info is in the DCB.
+ if (pDCB->m_rightSideIsWin32Debugger)
+ {
+
+ // If the Right Side is the win32 debugger of this process, then we need to throw a special breakpoint exception
+ // here instead of sending the sync complete event. The Right Side treats this the same as a sync complete
+ // event, but its also able to suspend unmanaged threads quickly.
+ // This also prevents races between sending the sync-complete and getting a native debug event
+ // (since the sync-complete becomes a native debug event, and all native debug events are serialized).
+ //
+ // Note: we reset the syncThreadIsLockFree event before sending the sync complete flare. This thread will set
+ // this event once its released the debugger lock. This will prevent the Right Side from suspending this thread
+ // until it has released the debugger lock.
+ Debugger::NotifyRightSideOfSyncComplete();
+ }
+ else
+#endif // FEATURE_INTEROP_DEBUGGING
+ {
+ STRESS_LOG0(LF_CORDB, LL_EVERYTHING, "GetIPCEventSendBuffer called in SendSyncCompleteIPCEvent\n");
+ // Send the Sync Complete event to the Right Side
+ DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer();
+ InitIPCEvent(ipce, DB_IPCE_SYNC_COMPLETE);
+
+ m_pRCThread->SendIPCEvent();
+ }
+}
+
+//
+// Lookup or create a DebuggerModule for the given pDomainFile.
+//
+// Arguments:
+// pDomainFile - non-null domain file.
+//
+// Returns:
+// DebuggerModule instance for the given domain file. May be lazily created.
+//
+// Notes:
+// @dbgtodo JMC - this should go away when we get rid of DebuggerModule.
+//
+
+DebuggerModule * Debugger::LookupOrCreateModule(DomainFile * pDomainFile)
+{
+ _ASSERTE(pDomainFile != NULL);
+ LOG((LF_CORDB, LL_INFO1000, "D::LOCM df=0x%x\n", pDomainFile));
+ DebuggerModule * pDModule = LookupOrCreateModule(pDomainFile->GetModule(), pDomainFile->GetAppDomain());
+ LOG((LF_CORDB, LL_INFO1000, "D::LOCM m=0x%x ad=0x%x -> dm=0x%x\n", pDomainFile->GetModule(), pDomainFile->GetAppDomain(), pDModule));
+ _ASSERTE(pDModule != NULL);
+ _ASSERTE(pDModule->GetDomainFile() == pDomainFile);
+
+ return pDModule;
+}
+
+// Overloaded Wrapper around for VMPTR_DomainFile-->DomainFile*
+//
+// Arguments:
+// vmDomainFile - VMPTR cookie for a domain file. This can be NullPtr().
+//
+// Returns:
+// Debugger Module instance for the given domain file. May be lazily created.
+//
+// Notes:
+// VMPTR comes from IPC events
+DebuggerModule * Debugger::LookupOrCreateModule(VMPTR_DomainFile vmDomainFile)
+{
+ DomainFile * pDomainFile = vmDomainFile.GetRawPtr();
+ if (pDomainFile == NULL)
+ {
+ return NULL;
+ }
+ return LookupOrCreateModule(pDomainFile);
+}
+
+// Lookup or create a DebuggerModule for the given (Module, AppDomain) pair.
+//
+// Arguments:
+// pModule - required runtime module. May be domain netural.
+// pAppDomain - required appdomain that the module is in.
+//
+// Returns:
+// Debugger Module isntance for the given domain file. May be lazily created.
+//
+DebuggerModule* Debugger::LookupOrCreateModule(Module* pModule, AppDomain *pAppDomain)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_INFO1000, "D::LOCM m=0x%x ad=0x%x\n", pModule, pAppDomain));
+
+ // DebuggerModules are relative to a specific AppDomain so we should always be looking up a module /
+ // AppDomain pair.
+ _ASSERTE( pModule != NULL );
+ _ASSERTE( pAppDomain != NULL );
+
+ // This is called from all over. We just need to lock in order to lookup. We don't need
+ // the lock when actually using the DebuggerModule (since it won't be unloaded as long as there is a thread
+ // in that appdomain). Many of our callers already have this lock, many don't.
+ // We can take the lock anyways because it's reentrant.
+ DebuggerDataLockHolder ch(g_pDebugger); // need to traverse module list
+
+ // if this is a module belonging to the system assembly, then scan
+ // the complete list of DebuggerModules looking for the one
+ // with a matching appdomain id
+ // it.
+
+ _ASSERTE( SystemDomain::SystemAssembly()->IsDomainNeutral() );
+
+ DebuggerModule* dmod = NULL;
+
+ if (m_pModules != NULL)
+ {
+ if (pModule->GetAssembly()->IsDomainNeutral())
+ {
+ // We have to make sure to lookup the module with the app domain parameter if the module lives in a shared assembly
+ dmod = m_pModules->GetModule(pModule, pAppDomain);
+ }
+ else
+ {
+ dmod = m_pModules->GetModule(pModule);
+ }
+ }
+
+ // If it doesn't exist, create it.
+ if (dmod == NULL)
+ {
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ DomainFile * pDomainFile = pModule->FindDomainFile(pAppDomain);
+ SIMPLIFYING_ASSUMPTION(pDomainFile != NULL);
+ dmod = AddDebuggerModule(pDomainFile); // throws
+ }
+ EX_CATCH_HRESULT(hr);
+ SIMPLIFYING_ASSUMPTION(dmod != NULL); // may not be true in OOM cases; but LS doesn't handle OOM.
+ }
+
+ // The module must be in the AppDomain that was requested
+ _ASSERTE( (dmod == NULL) || (dmod->GetAppDomain() == pAppDomain) );
+
+ LOG((LF_CORDB, LL_INFO1000, "D::LOCM m=0x%x ad=0x%x -> dm=0x%x\n", pModule, pAppDomain, dmod));
+ return dmod;
+}
+
+// Create a new DebuggerModule object
+//
+// Arguments:
+// pDomainFile- runtime domain file to create debugger module object around
+//
+// Returns:
+// New instnace of a DebuggerModule. Throws on failure.
+//
+DebuggerModule* Debugger::AddDebuggerModule(DomainFile * pDomainFile)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_INFO1000, "D::ADM df=0x%x\n", pDomainFile));
+ DebuggerDataLockHolder chInfo(this);
+
+ Module * pRuntimeModule = pDomainFile->GetCurrentModule();
+ AppDomain * pAppDomain = pDomainFile->GetAppDomain();
+
+ HRESULT hr = CheckInitModuleTable();
+ IfFailThrow(hr);
+
+ DebuggerModule* pModule = new (interopsafe) DebuggerModule(pRuntimeModule, pDomainFile, pAppDomain);
+ _ASSERTE(pModule != NULL); // throws on oom
+
+ TRACE_ALLOC(pModule);
+
+ m_pModules->AddModule(pModule); // throws
+ // @dbgtodo inspection/exceptions - this may leak module in OOM case. LS is not OOM resilient; and we
+ // expect to get rid of DebuggerModule anyways.
+
+ LOG((LF_CORDB, LL_INFO1000, "D::ADM df=0x%x -> dm=0x%x\n", pDomainFile, pModule));
+ return pModule;
+}
+
+//
+// TrapAllRuntimeThreads causes every Runtime thread that is executing
+// in the EE to trap and send the at safe point event to the RC thread as
+// soon as possible. It also sets the EE up so that Runtime threads that
+// are outside of the EE will trap when they try to re-enter.
+//
+// @TODO::
+// Neither pDbgLockHolder nor pAppDomain are used.
+void Debugger::TrapAllRuntimeThreads()
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT;
+ MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT;
+
+ // We acquired the lock b/c we're in a scope between LFES & UFES.
+ PRECONDITION(ThreadHoldsLock());
+
+ // This should never be called on a Temporary Helper thread.
+ PRECONDITION(IsDbgHelperSpecialThread() ||
+ (g_pEEInterface->GetThread() == NULL) ||
+ !g_pEEInterface->IsPreemptiveGCDisabled());
+ }
+ CONTRACTL_END;
+
+#if !defined(FEATURE_DBGIPC_TRANSPORT_VM)
+ // Only sync if RS requested it.
+ if (!m_RSRequestedSync)
+ {
+ return;
+ }
+ m_RSRequestedSync = FALSE;
+#endif
+
+ // If we're doing shutdown, then don't bother trying to communicate w/ the RS.
+ // If we're not the thread doing shutdown, then we may be asynchronously killed by the OS.
+ // If we are the thread in shutdown, don't TART b/c that may block and do complicated stuff.
+ if (g_fProcessDetach)
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO10000, "D::TART: Skipping for shutdown.\n");
+ return;
+ }
+
+
+ // Only try to start trapping if we're not already trapping.
+ if (m_trappingRuntimeThreads == FALSE)
+ {
+ bool fSuspended;
+
+ STRESS_LOG0(LF_CORDB, LL_INFO10000, "D::TART: Trapping all Runtime threads.\n");
+
+ // There's no way that we should be stopped and still trying to call this function.
+ _ASSERTE(!m_stopped);
+
+ // Mark that we're trapping now.
+ m_trappingRuntimeThreads = TRUE;
+
+ // Take the thread store lock.
+ STRESS_LOG0(LF_CORDB,LL_INFO1000, "About to lock thread Store\n");
+ ThreadSuspend::LockThreadStore(ThreadSuspend::SUSPEND_FOR_DEBUGGER);
+ STRESS_LOG0(LF_CORDB,LL_INFO1000, "Locked thread store\n");
+
+ // We start the suspension here, and let the helper thread finish it.
+ // If there's no helper thread, then we need to do helper duty.
+ {
+ SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE;
+ fSuspended = g_pEEInterface->StartSuspendForDebug(NULL, TRUE);
+ }
+
+ // We tell the RC Thread to check for other threads now and then and help them get synchronized. (This
+ // is similar to what is done when suspending threads for GC with the HandledJITCase() function.)
+
+ // This does not block.
+ // Pinging this will waken the helper thread (or temp H. thread) and tell it to sweep & send
+ // the sync complete.
+ m_pRCThread->WatchForStragglers();
+
+ // It's possible we may not have a real helper thread.
+ // - on startup in dllmain, helper is blocked on DllMain loader lock.
+ // - on shutdown, helper has been removed on us.
+ // In those cases, we need somebody to send the sync-complete, and handle
+ // managed events, and wait for the continue. So we pretend to be the helper thread.
+ STRESS_LOG0(LF_CORDB, LL_EVERYTHING, "D::SSCIPCE: Calling IsRCThreadReady()\n");
+
+ // We must check the helper thread status while under the lock.
+ _ASSERTE(ThreadHoldsLock());
+ // If we failed to suspend, then that means we must have multiple managed threads.
+ // That means that our helper is not blocked on starting up, thus we can wait infinite on it.
+ // Thus we don't need to do helper duty if the suspend fails.
+ bool fShouldDoHelperDuty = !m_pRCThread->IsRCThreadReady() && fSuspended;
+ if (fShouldDoHelperDuty && !g_fProcessDetach)
+ {
+ // In V1.0, we had the assumption that if the helper thread isn't ready yet, then we're in
+ // a state that SuspendForDebug will succeed on the first try, and thus we'll
+ // never call Sweep when doing helper thread duty.
+ _ASSERTE(fSuspended);
+
+ // This call will do a ton of work, it will toggle the lock,
+ // and it will block until we receive a continue!
+ DoHelperThreadDuty();
+
+ // We will have released the TSL after the call to continue.
+ }
+ else
+ {
+ // We have a live and active helper thread which will handle events
+ // from the RS now that we're stopped.
+ // We need to release the TSL which we acquired above. (The helper will
+ // likely take this lock while doing stuff).
+ STRESS_LOG0(LF_CORDB,LL_INFO1000, "About to unlock thread store!\n");
+ ThreadSuspend::UnlockThreadStore(FALSE, ThreadSuspend::SUSPEND_FOR_DEBUGGER);
+ STRESS_LOG0(LF_CORDB,LL_INFO1000, "TART: Unlocked thread store!\n");
+ }
+ _ASSERTE(ThreadHoldsLock()); // still hold the lock. (though it may have been toggled)
+ }
+}
+
+
+//
+// ReleaseAllRuntimeThreads releases all Runtime threads that may be
+// stopped after trapping and sending the at safe point event.
+//
+void Debugger::ReleaseAllRuntimeThreads(AppDomain *pAppDomain)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+
+ // We acquired the lock b/c we're in a scope between LFES & UFES.
+ PRECONDITION(ThreadHoldsLock());
+
+ // Currently, this is only done on a helper thread.
+ PRECONDITION(ThisIsHelperThreadWorker());
+
+ // Make sure that we were stopped...
+ PRECONDITION(m_trappingRuntimeThreads && m_stopped);
+ }
+ CONTRACTL_END;
+
+ //<TODO>@todo APPD if we want true isolation, remove this & finish the work</TODO>
+ pAppDomain = NULL;
+
+ STRESS_LOG1(LF_CORDB, LL_INFO10000, "D::RART: Releasing all Runtime threads"
+ "for AppD 0x%x.\n", pAppDomain);
+
+ // Mark that we're on our way now...
+ m_trappingRuntimeThreads = FALSE;
+ m_stopped = FALSE;
+
+ // Go ahead and resume the Runtime threads.
+ g_pEEInterface->ResumeFromDebug(pAppDomain);
+}
+
+// Given a method, get's its EnC version number. 1 if the method is not EnCed.
+// Note that MethodDescs are reused between versions so this will give us
+// the most recent EnC number.
+int Debugger::GetMethodEncNumber(MethodDesc * pMethod)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ THROWS;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ DebuggerJitInfo * dji = GetLatestJitInfoFromMethodDesc(pMethod);
+ if (dji == NULL)
+ {
+ // If there's no DJI, couldn't have been EnCed.
+ return 1;
+ }
+ return (int) dji->m_encVersion;
+}
+
+
+bool Debugger::IsJMCMethod(Module* pModule, mdMethodDef tkMethod)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ THROWS;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ PRECONDITION(CORDebuggerAttached());
+ }
+ CONTRACTL_END;
+
+#ifdef _DEBUG
+ Crst crstDbg(CrstIsJMCMethod, CRST_UNSAFE_ANYMODE);
+ PRECONDITION(crstDbg.IsSafeToTake());
+#endif
+
+ DebuggerMethodInfo *pInfo = GetOrCreateMethodInfo(pModule, tkMethod);
+
+ if (pInfo == NULL)
+ return false;
+
+ return pInfo->IsJMCFunction();
+}
+
+/******************************************************************************
+ * Called by Runtime when on a 1st chance Native Exception.
+ * This is likely when we hit a breakpoint / single-step.
+ * This is called for all native exceptions (except COM+) on managed threads,
+ * regardless of whether the debugger is attached.
+ ******************************************************************************/
+bool Debugger::FirstChanceNativeException(EXCEPTION_RECORD *exception,
+ CONTEXT *context,
+ DWORD code,
+ Thread *thread)
+{
+
+ // @@@
+ // Implement DebugInterface
+ // Can be called from EE exception code. Or from our M2UHandoffHijackFilter
+ // must be on managed thread.
+
+ CONTRACTL
+ {
+ SO_TOLERANT;
+ NOTHROW;
+
+ // No clear GC_triggers semantics here. See DispatchNativeException.
+ WRAPPER(GC_TRIGGERS);
+ MODE_ANY;
+
+ PRECONDITION(CheckPointer(exception));
+ PRECONDITION(CheckPointer(context));
+ PRECONDITION(CheckPointer(thread));
+ }
+ CONTRACTL_END;
+
+
+ // Ignore any notification exceptions sent from code:Debugger.SendRawEvent.
+ // This is not a common case, but could happen in some cases described
+ // in SendRawEvent. Either way, Left-Side and VM should just ignore these.
+ if (IsEventDebuggerNotification(exception, PTR_TO_CORDB_ADDRESS(g_pMSCorEE)))
+ {
+ return true;
+ }
+
+ bool retVal;
+
+ // Don't stop for native debugging anywhere inside our inproc-Filters.
+ CantStopHolder hHolder;
+
+ if (!CORDBUnrecoverableError(this))
+ {
+ retVal = DebuggerController::DispatchNativeException(exception, context,
+ code, thread);
+ }
+ else
+ {
+ retVal = false;
+ }
+
+ return retVal;
+}
+
+/******************************************************************************
+ *
+ ******************************************************************************/
+PRD_TYPE Debugger::GetPatchedOpcode(CORDB_ADDRESS_TYPE *ip)
+{
+ WRAPPER_NO_CONTRACT;
+
+ if (!CORDBUnrecoverableError(this))
+ {
+ return DebuggerController::GetPatchedOpcode(ip);
+ }
+ else
+ {
+ PRD_TYPE mt;
+ InitializePRD(&mt);
+ return mt;
+ }
+}
+
+/******************************************************************************
+ *
+ ******************************************************************************/
+BOOL Debugger::CheckGetPatchedOpcode(CORDB_ADDRESS_TYPE *address, /*OUT*/ PRD_TYPE *pOpcode)
+{
+ WRAPPER_NO_CONTRACT;
+ CONSISTENCY_CHECK(CheckPointer(address));
+ CONSISTENCY_CHECK(CheckPointer(pOpcode));
+
+ if (CORDebuggerAttached() && !CORDBUnrecoverableError(this))
+ {
+ return DebuggerController::CheckGetPatchedOpcode(address, pOpcode);
+ }
+ else
+ {
+ InitializePRD(pOpcode);
+ return FALSE;
+ }
+}
+
+/******************************************************************************
+ *
+ ******************************************************************************/
+void Debugger::TraceCall(const BYTE *code)
+{
+ CONTRACTL
+ {
+ // We're being called right before we call managed code. Can't trigger
+ // because there may be unprotected args on the stack.
+ MODE_COOPERATIVE;
+ GC_NOTRIGGER;
+
+ NOTHROW;
+ }
+ CONTRACTL_END;
+
+
+ Thread * pCurThread = g_pEEInterface->GetThread();
+ // Ensure we never even think about running managed code on the helper thread.
+ _ASSERTE(!ThisIsHelperThreadWorker() || !"You're running managed code on the helper thread");
+
+ // One threat is that our helper thread may be forced to execute a managed DLL main.
+ // In that case, it's before the helper thread proc is even executed, so our conventional
+ // IsHelperThread() checks are inadequate.
+ _ASSERTE((GetCurrentThreadId() != g_pRCThread->m_DbgHelperThreadOSTid) || !"You're running managed code on the helper thread");
+
+ _ASSERTE((g_pEEInterface->GetThreadFilterContext(pCurThread) == NULL) || !"Shouldn't run managed code w/ Filter-Context set");
+
+ if (!CORDBUnrecoverableError(this))
+ {
+ // There are situations where our callers can't tolerate us throwing.
+ EX_TRY
+ {
+ // Since we have a try catch and the debugger code can deal properly with
+ // faults occuring inside DebuggerController::DispatchTraceCall, we can safely
+ // establish a FAULT_NOT_FATAL region. This is required since some callers can't
+ // tolerate faults.
+ FAULT_NOT_FATAL();
+
+ DebuggerController::DispatchTraceCall(pCurThread, code);
+ }
+ EX_CATCH
+ {
+ // We're being called for our benefit, not our callers. So if we fail,
+ // they don't care.
+ // Failure for us means that some steppers may miss their notification
+ // for entering managed code.
+ LOG((LF_CORDB, LL_INFO10000, "Debugger::TraceCall - inside catch, %p\n", code));
+ }
+ EX_END_CATCH(SwallowAllExceptions);
+ }
+}
+
+/******************************************************************************
+ * For Just-My-Code (aka Just-User-Code).
+ * Invoked from a probe in managed code when we enter a user method and
+ * the flag (set by GetJMCFlagAddr) for that method is != 0.
+ * pIP - the ip within the method, right after the prolog.
+ * sp - stack pointer (frame pointer on x86) for the managed method we're entering.
+ * bsp - backing store pointer for the managed method we're entering
+ ******************************************************************************/
+void Debugger::OnMethodEnter(void * pIP)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_NOTRIGGER;
+ SO_NOT_MAINLINE;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_INFO1000000, "D::OnMethodEnter(ip=%p)\n", pIP));
+
+ if (!CORDebuggerAttached())
+ {
+ LOG((LF_CORDB, LL_INFO1000000, "D::OnMethodEnter returning since debugger attached.\n"));
+ return;
+ }
+ FramePointer fp = LEAF_MOST_FRAME;
+ DebuggerController::DispatchMethodEnter(pIP, fp);
+}
+/******************************************************************************
+ * GetJMCFlagAddr
+ * Provide an address of the flag that the JMC probes use to decide whether
+ * or not to call TriggerMethodEnter.
+ * Called for each method that we jit.
+ * md - method desc for the JMC probe
+ * returns an address of a flag that the probe can use.
+ ******************************************************************************/
+DWORD* Debugger::GetJMCFlagAddr(Module * pModule)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ SO_TOLERANT;
+ PRECONDITION(CheckPointer(pModule));
+ }
+ CONTRACTL_END;
+
+ // This callback will be invoked whenever we jit debuggable code.
+ // A debugger may not be attached yet, but we still need someplace
+ // to store this dword.
+ // Use the EE's module, because it's always around, even if a debugger
+ // is attached or not.
+ return &(pModule->m_dwDebuggerJMCProbeCount);
+}
+
+/******************************************************************************
+ * Updates the JMC flag on all the EE modules.
+ * We can do this as often as we'd like - though it's a perf hit.
+ ******************************************************************************/
+void Debugger::UpdateAllModuleJMCFlag(bool fStatus)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_INFO1000000, "D::UpdateModuleJMCFlag to %d\n", fStatus));
+
+ _ASSERTE(HasDebuggerDataLock());
+
+ // Loop through each module.
+ // The module table is lazily allocated. As soon as we set JMC status on any module, that will cause an
+ // allocation of the module table. So if the table isn't allocated no module has JMC set,
+ // and so there is nothing to update.
+ if (m_pModules != NULL)
+ {
+ HASHFIND f;
+ for (DebuggerModule * m = m_pModules->GetFirstModule(&f);
+ m != NULL;
+ m = m_pModules->GetNextModule(&f))
+ {
+ // the primary module may get called multiple times, but that's ok.
+ UpdateModuleJMCFlag(m->GetRuntimeModule(), fStatus);
+ } // end for all modules.
+ }
+}
+
+/******************************************************************************
+ * Updates the JMC flag on the given Primary module
+ * We can do this as often as we'd like - though it's a perf hit.
+ * If we've only changed methods in a single module, then we can just call this.
+ * If we do a more global thing (Such as enable MethodEnter), then that could
+ * affect all modules, so we use the UpdateAllModuleJMCFlag helper.
+ ******************************************************************************/
+void Debugger::UpdateModuleJMCFlag(Module * pRuntimeModule, bool fStatus)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(HasDebuggerDataLock());
+
+
+ DWORD * pFlag = &(pRuntimeModule->m_dwDebuggerJMCProbeCount);
+ _ASSERTE(pFlag != NULL);
+
+ if (pRuntimeModule->HasAnyJMCFunctions())
+ {
+ // If this is a user-code module, then update the JMC flag
+ // the probes look at so that we get MethodEnter callbacks.
+ *pFlag = fStatus;
+
+ LOG((LF_CORDB, LL_EVERYTHING, "D::UpdateModuleJMCFlag, module %p is user code\n", pRuntimeModule));
+ } else {
+ LOG((LF_CORDB, LL_EVERYTHING, "D::UpdateModuleJMCFlag, module %p is not-user code\n", pRuntimeModule));
+
+ // if non-user code, flag should be 0 so that we don't waste
+ // cycles in the callbacks.
+ _ASSERTE(*pFlag == 0);
+ }
+}
+
+// This sets the JMC status for the entire module.
+// fStatus - default status for whole module
+void Debugger::SetModuleDefaultJMCStatus(Module * pRuntimeModule, bool fStatus)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ PRECONDITION(ThisIsHelperThreadWorker());
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_INFO100000, "DM::SetJMCStatus, status=%d, this=%p\n", fStatus, this));
+
+ // Ensure that all active DMIs have our status.
+ // All new DMIs can lookup their status from us.
+ // This should also update the module count of active JMC DMI's.
+ DebuggerMethodInfoTable * pTable = g_pDebugger->GetMethodInfoTable();
+
+ if (pTable != NULL)
+ {
+ Debugger::DebuggerDataLockHolder debuggerDataLockHolder(g_pDebugger);
+ HASHFIND info;
+
+ for (DebuggerMethodInfo *dmi = pTable->GetFirstMethodInfo(&info);
+ dmi != NULL;
+ dmi = pTable->GetNextMethodInfo(&info))
+ {
+ if (dmi->GetRuntimeModule() == pRuntimeModule)
+ {
+ // This DMI is in this module, so update its status
+ dmi->SetJMCStatus(fStatus);
+ }
+ }
+ }
+
+ pRuntimeModule->SetJMCStatus(fStatus);
+
+#ifdef _DEBUG
+ // If we're disabling JMC in this module, then we shouldn't
+ // have any active JMC functions.
+ if (!fStatus)
+ {
+ _ASSERTE(!pRuntimeModule->HasAnyJMCFunctions());
+ }
+#endif
+}
+
+/******************************************************************************
+ * Called by GC to determine if it's safe to do a GC.
+ ******************************************************************************/
+bool Debugger::ThreadsAtUnsafePlaces(void)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ // If we're in shutdown mode, then all other threads are parked.
+ // Even if they claim to be at unsafe regions, they're still safe to do a GC. They won't touch
+ // their stacks.
+ if (m_fShutdownMode)
+ {
+ if (m_threadsAtUnsafePlaces > 0)
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO10000, "D::TAUP: Claiming safety in shutdown mode.%d\n", m_threadsAtUnsafePlaces);
+ }
+ return false;
+ }
+
+
+ return (m_threadsAtUnsafePlaces != 0);
+}
+
+//
+// SendBreakpoint is called by Runtime threads to send that they've
+// hit a breakpoint to the Right Side.
+//
+void Debugger::SendBreakpoint(Thread *thread, CONTEXT *context,
+ DebuggerBreakpoint *breakpoint)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ if (CORDBUnrecoverableError(this))
+ return;
+
+#ifdef _DEBUG
+ static BOOL shouldBreak = -1;
+ if (shouldBreak == -1)
+ shouldBreak = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgBreakOnSendBreakpoint);
+
+ if (shouldBreak > 0) {
+ _ASSERTE(!"DbgBreakOnSendBreakpoint");
+ }
+#endif
+
+ LOG((LF_CORDB, LL_INFO10000, "D::SB: breakpoint BP:0x%x\n", breakpoint));
+
+ _ASSERTE((g_pEEInterface->GetThread() &&
+ !g_pEEInterface->GetThread()->m_fPreemptiveGCDisabled) ||
+ g_fInControlC);
+
+ _ASSERTE(ThreadHoldsLock());
+
+ // Send a breakpoint event to the Right Side
+ DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer();
+ InitIPCEvent(ipce,
+ DB_IPCE_BREAKPOINT,
+ thread,
+ thread->GetDomain());
+ ipce->BreakpointData.breakpointToken.Set(breakpoint);
+ _ASSERTE( breakpoint->m_pAppDomain == ipce->vmAppDomain.GetRawPtr());
+
+ m_pRCThread->SendIPCEvent();
+}
+
+
+//---------------------------------------------------------------------------------------
+// Send a user breakpoint event for this thread and sycnhronize the process.
+//
+// Arguments:
+// pThread - non-null thread to send user breakpoint event for.
+//
+// Notes:
+// Can't assume that a debugger is attached (since it may detach before we get the lock).
+void Debugger::SendUserBreakpointAndSynchronize(Thread * pThread)
+{
+ AtSafePlaceHolder unsafePlaceHolder(pThread);
+
+ SENDIPCEVENT_BEGIN(this, pThread);
+
+ // Actually send the event
+ if (CORDebuggerAttached())
+ {
+ SendRawUserBreakpoint(pThread);
+ TrapAllRuntimeThreads();
+ }
+
+ SENDIPCEVENT_END;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// SendRawUserBreakpoint is called by Runtime threads to send that
+// they've hit a user breakpoint to the Right Side. This is the event
+// send only part, since it can be called from a few different places.
+//
+// Arguments:
+// pThread - [in] managed thread where user break point takes place.
+// mus be curernt thread.
+//
+void Debugger::SendRawUserBreakpoint(Thread * pThread)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_PREEMPTIVE;
+
+ PRECONDITION(pThread == GetThread());
+
+ PRECONDITION(ThreadHoldsLock());
+
+ // Debugger must have been attached to get us to this point.
+ // We hold the Debugger-lock, so debugger could not have detached from
+ // underneath us either.
+ PRECONDITION(CORDebuggerAttached());
+ }
+ CONTRACTL_END;
+
+ if (CORDBUnrecoverableError(this))
+ return;
+
+ LOG((LF_CORDB, LL_INFO10000, "D::SRUB: user breakpoint\n"));
+
+
+
+ // Send a breakpoint event to the Right Side
+ DebuggerIPCEvent* pEvent = m_pRCThread->GetIPCEventSendBuffer();
+ InitIPCEvent(pEvent,
+ DB_IPCE_USER_BREAKPOINT,
+ pThread,
+ pThread->GetDomain());
+
+ m_pRCThread->SendIPCEvent();
+}
+
+//
+// SendInterceptExceptionComplete is called by Runtime threads to send that
+// they've completed intercepting an exception to the Right Side. This is the event
+// send only part, since it can be called from a few different places.
+//
+void Debugger::SendInterceptExceptionComplete(Thread *thread)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ if (CORDBUnrecoverableError(this))
+ return;
+
+ LOG((LF_CORDB, LL_INFO10000, "D::SIEC: breakpoint\n"));
+
+ _ASSERTE(!g_pEEInterface->IsPreemptiveGCDisabled());
+ _ASSERTE(ThreadHoldsLock());
+
+ // Send a breakpoint event to the Right Side
+ DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer();
+ InitIPCEvent(ipce,
+ DB_IPCE_INTERCEPT_EXCEPTION_COMPLETE,
+ thread,
+ thread->GetDomain());
+
+ m_pRCThread->SendIPCEvent();
+}
+
+
+
+//
+// SendStep is called by Runtime threads to send that they've
+// completed a step to the Right Side.
+//
+void Debugger::SendStep(Thread *thread, CONTEXT *context,
+ DebuggerStepper *stepper,
+ CorDebugStepReason reason)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ if (CORDBUnrecoverableError(this))
+ return;
+
+ LOG((LF_CORDB, LL_INFO10000, "D::SS: step:token:0x%p reason:0x%x\n",
+ stepper, reason));
+
+ _ASSERTE((g_pEEInterface->GetThread() &&
+ !g_pEEInterface->GetThread()->m_fPreemptiveGCDisabled) ||
+ g_fInControlC);
+
+ _ASSERTE(ThreadHoldsLock());
+
+ // Send a step event to the Right Side
+ DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer();
+ InitIPCEvent(ipce,
+ DB_IPCE_STEP_COMPLETE,
+ thread,
+ thread->GetDomain());
+ ipce->StepData.stepperToken.Set(stepper);
+ ipce->StepData.reason = reason;
+ m_pRCThread->SendIPCEvent();
+}
+
+//-------------------------------------------------------------------------------------------------
+// Send an EnC remap opportunity and block until it is continued.
+//
+// dji - current method information
+// currentIP - IL offset within that method
+// resumeIP - address of a SIZE_T that the RS will write to cross-process if they take the
+// remap opportunity. *resumeIP is untouched if the RS does not remap.
+//-------------------------------------------------------------------------------------------------
+void Debugger::LockAndSendEnCRemapEvent(DebuggerJitInfo * dji, SIZE_T currentIP, SIZE_T *resumeIP)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS; // From SendIPCEvent
+ PRECONDITION(dji != NULL);
+ }
+ CONTRACTL_END;
+
+
+ LOG((LF_CORDB, LL_INFO10000, "D::LASEnCRE:\n"));
+
+ if (CORDBUnrecoverableError(this))
+ return;
+
+ MethodDesc * pFD = dji->m_fd;
+
+ // Note that the debugger lock is reentrant, so we may or may not hold it already.
+ Thread *thread = g_pEEInterface->GetThread();
+ SENDIPCEVENT_BEGIN(this, thread);
+
+ // Send an EnC remap event to the Right Side.
+ DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer();
+ InitIPCEvent(ipce,
+ DB_IPCE_ENC_REMAP,
+ thread,
+ thread->GetDomain());
+
+ ipce->EnCRemap.currentVersionNumber = dji->m_encVersion;
+ ipce->EnCRemap.resumeVersionNumber = dji->m_methodInfo->GetCurrentEnCVersion();;
+ ipce->EnCRemap.currentILOffset = currentIP;
+ ipce->EnCRemap.resumeILOffset = resumeIP;
+ ipce->EnCRemap.funcMetadataToken = pFD->GetMemberDef();
+
+ LOG((LF_CORDB, LL_INFO10000, "D::LASEnCRE: token 0x%x, from version %d to %d\n",
+ ipce->EnCRemap.funcMetadataToken, ipce->EnCRemap.currentVersionNumber, ipce->EnCRemap.resumeVersionNumber));
+
+ Module *pRuntimeModule = pFD->GetModule();
+
+ DebuggerModule * pDModule = LookupOrCreateModule(pRuntimeModule, thread->GetDomain());
+ ipce->EnCRemap.vmDomainFile.SetRawPtr((pDModule ? pDModule->GetDomainFile() : NULL));
+
+ LOG((LF_CORDB, LL_INFO10000, "D::LASEnCRE: %s::%s "
+ "dmod:0x%x, methodDef:0x%x \n",
+ pFD->m_pszDebugClassName, pFD->m_pszDebugMethodName,
+ pDModule,
+ ipce->EnCRemap.funcMetadataToken));
+
+ // IPC event is now initialized, so we can send it over.
+ SendSimpleIPCEventAndBlock();
+
+ // This will block on the continue
+ SENDIPCEVENT_END;
+
+ LOG((LF_CORDB, LL_INFO10000, "D::LASEnCRE: done\n"));
+
+}
+
+// Send the RemapComplete event and block until the debugger Continues
+// pFD - specifies the method in which we've remapped into
+void Debugger::LockAndSendEnCRemapCompleteEvent(MethodDesc *pFD)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_INFO10000, "D::LASEnCRE:\n"));
+
+ if (CORDBUnrecoverableError(this))
+ return;
+
+ Thread *thread = g_pEEInterface->GetThread();
+ // Note that the debugger lock is reentrant, so we may or may not hold it already.
+ SENDIPCEVENT_BEGIN(this, thread);
+
+ EX_TRY
+ {
+ // Ensure the DJI for the latest version of this method has been pre-created.
+ // It's not clear whether this is necessary or not, but it shouldn't hurt since
+ // we're going to need to create it anyway since we'll be debugging inside it.
+ DebuggerJitInfo *dji = g_pDebugger->GetLatestJitInfoFromMethodDesc(pFD);
+ (void)dji; //prevent "unused variable" error from GCC
+ _ASSERTE( dji != NULL );
+ }
+ EX_CATCH
+ {
+ // GetLatestJitInfo could throw on OOM, but the debugger isn't resiliant to OOM.
+ // I'm not aware of any other legitimate reason why it may throw, so we'll ASSERT
+ // if it fails.
+ _ASSERTE(!"Unexpected exception from Debugger::GetLatestJitInfoFromMethodDesc on EnC remap complete");
+ }
+ EX_END_CATCH(RethrowTerminalExceptions);
+
+ // Send an EnC remap complete event to the Right Side.
+ DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer();
+ InitIPCEvent(ipce,
+ DB_IPCE_ENC_REMAP_COMPLETE,
+ thread,
+ thread->GetDomain());
+
+
+ ipce->EnCRemapComplete.funcMetadataToken = pFD->GetMemberDef();
+
+ Module *pRuntimeModule = pFD->GetModule();
+
+ DebuggerModule * pDModule = LookupOrCreateModule(pRuntimeModule, thread->GetDomain());
+ ipce->EnCRemapComplete.vmDomainFile.SetRawPtr((pDModule ? pDModule->GetDomainFile() : NULL));
+
+
+ LOG((LF_CORDB, LL_INFO10000, "D::LASEnCRC: %s::%s "
+ "dmod:0x%x, methodDef:0x%x \n",
+ pFD->m_pszDebugClassName, pFD->m_pszDebugMethodName,
+ pDModule,
+ ipce->EnCRemap.funcMetadataToken));
+
+ // IPC event is now initialized, so we can send it over.
+ SendSimpleIPCEventAndBlock();
+
+ // This will block on the continue
+ SENDIPCEVENT_END;
+
+ LOG((LF_CORDB, LL_INFO10000, "D::LASEnCRC: done\n"));
+
+}
+//
+// This function sends a notification to the RS about a specific update that has occurred as part of
+// applying an Edit and Continue. We send notification only for function add/update and field add.
+// At this point, the EE is already stopped for handling an EnC ApplyChanges operation, so no need
+// to take locks etc.
+//
+void Debugger::SendEnCUpdateEvent(DebuggerIPCEventType eventType,
+ Module * pModule,
+ mdToken memberToken,
+ mdTypeDef classToken,
+ SIZE_T enCVersion)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_INFO10000, "D::LASEnCUFE:\n"));
+
+ _ASSERTE(eventType == DB_IPCE_ENC_UPDATE_FUNCTION ||
+ eventType == DB_IPCE_ENC_ADD_FUNCTION ||
+ eventType== DB_IPCE_ENC_ADD_FIELD);
+
+ if (CORDBUnrecoverableError(this))
+ return;
+
+ // Send an EnC UpdateFunction event to the Right Side.
+ DebuggerIPCEvent* event = m_pRCThread->GetIPCEventSendBuffer();
+ InitIPCEvent(event,
+ eventType,
+ NULL,
+ NULL);
+
+ event->EnCUpdate.newVersionNumber = enCVersion;
+ event->EnCUpdate.memberMetadataToken = memberToken;
+ // we have to pass the class token across to the RS because we cannot look it up over
+ // there based on the added field/method because the metadata on the RS will not yet
+ // have the changes applied, so the token will not exist in its metadata and we have
+ // no way to find it.
+ event->EnCUpdate.classMetadataToken = classToken;
+
+ _ASSERTE(pModule);
+ // we don't support shared assemblies, so must have an appdomain
+ _ASSERTE(pModule->GetDomain()->IsAppDomain());
+
+ DebuggerModule * pDModule = LookupOrCreateModule(pModule, pModule->GetDomain()->AsAppDomain());
+ event->EnCUpdate.vmDomainFile.SetRawPtr((pDModule ? pDModule->GetDomainFile() : NULL));
+
+ m_pRCThread->SendIPCEvent();
+
+ LOG((LF_CORDB, LL_INFO10000, "D::LASEnCUE: done\n"));
+
+}
+
+
+//
+// Send a BreakpointSetError event to the Right Side if the given patch is for a breakpoint. Note: we don't care if this
+// fails, there is nothing we can do about it anyway, and the breakpoint just wont hit.
+//
+void Debugger::LockAndSendBreakpointSetError(PATCH_UNORDERED_ARRAY * listUnbindablePatches)
+{
+ CONTRACTL
+ {
+ MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT;
+ GC_TRIGGERS;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(listUnbindablePatches != NULL);
+
+ if (CORDBUnrecoverableError(this))
+ return;
+
+
+ ULONG count = listUnbindablePatches->Count();
+ _ASSERTE(count > 0); // must send at least 1 event.
+
+
+ Thread *thread = g_pEEInterface->GetThread();
+ // Note that the debugger lock is reentrant, so we may or may not hold it already.
+ SENDIPCEVENT_BEGIN(this, thread);
+
+ DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer();
+
+ for(ULONG i = 0; i < count; i++)
+ {
+ DebuggerControllerPatch *patch = listUnbindablePatches->Table()[i];
+ _ASSERTE(patch != NULL);
+
+ // Only do this for breakpoint controllers
+ DebuggerController *controller = patch->controller;
+
+ if (controller->GetDCType() != DEBUGGER_CONTROLLER_BREAKPOINT)
+ {
+ continue;
+ }
+
+ LOG((LF_CORDB, LL_INFO10000, "D::LASBSE:\n"));
+
+ // Send a breakpoint set error event to the Right Side.
+ InitIPCEvent(ipce, DB_IPCE_BREAKPOINT_SET_ERROR, thread, thread->GetDomain());
+
+ ipce->BreakpointSetErrorData.breakpointToken.Set(static_cast<DebuggerBreakpoint*> (controller));
+
+ // IPC event is now initialized, so we can send it over.
+ m_pRCThread->SendIPCEvent();
+ }
+
+ // Stop all Runtime threads
+ TrapAllRuntimeThreads();
+
+ // This will block on the continue
+ SENDIPCEVENT_END;
+
+}
+
+//
+// Called from the controller to lock the debugger for event
+// sending. This is called before controller events are sent, like
+// breakpoint, step complete, and thread started.
+//
+// Note that it's possible that the debugger detached (and destroyed our IPC
+// events) while we're waiting for our turn.
+// So Callers should check for that case.
+void Debugger::LockForEventSending(DebuggerLockHolder *dbgLockHolder)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_PREEMPTIVE;
+ }
+ CONTRACTL_END;
+
+ // @todo - Force our parents to bump up the stop-count. That way they can
+ // guarantee it's balanced.
+ IncCantStopCount();
+ _ASSERTE(IsInCantStopRegion());
+
+ // What we need is for caller to get the debugger lock
+ if (dbgLockHolder != NULL)
+ {
+ dbgLockHolder->Acquire();
+ }
+
+#ifdef _DEBUG
+ // Track our TID. We're not re-entrant.
+ //_ASSERTE(m_tidLockedForEventSending == 0);
+ m_tidLockedForEventSending = GetCurrentThreadId();
+#endif
+
+}
+
+//
+// Called from the controller to unlock the debugger from event
+// sending. This is called after controller events are sent, like
+// breakpoint, step complete, and thread started.
+//
+void Debugger::UnlockFromEventSending(DebuggerLockHolder *dbgLockHolder)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_PREEMPTIVE;
+ }
+ CONTRACTL_END;
+
+#ifdef _DEBUG
+ //_ASSERTE(m_tidLockedForEventSending == GetCurrentThreadId());
+ m_tidLockedForEventSending = 0;
+#endif
+ if (dbgLockHolder != NULL)
+ {
+ dbgLockHolder->Release();
+ }
+ // @todo - Force our parents to bump up the stop-count. That way they can
+ // guarantee it's balanced.
+ _ASSERTE(IsInCantStopRegion());
+ DecCantStopCount();
+}
+
+
+//
+// Called from the controller after all events have been sent for a
+// thread to sync the process.
+//
+void Debugger::SyncAllThreads(DebuggerLockHolder *dbgLockHolder)
+{
+ CONTRACTL
+ {
+ MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT;
+ MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT;
+ }
+ CONTRACTL_END;
+
+ if (CORDBUnrecoverableError(this))
+ return;
+
+ STRESS_LOG0(LF_CORDB, LL_INFO10000, "D::SAT: sync all threads.\n");
+
+ Thread *pThread = g_pEEInterface->GetThread();
+ (void)pThread; //prevent "unused variable" error from GCC
+ _ASSERTE((pThread &&
+ !pThread->m_fPreemptiveGCDisabled) ||
+ g_fInControlC);
+
+ _ASSERTE(ThreadHoldsLock());
+
+ // Stop all Runtime threads
+ TrapAllRuntimeThreads();
+}
+
+//---------------------------------------------------------------------------------------
+// Launch a debugger and then trigger a breakpoint (either managed or native)
+//
+// Arguments:
+// useManagedBPForManagedAttach - TRUE if we should stop with a managed breakpoint
+// when managed attached, FALSE if we should always
+// stop with a native breakpoint
+// pThread - the managed thread that attempts to launch the registered debugger
+// pExceptionInfo - the unhandled exception info
+// explicitUserRequest - TRUE if this attach is caused by a call to the Debugger.Launch() API.
+//
+// Returns:
+// S_OK on success. Else failure.
+//
+// Notes:
+// This function doesn't try to stop the launched native debugger by calling DebugBreak().
+// It sends a breakpoint event only for managed debuggers.
+//
+HRESULT Debugger::LaunchDebuggerForUser(Thread * pThread, EXCEPTION_POINTERS * pExceptionInfo,
+ BOOL useManagedBPForManagedAttach, BOOL explicitUserRequest)
+{
+ WRAPPER_NO_CONTRACT;
+
+ LOG((LF_CORDB, LL_INFO10000, "D::LDFU: Attaching Debugger.\n"));
+
+ //
+ // Initiate a jit attach
+ //
+ JitAttach(pThread, pExceptionInfo, useManagedBPForManagedAttach, explicitUserRequest);
+
+ if (useManagedBPForManagedAttach)
+ {
+ if(CORDebuggerAttached() && (g_pEEInterface->GetThread() != NULL))
+ {
+ //
+ // Send a managed-breakpoint.
+ //
+ SendUserBreakpointAndSynchronize(g_pEEInterface->GetThread());
+ }
+ else if (!CORDebuggerAttached() && IsDebuggerPresent())
+ {
+ //
+ // If the registered debugger is not a managed debugger, send a native breakpoint
+ //
+ DebugBreak();
+ }
+ }
+ else if(!useManagedBPForManagedAttach)
+ {
+ //
+ // Send a native breakpoint
+ //
+ DebugBreak();
+ }
+
+ if (!IsDebuggerPresent())
+ {
+ LOG((LF_CORDB, LL_ERROR, "D::LDFU: Failed to launch the debugger.\n"));
+ }
+
+ return S_OK;
+}
+
+
+// The following JDI structures will be passed to a debugger on Vista. Because we do not know when the debugger
+// will be done looking at them, and there is at most one debugger attaching to the process, we always set them
+// once and leave them set without the risk of clobbering something we care about.
+JIT_DEBUG_INFO Debugger::s_DebuggerLaunchJitInfo = {0};
+EXCEPTION_RECORD Debugger::s_DebuggerLaunchJitInfoExceptionRecord = {0};
+CONTEXT Debugger::s_DebuggerLaunchJitInfoContext = {0};
+
+//----------------------------------------------------------------------------
+//
+// InitDebuggerLaunchJitInfo - initialize JDI structure on Vista
+//
+// Arguments:
+// pThread - the managed thread with the unhandled excpetion
+// pExceptionInfo - unhandled exception info
+//
+// Return Value:
+// None
+//
+//----------------------------------------------------------------------------
+void Debugger::InitDebuggerLaunchJitInfo(Thread * pThread, EXCEPTION_POINTERS * pExceptionInfo)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ _ASSERTE((pExceptionInfo != NULL) &&
+ (pExceptionInfo->ContextRecord != NULL) &&
+ (pExceptionInfo->ExceptionRecord != NULL));
+
+ if ((pExceptionInfo == NULL) || (pExceptionInfo->ContextRecord == NULL) || (pExceptionInfo->ExceptionRecord == NULL))
+ {
+ return;
+ }
+
+ s_DebuggerLaunchJitInfoExceptionRecord = *pExceptionInfo->ExceptionRecord;
+ s_DebuggerLaunchJitInfoContext = *pExceptionInfo->ContextRecord;
+
+ s_DebuggerLaunchJitInfo.dwSize = sizeof(s_DebuggerLaunchJitInfo);
+ s_DebuggerLaunchJitInfo.dwThreadID = pThread == NULL ? GetCurrentThreadId() : pThread->GetOSThreadId();
+ s_DebuggerLaunchJitInfo.lpExceptionRecord = reinterpret_cast<ULONG64>(&s_DebuggerLaunchJitInfoExceptionRecord);
+ s_DebuggerLaunchJitInfo.lpContextRecord = reinterpret_cast<ULONG64>(&s_DebuggerLaunchJitInfoContext);
+ s_DebuggerLaunchJitInfo.lpExceptionAddress = s_DebuggerLaunchJitInfoExceptionRecord.ExceptionAddress != NULL ?
+ reinterpret_cast<ULONG64>(s_DebuggerLaunchJitInfoExceptionRecord.ExceptionAddress) :
+ reinterpret_cast<ULONG64>(reinterpret_cast<PVOID>(GetIP(pExceptionInfo->ContextRecord)));
+
+#if defined(_TARGET_X86_)
+ s_DebuggerLaunchJitInfo.dwProcessorArchitecture = PROCESSOR_ARCHITECTURE_INTEL;
+#elif defined(_TARGET_AMD64_)
+ s_DebuggerLaunchJitInfo.dwProcessorArchitecture = PROCESSOR_ARCHITECTURE_AMD64;
+#elif defined(_TARGET_ARM_)
+ s_DebuggerLaunchJitInfo.dwProcessorArchitecture = PROCESSOR_ARCHITECTURE_ARM;
+#elif defined(_TARGET_ARM64_)
+ s_DebuggerLaunchJitInfo.dwProcessorArchitecture = PROCESSOR_ARCHITECTURE_ARM64;
+#else
+#error Unknown processor.
+#endif
+}
+
+
+//----------------------------------------------------------------------------
+//
+// GetDebuggerLaunchJitInfo - retrieve the initialized JDI structure on Vista
+//
+// Arguments:
+// None
+//
+// Return Value:
+// JIT_DEBUG_INFO * - pointer to JDI structure
+//
+//----------------------------------------------------------------------------
+JIT_DEBUG_INFO * Debugger::GetDebuggerLaunchJitInfo(void)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ _ASSERTE((s_DebuggerLaunchJitInfo.lpExceptionAddress != NULL) &&
+ (s_DebuggerLaunchJitInfo.lpExceptionRecord != NULL) &&
+ (s_DebuggerLaunchJitInfo.lpContextRecord != NULL) &&
+ (((EXCEPTION_RECORD *)(s_DebuggerLaunchJitInfo.lpExceptionRecord))->ExceptionAddress != NULL));
+
+ return &s_DebuggerLaunchJitInfo;
+}
+#endif // !DACCESS_COMPILE
+
+
+// This function checks the registry for the debug launch setting upon encountering an exception or breakpoint.
+DebuggerLaunchSetting Debugger::GetDbgJITDebugLaunchSetting()
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+#if FEATURE_PAL
+ DebuggerLaunchSetting setting = DLS_ATTACH_DEBUGGER;
+#else
+ BOOL bAuto = FALSE;
+
+ DebuggerLaunchSetting setting = DLS_ASK_USER;
+
+ DWORD cchDbgFormat = MAX_LONGPATH;
+ INDEBUG(DWORD cchOldDbgFormat = cchDbgFormat);
+
+#if defined(DACCESS_COMPILE)
+ WCHAR * wszDbgFormat = new (nothrow) WCHAR[cchDbgFormat];
+#else
+ WCHAR * wszDbgFormat = new (interopsafe, nothrow) WCHAR[cchDbgFormat];
+#endif // DACCESS_COMPILE
+
+ if (wszDbgFormat == NULL)
+ {
+ return setting;
+ }
+
+ HRESULT hr = GetDebuggerSettingInfoWorker(wszDbgFormat, &cchDbgFormat, &bAuto);
+ while (hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER))
+ {
+ _ASSERTE(cchDbgFormat > cchOldDbgFormat);
+ INDEBUG(cchOldDbgFormat = cchDbgFormat);
+
+#if defined(DACCESS_COMPILE)
+ delete [] wszDbgFormat;
+ wszDbgFormat = new (nothrow) WCHAR[cchDbgFormat];
+#else
+ DeleteInteropSafe(wszDbgFormat);
+ wszDbgFormat = new (interopsafe, nothrow) WCHAR[cchDbgFormat];
+#endif // DACCESS_COMPILE
+
+ if (wszDbgFormat == NULL)
+ {
+ return setting;
+ }
+
+ hr = GetDebuggerSettingInfoWorker(wszDbgFormat, &cchDbgFormat, &bAuto);
+ }
+
+#if defined(DACCESS_COMPILE)
+ delete [] wszDbgFormat;
+#else
+ DeleteInteropSafe(wszDbgFormat);
+#endif // DACCESS_COMPILE
+
+ if (SUCCEEDED(hr) && bAuto)
+ {
+ setting = DLS_ATTACH_DEBUGGER;
+ }
+#endif // FEATURE_PAL
+
+ return setting;
+}
+
+// Returns a bitfield reflecting the managed debugging state at the time of
+// the jit attach.
+CLR_DEBUGGING_PROCESS_FLAGS Debugger::GetAttachStateFlags()
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return (CLR_DEBUGGING_PROCESS_FLAGS)
+ ((m_attachingForManagedEvent ? CLR_DEBUGGING_MANAGED_EVENT_PENDING : 0)
+ | (m_userRequestedDebuggerLaunch ? CLR_DEBUGGING_MANAGED_EVENT_DEBUGGER_LAUNCH : 0));
+}
+
+#ifndef DACCESS_COMPILE
+//-----------------------------------------------------------------------------
+// Get the full launch string for a jit debugger.
+//
+// If a jit-debugger is registed, then writes string into pStrArgsBuf and
+// return true.
+//
+// If no jit-debugger is registered, then return false.
+//
+// Throws on error (like OOM).
+//-----------------------------------------------------------------------------
+bool Debugger::GetCompleteDebuggerLaunchString(SString * pStrArgsBuf)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+#ifndef FEATURE_PAL
+ DWORD pid = GetCurrentProcessId();
+
+ SString ssDebuggerString;
+ GetDebuggerSettingInfo(ssDebuggerString, NULL);
+
+ if (ssDebuggerString.IsEmpty())
+ {
+ // No jit-debugger available. Don't make one up.
+ return false;
+ }
+
+ // There is no security concern to expect that the debug string we retrieve from HKLM follows a certain
+ // format because changing HKLM keys requires admin priviledge. Padding with zeros is not a security mitigation,
+ // but rather a forward looking compability measure. If future verions of Windows introduces more parameters for
+ // JIT debugger launch, it is preferrable to pass zeros than other random values for those unsupported parameters.
+ pStrArgsBuf->Printf(ssDebuggerString, pid, GetUnmanagedAttachEvent(), GetDebuggerLaunchJitInfo(), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+
+ return true;
+#else // !FEATURE_PAL
+ return false;
+#endif // !FEATURE_PAL
+}
+
+// Proxy code for EDA
+struct EnsureDebuggerAttachedParams
+{
+ Debugger * m_pThis;
+ HRESULT m_retval;
+ PROCESS_INFORMATION * m_pProcessInfo;
+ EnsureDebuggerAttachedParams() :
+ m_pThis(NULL), m_retval(E_FAIL), m_pProcessInfo(NULL) {LIMITED_METHOD_CONTRACT; }
+};
+
+// This is called by the helper thread
+void EDAHelperStub(EnsureDebuggerAttachedParams * p)
+{
+ WRAPPER_NO_CONTRACT;
+
+ p->m_retval = p->m_pThis->EDAHelper(p->m_pProcessInfo);
+}
+
+// This gets called just like the normal version, but it sends the call over to the helper thread
+HRESULT Debugger::EDAHelperProxy(PROCESS_INFORMATION * pProcessInfo)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(!ThisIsHelperThreadWorker());
+ _ASSERTE(ThreadHoldsLock());
+
+ HRESULT hr = LazyInitWrapper();
+ if (FAILED(hr))
+ {
+ // We already stress logged this case.
+ return hr;
+ }
+
+
+ if (!IsGuardPageGone())
+ {
+ return EDAHelper(pProcessInfo);
+ }
+
+ EnsureDebuggerAttachedParams p;
+ p.m_pThis = this;
+ p.m_pProcessInfo = pProcessInfo;
+
+ LOG((LF_CORDB, LL_INFO1000000, "D::EDAHelperProxy\n"));
+ m_pRCThread->DoFavor((FAVORCALLBACK) EDAHelperStub, &p);
+ LOG((LF_CORDB, LL_INFO1000000, "D::EDAHelperProxy return\n"));
+
+ return p.m_retval;
+}
+
+// E_ABORT - if the attach was declined
+// S_OK - Jit-attach successfully started
+HRESULT Debugger::EDAHelper(PROCESS_INFORMATION *pProcessInfo)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT;
+
+ PRECONDITION(ThisMaybeHelperThread()); // on helper if stackoverflow.
+ }
+ CONTRACTL_END;
+
+#ifndef FEATURE_PAL
+ LOG((LF_CORDB, LL_INFO10000, "D::EDA: thread 0x%x is launching the debugger.\n", GetCurrentThreadId()));
+
+ _ASSERTE(HasLazyData());
+
+ // Another potential hang. This may get run on the helper if we have a stack overflow.
+ // Hopefully the odds of 1 thread hitting a stack overflow while another is stuck holding the heap
+ // lock is very small.
+ SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE;
+
+ BOOL fCreateSucceeded = FALSE;
+
+ StackSString strDbgCommand;
+ const WCHAR * wszDbgCommand = NULL;
+ SString strCurrentDir;
+ const WCHAR * wszCurrentDir = NULL;
+
+ EX_TRY
+ {
+
+ // Get the debugger to launch. The returned string is via the strDbgCommand out param. Throws on error.
+ bool fHasDebugger = GetCompleteDebuggerLaunchString(&strDbgCommand);
+ if (fHasDebugger)
+ {
+ wszDbgCommand = strDbgCommand.GetUnicode();
+ _ASSERTE(wszDbgCommand != NULL); // would have thrown on oom.
+
+ LOG((LF_CORDB, LL_INFO10000, "D::EDA: launching with command [%S]\n", wszDbgCommand));
+
+ ClrGetCurrentDirectory(strCurrentDir);
+ wszCurrentDir = strCurrentDir.GetUnicode();
+ }
+ }
+ EX_CATCH
+ {
+ }
+ EX_END_CATCH(SwallowAllExceptions);
+
+ STARTUPINFOW startupInfo = {0};
+ startupInfo.cb = sizeof(STARTUPINFOW);
+
+ DWORD errCreate = 0;
+
+ if (wszDbgCommand != NULL)
+ {
+ // Create the debugger process
+ // When we are launching an debugger, we need to let the child process inherit our handles.
+ // This is necessary for the debugger to signal us that the attach is complete.
+ fCreateSucceeded = WszCreateProcess(NULL, const_cast<WCHAR*> (wszDbgCommand),
+ NULL, NULL,
+ TRUE,
+ CREATE_NEW_CONSOLE,
+ NULL, wszCurrentDir,
+ &startupInfo,
+ pProcessInfo);
+ errCreate = GetLastError();
+ }
+
+ if (!fCreateSucceeded)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "D::EDA: debugger did not launch successfully.\n"));
+ return E_ABORT;
+ }
+
+ LOG((LF_CORDB, LL_INFO10000, "D::EDA: debugger launched successfully.\n"));
+ return S_OK;
+#else // !FEATURE_PAL
+ return E_ABORT;
+#endif // !FEATURE_PAL
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+// This function decides who wins the race for any jit attach and marks the appropriate state that a jit
+// attach is in progress.
+//
+// Arguments
+// willSendManagedEvent - indicates whether or not we plan to send a managed debug event after the jit attach
+// explicitUserRequest - TRUE if this attach is caused by a call to the Debugger.Launch() API.
+//
+// Returns
+// TRUE - if some other thread already has jit attach in progress -> this thread should block until that is complete
+// FALSE - this is the first thread to jit attach -> this thread should launch the debugger
+//
+//
+BOOL Debugger::PreJitAttach(BOOL willSendManagedEvent, BOOL willLaunchDebugger, BOOL explicitUserRequest)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_PREEMPTIVE;
+ PRECONDITION(!ThisIsHelperThreadWorker());
+ }
+ CONTRACTL_END;
+
+ LOG( (LF_CORDB, LL_INFO10000, "D::PreJA: Entering\n") );
+
+ // Multiple threads may be calling this, so need to take the lock.
+ if(!m_jitAttachInProgress)
+ {
+ // TODO: This is a known deadlock! Debugger::PreJitAttach is called during WatsonLastChance.
+ // If the event (exception/crash) happens while this thread is holding the ThreadStore
+ // lock, we may deadlock if another thread holds the DebuggerMutex and is waiting on
+ // the ThreadStore lock. The DebuggerMutex has to be broken into two smaller locks
+ // so that you can take that lock here when holding the ThreadStore lock.
+ DebuggerLockHolder dbgLockHolder(this);
+
+ if (!m_jitAttachInProgress)
+ {
+ m_jitAttachInProgress = TRUE;
+ m_attachingForManagedEvent = willSendManagedEvent;
+ m_launchingDebugger = willLaunchDebugger;
+ m_userRequestedDebuggerLaunch = explicitUserRequest;
+ ResetEvent(GetUnmanagedAttachEvent());
+ ResetEvent(GetAttachEvent());
+ LOG( (LF_CORDB, LL_INFO10000, "D::PreJA: Leaving - first thread\n") );
+ return TRUE;
+ }
+ }
+
+ LOG( (LF_CORDB, LL_INFO10000, "D::PreJA: Leaving - following thread\n") );
+ return FALSE;
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+// This function gets the jit debugger launched and waits for the native attach to complete
+// Make sure you called PreJitAttach and it returned TRUE before you call this
+//
+// Arguments:
+// pThread - the managed thread with the unhandled excpetion
+// pExceptionInfo - the unhandled exception info
+//
+// Returns:
+// S_OK if the debugger was launched successfully and a failing HRESULT otherwise
+//
+HRESULT Debugger::LaunchJitDebuggerAndNativeAttach(Thread * pThread, EXCEPTION_POINTERS * pExceptionInfo)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_PREEMPTIVE;
+ PRECONDITION(!ThisIsHelperThreadWorker());
+ }
+ CONTRACTL_END;
+
+ // You need to have called PreJitAttach first to determine which thread gets to launch the debugger
+ _ASSERTE(m_jitAttachInProgress);
+
+ LOG( (LF_CORDB, LL_INFO10000, "D::LJDANA: Entering\n") );
+ PROCESS_INFORMATION processInfo = {0};
+ DebuggerLockHolder dbgLockHolder(this);
+
+ // <TODO>
+ // If the JIT debugger failed to launch or if there is no JIT debugger, EDAHelperProxy will
+ // switch to preemptive GC mode to display a dialog to the user indicating the JIT debugger
+ // was unavailable. There are some rare cases where this could cause a deadlock with the
+ // debugger lock; however these are rare enough that fixing this doesn't meet the bar for
+ // Whidbey at this point. We might want to revisit this later however.
+ // </TODO>
+ CONTRACT_VIOLATION(GCViolation);
+
+ {
+ LOG((LF_CORDB, LL_INFO1000, "D::EDA: Initialize JDI.\n"));
+
+ EXCEPTION_POINTERS exceptionPointer;
+ EXCEPTION_RECORD exceptionRecord;
+ CONTEXT context;
+
+ if (pExceptionInfo == NULL)
+ {
+ ZeroMemory(&exceptionPointer, sizeof(exceptionPointer));
+ ZeroMemory(&exceptionRecord, sizeof(exceptionRecord));
+ ZeroMemory(&context, sizeof(context));
+
+ context.ContextFlags = CONTEXT_CONTROL;
+ ClrCaptureContext(&context);
+
+ exceptionRecord.ExceptionAddress = reinterpret_cast<PVOID>(GetIP(&context));
+ exceptionPointer.ContextRecord = &context;
+ exceptionPointer.ExceptionRecord = &exceptionRecord;
+
+ pExceptionInfo = &exceptionPointer;
+ }
+
+ InitDebuggerLaunchJitInfo(pThread, pExceptionInfo);
+ }
+
+ // This will make the CreateProcess call to create the debugger process.
+ // We then expect that the debugger process will turn around and attach to us.
+ HRESULT hr = EDAHelperProxy(&processInfo);
+ if(FAILED(hr))
+ {
+ return hr;
+ }
+
+ LOG((LF_CORDB, LL_INFO10000, "D::LJDANA: waiting on m_exUnmanagedAttachEvent and debugger's process handle\n"));
+ DWORD dwHandles = 2;
+ HANDLE arrHandles[2];
+ arrHandles[0] = GetUnmanagedAttachEvent();
+ arrHandles[1] = processInfo.hProcess;
+
+ // Let the helper thread do the attach logic for us and wait for the
+ // attach event. Must release the lock before blocking on a wait.
+ dbgLockHolder.Release();
+
+ // Wait for one or the other to be set. Multiple threads could be waiting here.
+ // The events are manual events, so when they go high, all threads will be released.
+ DWORD res = WaitForMultipleObjectsEx(dwHandles, arrHandles, FALSE, INFINITE, FALSE);
+
+ // We no long need to keep handles to the debugger process.
+ CloseHandle(processInfo.hProcess);
+ CloseHandle(processInfo.hThread);
+
+ // Indicate to the caller that the attach was aborted
+ if (res == WAIT_OBJECT_0 + 1)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "D::LJDANA: Debugger process is unexpectedly terminated!\n"));
+ return E_FAIL;
+ }
+
+ // Otherwise, attach was successful (Note, only native attach is done so far)
+ _ASSERTE((res == WAIT_OBJECT_0) && "WaitForMultipleObjectsEx failed!");
+ LOG( (LF_CORDB, LL_INFO10000, "D::LJDANA: Leaving\n") );
+ return S_OK;
+
+}
+
+// Blocks until the debugger completes jit attach
+void Debugger::WaitForDebuggerAttach()
+{
+ LIMITED_METHOD_CONTRACT;
+
+ LOG( (LF_CORDB, LL_INFO10000, "D::WFDA:Entering\n") );
+
+ // if this thread previously called LaunchDebuggerAndNativeAttach then this wait is spurious,
+ // the event is still set and it continues immediately. If this is an auxilliary thread however
+ // then the wait is necessary
+ // If we are not launching the debugger (e.g. unhandled exception on Win7), then we should not
+ // wait on the unmanaged attach event. If the debugger is launched by the OS, then the unmanaged
+ // attach event passed to the debugger is created by the OS, not by us, so our event will never
+ // be signaled.
+ if (m_launchingDebugger)
+ {
+ WaitForSingleObject(GetUnmanagedAttachEvent(), INFINITE);
+ }
+
+ // Wait until the pending managed debugger attach is completed
+ if (CORDebuggerPendingAttach() && !CORDebuggerAttached())
+ {
+ LOG( (LF_CORDB, LL_INFO10000, "D::WFDA: Waiting for managed attach too\n") );
+ WaitForSingleObject(GetAttachEvent(), INFINITE);
+ }
+
+ // We can't reset the event here because some threads may
+ // be just about to wait on it. If we reset it before the
+ // other threads hit the wait, they'll block.
+
+ // We have an innate race here that can't easily fix. The best
+ // we can do is have a super small window (by moving the reset as
+ // far out this making it very unlikely that a thread will
+ // hit the window.
+
+ LOG( (LF_CORDB, LL_INFO10000, "D::WFDA: Leaving\n") );
+}
+
+// Cleans up after jit attach is complete
+void Debugger::PostJitAttach()
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_PREEMPTIVE;
+ PRECONDITION(!ThisIsHelperThreadWorker());
+ }
+ CONTRACTL_END;
+
+ LOG( (LF_CORDB, LL_INFO10000, "D::PostJA: Entering\n") );
+ // Multiple threads may be calling this, so need to take the lock.
+ DebuggerLockHolder dbgLockHolder(this);
+
+ // clear the attaching flags which allows other threads to initiate jit attach if needed
+ m_jitAttachInProgress = FALSE;
+ m_attachingForManagedEvent = FALSE;
+ m_launchingDebugger = FALSE;
+ m_userRequestedDebuggerLaunch = FALSE;
+ // set the attaching events to unblock other threads waiting on this attach
+ // regardless of whether or not it completed
+ SetEvent(GetUnmanagedAttachEvent());
+ SetEvent(GetAttachEvent());
+ LOG( (LF_CORDB, LL_INFO10000, "D::PostJA: Leaving\n") );
+}
+
+//---------------------------------------------------------------------------------------
+// Launches a debugger and blocks waiting for it to either attach or abort the attach.
+//
+// Arguments:
+// pThread - the managed thread with the unhandled excpetion
+// pExceptionInfo - the unhandled exception info
+// willSendManagedEvent - TRUE if after getting attached we will send a managed debug event
+// explicitUserRequest - TRUE if this attach is caused by a call to the Debugger.Launch() API.
+//
+// Returns:
+// None. Callers can requery if a debugger is attached.
+//
+// Assumptions:
+// This may be called by multiple threads, each firing their own debug events. This function will handle locking.
+// Thus this could block for an arbitrary length of time:
+// - may need to prompt the user to decide if an attach occurs.
+// - may block waiting for a debugger to attach.
+//
+// Notes:
+// The launch string is retrieved from code:GetDebuggerSettingInfo.
+// This will not do a sync-complete. Instead, the caller can send a debug event (the jit-attach
+// event, such as a User-breakpoint or unhandled exception) and that can send a sync-complete,
+// just as if the debugger was always attached. This ensures that the jit-attach event is in the
+// same callback queue as any faked-up events that the Right-side Shim creates.
+//
+void Debugger::JitAttach(Thread * pThread, EXCEPTION_POINTERS * pExceptionInfo, BOOL willSendManagedEvent, BOOL explicitUserRequest)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_ANY;
+
+ PRECONDITION(!ThisIsHelperThreadWorker()); // Must be a managed thread
+ }
+ CONTRACTL_END;
+
+ if (IsDebuggerPresent())
+ return;
+
+ GCX_PREEMP_EEINTERFACE_TOGGLE_IFTHREAD();
+
+ EnsureDebuggerAttached(pThread, pExceptionInfo, willSendManagedEvent, explicitUserRequest);
+}
+
+//-----------------------------------------------------------------------------
+// Ensure that a debugger is attached. Will jit-attach if needed.
+//
+// Arguments
+// pThread - the managed thread with the unhandled excpetion
+// pExceptionInfo - the unhandled exception info
+// willSendManagedEvent - true if after getting (or staying) attached we will send
+// a managed debug event
+// explicitUserRequest - true if this attach is caused by a call to the
+// Debugger.Launch() API.
+//
+// Returns:
+// None. Either a debugger is attached or it is not.
+//
+// Notes:
+// There are several intermediate possible outcomes:
+// - Debugger already attached before this was called.
+// - JIT-atttach debugger spawned, and attached successfully.
+// - JIT-attach debugger spawned, but declined to attach.
+// - Failed to spawn jit-attach debugger.
+//
+// Ultimately, the only thing that matters at the end is whether a debugger
+// is now attached, which is retreived via CORDebuggerAttached().
+//-----------------------------------------------------------------------------
+void Debugger::EnsureDebuggerAttached(Thread * pThread, EXCEPTION_POINTERS * pExceptionInfo, BOOL willSendManagedEvent, BOOL explicitUserRequest)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_PREEMPTIVE;
+ PRECONDITION(!ThisIsHelperThreadWorker());
+ }
+ CONTRACTL_END;
+
+ LOG( (LF_CORDB,LL_INFO10000,"D::EDA\n") );
+
+ HRESULT hr = S_OK;
+
+ // We could be in three states:
+ // 1) no debugger attached
+ // 2) native attached but not managed (yet?)
+ // 3) native attached and managed
+
+
+ // There is a race condition here that can be hit if multiple threads
+ // were to trigger jit attach at the right time
+ // Thread 1 starts jit attach
+ // Thread 2 also starts jit attach and gets to waiting for the attach complete
+ // Thread 1 rapidly completes the jit attach then starts it again
+ // Thread 2 may still be waiting from the first jit attach at this point
+ //
+ // Note that this isn't all that bad because if the debugger hasn't actually detached
+ // in the middle then the second jit attach will complete almost instantly and thread 2
+ // is unblocked. If the debugger did detach in the middle then it seems reasonable for
+ // thread 2 to continue to wait until until the debugger is attached once again for the
+ // second attach. Basically if one jit attach completes and restarts fast enough it might
+ // just go unnoticed by some threads and it will be as if it never happened. Doesn't seem
+ // that bad as long as we know another jit attach is again in progress.
+
+ BOOL startedJitAttach = FALSE;
+
+ // First check to see if we need to launch the debugger ourselves
+ if(PreJitAttach(willSendManagedEvent, TRUE, explicitUserRequest))
+ {
+ // if the debugger is already attached then we can't launch one
+ // and whatever attach state we are in is just what we get
+ if(IsDebuggerPresent())
+ {
+ // unblock other threads waiting on our attach and clean up
+ PostJitAttach();
+ return;
+ }
+ else
+ {
+ hr = LaunchJitDebuggerAndNativeAttach(pThread, pExceptionInfo);
+ if(FAILED(hr))
+ {
+ // unblock other threads waiting on our attach and clean up
+ PostJitAttach();
+ return;
+ }
+ }
+ startedJitAttach = TRUE;
+ }
+
+ // at this point someone should have launched the native debugger and
+ // it is somewhere between not attached and attach complete
+ // (it might have even been completely attached before this function even started)
+ // step 2 - wait for the attach to complete
+ WaitForDebuggerAttach();
+
+ // step 3 - if we initiated then we also cleanup
+ if(startedJitAttach)
+ PostJitAttach();
+ LOG( (LF_CORDB, LL_INFO10000, "D::EDA:Leaving\n") );
+}
+
+
+// Proxy code for AttachDebuggerForBreakpoint
+// Structure used in the proxy function callback
+struct SendExceptionOnHelperThreadParams
+{
+ Debugger *m_pThis;
+ HRESULT m_retval;
+ Thread *m_pThread;
+ OBJECTHANDLE m_exceptionHandle;
+ bool m_continuable;
+ FramePointer m_framePointer;
+ SIZE_T m_nOffset;
+ CorDebugExceptionCallbackType m_eventType;
+ DWORD m_dwFlags;
+
+
+ SendExceptionOnHelperThreadParams() :
+ m_pThis(NULL),
+ m_retval(S_OK),
+ m_pThread(NULL)
+ {LIMITED_METHOD_CONTRACT; }
+};
+
+//**************************************************************************
+// This function sends Exception and ExceptionCallback2 event.
+//
+// Arguments:
+// pThread : managed thread which exception takes place
+// exceptionHandle : handle to the managed exception object (usually
+// something derived from System.Exception)
+// fContinuable : true iff continuable
+// framePointer : frame pointer associated with callback.
+// nOffset : il offset associated with callback.
+// eventType : type of callback
+// dwFlags : additional flags (see CorDebugExceptionFlags).
+//
+// Returns:
+// S_OK on sucess. Else some error. May also throw.
+//
+// Notes:
+// This is a helper for code:Debugger.SendExceptionEventsWorker.
+// See code:Debugger.SendException for more details about parameters.
+// This is always called on a managed thread (never the helper thread)
+// This will synchronize and block.
+//**************************************************************************
+HRESULT Debugger::SendExceptionHelperAndBlock(
+ Thread *pThread,
+ OBJECTHANDLE exceptionHandle,
+ bool fContinuable,
+ FramePointer framePointer,
+ SIZE_T nOffset,
+ CorDebugExceptionCallbackType eventType,
+ DWORD dwFlags)
+
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_ANY;
+
+ PRECONDITION(CheckPointer(pThread));
+ }
+ CONTRACTL_END;
+
+ HRESULT hr = S_OK;
+
+ // This is a normal event to send from LS to RS
+ SENDIPCEVENT_BEGIN(this, pThread);
+
+ // This function can be called on helper thread or managed thread.
+ // However, we should be holding locks upon entry
+
+ DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer();
+
+ //
+ // Send pre-Whidbey EXCEPTION IPC event.
+ //
+ InitIPCEvent(ipce, DB_IPCE_EXCEPTION, pThread, pThread->GetDomain());
+
+ ipce->Exception.vmExceptionHandle.SetRawPtr(exceptionHandle);
+ ipce->Exception.firstChance = (eventType == DEBUG_EXCEPTION_FIRST_CHANCE);
+ ipce->Exception.continuable = fContinuable;
+ hr = m_pRCThread->SendIPCEvent();
+
+ _ASSERTE(SUCCEEDED(hr) && "D::SE: Send ExceptionCallback event failed.");
+
+ //
+ // Send Whidbey EXCEPTION IPC event.
+ //
+ InitIPCEvent(ipce, DB_IPCE_EXCEPTION_CALLBACK2, pThread, pThread->GetDomain());
+
+ ipce->ExceptionCallback2.framePointer = framePointer;
+ ipce->ExceptionCallback2.eventType = eventType;
+ ipce->ExceptionCallback2.nOffset = nOffset;
+ ipce->ExceptionCallback2.dwFlags = dwFlags;
+ ipce->ExceptionCallback2.vmExceptionHandle.SetRawPtr(exceptionHandle);
+
+ LOG((LF_CORDB, LL_INFO10000, "D::SE: sending ExceptionCallback2 event"));
+ hr = m_pRCThread->SendIPCEvent();
+
+ if (eventType == DEBUG_EXCEPTION_FIRST_CHANCE)
+ {
+ pThread->GetExceptionState()->GetFlags()->SetSentDebugFirstChance();
+ }
+ else
+ {
+ _ASSERTE(eventType == DEBUG_EXCEPTION_UNHANDLED);
+ }
+
+ _ASSERTE(SUCCEEDED(hr) && "D::SE: Send ExceptionCallback2 event failed.");
+
+ if (SUCCEEDED(hr))
+ {
+ // Stop all Runtime threads
+ TrapAllRuntimeThreads();
+ }
+
+ // Let other Runtime threads handle their events.
+ SENDIPCEVENT_END;
+
+ return hr;
+
+}
+
+// Send various first-chance / unhandled exception events.
+//
+// Assumptions:
+// Caller has already determined that we want to send exception events.
+//
+// Notes:
+// This is a helper function for code:Debugger.SendException
+void Debugger::SendExceptionEventsWorker(
+ Thread * pThread,
+ bool fFirstChance,
+ bool fIsInterceptable,
+ bool fContinuable,
+ SIZE_T currentIP,
+ FramePointer framePointer,
+ bool atSafePlace)
+{
+ HRESULT hr = S_OK;
+
+ ThreadExceptionState* pExState = pThread->GetExceptionState();
+ //
+ // Figure out parameters to the IPC events.
+ //
+ const BYTE *ip;
+
+ SIZE_T nOffset = (SIZE_T)ICorDebugInfo::NO_MAPPING;
+ DebuggerMethodInfo *pDebugMethodInfo = NULL;
+
+ // If we're passed a zero IP or SP, then go to the ThreadExceptionState on the thread to get the data. Note:
+ // we can only do this if there is a context in the pExState. There are cases (most notably the
+ // EEPolicy::HandleFatalError case) where we don't have that. So we just leave the IP/SP 0.
+ if ((currentIP == 0) && (pExState->GetContextRecord() != NULL))
+ {
+ ip = (BYTE *)GetIP(pExState->GetContextRecord());
+ }
+ else
+ {
+ ip = (BYTE *)currentIP;
+ }
+
+ if (g_pEEInterface->IsManagedNativeCode(ip))
+ {
+
+ MethodDesc *pMethodDesc = g_pEEInterface->GetNativeCodeMethodDesc(PCODE(ip));
+ _ASSERTE(pMethodDesc != NULL);
+
+ if (pMethodDesc != NULL)
+ {
+ DebuggerJitInfo *pDebugJitInfo = GetJitInfo(pMethodDesc, ip, &pDebugMethodInfo);
+
+ if (pDebugJitInfo != NULL)
+ {
+ SIZE_T nativeOffset = CodeRegionInfo::GetCodeRegionInfo(pDebugJitInfo, pMethodDesc).AddressToOffset(ip);
+ CorDebugMappingResult mapResult;
+ DWORD which;
+
+ nOffset = pDebugJitInfo->MapNativeOffsetToIL(nativeOffset, &mapResult, &which);
+ }
+ }
+ }
+
+ DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer();
+
+ if (fFirstChance)
+ {
+ // We can call into this method when there is no exception in progress to alert
+ // the debugger to a stack overflow, however that case should never specify first
+ // chance. An exception must be in progress to check the flags on the exception state
+ _ASSERTE(pThread->IsExceptionInProgress());
+
+ //
+ // Send the first chance exception if we have not already and if it is not suppressed
+ //
+ if (m_sendExceptionsOutsideOfJMC && !pExState->GetFlags()->SentDebugFirstChance())
+ {
+ // Blocking here is especially important so that the debugger can mark any code as JMC.
+ hr = SendExceptionHelperAndBlock(
+ pThread,
+ g_pEEInterface->GetThreadException(pThread),
+ fContinuable,
+ framePointer,
+ nOffset,
+ DEBUG_EXCEPTION_FIRST_CHANCE,
+ fIsInterceptable ? DEBUG_EXCEPTION_CAN_BE_INTERCEPTED : 0);
+
+ {
+ // Toggle GC into COOP to block this thread.
+ GCX_COOP_EEINTERFACE();
+
+ //
+ // If we weren't at a safe place when we enabled PGC, then go ahead and unmark that fact now that we've successfully
+ // disabled.
+ //
+ if (!atSafePlace)
+ {
+ g_pDebugger->DecThreadsAtUnsafePlaces();
+ }
+
+ ProcessAnyPendingEvals(pThread);
+
+ //
+ // If we weren't at a safe place, increment the unsafe count before we enable preemptive mode.
+ //
+ if (!atSafePlace)
+ {
+ g_pDebugger->IncThreadsAtUnsafePlaces();
+ }
+ } // end of GCX_CCOP_EEINTERFACE();
+ } //end if (m_sendExceptionsOutsideOfJMC && !SentDebugFirstChance())
+
+ //
+ // If this is a JMC function, then we send a USER's first chance as well.
+ //
+ if ((pDebugMethodInfo != NULL) &&
+ pDebugMethodInfo->IsJMCFunction() &&
+ !pExState->GetFlags()->SentDebugUserFirstChance())
+ {
+ SENDIPCEVENT_BEGIN(this, pThread);
+
+ InitIPCEvent(ipce, DB_IPCE_EXCEPTION_CALLBACK2, pThread, pThread->GetDomain());
+
+ ipce->ExceptionCallback2.framePointer = framePointer;
+ ipce->ExceptionCallback2.eventType = DEBUG_EXCEPTION_USER_FIRST_CHANCE;
+ ipce->ExceptionCallback2.nOffset = nOffset;
+ ipce->ExceptionCallback2.dwFlags = fIsInterceptable ? DEBUG_EXCEPTION_CAN_BE_INTERCEPTED : 0;
+ ipce->ExceptionCallback2.vmExceptionHandle.SetRawPtr(g_pEEInterface->GetThreadException(pThread));
+
+ LOG((LF_CORDB, LL_INFO10000, "D::SE: sending ExceptionCallback2 (USER FIRST CHANCE)"));
+ hr = m_pRCThread->SendIPCEvent();
+
+ _ASSERTE(SUCCEEDED(hr) && "D::SE: Send ExceptionCallback2 (User) event failed.");
+
+ if (SUCCEEDED(hr))
+ {
+ // Stop all Runtime threads
+ TrapAllRuntimeThreads();
+ }
+
+ pExState->GetFlags()->SetSentDebugUserFirstChance();
+
+ // Let other Runtime threads handle their events.
+ SENDIPCEVENT_END;
+
+ } // end if (!SentDebugUserFirstChance)
+
+ } // end if (firstChance)
+ else
+ {
+ // unhandled exception case
+ // if there is no exception in progress then we are sending a fake exception object
+ // as an indication of a fatal error (stack overflow). In this case it is illegal
+ // to read GetFlags() from the exception state.
+ // else if there is an exception in progress we only want to send the notification if
+ // we did not already send a CHF, previous unhandled, or unwind begin notification
+ BOOL sendNotification = TRUE;
+ if(pThread->IsExceptionInProgress())
+ {
+ sendNotification = !pExState->GetFlags()->DebugCatchHandlerFound() &&
+ !pExState->GetFlags()->SentDebugUnhandled() &&
+ !pExState->GetFlags()->SentDebugUnwindBegin();
+ }
+
+ if(sendNotification)
+ {
+ hr = SendExceptionHelperAndBlock(
+ pThread,
+ g_pEEInterface->GetThreadException(pThread),
+ fContinuable,
+ LEAF_MOST_FRAME,
+ (SIZE_T)ICorDebugInfo::NO_MAPPING,
+ DEBUG_EXCEPTION_UNHANDLED,
+ fIsInterceptable ? DEBUG_EXCEPTION_CAN_BE_INTERCEPTED : 0);
+
+ if(pThread->IsExceptionInProgress())
+ {
+ pExState->GetFlags()->SetSentDebugUnhandled();
+ }
+ }
+
+ } // end if (!firstChance)
+}
+
+//
+// SendException is called by Runtime threads to send that they've hit an Managed exception to the Right Side.
+// This may block this thread and suspend the debuggee, and let the debugger inspect us.
+//
+// The thread's throwable should be set so that the debugger can inspect the current exception.
+// It does not report native exceptions in native code (which is consistent because those don't have a
+// managed exception object).
+//
+// This may kick off a jit-attach (in which case fAttaching==true), and so may be called even when no debugger
+// is yet involved.
+//
+// Parameters:
+// pThread - the thread throwing the exception.
+// fFirstChance - true if this is a first chance exception. False if this is an unhandled exception.
+// currentIP - absolute native address of the exception if it is from managed code. If this is 0, we try to find it
+// based off the thread's current exception state.
+// currentSP - stack pointer of the exception. This will get converted into a FramePointer and then used by the debugger
+// to identify which stack frame threw the exception.
+// currentBSP - additional information for IA64 only to identify the stack frame.
+// fContinuable - not used.
+// fAttaching - true iff this exception may initiate a jit-attach. In the common case, if this is true, then
+// CorDebuggerAttached() is false. However, since a debugger can attach at any time, it's possible
+// for another debugger to race against the jit-attach and win. Thus this may err on the side of being true.
+// fForceNonInterceptable - This is used to determine if the exception is continuable (ie "Interceptible",
+// we can handle a DB_IPCE_INTERCEPT_EXCEPTION event for it). If true, then the exception can not be continued.
+// If false, we get continuation status from the exception properties of the current thread.
+//
+// Returns:
+// S_OK on success (common case by far).
+// propogates other errors.
+//
+HRESULT Debugger::SendException(Thread *pThread,
+ bool fFirstChance,
+ SIZE_T currentIP,
+ SIZE_T currentSP,
+ bool fContinuable, // not used by RS.
+ bool fAttaching,
+ bool fForceNonInterceptable,
+ EXCEPTION_POINTERS * pExceptionInfo)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+
+ MODE_ANY;
+
+ PRECONDITION(HasLazyData());
+ PRECONDITION(CheckPointer(pThread));
+ PRECONDITION((pThread->GetFilterContext() == NULL) || !fFirstChance);
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_INFO10000, "D::SendException\n"));
+
+ if (CORDBUnrecoverableError(this))
+ {
+ return (E_FAIL);
+ }
+
+ // Mark if we're at an unsafe place.
+ AtSafePlaceHolder unsafePlaceHolder(pThread);
+
+ // Grab the exception name from the current exception object to pass to the JIT attach.
+ bool fIsInterceptable;
+
+ if (fForceNonInterceptable)
+ {
+ fIsInterceptable = false;
+ m_forceNonInterceptable = true;
+ }
+ else
+ {
+ fIsInterceptable = IsInterceptableException(pThread);
+ m_forceNonInterceptable = false;
+ }
+
+ ThreadExceptionState* pExState = pThread->GetExceptionState();
+ BOOL managedEventNeeded = ((!fFirstChance) ||
+ (fFirstChance && (!pExState->GetFlags()->SentDebugFirstChance() || !pExState->GetFlags()->SentDebugUserFirstChance())));
+
+ // There must be a managed exception object to send a managed exception event
+ if (g_pEEInterface->IsThreadExceptionNull(pThread) && (pThread->LastThrownObjectHandle() == NULL))
+ {
+ managedEventNeeded = FALSE;
+ }
+
+ if (fAttaching)
+ {
+ JitAttach(pThread, pExceptionInfo, managedEventNeeded, FALSE);
+ // If the jit-attach occurred, CORDebuggerAttached() may now be true and we can
+ // just act as if a debugger was always attached.
+ }
+
+ if(managedEventNeeded)
+ {
+ {
+ // We have to send enabled, so enable now.
+ GCX_PREEMP_EEINTERFACE();
+
+ // Send the exception events. Even in jit-attach case, we should now be fully attached.
+ if (CORDebuggerAttached())
+ {
+ // Initialize frame-pointer associated with exception notification.
+ LPVOID stackPointer;
+ if ((currentSP == 0) && (pExState->GetContextRecord() != NULL))
+ {
+ stackPointer = dac_cast<PTR_VOID>(GetSP(pExState->GetContextRecord()));
+ }
+ else
+ {
+ stackPointer = (LPVOID)currentSP;
+ }
+ FramePointer framePointer = FramePointer::MakeFramePointer(stackPointer);
+
+
+ // Do the real work of sending the events
+ SendExceptionEventsWorker(
+ pThread,
+ fFirstChance,
+ fIsInterceptable,
+ fContinuable,
+ currentIP,
+ framePointer,
+ !unsafePlaceHolder.IsAtUnsafePlace());
+ }
+ else
+ {
+ LOG((LF_CORDB,LL_INFO100, "D:SE: Skipping SendIPCEvent because not supposed to send anything, or RS detached.\n"));
+ }
+ }
+
+ // If we weren't at a safe place when we switched to PREEMPTIVE, then go ahead and unmark that fact now
+ // that we're successfully back in COOPERATIVE mode.
+ unsafePlaceHolder.Clear();
+
+ {
+ GCX_COOP_EEINTERFACE();
+ ProcessAnyPendingEvals(pThread);
+ }
+ }
+
+ if (CORDebuggerAttached())
+ {
+ return S_FALSE;
+ }
+ else
+ {
+ return S_OK;
+ }
+}
+
+
+/*
+ * ProcessAnyPendingEvals
+ *
+ * This function checks for, and then processes, any pending func-evals.
+ *
+ * Parameters:
+ * pThread - The thread to process.
+ *
+ * Returns:
+ * None.
+ *
+ */
+void Debugger::ProcessAnyPendingEvals(Thread *pThread)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ }
+ CONTRACTL_END;
+
+#ifndef DACCESS_COMPILE
+
+ // If no debugger is attached, then no evals to process.
+ // We may get here in oom situations during jit-attach, so we'll check now and be safe.
+ if (!CORDebuggerAttached())
+ {
+ return;
+ }
+
+ //
+ // Note: if there is a filter context installed, we may need remove it, do the eval, then put it back. I'm not 100%
+ // sure which yet... it kinda depends on whether or not we really need the filter context updated due to a
+ // collection during the func eval...
+ //
+ // If we need to do a func eval on this thread, then there will be a pending eval registered for this thread. We'll
+ // loop so long as there are pending evals registered. We block in FuncEvalHijackWorker after sending up the
+ // FuncEvalComplete event, so if the user asks for another func eval then there will be a new pending eval when we
+ // loop and check again.
+ //
+ DebuggerPendingFuncEval *pfe;
+
+ while (GetPendingEvals() != NULL && (pfe = GetPendingEvals()->GetPendingEval(pThread)) != NULL)
+ {
+ DebuggerEval *pDE = pfe->pDE;
+
+ _ASSERTE(pDE->m_evalDuringException);
+ _ASSERTE(pDE->m_thread == GetThread());
+
+ // Remove the pending eval from the hash. This ensures that if we take a first chance exception during the eval
+ // that we can do another nested eval properly.
+ GetPendingEvals()->RemovePendingEval(pThread);
+
+ // Go ahead and do the pending func eval. pDE is invalid after this.
+ void *ret;
+ ret = ::FuncEvalHijackWorker(pDE);
+
+
+ // The return value should be NULL when FuncEvalHijackWorker is called as part of an exception.
+ _ASSERTE(ret == NULL);
+ }
+
+ // If we need to re-throw a ThreadAbortException, go ahead and do it now.
+ if (GetThread()->m_StateNC & Thread::TSNC_DebuggerReAbort)
+ {
+ // Now clear the bit else we'll see it again when we process the Exception notification
+ // from this upcoming UserAbort exception.
+ pThread->ResetThreadStateNC(Thread::TSNC_DebuggerReAbort);
+ pThread->UserAbort(Thread::TAR_Thread, EEPolicy::TA_Safe, INFINITE, Thread::UAC_Normal);
+ }
+
+#endif
+
+}
+
+
+/*
+ * FirstChanceManagedException is called by Runtime threads when crawling the managed stack frame
+ * for a handler for the exception. It is called for each managed call on the stack.
+ *
+ * Parameters:
+ * pThread - The thread the exception is occurring on.
+ * currentIP - the IP in the current stack frame.
+ * currentSP - the SP in the current stack frame.
+ *
+ * Returns:
+ * Always FALSE.
+ *
+ */
+bool Debugger::FirstChanceManagedException(Thread *pThread, SIZE_T currentIP, SIZE_T currentSP)
+{
+
+ // @@@
+ // Implement DebugInterface
+ // Can only be called from EE/exception
+ // must be on managed thread.
+
+ CONTRACTL
+ {
+ THROWS;
+ MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT;
+
+ PRECONDITION(CORDebuggerAttached());
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_INFO10000, "D::FCE: First chance exception, TID:0x%x, \n", GetThreadIdHelper(pThread)));
+
+ _ASSERTE(GetThread() != NULL);
+
+#ifdef _DEBUG
+ static ConfigDWORD d_fce;
+ if (d_fce.val(CLRConfig::INTERNAL_D__FCE))
+ _ASSERTE(!"Stop in Debugger::FirstChanceManagedException?");
+#endif
+
+ SendException(pThread, TRUE, currentIP, currentSP, FALSE, FALSE, FALSE, NULL);
+
+ return false;
+}
+
+
+/*
+ * FirstChanceManagedExceptionCatcherFound is called by Runtime threads when crawling the
+ * managed stack frame and a handler for the exception is found.
+ *
+ * Parameters:
+ * pThread - The thread the exception is occurring on.
+ * pTct - Contains the function information that has the catch clause.
+ * pEHClause - Contains the native offset information of the catch clause.
+ *
+ * Returns:
+ * None.
+ *
+ */
+void Debugger::FirstChanceManagedExceptionCatcherFound(Thread *pThread,
+ MethodDesc *pMD, TADDR pMethodAddr,
+ BYTE *currentSP,
+ EE_ILEXCEPTION_CLAUSE *pEHClause)
+{
+
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS_FROM_GETJITINFO;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ // @@@
+ // Implements DebugInterface
+ // Call by EE/exception. Must be on managed thread
+ _ASSERTE(GetThread() != NULL);
+
+ // Quick check.
+ if (!CORDebuggerAttached())
+ {
+ return;
+ }
+
+ // Compute the offset
+
+ DWORD nOffset = (DWORD)(SIZE_T)ICorDebugInfo::NO_MAPPING;
+ DebuggerMethodInfo *pDebugMethodInfo = NULL;
+ DebuggerJitInfo *pDebugJitInfo = NULL;
+ bool isInJMCFunction = false;
+
+ if (pMD != NULL)
+ {
+ _ASSERTE(!pMD->IsILStub());
+
+ pDebugJitInfo = GetJitInfo(pMD, (const BYTE *) pMethodAddr, &pDebugMethodInfo);
+ if (pDebugMethodInfo != NULL)
+ {
+ isInJMCFunction = pDebugMethodInfo->IsJMCFunction();
+ }
+ }
+
+ // Here we check if debugger opted-out of receiving exception related events from outside of JMC methods
+ // or this exception ever crossed JMC frame (in this case we have already sent user first chance event)
+ if (m_sendExceptionsOutsideOfJMC ||
+ isInJMCFunction ||
+ pThread->GetExceptionState()->GetFlags()->SentDebugUserFirstChance())
+ {
+ if (pDebugJitInfo != NULL)
+ {
+ CorDebugMappingResult mapResult;
+ DWORD which;
+
+ // Map the native instruction to the IL instruction.
+ // Be sure to skip past the prolog on amd64/arm to get the right IL
+ // instruction (on x86 there will not be a prolog as x86 does not use
+ // funclets).
+ nOffset = pDebugJitInfo->MapNativeOffsetToIL(
+ pEHClause->HandlerStartPC,
+ &mapResult,
+ &which,
+ TRUE
+ );
+ }
+
+ bool fIsInterceptable = IsInterceptableException(pThread);
+ m_forceNonInterceptable = false;
+ DWORD dwFlags = fIsInterceptable ? DEBUG_EXCEPTION_CAN_BE_INTERCEPTED : 0;
+
+ FramePointer fp = FramePointer::MakeFramePointer(currentSP);
+ SendCatchHandlerFound(pThread, fp, nOffset, dwFlags);
+ }
+
+ // flag that we catch handler found so that we won't send other mutually exclusive events
+ // such as unwind begin or unhandled
+ pThread->GetExceptionState()->GetFlags()->SetDebugCatchHandlerFound();
+}
+
+// Filter to trigger CHF callback
+// Notify of a catch-handler found callback.
+LONG Debugger::NotifyOfCHFFilter(EXCEPTION_POINTERS* pExceptionPointers, PVOID pData)
+{
+ CONTRACTL
+ {
+ if ((GetThread() == NULL) || g_pEEInterface->IsThreadExceptionNull(GetThread()))
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ else
+ {
+ THROWS;
+ MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT;
+ }
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ SCAN_IGNORE_TRIGGER; // Scan can't handle conditional contracts.
+
+ // @@@
+ // Implements DebugInterface
+ // Can only be called from EE
+
+ // If no debugger is attached, then don't bother sending the events.
+ // This can't kick off a jit-attach.
+ if (!CORDebuggerAttached())
+ {
+ return EXCEPTION_CONTINUE_SEARCH;
+ }
+
+ //
+ // If this exception has never bubbled thru to managed code, then there is no
+ // useful information for the debugger and, in fact, it may be a completely
+ // internally handled runtime exception, so we should do nothing.
+ //
+ if ((GetThread() == NULL) || g_pEEInterface->IsThreadExceptionNull(GetThread()))
+ {
+ return EXCEPTION_CONTINUE_SEARCH;
+ }
+
+ // Caller must pass in the stack address. This should match up w/ a Frame.
+ BYTE * pCatcherStackAddr = (BYTE*) pData;
+
+ // If we don't have any catcher frame, then use ebp from the context.
+ if (pData == NULL)
+ {
+ pCatcherStackAddr = (BYTE*) GetFP(pExceptionPointers->ContextRecord);
+ }
+ else
+ {
+#ifdef _DEBUG
+ _ASSERTE(pData != NULL);
+ {
+ // We want the CHF stack addr to match w/ the Internal Frame Cordbg sees
+ // in the stacktrace.
+ // The Internal Frame comes from an EE Frame. This means that the CHF stack
+ // addr must match that EE Frame exactly. Let's check that now.
+
+ Frame * pFrame = reinterpret_cast<Frame*>(pData);
+ // Calling a virtual method will enforce that we have a valid Frame. ;)
+ // If we got passed in a random catch address, then when we cast to a Frame
+ // the vtable pointer will be bogus and this call will AV.
+ Frame::ETransitionType e;
+ e = pFrame->GetTransitionType();
+ }
+#endif
+ }
+
+ // @todo - when Stubs-In-Stacktraces is always enabled, remove this.
+ if (!g_EnableSIS)
+ {
+ return EXCEPTION_CONTINUE_SEARCH;
+ }
+
+ // Stubs don't have an IL offset.
+ const SIZE_T offset = (SIZE_T)ICorDebugInfo::NO_MAPPING;
+ Thread *pThread = GetThread();
+ DWORD dwFlags = IsInterceptableException(pThread) ? DEBUG_EXCEPTION_CAN_BE_INTERCEPTED : 0;
+ m_forceNonInterceptable = false;
+
+ FramePointer fp = FramePointer::MakeFramePointer(pCatcherStackAddr);
+
+ //
+ // If we have not sent a first-chance notification, do so now.
+ //
+ ThreadExceptionState* pExState = pThread->GetExceptionState();
+
+ if (!pExState->GetFlags()->SentDebugFirstChance())
+ {
+ SendException(pThread,
+ TRUE, // first-chance
+ (SIZE_T)(GetIP(pExceptionPointers->ContextRecord)), // IP
+ (SIZE_T)pCatcherStackAddr, // SP
+ FALSE, // fContinuable
+ FALSE, // attaching
+ TRUE, // ForceNonInterceptable since we are transition stub, the first and last place
+ // that will see this exception.
+ pExceptionPointers);
+ }
+
+ // Here we check if debugger opted-out of receiving exception related events from outside of JMC methods
+ // or this exception ever crossed JMC frame (in this case we have already sent user first chance event)
+ if (m_sendExceptionsOutsideOfJMC || pExState->GetFlags()->SentDebugUserFirstChance())
+ {
+ SendCatchHandlerFound(pThread, fp, offset, dwFlags);
+ }
+
+ // flag that we catch handler found so that we won't send other mutually exclusive events
+ // such as unwind begin or unhandled
+ pExState->GetFlags()->SetDebugCatchHandlerFound();
+
+#ifdef DEBUGGING_SUPPORTED
+
+
+ if ( (pThread != NULL) &&
+ (pThread->IsExceptionInProgress()) &&
+ (pThread->GetExceptionState()->GetFlags()->DebuggerInterceptInfo()) )
+ {
+ //
+ // The debugger wants to intercept this exception. It may return in a failure case,
+ // in which case we want to continue thru this path.
+ //
+ ClrDebuggerDoUnwindAndIntercept(X86_FIRST_ARG(EXCEPTION_CHAIN_END) pExceptionPointers->ExceptionRecord);
+ }
+#endif // DEBUGGING_SUPPORTED
+
+ return EXCEPTION_CONTINUE_SEARCH;
+}
+
+
+// Actually send the catch handler found event.
+// This can be used to send CHF for both regular managed catchers as well
+// as stubs that catch (Func-eval, COM-Interop, AppDomains)
+void Debugger::SendCatchHandlerFound(
+ Thread * pThread,
+ FramePointer fp,
+ SIZE_T nOffset,
+ DWORD dwFlags
+)
+{
+
+ CONTRACTL
+ {
+ THROWS;
+ MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_INFO10000, "D::FirstChanceManagedExceptionCatcherFound\n"));
+
+ if (pThread == NULL)
+ {
+ _ASSERTE(!"Bad parameter");
+ LOG((LF_CORDB, LL_INFO10000, "D::FirstChanceManagedExceptionCatcherFound - Bad parameter.\n"));
+ return;
+ }
+
+ if (CORDBUnrecoverableError(this))
+ {
+ return;
+ }
+
+ //
+ // Mark if we're at an unsafe place.
+ //
+ AtSafePlaceHolder unsafePlaceHolder(pThread);
+
+ {
+ GCX_COOP_EEINTERFACE();
+
+ {
+ SENDIPCEVENT_BEGIN(this, pThread);
+
+ if (CORDebuggerAttached() &&
+ !pThread->GetExceptionState()->GetFlags()->DebugCatchHandlerFound() &&
+ !pThread->GetExceptionState()->GetFlags()->SentDebugUnhandled() &&
+ !pThread->GetExceptionState()->GetFlags()->SentDebugUnwindBegin())
+ {
+ HRESULT hr;
+
+ //
+ // Figure out parameters to the IPC events.
+ //
+ DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer();
+
+ //
+ // Send Whidbey EXCEPTION IPC event.
+ //
+ InitIPCEvent(ipce, DB_IPCE_EXCEPTION_CALLBACK2, pThread, pThread->GetDomain());
+
+ ipce->ExceptionCallback2.framePointer = fp;
+ ipce->ExceptionCallback2.eventType = DEBUG_EXCEPTION_CATCH_HANDLER_FOUND;
+ ipce->ExceptionCallback2.nOffset = nOffset;
+ ipce->ExceptionCallback2.dwFlags = dwFlags;
+ ipce->ExceptionCallback2.vmExceptionHandle.SetRawPtr(g_pEEInterface->GetThreadException(pThread));
+
+ LOG((LF_CORDB, LL_INFO10000, "D::FCMECF: sending ExceptionCallback2"));
+ hr = m_pRCThread->SendIPCEvent();
+
+ _ASSERTE(SUCCEEDED(hr) && "D::FCMECF: Send ExceptionCallback2 event failed.");
+
+ //
+ // Stop all Runtime threads
+ //
+ TrapAllRuntimeThreads();
+
+ } // end if (!Attached)
+ else
+ {
+ LOG((LF_CORDB,LL_INFO1000, "D:FCMECF: Skipping SendIPCEvent because RS detached.\n"));
+ }
+
+ //
+ // Let other Runtime threads handle their events.
+ //
+ SENDIPCEVENT_END;
+ }
+
+ //
+ // If we weren't at a safe place when we enabled PGC, then go ahead and unmark that fact now that we've successfully
+ // disabled.
+ //
+ unsafePlaceHolder.Clear();
+
+ ProcessAnyPendingEvals(pThread);
+ } // end of GCX_COOP_EEINTERFACE();
+
+ return;
+}
+
+/*
+ * ManagedExceptionUnwindBegin is called by Runtime threads when crawling the
+ * managed stack frame and unwinding them.
+ *
+ * Parameters:
+ * pThread - The thread the unwind is occurring on.
+ *
+ * Returns:
+ * None.
+ *
+ */
+void Debugger::ManagedExceptionUnwindBegin(Thread *pThread)
+{
+ CONTRACTL
+ {
+ MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT;
+ MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT;
+ }
+ CONTRACTL_END;
+
+ // @@@
+ // Implements DebugInterface
+ // Can only be called on managed threads
+ //
+
+ LOG((LF_CORDB, LL_INFO10000, "D::ManagedExceptionUnwindBegin\n"));
+
+ if (pThread == NULL)
+ {
+ _ASSERTE(!"Bad parameter");
+ LOG((LF_CORDB, LL_INFO10000, "D::ManagedExceptionUnwindBegin - Bad parameter.\n"));
+ return;
+ }
+
+ if (CORDBUnrecoverableError(this))
+ {
+ return;
+ }
+
+ //
+ // Mark if we're at an unsafe place.
+ //
+ AtSafePlaceHolder unsafePlaceHolder(pThread);
+ {
+ GCX_COOP_EEINTERFACE();
+
+ {
+ SENDIPCEVENT_BEGIN(this, pThread);
+
+ if (CORDebuggerAttached() &&
+ !pThread->GetExceptionState()->GetFlags()->SentDebugUnwindBegin() &&
+ !pThread->GetExceptionState()->GetFlags()->DebugCatchHandlerFound() &&
+ !pThread->GetExceptionState()->GetFlags()->SentDebugUnhandled())
+ {
+ HRESULT hr;
+
+ DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer();
+
+ //
+ // Send Whidbey EXCEPTION IPC event.
+ //
+ InitIPCEvent(ipce, DB_IPCE_EXCEPTION_UNWIND, pThread, pThread->GetDomain());
+
+ ipce->ExceptionUnwind.eventType = DEBUG_EXCEPTION_UNWIND_BEGIN;
+ ipce->ExceptionUnwind.dwFlags = 0;
+
+ LOG((LF_CORDB, LL_INFO10000, "D::MEUB: sending ExceptionUnwind event"));
+ hr = m_pRCThread->SendIPCEvent();
+
+ _ASSERTE(SUCCEEDED(hr) && "D::MEUB: Send ExceptionUnwind event failed.");
+
+ pThread->GetExceptionState()->GetFlags()->SetSentDebugUnwindBegin();
+
+ //
+ // Stop all Runtime threads
+ //
+ TrapAllRuntimeThreads();
+
+ } // end if (!Attached)
+
+ //
+ // Let other Runtime threads handle their events.
+ //
+ SENDIPCEVENT_END;
+ }
+
+ //
+ // If we weren't at a safe place when we enabled PGC, then go ahead and unmark that fact now that we've successfully
+ // disabled.
+ //
+ unsafePlaceHolder.Clear();
+ }
+
+ return;
+}
+
+/*
+ * DeleteInterceptContext
+ *
+ * This function is called by the VM to release any debugger specific information for an
+ * exception object. It is called when the VM releases its internal exception stuff, i.e.
+ * ExInfo on X86 and ExceptionTracker on WIN64.
+ *
+ *
+ * Parameters:
+ * pContext - Debugger specific context.
+ *
+ * Returns:
+ * None.
+ *
+ * Notes:
+ * pContext is just a pointer to a DebuggerContinuableExceptionBreakpoint.
+ *
+ */
+void Debugger::DeleteInterceptContext(void *pContext)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ DebuggerContinuableExceptionBreakpoint *pBp = (DebuggerContinuableExceptionBreakpoint *)pContext;
+
+ if (pBp != NULL)
+ {
+ DeleteInteropSafe(pBp);
+ }
+}
+
+
+// Get the frame point for an exception handler
+FramePointer GetHandlerFramePointer(BYTE *pStack)
+{
+ FramePointer handlerFP;
+
+#if !defined(_TARGET_ARM_) && !defined(_TARGET_ARM64_)
+ // Refer to the comment in DispatchUnwind() to see why we have to add
+ // sizeof(LPVOID) to the handler ebp.
+ handlerFP = FramePointer::MakeFramePointer(LPVOID(pStack + sizeof(void*)));
+#else
+ // ARM is similar to IA64 in that it uses the establisher frame as the
+ // handler. in this case we don't need to add sizeof(void*) to the FP.
+ handlerFP = FramePointer::MakeFramePointer((LPVOID)pStack);
+#endif // _TARGET_ARM_
+
+ return handlerFP;
+}
+
+//
+// ExceptionFilter is called by the Runtime threads when an exception
+// is being processed.
+// - fd - MethodDesc of filter function
+// - pMethodAddr - any address inside of the method. This lets us resolve exactly which version
+// of the method is being executed (for EnC)
+// - offset - native offset to handler.
+// - pStack, pBStore - stack pointers.
+//
+void Debugger::ExceptionFilter(MethodDesc *fd, TADDR pMethodAddr, SIZE_T offset, BYTE *pStack)
+{
+ CONTRACTL
+ {
+ MODE_COOPERATIVE;
+ NOTHROW;
+ GC_NOTRIGGER;
+
+ PRECONDITION(!IsDbgHelperSpecialThread());
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB,LL_INFO10000, "D::EF: pStack:0x%x MD: %s::%s, offset:0x%x\n",
+ pStack, fd->m_pszDebugClassName, fd->m_pszDebugMethodName, offset));
+
+ //
+ // !!! Need to think through logic for when to step through filter code -
+ // perhaps only during a "step in".
+ //
+
+ //
+ // !!! Eventually there may be some weird mechanics introduced for
+ // returning from the filter that we have to understand. For now we should
+ // be able to proceed normally.
+ //
+
+ FramePointer handlerFP;
+ handlerFP = GetHandlerFramePointer(pStack);
+
+ DebuggerJitInfo * pDJI = NULL;
+ EX_TRY
+ {
+ pDJI = GetJitInfo(fd, (const BYTE *) pMethodAddr);
+ }
+ EX_CATCH
+ {
+ }
+ EX_END_CATCH(SwallowAllExceptions);
+
+ if (!fd->IsDynamicMethod() && (pDJI == NULL))
+ {
+ // The only way we shouldn't have a DJI is from a dynamic method or from oom (which the LS doesn't handle).
+ _ASSERTE(!"Debugger doesn't support OOM scenarios.");
+ return;
+ }
+
+ DebuggerController::DispatchUnwind(g_pEEInterface->GetThread(),
+ fd, pDJI, offset, handlerFP, STEP_EXCEPTION_FILTER);
+}
+
+
+//
+// ExceptionHandle is called by Runtime threads when an exception is
+// being handled.
+// - fd - MethodDesc of filter function
+// - pMethodAddr - any address inside of the method. This lets us resolve exactly which version
+// of the method is being executed (for EnC)
+// - offset - native offset to handler.
+// - pStack, pBStore - stack pointers.
+//
+void Debugger::ExceptionHandle(MethodDesc *fd, TADDR pMethodAddr, SIZE_T offset, BYTE *pStack)
+{
+ CONTRACTL
+ {
+ MODE_COOPERATIVE;
+ NOTHROW;
+ GC_NOTRIGGER;
+
+ PRECONDITION(!IsDbgHelperSpecialThread());
+ }
+ CONTRACTL_END;
+
+
+ FramePointer handlerFP;
+ handlerFP = GetHandlerFramePointer(pStack);
+
+ DebuggerJitInfo * pDJI = NULL;
+ EX_TRY
+ {
+ pDJI = GetJitInfo(fd, (const BYTE *) pMethodAddr);
+ }
+ EX_CATCH
+ {
+ }
+ EX_END_CATCH(SwallowAllExceptions);
+
+ if (!fd->IsDynamicMethod() && (pDJI == NULL))
+ {
+ // The only way we shouldn't have a DJI is from a dynamic method or from oom (which the LS doesn't handle).
+ _ASSERTE(!"Debugger doesn't support OOM scenarios.");
+ return;
+ }
+
+
+ DebuggerController::DispatchUnwind(g_pEEInterface->GetThread(),
+ fd, pDJI, offset, handlerFP, STEP_EXCEPTION_HANDLER);
+}
+
+BOOL Debugger::ShouldAutoAttach()
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(!CORDebuggerAttached());
+
+ // We're relying on the caller to determine the
+
+ LOG((LF_CORDB, LL_INFO1000000, "D::SAD\n"));
+
+ // Check if the user has specified a seting in the registry about what he
+ // wants done when an unhandled exception occurs.
+ DebuggerLaunchSetting dls = GetDbgJITDebugLaunchSetting();
+
+ return (dls == DLS_ATTACH_DEBUGGER);
+
+ // @TODO cache the debugger launch setting.
+
+}
+
+BOOL Debugger::FallbackJITAttachPrompt()
+{
+ _ASSERTE(!CORDebuggerAttached());
+ return (ATTACH_YES == this->ShouldAttachDebuggerProxy(false));
+}
+
+void Debugger::MarkDebuggerAttachedInternal()
+{
+ LIMITED_METHOD_CONTRACT;
+
+ // Attach is complete now.
+ LOG((LF_CORDB, LL_INFO10000, "D::FEDA: Attach Complete!\n"));
+ g_pEEInterface->MarkDebuggerAttached();
+
+ _ASSERTE(HasLazyData());
+}
+void Debugger::MarkDebuggerUnattachedInternal()
+{
+ LIMITED_METHOD_CONTRACT;
+
+ _ASSERTE(HasLazyData());
+
+ g_pEEInterface->MarkDebuggerUnattached();
+}
+
+//-----------------------------------------------------------------------------
+// Favor to do lazy initialization on helper thread.
+// This is needed to allow lazy intialization in Stack Overflow scenarios.
+// We may or may not already be initialized.
+//-----------------------------------------------------------------------------
+void LazyInitFavor(void *)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+ Debugger::DebuggerLockHolder dbgLockHolder(g_pDebugger);
+ HRESULT hr;
+ hr = g_pDebugger->LazyInitWrapper();
+ (void)hr; //prevent "unused variable" error from GCC
+
+ // On checked builds, warn that we're hitting a scenario that debugging doesn't support.
+ _ASSERTE(SUCCEEDED(hr) || !"Couldn't initialize lazy data for LastChanceManagedException");
+}
+
+/******************************************************************************
+ *
+ ******************************************************************************/
+LONG Debugger::LastChanceManagedException(EXCEPTION_POINTERS * pExceptionInfo,
+ Thread *pThread,
+ BOOL jitAttachRequested)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ // @@@
+ // Implements DebugInterface.
+ // Can be run only on managed thread.
+
+ LOG((LF_CORDB, LL_INFO10000, "D::LastChanceManagedException\n"));
+
+ // Don't stop for native debugging anywhere inside our inproc-Filters.
+ CantStopHolder hHolder;
+
+ EXCEPTION_RECORD * pExceptionRecord = pExceptionInfo->ExceptionRecord;
+ CONTEXT * pContext = pExceptionInfo->ContextRecord;
+
+ // You're allowed to call this function with a NULL exception record and context. If you do, then its assumed
+ // that we want to head right down to asking the user if they want to attach a debugger. No need to try to
+ // dispatch the exception to the debugger controllers. You have to pass NULL for both the exception record and
+ // the context, though. They're a pair. Both have to be NULL, or both have to be valid.
+ _ASSERTE(((pExceptionRecord != NULL) && (pContext != NULL)) ||
+ ((pExceptionRecord == NULL) && (pContext == NULL)));
+
+ if (CORDBUnrecoverableError(this))
+ {
+ return ExceptionContinueSearch;
+ }
+
+ // We don't do anything on the second pass
+ if ((pExceptionRecord != NULL) && ((pExceptionRecord->ExceptionFlags & EXCEPTION_UNWINDING) != 0))
+ {
+ return ExceptionContinueSearch;
+ }
+
+ // Let the controllers have a chance at it - this may be the only handler which can catch the exception if this
+ // is a native patch.
+
+ if ((pThread != NULL) &&
+ (pContext != NULL) &&
+ CORDebuggerAttached() &&
+ DebuggerController::DispatchNativeException(pExceptionRecord,
+ pContext,
+ pExceptionRecord->ExceptionCode,
+ pThread))
+ {
+ return ExceptionContinueExecution;
+ }
+
+ // Otherwise, run our last chance exception logic
+ ATTACH_ACTION action;
+ action = ATTACH_NO;
+
+ if (CORDebuggerAttached() || jitAttachRequested)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "D::BEH ... debugger attached.\n"));
+
+ Thread *thread = g_pEEInterface->GetThread();
+ _ASSERTE((thread != NULL) && (thread == pThread));
+
+ // ExceptionFlags is 0 for continuable, EXCEPTION_NONCONTINUABLE otherwise. Note that if we don't have an
+ // exception record, then we assume this is a non-continuable exception.
+ bool continuable = (pExceptionRecord != NULL) && (pExceptionRecord->ExceptionFlags == 0);
+
+ LOG((LF_CORDB, LL_INFO10000, "D::BEH ... sending exception.\n"));
+
+ HRESULT hr = E_FAIL;
+
+ // In the jit-attach case, lazy-init. We may be in a stack-overflow, so do it via a favor to avoid
+ // using this thread's stack space.
+ if (jitAttachRequested)
+ {
+ m_pRCThread->DoFavor((FAVORCALLBACK) LazyInitFavor, NULL);
+ }
+
+ // The only way we don't have lazy data at this point is in an OOM scenario, which
+ // the debugger doesn't support.
+ if (!HasLazyData())
+ {
+ return ExceptionContinueSearch;
+ }
+
+
+ // In Whidbey, we used to set the filter CONTEXT when we hit an unhandled exception while doing
+ // mixed-mode debugging. This helps the debugger walk the stack since it can skip the leaf
+ // portion of the stack (including stack frames in the runtime) and start the stackwalk at the
+ // faulting stack frame. The code to set the filter CONTEXT is in a hijack function which is only
+ // used during mixed-mode debugging.
+ if (m_pRCThread->GetDCB()->m_rightSideIsWin32Debugger)
+ {
+ GCX_COOP();
+
+ _ASSERTE(thread->GetFilterContext() == NULL);
+ thread->SetFilterContext(pExceptionInfo->ContextRecord);
+ }
+ EX_TRY
+ {
+ // We pass the attaching status to SendException so that it knows
+ // whether to attach a debugger or not. We should really do the
+ // attach stuff out here and not bother with the flag.
+ hr = SendException(thread,
+ FALSE,
+ ((pContext != NULL) ? (SIZE_T)GetIP(pContext) : NULL),
+ ((pContext != NULL) ? (SIZE_T)GetSP(pContext) : NULL),
+ continuable,
+ !!jitAttachRequested, // If we are JIT attaching on an unhandled exceptioin, we force
+ !!jitAttachRequested, // the exception to be uninterceptable.
+ pExceptionInfo);
+ }
+ EX_CATCH
+ {
+ }
+ EX_END_CATCH(SwallowAllExceptions);
+ if (m_pRCThread->GetDCB()->m_rightSideIsWin32Debugger)
+ {
+ GCX_COOP();
+
+ thread->SetFilterContext(NULL);
+ }
+ }
+ else
+ {
+ // Note: we don't do anything on NO or TERMINATE. We just return to the exception logic, which will abort the
+ // app or not depending on what the CLR impl decides is appropiate.
+ _ASSERTE(action == ATTACH_TERMINATE || action == ATTACH_NO);
+ }
+
+ return ExceptionContinueSearch;
+}
+
+//
+// NotifyUserOfFault notifies the user of a fault (unhandled exception
+// or user breakpoint) in the process, giving them the option to
+// attach a debugger or terminate the application.
+//
+int Debugger::NotifyUserOfFault(bool userBreakpoint, DebuggerLaunchSetting dls)
+{
+ LOG((LF_CORDB, LL_INFO1000000, "D::NotifyUserOfFault\n"));
+
+ CONTRACTL
+ {
+ NOTHROW;
+ MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT;;
+ MODE_PREEMPTIVE;
+ }
+ CONTRACTL_END;
+
+ int result = IDCANCEL;
+
+ if (!CORDebuggerAttached())
+ {
+ DWORD pid;
+ DWORD tid;
+
+ pid = GetCurrentProcessId();
+ tid = GetCurrentThreadId();
+
+ DWORD flags = 0;
+ UINT resIDMessage = 0;
+
+ if (userBreakpoint)
+ {
+ resIDMessage = IDS_DEBUG_USER_BREAKPOINT_MSG;
+ flags |= MB_ABORTRETRYIGNORE | MB_ICONEXCLAMATION;
+ }
+ else
+ {
+ resIDMessage = IDS_DEBUG_UNHANDLED_EXCEPTION_MSG;
+ flags |= MB_OKCANCEL | MB_ICONEXCLAMATION;
+ }
+
+ {
+ // Another potential hang. This may get run on the helper if we have a stack overflow.
+ // Hopefully the odds of 1 thread hitting a stack overflow while another is stuck holding the heap
+ // lock is very small.
+ SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE;
+
+ result = MessageBox(resIDMessage, IDS_DEBUG_SERVICE_CAPTION,
+ flags, TRUE, TRUE, pid, pid, tid, tid);
+ }
+ }
+
+ LOG((LF_CORDB, LL_INFO1000000, "D::NotifyUserOfFault left\n"));
+ return result;
+}
+
+
+// Proxy for ShouldAttachDebugger
+struct ShouldAttachDebuggerParams {
+ Debugger* m_pThis;
+ bool m_fIsUserBreakpoint;
+ Debugger::ATTACH_ACTION m_retval;
+};
+
+// This is called by the helper thread
+void ShouldAttachDebuggerStub(ShouldAttachDebuggerParams * p)
+{
+ WRAPPER_NO_CONTRACT;
+
+ p->m_retval = p->m_pThis->ShouldAttachDebugger(p->m_fIsUserBreakpoint);
+}
+
+// This gets called just like the normal version, but it sends the call over to the helper thread
+Debugger::ATTACH_ACTION Debugger::ShouldAttachDebuggerProxy(bool fIsUserBreakpoint)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ }
+ CONTRACTL_END;
+
+ if (!HasLazyData())
+ {
+ DebuggerLockHolder lockHolder(this);
+ HRESULT hr = LazyInitWrapper();
+ if (FAILED(hr))
+ {
+ // We already stress logged this case.
+ return ATTACH_NO;
+ }
+ }
+
+
+ if (!IsGuardPageGone())
+ return ShouldAttachDebugger(fIsUserBreakpoint);
+
+ ShouldAttachDebuggerParams p;
+ p.m_pThis = this;
+ p.m_fIsUserBreakpoint = fIsUserBreakpoint;
+
+ LOG((LF_CORDB, LL_INFO1000000, "D::SADProxy\n"));
+ m_pRCThread->DoFavor((FAVORCALLBACK) ShouldAttachDebuggerStub, &p);
+ LOG((LF_CORDB, LL_INFO1000000, "D::SADProxy return %d\n", p.m_retval));
+
+ return p.m_retval;
+}
+
+//---------------------------------------------------------------------------------------
+// Do policy to determine if we should attach a debugger.
+//
+// Arguments:
+// fIsUserBreakpoint - true iff this is in response to a user-breakpoint, else false.
+//
+// Returns:
+// Action to perform based off policy.
+// ATTACH_NO if a debugger is already attached.
+Debugger::ATTACH_ACTION Debugger::ShouldAttachDebugger(bool fIsUserBreakpoint)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+
+ LOG((LF_CORDB, LL_INFO1000000, "D::SAD\n"));
+
+ // If the debugger is already attached, not necessary to re-attach
+ if (CORDebuggerAttached())
+ {
+ return ATTACH_NO;
+ }
+
+ // Check if the user has specified a seting in the registry about what he wants done when an unhandled exception
+ // occurs.
+ DebuggerLaunchSetting dls = GetDbgJITDebugLaunchSetting();
+
+
+ if (dls == DLS_ATTACH_DEBUGGER)
+ {
+ return ATTACH_YES;
+ }
+ else
+ {
+ // Only ask the user once if they wish to attach a debugger. This is because LastChanceManagedException can be called
+ // twice, which causes ShouldAttachDebugger to be called twice, which causes the user to have to answer twice.
+ static BOOL s_fHasAlreadyAsked = FALSE;
+ static ATTACH_ACTION s_action;
+
+
+ // This lock is also part of the above workaround.
+ // Must go to preemptive to take this lock since we'll trigger down the road.
+ GCX_PREEMP();
+ DebuggerLockHolder lockHolder(this);
+
+ // We always want to ask about user breakpoints!
+ if (!s_fHasAlreadyAsked || fIsUserBreakpoint)
+ {
+ if (!fIsUserBreakpoint)
+ s_fHasAlreadyAsked = TRUE;
+
+ // While we could theoretically run into a deadlock if another thread
+ // which acquires the debugger lock in cooperative GC mode is blocked
+ // on this thread while it is running arbitrary user code out of the
+ // MessageBox message pump, given that this codepath will only be used
+ // on Win9x and that the chances of this happenning are quite slim,
+ // for Whidbey a GCViolation is acceptable.
+ CONTRACT_VIOLATION(GCViolation);
+
+ // Ask the user if they want to attach
+ int iRes = NotifyUserOfFault(fIsUserBreakpoint, dls);
+
+ // If it's a user-defined breakpoint, they must hit Retry to launch
+ // the debugger. If it's an unhandled exception, user must press
+ // Cancel to attach the debugger.
+ if ((iRes == IDCANCEL) || (iRes == IDRETRY))
+ s_action = ATTACH_YES;
+
+ else if ((iRes == IDABORT) || (iRes == IDOK))
+ s_action = ATTACH_TERMINATE;
+
+ else
+ s_action = ATTACH_NO;
+ }
+
+ // dbgLockHolder goes out of scope - implicit Release
+ return s_action;
+ }
+}
+
+
+//---------------------------------------------------------------------------------------
+// SendUserBreakpoint is called by Runtime threads to send that they've hit
+// a user breakpoint to the Right Side.
+//
+// Parameters:
+// thread - managed thread that the breakpoint is on
+//
+// Notes:
+// A user breakpoint is generally triggered by a call to System.Diagnostics.Debugger.Break.
+// This can be very common. VB's 'stop' statement compiles to a Debugger.Break call.
+// Some other CLR facilities (MDAs) may call this directly too.
+//
+// This may trigger a Jit attach.
+// If the debugger is already attached, this will issue a step-out so that the UserBreakpoint
+// appears to come from the callsite.
+void Debugger::SendUserBreakpoint(Thread * thread)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_ANY;
+
+ PRECONDITION(thread != NULL);
+ PRECONDITION(thread == ::GetThread());
+ }
+ CONTRACTL_END;
+
+
+#ifdef _DEBUG
+ // For testing Watson, we want a consistent way to be able to generate a
+ // Fatal Execution Error
+ // So we have a debug-only knob in this particular managed call that can be used
+ // to artificially inject the error.
+ // This is only for testing.
+ static int fDbgInjectFEE = -1;
+
+ if (fDbgInjectFEE == -1)
+ fDbgInjectFEE = UnsafeGetConfigDWORD(CLRConfig::INTERNAL_DbgInjectFEE);
+
+ if (fDbgInjectFEE)
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO10000, "Debugger posting bogus FEE b/c knob DbgInjectFEE is set.\n");
+ EEPOLICY_HANDLE_FATAL_ERROR(COR_E_EXECUTIONENGINE);
+ // These never return.
+ }
+#endif
+
+ if (CORDBUnrecoverableError(this))
+ {
+ return;
+ }
+
+ // UserBreakpoint behaves differently if we're under a debugger vs. a jit-attach.
+ // If we're under the debugger, it does an additional step-out to get us back to the call site.
+
+ // If already attached, then do a step-out and send the userbreak event.
+ if (CORDebuggerAttached())
+ {
+ // A debugger is already attached, so setup a DebuggerUserBreakpoint controller to get us out of the helper
+ // that got us here. The DebuggerUserBreakpoint will call AttachDebuggerForBreakpoint for us when we're out
+ // of the helper. The controller will delete itself when its done its work.
+ DebuggerUserBreakpoint::HandleDebugBreak(thread);
+ return;
+ }
+
+ ATTACH_ACTION dbgAction = ShouldAttachDebugger(true);
+
+ // No debugger is attached. Consider a JIT attach.
+ // This will do ShouldAttachDebugger() and wait for the results.
+ // - It may terminate if the user requested that.
+ // - It may do a full jit-attach.
+ if (dbgAction == ATTACH_YES)
+ {
+ JitAttach(thread, NULL, TRUE, FALSE);
+ }
+ else if (dbgAction == ATTACH_TERMINATE)
+ {
+ // ATTACH_TERMINATE indicates the the user wants to terminate the app.
+ LOG((LF_CORDB, LL_INFO10000, "D::SUB: terminating this process due to user request\n"));
+
+ // Should this go through the host?
+ TerminateProcess(GetCurrentProcess(), 0);
+ _ASSERTE(!"Should never reach this point.");
+ }
+ else
+ {
+ _ASSERTE(dbgAction == ATTACH_NO);
+ }
+
+ if (CORDebuggerAttached())
+ {
+ // On jit-attach, we just send the UserBreak event. Don't do an extra step-out.
+ SendUserBreakpointAndSynchronize(thread);
+ }
+ else if (IsDebuggerPresent())
+ {
+ DebugBreak();
+ }
+}
+
+
+// void Debugger::ThreadCreated(): ThreadCreated is called when
+// a new Runtime thread has been created, but before its ever seen
+// managed code. This is a callback invoked by the EE into the Debugger.
+// This will create a DebuggerThreadStarter patch, which will set
+// a patch at the first instruction in the managed code. When we hit
+// that patch, the DebuggerThreadStarter will invoke ThreadStarted, below.
+//
+// Thread* pRuntimeThread: The EE Thread object representing the
+// runtime thread that has just been created.
+void Debugger::ThreadCreated(Thread* pRuntimeThread)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ // @@@
+ // This function implements the DebugInterface. But it is also called from Attach
+ // logic internally.
+ //
+
+ if (CORDBUnrecoverableError(this))
+ return;
+
+ LOG((LF_CORDB, LL_INFO100, "D::TC: thread created for 0x%x. ******\n",
+ GetThreadIdHelper(pRuntimeThread)));
+
+ // Sanity check the thread.
+ _ASSERTE(pRuntimeThread != NULL);
+ _ASSERTE(pRuntimeThread->GetThreadId() != 0);
+
+
+ // Create a thread starter and enable its WillEnterManaged code
+ // callback. This will cause the starter to trigger once the
+ // thread has hit managed code, which will cause
+ // Debugger::ThreadStarted() to be called. NOTE: the starter will
+ // be deleted automatically when its done its work.
+ DebuggerThreadStarter *starter = new (interopsafe, nothrow) DebuggerThreadStarter(pRuntimeThread);
+
+ if (starter == NULL)
+ {
+ CORDBDebuggerSetUnrecoverableWin32Error(this, 0, false);
+ return;
+ }
+
+ starter->EnableTraceCall(LEAF_MOST_FRAME);
+}
+
+
+// void Debugger::ThreadStarted(): ThreadStarted is called when
+// a new Runtime thread has reached its first managed code. This is
+// called by the DebuggerThreadStarter patch's SendEvent method.
+//
+// Thread* pRuntimeThread: The EE Thread object representing the
+// runtime thread that has just hit managed code.
+void Debugger::ThreadStarted(Thread* pRuntimeThread)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ // @@@
+ // This method implemented DebugInterface but it is also called from Controller
+
+ if (CORDBUnrecoverableError(this))
+ return;
+
+ LOG((LF_CORDB, LL_INFO100, "D::TS: thread attach : ID=%#x AD:%#x\n",
+ GetThreadIdHelper(pRuntimeThread), pRuntimeThread->GetDomain()));
+
+ // We just need to send a VMPTR_Thread. The RS will get everything else it needs from DAC.
+ //
+
+ _ASSERTE((g_pEEInterface->GetThread() &&
+ !g_pEEInterface->GetThread()->m_fPreemptiveGCDisabled) ||
+ g_fInControlC);
+ _ASSERTE(ThreadHoldsLock());
+
+ DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer();
+ InitIPCEvent(ipce,
+ DB_IPCE_THREAD_ATTACH,
+ pRuntimeThread,
+ pRuntimeThread->GetDomain());
+
+
+ m_pRCThread->SendIPCEvent();
+
+ //
+ // Well, if this thread got created _after_ we started sync'ing
+ // then its Runtime thread flags don't have the fact that there
+ // is a debug suspend pending. We need to call over to the
+ // Runtime and set the flag in the thread now...
+ //
+ if (m_trappingRuntimeThreads)
+ {
+ g_pEEInterface->MarkThreadForDebugSuspend(pRuntimeThread);
+ }
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// DetachThread is called by Runtime threads when they are completing
+// their execution and about to be destroyed.
+//
+// Arguments:
+// pRuntimeThread - Pointer to the runtime's thread object to detach.
+//
+// Return Value:
+// None
+//
+//---------------------------------------------------------------------------------------
+void Debugger::DetachThread(Thread *pRuntimeThread)
+{
+ CONTRACTL
+ {
+ MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT;
+ MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT;
+ }
+ CONTRACTL_END;
+
+ if (CORDBUnrecoverableError(this))
+ {
+ return;
+ }
+
+ if (m_ignoreThreadDetach)
+ {
+ return;
+ }
+
+ _ASSERTE (pRuntimeThread != NULL);
+
+
+ LOG((LF_CORDB, LL_INFO100, "D::DT: thread detach : ID=%#x AD:%#x.\n",
+ GetThreadIdHelper(pRuntimeThread), pRuntimeThread->GetDomain()));
+
+
+ // We may be killing a thread before the Thread-starter fired.
+ // So check (and cancel) any outstanding thread-starters.
+ // If we don't, this old thread starter may conflict w/ a new thread-starter
+ // if AppDomains or EE Thread's get recycled.
+ DebuggerController::CancelOutstandingThreadStarter(pRuntimeThread);
+
+ // Controller lock is bigger than debugger lock.
+ // Don't take debugger lock before the CancelOutStandingThreadStarter function.
+ SENDIPCEVENT_BEGIN(this, pRuntimeThread);
+
+ if (CORDebuggerAttached())
+ {
+ // Send a detach thread event to the Right Side.
+ DebuggerIPCEvent * pEvent = m_pRCThread->GetIPCEventSendBuffer();
+
+ InitIPCEvent(pEvent,
+ DB_IPCE_THREAD_DETACH,
+ pRuntimeThread,
+ pRuntimeThread->GetDomain());
+
+ m_pRCThread->SendIPCEvent();
+
+ // Stop all Runtime threads
+ TrapAllRuntimeThreads();
+
+ // This prevents a race condition where we blocked on the Lock()
+ // above while another thread was sending an event and while we
+ // were blocked the debugger suspended us and so we wouldn't be
+ // resumed after the suspension about to happen below.
+ pRuntimeThread->ResetThreadStateNC(Thread::TSNC_DebuggerUserSuspend);
+ }
+ else
+ {
+ LOG((LF_CORDB,LL_INFO1000, "D::DT: Skipping SendIPCEvent because RS detached."));
+ }
+
+ SENDIPCEVENT_END;
+}
+
+
+//
+// SuspendComplete is called when the last Runtime thread reaches a safe point in response to having its trap flags set.
+// This may be called on either the real helper thread or someone doing helper thread duty.
+//
+BOOL Debugger::SuspendComplete()
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+
+ // This will is conceptually mode-cooperative.
+ // But we haven't marked the runtime as stopped yet (m_stopped), so the contract
+ // subsystem doesn't realize it yet.
+ DISABLED(MODE_COOPERATIVE);
+ }
+ CONTRACTL_END;
+
+ // @@@
+ // Call from RCThread::MainLoop and TemporaryHelperThreadMainLoop.
+ // when all threads suspended. Can happen on managed thread or helper thread.
+ // If happen on managed thread, it must be doing the helper thread duty.
+ //
+
+ _ASSERTE(ThreadStore::HoldingThreadStore() || g_fProcessDetach);
+
+ // We should be holding debugger lock m_mutex.
+ _ASSERTE(ThreadHoldsLock());
+
+ // We can't throw here (we're in the middle of the runtime suspension logic).
+ // But things below us throw. So we catch the exception, but then what state are we in?
+
+ _ASSERTE((!g_pEEInterface->GetThread() || !g_pEEInterface->GetThread()->m_fPreemptiveGCDisabled) || g_fInControlC);
+ _ASSERTE(ThisIsHelperThreadWorker());
+
+ STRESS_LOG0(LF_CORDB, LL_INFO10000, "D::SC: suspension complete\n");
+
+ // We have suspended runtime.
+
+ // We're stopped now. Marking m_stopped allows us to use MODE_COOPERATIVE contracts.
+ _ASSERTE(!m_stopped && m_trappingRuntimeThreads);
+ m_stopped = true;
+
+
+ // Send the sync complete event to the Right Side.
+ {
+ // If we fail to send the SyncComplete, what do we do?
+ CONTRACT_VIOLATION(ThrowsViolation);
+
+ SendSyncCompleteIPCEvent(); // sets m_stopped = true...
+ }
+
+ // Everything in the next scope is meant to mimic what we do UnlockForEventSending minus EnableEventHandling.
+ // We do the EEH part when we get the Continue event.
+ {
+#ifdef _DEBUG
+ //_ASSERTE(m_tidLockedForEventSending == GetCurrentThreadId());
+ m_tidLockedForEventSending = 0;
+#endif
+
+ //
+ // Event handling is re-enabled by the RCThread in response to a
+ // continue message from the Right Side.
+
+ }
+
+ // @todo - what should we do if this function failed?
+ return TRUE;
+}
+
+
+
+
+//---------------------------------------------------------------------------------------
+//
+// Debugger::SendCreateAppDomainEvent - notify the RS of an AppDomain
+//
+// Arguments:
+// pRuntimeAppdomain - pointer to the AppDomain
+//
+// Return Value:
+// None
+//
+// Notes:
+// This is used to notify the debugger of either a newly created
+// AppDomain (when fAttaching is FALSE) or of existing AppDomains
+// at attach time (fAttaching is TRUE). In both cases, this should
+// be called before any LoadModule/LoadAssembly events are sent for
+// this domain. Otherwise the RS will get an event for an AppDomain
+// it doesn't recognize and ASSERT.
+//
+// For the non-attach case this means there is no need to enumerate
+// the assemblies/modules in an AppDomain after sending this event
+// because we know there won't be any.
+//
+
+void Debugger::SendCreateAppDomainEvent(AppDomain * pRuntimeAppDomain)
+{
+ CONTRACTL
+ {
+ MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT;
+ MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT;
+
+ MODE_COOPERATIVE;
+ }
+ CONTRACTL_END;
+
+ if (CORDBUnrecoverableError(this))
+ {
+ return;
+ }
+
+ STRESS_LOG2(LF_CORDB, LL_INFO10000, "D::SCADE: AppDomain creation:%#08x, %#08x\n",
+ pRuntimeAppDomain, pRuntimeAppDomain->GetId().m_dwId);
+
+
+
+ Thread *pThread = g_pEEInterface->GetThread();
+ SENDIPCEVENT_BEGIN(this, pThread);
+
+
+
+ // We may have detached while waiting in LockForEventSending,
+ // in which case we can't send the event.
+ if (CORDebuggerAttached())
+ {
+ // Send a create appdomain event to the Right Side.
+ DebuggerIPCEvent * pEvent = m_pRCThread->GetIPCEventSendBuffer();
+
+ InitIPCEvent(pEvent,
+ DB_IPCE_CREATE_APP_DOMAIN,
+ pThread,
+ pRuntimeAppDomain);
+
+ // Only send a pointer to the AppDomain, the RS will get everything else via DAC.
+ pEvent->AppDomainData.vmAppDomain.SetRawPtr(pRuntimeAppDomain);
+ m_pRCThread->SendIPCEvent();
+
+ TrapAllRuntimeThreads();
+ }
+
+ // Let other Runtime threads handle their events.
+ SENDIPCEVENT_END;
+
+}
+
+
+
+
+//
+// SendExitAppDomainEvent is called when an app domain is destroyed.
+//
+void Debugger::SendExitAppDomainEvent(AppDomain* pRuntimeAppDomain)
+{
+ CONTRACTL
+ {
+ MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT;
+ MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT;
+ }
+ CONTRACTL_END;
+
+ if (CORDBUnrecoverableError(this))
+ return;
+
+ LOG((LF_CORDB, LL_INFO100, "D::EAD: Exit AppDomain 0x%08x.\n",
+ pRuntimeAppDomain));
+
+ STRESS_LOG3(LF_CORDB, LL_INFO10000, "D::EAD: AppDomain exit:%#08x, %#08x, %#08x\n",
+ pRuntimeAppDomain, pRuntimeAppDomain->GetId().m_dwId, CORDebuggerAttached());
+
+ Thread *thread = g_pEEInterface->GetThread();
+ // Prevent other Runtime threads from handling events.
+ SENDIPCEVENT_BEGIN(this, thread);
+
+ if (CORDebuggerAttached())
+ {
+ if (pRuntimeAppDomain->IsDefaultDomain() )
+ {
+ // The Debugger expects to never get an unload event for the default Domain.
+ // Currently we should never get here because g_fProcessDetach will be true by
+ // the time this method is called. However, we'd like to know if this ever changes
+ _ASSERTE(!"Trying to deliver notification of unload for default domain" );
+ return;
+ }
+
+ // Send the exit appdomain event to the Right Side.
+ DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer();
+ InitIPCEvent(ipce,
+ DB_IPCE_EXIT_APP_DOMAIN,
+ thread,
+ pRuntimeAppDomain);
+ m_pRCThread->SendIPCEvent();
+
+ // Delete any left over modules for this appdomain.
+ // Note that we're doing this under the lock.
+ if (m_pModules != NULL)
+ {
+ DebuggerDataLockHolder ch(this);
+ m_pModules->RemoveModules(pRuntimeAppDomain);
+ }
+
+ // Stop all Runtime threads
+ TrapAllRuntimeThreads();
+ }
+ else
+ {
+ LOG((LF_CORDB,LL_INFO1000, "D::EAD: Skipping SendIPCEvent because RS detached."));
+ }
+
+ SENDIPCEVENT_END;
+}
+
+
+
+//
+// LoadAssembly is called when a new Assembly gets loaded.
+//
+void Debugger::LoadAssembly(DomainAssembly * pDomainAssembly)
+{
+ CONTRACTL
+ {
+ MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT;
+ MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT;
+ }
+ CONTRACTL_END;
+
+ if (CORDBUnrecoverableError(this))
+ return;
+
+ LOG((LF_CORDB, LL_INFO100, "D::LA: Load Assembly Asy:0x%p AD:0x%p which:%ls\n",
+ pDomainAssembly, pDomainAssembly->GetAppDomain(), pDomainAssembly->GetAssembly()->GetDebugName() ));
+
+ if (!CORDebuggerAttached())
+ {
+ return;
+ }
+
+ Thread *pThread = g_pEEInterface->GetThread();
+ SENDIPCEVENT_BEGIN(this, pThread)
+
+
+ if (CORDebuggerAttached())
+ {
+ // Send a load assembly event to the Right Side.
+ DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer();
+ InitIPCEvent(ipce,
+ DB_IPCE_LOAD_ASSEMBLY,
+ pThread,
+ pDomainAssembly->GetAppDomain());
+
+ ipce->AssemblyData.vmDomainAssembly.SetRawPtr(pDomainAssembly);
+
+ m_pRCThread->SendIPCEvent();
+ }
+ else
+ {
+ LOG((LF_CORDB,LL_INFO1000, "D::LA: Skipping SendIPCEvent because RS detached."));
+ }
+
+ // Stop all Runtime threads
+ if (CORDebuggerAttached())
+ {
+ TrapAllRuntimeThreads();
+ }
+
+ SENDIPCEVENT_END;
+}
+
+
+
+//
+// UnloadAssembly is called when a Runtime thread unloads an assembly.
+//
+void Debugger::UnloadAssembly(DomainAssembly * pDomainAssembly)
+{
+ CONTRACTL
+ {
+ MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT;
+ MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT;
+ }
+ CONTRACTL_END;
+
+ if (CORDBUnrecoverableError(this))
+ return;
+
+ LOG((LF_CORDB, LL_INFO100, "D::UA: Unload Assembly Asy:0x%p AD:0x%p which:%ls\n",
+ pDomainAssembly, pDomainAssembly->GetAppDomain(), pDomainAssembly->GetAssembly()->GetDebugName() ));
+
+ Thread *thread = g_pEEInterface->GetThread();
+ // Note that the debugger lock is reentrant, so we may or may not hold it already.
+ SENDIPCEVENT_BEGIN(this, thread);
+
+ // Send the unload assembly event to the Right Side.
+ DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer();
+
+ InitIPCEvent(ipce,
+ DB_IPCE_UNLOAD_ASSEMBLY,
+ thread,
+ pDomainAssembly->GetAppDomain());
+ ipce->AssemblyData.vmDomainAssembly.SetRawPtr(pDomainAssembly);
+
+ SendSimpleIPCEventAndBlock();
+
+ // This will block on the continue
+ SENDIPCEVENT_END;
+
+}
+
+
+
+
+//
+// LoadModule is called when a Runtime thread loads a new module and a debugger
+// is attached. This also includes when a domain-neutral module is "loaded" into
+// a new domain.
+//
+// TODO: remove pszModuleName and perhaps other args.
+void Debugger::LoadModule(Module* pRuntimeModule,
+ LPCWSTR pszModuleName, // module file name.
+ DWORD dwModuleName, // length of pszModuleName in chars, not including null.
+ Assembly *pAssembly,
+ AppDomain *pAppDomain,
+ DomainFile * pDomainFile,
+ BOOL fAttaching)
+{
+
+ CONTRACTL
+ {
+ NOTHROW; // not protected for Throws.
+ MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT;
+ }
+ CONTRACTL_END;
+
+ // @@@@
+ // Implement DebugInterface but can be called internally as well.
+ // This can be called by EE loading module or when we are attaching called by IteratingAppDomainForAttaching
+ //
+ _ASSERTE(!fAttaching);
+
+ if (CORDBUnrecoverableError(this))
+ return;
+
+ // If this is a dynamic module, then it's part of a multi-module assembly. The manifest
+ // module within the assembly contains metadata for all the module names in the assembly.
+ // When a new dynamic module is created, the manifest module's metadata is updated to
+ // include the new module (see code:Assembly.CreateDynamicModule).
+ // So we need to update the RS's copy of the metadata. One place the manifest module's
+ // metadata gets used is in code:DacDbiInterfaceImpl.GetModuleSimpleName
+ //
+ // See code:ReflectionModule.CaptureModuleMetaDataToMemory for why we send the metadata-refresh here.
+ if (pRuntimeModule->IsReflection() && !pRuntimeModule->IsManifest() && !fAttaching)
+ {
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // The loader lookups may throw or togggle GC mode, so do them inside a TRY/Catch and
+ // outside any debugger locks.
+ Module * pManifestModule = pRuntimeModule->GetAssembly()->GetManifestModule();
+
+ _ASSERTE(pManifestModule != pRuntimeModule);
+ _ASSERTE(pManifestModule->IsManifest());
+ _ASSERTE(pManifestModule->GetAssembly() == pRuntimeModule->GetAssembly());
+
+ DomainFile * pManifestDomainFile = pManifestModule->GetDomainFile(pAppDomain);
+
+ DebuggerLockHolder dbgLockHolder(this);
+
+ // Raise the debug event.
+ // This still tells the debugger that the manifest module metadata is invalid and needs to
+ // be refreshed.
+ DebuggerIPCEvent eventMetadataUpdate;
+ InitIPCEvent(&eventMetadataUpdate, DB_IPCE_METADATA_UPDATE, NULL, pAppDomain);
+
+ eventMetadataUpdate.MetadataUpdateData.vmDomainFile.SetRawPtr(pManifestDomainFile);
+
+ SendRawEvent(&eventMetadataUpdate);
+ }
+ EX_CATCH_HRESULT(hr);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+ }
+
+
+ DebuggerModule * module = NULL;
+
+ Thread *pThread = g_pEEInterface->GetThread();
+ SENDIPCEVENT_BEGIN(this, pThread);
+
+
+#ifdef FEATURE_FUSION
+ // Fix for issue Whidbey - 106398
+ // Populate the pdb to fusion cache.
+
+ //
+ if (pRuntimeModule->IsIStream() == FALSE)
+ {
+ SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE;
+
+ HRESULT hrCopy = S_OK;
+ EX_TRY
+ {
+ pRuntimeModule->FusionCopyPDBs(pRuntimeModule->GetPath());
+ }
+ EX_CATCH_HRESULT(hrCopy); // ignore failures
+ }
+#endif // FEATURE_FUSION
+
+ DebuggerIPCEvent* ipce = NULL;
+
+ // Don't create new record if already loaded. We do still want to send the ModuleLoad event, however.
+ // The RS has logic to ignore duplicate ModuleLoad events. We have to send what could possibly be a dup, though,
+ // due to some really nasty issues with getting proper assembly and module load events from the loader when dealing
+ // with shared assemblies.
+ module = LookupOrCreateModule(pDomainFile);
+ _ASSERTE(module != NULL);
+
+
+ // During a real LoadModule event, debugger can change jit flags.
+ // Can't do this during a fake event sent on attach.
+ // This is cleared after we send the LoadModule event.
+ module->SetCanChangeJitFlags(true);
+
+
+ // @dbgtodo inspection - Check whether the DomainFile we get is consistent with the Module and AppDomain we get.
+ // We should simply things when we actually get rid of DebuggerModule, possibly by just passing the
+ // DomainFile around.
+ _ASSERTE(module->GetDomainFile() == pDomainFile);
+ _ASSERTE(module->GetAppDomain() == pDomainFile->GetAppDomain());
+ _ASSERTE(module->GetRuntimeModule() == pDomainFile->GetModule());
+
+ // Send a load module event to the Right Side.
+ ipce = m_pRCThread->GetIPCEventSendBuffer();
+ InitIPCEvent(ipce,DB_IPCE_LOAD_MODULE, pThread, pAppDomain);
+
+ ipce->LoadModuleData.vmDomainFile.SetRawPtr(pDomainFile);
+
+ m_pRCThread->SendIPCEvent();
+
+ {
+ // Stop all Runtime threads
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ TrapAllRuntimeThreads();
+ }
+ EX_CATCH_HRESULT(hr); // @dbgtodo synchronization - catch exception and go on to restore state.
+ // Synchronization feature crew needs to figure out what happens to TrapAllRuntimeThreads().
+ }
+
+ SENDIPCEVENT_END;
+
+ // need to update pdb stream for SQL passed in pdb stream
+ // regardless attach or not.
+ //
+ if (pRuntimeModule->IsIStream())
+ {
+ // Just ignore failures. Caller was just sending a debug event and we don't
+ // want that to interop non-debugging functionality.
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ SendUpdateModuleSymsEventAndBlock(pRuntimeModule, pAppDomain);
+ }
+ EX_CATCH_HRESULT(hr);
+ }
+
+ // Now that we're done with the load module event, can no longer change Jit flags.
+ module->SetCanChangeJitFlags(false);
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Special LS-only notification that a module has reached the FILE_LOADED level. For now
+// this is only useful to bind breakpoints in generic instantiations from NGENd modules
+// that we couldn't bind earlier (at LoadModule notification time) because the method
+// iterator refuses to consider modules earlier than the FILE_LOADED level. Normally
+// generic instantiations would have their breakpoints bound when they get JITted, but in
+// the case of NGEN that may never happen, so we need to bind them here.
+//
+// Arguments:
+// * pRuntimeModule - Module that just loaded
+// * pAppDomain - AD into which the Module was loaded
+//
+// Assumptions:
+// This is called during the loading process, and blocks that process from
+// completing. The module has reached the FILE_LOADED stage, but typically not yet
+// the IsReadyForTypeLoad stage.
+//
+
+void Debugger::LoadModuleFinished(Module * pRuntimeModule, AppDomain * pAppDomain)
+{
+ CONTRACTL
+ {
+ SUPPORTS_DAC;
+ STANDARD_VM_CHECK;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(pRuntimeModule != NULL);
+ _ASSERTE(pAppDomain != NULL);
+
+ if (CORDBUnrecoverableError(this))
+ return;
+
+ // Just as an optimization, skip binding breakpoints if there's no debugger attached.
+ // If a debugger attaches at some point after here, it will be able to bind patches
+ // by making the request at that time. If a debugger detaches at some point after
+ // here, there's no harm in having extra patches bound.
+ if (!CORDebuggerAttached())
+ return;
+
+ // For now, this notification only does interesting work if the module that loaded is
+ // an NGENd module, because all we care about in this notification is ensuring NGENd
+ // methods get breakpoints bound on them
+ if (!pRuntimeModule->HasNativeImage())
+ return;
+
+ // This notification is called just before MODULE_READY_FOR_TYPELOAD gets set. But
+ // for shared modules (loaded into multiple domains), MODULE_READY_FOR_TYPELOAD has
+ // already been set if this module was already loaded into an earlier domain. For
+ // such cases, there's no need to bind breakpoints now because the module has already
+ // been fully loaded into at least one domain, and breakpoint binding has already
+ // been done for us
+ if (pRuntimeModule->IsReadyForTypeLoad())
+ return;
+
+#ifdef _DEBUG
+ {
+ // This notification is called once the module is loaded
+ DomainFile * pDomainFile = pRuntimeModule->FindDomainFile(pAppDomain);
+ _ASSERTE((pDomainFile != NULL) && (pDomainFile->GetLoadLevel() >= FILE_LOADED));
+ }
+#endif // _DEBUG
+
+ // Find all IL Master patches for this module, and bind & activate their
+ // corresponding slave patches.
+ {
+ DebuggerController::ControllerLockHolder ch;
+
+ HASHFIND info;
+ DebuggerPatchTable * pTable = DebuggerController::GetPatchTable();
+
+ for (DebuggerControllerPatch * pMasterPatchCur = pTable->GetFirstPatch(&info);
+ pMasterPatchCur != NULL;
+ pMasterPatchCur = pTable->GetNextPatch(&info))
+ {
+ if (!pMasterPatchCur->IsILMasterPatch())
+ continue;
+
+ DebuggerMethodInfo *dmi = GetOrCreateMethodInfo(pMasterPatchCur->key.module, pMasterPatchCur->key.md);
+
+ // Found a relevant IL master patch. Now bind all corresponding slave patches
+ // that belong to this Module
+ DebuggerMethodInfo::DJIIterator it;
+ dmi->IterateAllDJIs(pAppDomain, pRuntimeModule, &it);
+ for (; !it.IsAtEnd(); it.Next())
+ {
+ DebuggerJitInfo *dji = it.Current();
+ _ASSERTE(dji->m_jitComplete);
+
+ if (dji->m_encVersion != pMasterPatchCur->GetEnCVersion())
+ continue;
+
+ // Do we already have a slave for this DJI & Controller? If so, no need
+ // to add another one
+ BOOL fSlaveExists = FALSE;
+ HASHFIND f;
+ for (DebuggerControllerPatch * pSlavePatchCur = pTable->GetFirstPatch(&f);
+ pSlavePatchCur != NULL;
+ pSlavePatchCur = pTable->GetNextPatch(&f))
+ {
+ if (pSlavePatchCur->IsILSlavePatch() &&
+ (pSlavePatchCur->GetDJI() == dji) &&
+ (pSlavePatchCur->controller == pMasterPatchCur->controller))
+ {
+ fSlaveExists = TRUE;
+ break;
+ }
+ }
+
+ if (fSlaveExists)
+ continue;
+
+ pMasterPatchCur->controller->AddBindAndActivateILSlavePatch(pMasterPatchCur, dji);
+ }
+ }
+ }
+}
+
+
+// Send the raw event for Updating symbols. Debugger must query for contents from out-of-process
+//
+// Arguments:
+// pRuntimeModule - required, module to send symbols for. May be domain neutral.
+// pAppDomain - required, appdomain that module is in.
+//
+// Notes:
+// This is just a ping event. Debugger must query for actual symbol contents.
+// This keeps the launch + attach cases identical.
+// This just sends the raw event and does not synchronize the runtime.
+// Use code:Debugger.SendUpdateModuleSymsEventAndBlock for that.
+void Debugger::SendRawUpdateModuleSymsEvent(Module *pRuntimeModule, AppDomain *pAppDomain)
+{
+// @telest - do we need an #ifdef FEATURE_FUSION here?
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_PREEMPTIVE;
+
+ PRECONDITION(ThreadHoldsLock());
+
+ // Debugger must have been attached to get us to this point.
+ // We hold the Debugger-lock, so debugger could not have detached from
+ // underneath us either.
+ PRECONDITION(CORDebuggerAttached());
+ }
+ CONTRACTL_END;
+
+ if (CORDBUnrecoverableError(this))
+ return;
+
+ // This event is used to trigger the ICorDebugManagedCallback::UpdateModuleSymbols
+ // callback. That callback is defined to pass a PDB stream, and so we still use this
+ // only for legacy compatibility reasons when we've actually got PDB symbols.
+ // New clients know they must request a new symbol reader after ClassLoad events.
+ if (pRuntimeModule->GetInMemorySymbolStreamFormat() != eSymbolFormatPDB)
+ return; // Non-PDB symbols
+
+ DebuggerModule* module = LookupOrCreateModule(pRuntimeModule, pAppDomain);
+ PREFIX_ASSUME(module != NULL);
+
+ DebuggerIPCEvent* ipce = NULL;
+ ipce = m_pRCThread->GetIPCEventSendBuffer();
+ InitIPCEvent(ipce, DB_IPCE_UPDATE_MODULE_SYMS,
+ g_pEEInterface->GetThread(),
+ pAppDomain);
+
+ ipce->UpdateModuleSymsData.vmDomainFile.SetRawPtr((module ? module->GetDomainFile() : NULL));
+
+ m_pRCThread->SendIPCEvent();
+}
+
+//
+// UpdateModuleSyms is called when the symbols for a module need to be
+// sent to the Right Side because they've changed.
+//
+// Arguments:
+// pRuntimeModule - required, module to send symbols for. May be domain neutral.
+// pAppDomain - required, appdomain that module is in.
+//
+//
+// Notes:
+// This will send the event (via code:Debugger.SendRawUpdateModuleSymsEvent) and then synchronize
+// the runtime waiting for a continue.
+//
+// This should only be called in cases where we reasonably expect to send symbols.
+// However, this may not send symbols if the symbols aren't available.
+void Debugger::SendUpdateModuleSymsEventAndBlock(Module* pRuntimeModule, AppDomain *pAppDomain)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ if (CORDBUnrecoverableError(this) || !CORDebuggerAttached())
+ {
+ return;
+ }
+
+ CGrowableStream * pStream = pRuntimeModule->GetInMemorySymbolStream();
+ LOG((LF_CORDB, LL_INFO10000, "D::UMS: update module syms RuntimeModule:0x%08x CGrowableStream:0x%08x\n", pRuntimeModule, pStream));
+ if (pStream == NULL)
+ {
+ // No in-memory Pdb available.
+ STRESS_LOG1(LF_CORDB, LL_INFO10000, "No syms available %p", pRuntimeModule);
+ return;
+ }
+
+ SENDIPCEVENT_BEGIN(this, g_pEEInterface->GetThread()); // toggles to preemptive
+
+ // Actually send the event
+ if (CORDebuggerAttached())
+ {
+ SendRawUpdateModuleSymsEvent(pRuntimeModule, pAppDomain);
+ TrapAllRuntimeThreads();
+ }
+
+ SENDIPCEVENT_END;
+}
+
+
+//
+// UnloadModule is called by the Runtime for each module (including shared ones)
+// in an AppDomain that is being unloaded, when a debugger is attached.
+// In the EE, a module may be domain-neutral and therefore shared across all AppDomains.
+// We abstract this detail away in the Debugger and consider each such EE module to correspond
+// to multiple "Debugger Module" instances (one per AppDomain).
+// Therefore, this doesn't necessarily mean the runtime is unloading the module, just
+// that the Debugger should consider it's (per-AppDomain) DebuggerModule to be unloaded.
+//
+void Debugger::UnloadModule(Module* pRuntimeModule,
+ AppDomain *pAppDomain)
+{
+ CONTRACTL
+ {
+ MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT;
+ MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT;
+ }
+ CONTRACTL_END;
+
+ // @@@@
+ // implements DebugInterface.
+ // can only called by EE on Module::NotifyDebuggerUnload
+ //
+
+ if (CORDBUnrecoverableError(this))
+ return;
+
+
+
+ LOG((LF_CORDB, LL_INFO100, "D::UM: unload module Mod:%#08x AD:%#08x runtimeMod:%#08x modName:%ls\n",
+ LookupOrCreateModule(pRuntimeModule, pAppDomain), pAppDomain, pRuntimeModule, pRuntimeModule->GetDebugName()));
+
+
+ Thread *thread = g_pEEInterface->GetThread();
+ SENDIPCEVENT_BEGIN(this, thread);
+
+ if (CORDebuggerAttached())
+ {
+
+ DebuggerModule* module = LookupOrCreateModule(pRuntimeModule, pAppDomain);
+ if (module == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO100, "D::UM: module already unloaded AD:%#08x runtimeMod:%#08x modName:%ls\n",
+ pAppDomain, pRuntimeModule, pRuntimeModule->GetDebugName()));
+ goto LExit;
+ }
+ _ASSERTE(module != NULL);
+
+ STRESS_LOG3(LF_CORDB, LL_INFO10000, "D::UM: Unloading Mod:%#08x, %#08x, %#08x\n",
+ pRuntimeModule, pAppDomain, pRuntimeModule->IsIStream());
+
+ // Note: the appdomain the module was loaded in must match the appdomain we're unloading it from. If it doesn't,
+ // then we've either found the wrong DebuggerModule in LookupModule or we were passed bad data.
+ _ASSERTE(module->GetAppDomain() == pAppDomain);
+
+ // Send the unload module event to the Right Side.
+ DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer();
+ InitIPCEvent(ipce, DB_IPCE_UNLOAD_MODULE, thread, pAppDomain);
+ ipce->UnloadModuleData.vmDomainFile.SetRawPtr((module ? module->GetDomainFile() : NULL));
+ ipce->UnloadModuleData.debuggerAssemblyToken.Set(pRuntimeModule->GetClassLoader()->GetAssembly());
+ m_pRCThread->SendIPCEvent();
+
+ //
+ // Cleanup the module (only for resources consumed when a debugger is attached)
+ //
+
+ // Remove all patches that apply to this module/AppDomain combination
+ AppDomain* domainToRemovePatchesIn = NULL; // all domains by default
+ if( pRuntimeModule->GetAssembly()->IsDomainNeutral() )
+ {
+ // Deactivate all the patches specific to the AppDomain being unloaded
+ domainToRemovePatchesIn = pAppDomain;
+ }
+ // Note that we'll explicitly NOT delete DebuggerControllers, so that
+ // the Right Side can delete them later.
+ DebuggerController::RemovePatchesFromModule(pRuntimeModule, domainToRemovePatchesIn);
+
+ // Deactive all JMC functions in this module. We don't do this for shared assemblies
+ // because JMC status is not maintained on a per-AppDomain basis and we don't
+ // want to change the JMC behavior of the module in other domains.
+ if( !pRuntimeModule->GetAssembly()->IsDomainNeutral() )
+ {
+ LOG((LF_CORDB, LL_EVERYTHING, "Setting all JMC methods to false:\n"));
+ DebuggerDataLockHolder debuggerDataLockHolder(this);
+ DebuggerMethodInfoTable * pTable = GetMethodInfoTable();
+ if (pTable != NULL)
+ {
+ HASHFIND info;
+
+ for (DebuggerMethodInfo *dmi = pTable->GetFirstMethodInfo(&info);
+ dmi != NULL;
+ dmi = pTable->GetNextMethodInfo(&info))
+ {
+ if (dmi->m_module == pRuntimeModule)
+ {
+ dmi->SetJMCStatus(false);
+ }
+ }
+ }
+ LOG((LF_CORDB, LL_EVERYTHING, "Done clearing JMC methods!\n"));
+ }
+
+ // Delete the Left Side representation of the module.
+ if (m_pModules != NULL)
+ {
+ DebuggerDataLockHolder chInfo(this);
+ m_pModules->RemoveModule(pRuntimeModule, pAppDomain);
+ }
+
+ // Stop all Runtime threads
+ TrapAllRuntimeThreads();
+ }
+ else
+ {
+ LOG((LF_CORDB,LL_INFO1000, "D::UM: Skipping SendIPCEvent because RS detached."));
+ }
+
+LExit:
+ SENDIPCEVENT_END;
+}
+
+// Called when this module is completely gone from ALL AppDomains, regardless of
+// whether a debugger is attached.
+// Note that this doesn't get called until after the ADUnload is complete, which happens
+// asyncronously in Whidbey (and won't happen at all if the process shuts down first).
+// This is normally not called only domain-neutral assemblies because they can't be unloaded.
+// However, it may be called if the loader fails to completely load a domain-neutral assembly.
+void Debugger::DestructModule(Module *pModule)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_INFO100, "D::DM: destruct module runtimeMod:%#08x modName:%ls\n",
+ pModule, pModule->GetDebugName()));
+
+ // @@@
+ // Implements DebugInterface.
+ // It is called for Module::Destruct. We do not need to send any IPC event.
+
+ DebuggerLockHolder dbgLockHolder(this);
+
+ // We should have removed all patches at AD unload time (or detach time if the
+ // debugger detached).
+ _ASSERTE( !DebuggerController::ModuleHasPatches(pModule) );
+
+ // Do module clean-up that applies even when no debugger is attached.
+ // Ideally, we might like to do this cleanup more eagerly and detministically,
+ // but we don't currently get any early AD unload callback from the loader
+ // when no debugger is attached. Perhaps we should make the loader
+ // call this callback earlier.
+ RemoveModuleReferences(pModule);
+}
+
+
+// Internal helper to remove all the DJIs / DMIs and other references for a given Module.
+// If we don't remove the DJIs / DMIs, then we're subject to recycling bugs because the underlying
+// MethodDescs will get removed. Thus we'll look up a new MD and it will pull up an old DMI that matched
+// the old MD. Now the DMI and MD are out of sync and it's downhill from there.
+// Note that DMIs may be used (and need cleanup) even when no debugger is attached.
+void Debugger::RemoveModuleReferences( Module* pModule )
+{
+ _ASSERTE( ThreadHoldsLock() );
+
+ // We want to remove all references to the module from the various
+ // tables. It's not just possible, but probable, that the module
+ // will be re-loaded at the exact same address, and in that case,
+ // we'll have piles of entries in our DJI table that mistakenly
+ // match this new module.
+ // Note that this doesn't apply to domain neutral assemblies, that only
+ // get unloaded when the process dies. We won't be reclaiming their
+ // DJIs/patches b/c the process is going to die, so we'll reclaim
+ // the memory when the various hashtables are unloaded.
+
+ if (m_pMethodInfos != NULL)
+ {
+ HRESULT hr = S_OK;
+ if (!HasLazyData())
+ {
+ hr = LazyInitWrapper();
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ DebuggerDataLockHolder debuggerDataLockHolder(this);
+
+ m_pMethodInfos->ClearMethodsOfModule(pModule);
+
+ // DebuggerDataLockHolder out of scope - release implied
+ }
+ }
+}
+
+//---------------------------------------------------------------------------------------
+//
+// SendClassLoadUnloadEvent - notify the RS of a class either loading or unloading.
+//
+// Arguments:
+//
+// fAttaching - true if a debugger is in the process of attaching
+//
+// Return Value:
+// None
+//
+//---------------------------------------------------------------------------------------
+void Debugger::SendClassLoadUnloadEvent (mdTypeDef classMetadataToken,
+ DebuggerModule * pClassDebuggerModule,
+ Assembly *pAssembly,
+ AppDomain *pAppDomain,
+ BOOL fIsLoadEvent)
+{
+ CONTRACTL
+ {
+ MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT;
+ MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT;
+ }
+ CONTRACTL_END;
+
+
+ LOG((LF_CORDB,LL_INFO10000, "D::SCLUE: Tok:0x%x isLoad:0x%x Mod:%#08x AD:%#08x\n",
+ classMetadataToken, fIsLoadEvent, pClassDebuggerModule, pAppDomain));
+
+ DebuggerIPCEvent * pEvent = m_pRCThread->GetIPCEventSendBuffer();
+
+ BOOL fIsReflection = pClassDebuggerModule->GetRuntimeModule()->IsReflection();
+
+ if (fIsLoadEvent == TRUE)
+ {
+ // We need to update Metadata before Symbols (since symbols depend on metadata)
+ // It's debatable which needs to come first: Class Load or Sym update.
+ // V1.1 sent Sym Update first so that binding at the class load has the latest symbols.
+ // However, The Class Load may need to be in sync with updating new metadata,
+ // and that has to come before the Sym update.
+ InitIPCEvent(pEvent, DB_IPCE_LOAD_CLASS, g_pEEInterface->GetThread(), pAppDomain);
+
+ pEvent->LoadClass.classMetadataToken = classMetadataToken;
+ pEvent->LoadClass.vmDomainFile.SetRawPtr((pClassDebuggerModule ? pClassDebuggerModule->GetDomainFile() : NULL));
+ pEvent->LoadClass.classDebuggerAssemblyToken.Set(pAssembly);
+
+
+ // For class loads in dynamic modules, RS knows that the metadata has now grown and is invalid.
+ // RS will re-fetch new metadata from out-of-process.
+ }
+ else
+ {
+ InitIPCEvent(pEvent, DB_IPCE_UNLOAD_CLASS, g_pEEInterface->GetThread(), pAppDomain);
+
+ pEvent->UnloadClass.classMetadataToken = classMetadataToken;
+ pEvent->UnloadClass.vmDomainFile.SetRawPtr((pClassDebuggerModule ? pClassDebuggerModule->GetDomainFile() : NULL));
+ pEvent->UnloadClass.classDebuggerAssemblyToken.Set(pAssembly);
+ }
+
+ m_pRCThread->SendIPCEvent();
+
+ if (fIsLoadEvent && fIsReflection)
+ {
+ // Send the raw event, but don't actually sync and block the runtime.
+ SendRawUpdateModuleSymsEvent(pClassDebuggerModule->GetRuntimeModule(), pAppDomain);
+ }
+
+}
+
+
+
+/******************************************************************************
+ *
+ ******************************************************************************/
+BOOL Debugger::SendSystemClassLoadUnloadEvent(mdTypeDef classMetadataToken,
+ Module *classModule,
+ BOOL fIsLoadEvent)
+{
+ CONTRACTL
+ {
+ MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT;
+ MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT;
+ }
+ CONTRACTL_END;
+
+ if (!m_dClassLoadCallbackCount)
+ {
+ return FALSE;
+ }
+
+ BOOL fRetVal = FALSE;
+
+ Assembly *pAssembly = classModule->GetAssembly();
+
+ if (!m_pAppDomainCB->Lock())
+ return (FALSE);
+
+ AppDomainInfo *pADInfo = m_pAppDomainCB->FindFirst();
+
+ while (pADInfo != NULL)
+ {
+ AppDomain *pAppDomain = pADInfo->m_pAppDomain;
+ _ASSERTE(pAppDomain != NULL);
+
+ // Only notify for app domains where the module has been fully loaded already
+ // We used to make a different check here domain->ContainsAssembly() but that
+ // triggers too early in the loading process. FindDomainFile will not become
+ // non-NULL until the module is fully loaded into the domain which is what we
+ // want.
+ if ((classModule->FindDomainFile(pAppDomain) != NULL ) &&
+ !(fIsLoadEvent && pAppDomain->IsUnloading()) )
+ {
+ // Find the Left Side module that this class belongs in.
+ DebuggerModule* pModule = LookupOrCreateModule(classModule, pAppDomain);
+ _ASSERTE(pModule != NULL);
+
+ // Only send a class load event if they're enabled for this module.
+ if (pModule && pModule->ClassLoadCallbacksEnabled())
+ {
+ SendClassLoadUnloadEvent(classMetadataToken,
+ pModule,
+ pAssembly,
+ pAppDomain,
+ fIsLoadEvent);
+ fRetVal = TRUE;
+ }
+ }
+
+ pADInfo = m_pAppDomainCB->FindNext(pADInfo);
+ }
+
+ m_pAppDomainCB->Unlock();
+
+ return fRetVal;
+}
+
+
+//
+// LoadClass is called when a Runtime thread loads a new Class.
+// Returns TRUE if an event is sent, FALSE otherwise
+BOOL Debugger::LoadClass(TypeHandle th,
+ mdTypeDef classMetadataToken,
+ Module *classModule,
+ AppDomain *pAppDomain)
+{
+ CONTRACTL
+ {
+ MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT;
+ MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT;
+ }
+ CONTRACTL_END;
+
+ // @@@
+ // Implements DebugInterface
+ // This can be called by EE/Loader when class is loaded.
+ //
+
+ BOOL fRetVal = FALSE;
+
+ if (CORDBUnrecoverableError(this))
+ return FALSE;
+
+ // Note that pAppDomain may be null. The AppDomain isn't used here, and doesn't make a lot of sense since
+ // we may be delivering the notification for a class in an assembly which is loaded into multiple AppDomains. We
+ // handle this in SendSystemClassLoadUnloadEvent below by looping through all AppDomains and dispatching
+ // events for each that contain this assembly.
+
+ LOG((LF_CORDB, LL_INFO10000, "D::LC: load class Tok:%#08x Mod:%#08x AD:%#08x classMod:%#08x modName:%ls\n",
+ classMetadataToken, (pAppDomain == NULL) ? NULL : LookupOrCreateModule(classModule, pAppDomain),
+ pAppDomain, classModule, classModule->GetDebugName()));
+
+ //
+ // If we're attaching, then we only need to send the event. We
+ // don't need to disable event handling or lock the debugger
+ // object.
+ //
+ SENDIPCEVENT_BEGIN(this, g_pEEInterface->GetThread());
+
+ if (CORDebuggerAttached())
+ {
+ fRetVal = SendSystemClassLoadUnloadEvent(classMetadataToken, classModule, TRUE);
+
+ if (fRetVal == TRUE)
+ {
+ // Stop all Runtime threads
+ TrapAllRuntimeThreads();
+ }
+ }
+ else
+ {
+ LOG((LF_CORDB,LL_INFO1000, "D::LC: Skipping SendIPCEvent because RS detached."));
+ }
+
+ SENDIPCEVENT_END;
+
+ return fRetVal;
+}
+
+
+//
+// UnloadClass is called when a Runtime thread unloads a Class.
+//
+void Debugger::UnloadClass(mdTypeDef classMetadataToken,
+ Module *classModule,
+ AppDomain *pAppDomain)
+{
+ CONTRACTL
+ {
+ MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT;
+ MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT;
+ }
+ CONTRACTL_END;
+
+ // @@@
+ // Implements DebugInterface
+ // Can only be called from EE
+
+ if (CORDBUnrecoverableError(this))
+ {
+ return;
+ }
+
+ LOG((LF_CORDB, LL_INFO10000, "D::UC: unload class Tok:0x%08x Mod:%#08x AD:%#08x runtimeMod:%#08x modName:%ls\n",
+ classMetadataToken, LookupOrCreateModule(classModule, pAppDomain), pAppDomain, classModule, classModule->GetDebugName()));
+
+ Assembly *pAssembly = classModule->GetClassLoader()->GetAssembly();
+ DebuggerModule *pModule = LookupOrCreateModule(classModule, pAppDomain);
+
+ if ((pModule == NULL) || !pModule->ClassLoadCallbacksEnabled())
+ {
+ return;
+ }
+
+ SENDIPCEVENT_BEGIN(this, g_pEEInterface->GetThread());
+
+ if (CORDebuggerAttached())
+ {
+ _ASSERTE((pAppDomain != NULL) && (pAssembly != NULL) && (pModule != NULL));
+
+ SendClassLoadUnloadEvent(classMetadataToken, pModule, pAssembly, pAppDomain, FALSE);
+
+ // Stop all Runtime threads
+ TrapAllRuntimeThreads();
+ }
+ else
+ {
+ LOG((LF_CORDB,LL_INFO1000, "D::UC: Skipping SendIPCEvent because RS detached."));
+ }
+
+ // Let other Runtime threads handle their events.
+ SENDIPCEVENT_END;
+
+}
+
+/******************************************************************************
+ *
+ ******************************************************************************/
+void Debugger::FuncEvalComplete(Thread* pThread, DebuggerEval *pDE)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+#ifndef DACCESS_COMPILE
+
+ if (CORDBUnrecoverableError(this))
+ return;
+
+ LOG((LF_CORDB, LL_INFO1000, "D::FEC: func eval complete pDE:%p evalType:%d %s %s\n",
+ pDE, pDE->m_evalType, pDE->m_successful ? "Success" : "Fail", pDE->m_aborted ? "Abort" : "Completed"));
+
+
+ _ASSERTE(pDE->m_completed);
+ _ASSERTE((g_pEEInterface->GetThread() && !g_pEEInterface->GetThread()->m_fPreemptiveGCDisabled) || g_fInControlC);
+ _ASSERTE(ThreadHoldsLock());
+
+ // If we need to rethrow a ThreadAbortException then set the thread's state so we remember that.
+ if (pDE->m_rethrowAbortException)
+ {
+ pThread->SetThreadStateNC(Thread::TSNC_DebuggerReAbort);
+ }
+
+
+ //
+ // Get the domain that the result is valid in. The RS will cache this in the ICorDebugValue
+ // Note: it's possible that the AppDomain has (or is about to be) unloaded, which could lead to a
+ // crash when we use the DebuggerModule. Ideally we'd only be using AppDomain IDs here.
+ // We can't easily convert our ADID to an AppDomain* (SystemDomain::GetAppDomainFromId)
+ // because we can't proove that that the AppDomain* would be valid (not unloaded).
+ //
+ AppDomain *pDomain = pThread->GetDomain();
+ AppDomain *pResultDomain = ((pDE->m_debuggerModule == NULL) ? pDomain : pDE->m_debuggerModule->GetAppDomain());
+ _ASSERTE( pResultDomain->GetId() == pDE->m_appDomainId );
+
+ // Send a func eval complete event to the Right Side.
+ DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer();
+ InitIPCEvent(ipce, DB_IPCE_FUNC_EVAL_COMPLETE, pThread, pDomain);
+
+ ipce->FuncEvalComplete.funcEvalKey = pDE->m_funcEvalKey;
+ ipce->FuncEvalComplete.successful = pDE->m_successful;
+ ipce->FuncEvalComplete.aborted = pDE->m_aborted;
+ ipce->FuncEvalComplete.resultAddr = pDE->m_result;
+ ipce->FuncEvalComplete.vmAppDomain.SetRawPtr(pResultDomain);
+ ipce->FuncEvalComplete.vmObjectHandle = pDE->m_vmObjectHandle;
+
+ LOG((LF_CORDB, LL_INFO1000, "D::FEC: TypeHandle is %p\n", pDE->m_resultType.AsPtr()));
+
+ Debugger::TypeHandleToExpandedTypeInfo(pDE->m_retValueBoxing, // whether return values get boxed or not depends on the particular FuncEval we're doing...
+ pResultDomain,
+ pDE->m_resultType,
+ &ipce->FuncEvalComplete.resultType);
+
+ _ASSERTE(ipce->FuncEvalComplete.resultType.elementType != ELEMENT_TYPE_VALUETYPE);
+
+ // We must adjust the result address to point to the right place
+ ipce->FuncEvalComplete.resultAddr = ArgSlotEndianessFixup((ARG_SLOT*)ipce->FuncEvalComplete.resultAddr,
+ GetSizeForCorElementType(ipce->FuncEvalComplete.resultType.elementType));
+
+ LOG((LF_CORDB, LL_INFO1000, "D::FEC: returned el %04x resultAddr %p\n",
+ ipce->FuncEvalComplete.resultType.elementType, ipce->FuncEvalComplete.resultAddr));
+
+ m_pRCThread->SendIPCEvent();
+
+#endif
+}
+
+/******************************************************************************
+ *
+ ******************************************************************************/
+bool Debugger::ResumeThreads(AppDomain* pAppDomain)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ PRECONDITION(ThisIsHelperThreadWorker());
+ }
+ CONTRACTL_END;
+
+ // Okay, mark that we're not stopped anymore and let the
+ // Runtime threads go...
+ ReleaseAllRuntimeThreads(pAppDomain);
+
+ // Return that we've continued the process.
+ return true;
+}
+
+
+class CodeBuffer
+{
+public:
+
+ BYTE *getCodeBuffer(DebuggerJitInfo *dji)
+ {
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ CodeRegionInfo codeRegionInfo = CodeRegionInfo::GetCodeRegionInfo(dji);
+
+ if (codeRegionInfo.getAddrOfColdCode())
+ {
+ _ASSERTE(codeRegionInfo.getSizeOfHotCode() != 0);
+ _ASSERTE(codeRegionInfo.getSizeOfColdCode() != 0);
+ S_SIZE_T totalSize = S_SIZE_T( codeRegionInfo.getSizeOfHotCode() ) +
+ S_SIZE_T( codeRegionInfo.getSizeOfColdCode() );
+ if ( totalSize.IsOverflow() )
+ {
+ _ASSERTE(0 && "Buffer overflow error in getCodeBuffer");
+ return NULL;
+ }
+
+ BYTE *code = (BYTE *) buffer.AllocNoThrow( totalSize.Value() );
+ if (code)
+ {
+ memcpy(code,
+ (void *) codeRegionInfo.getAddrOfHotCode(),
+ codeRegionInfo.getSizeOfHotCode());
+
+ memcpy(code + codeRegionInfo.getSizeOfHotCode(),
+ (void *) codeRegionInfo.getAddrOfColdCode(),
+ codeRegionInfo.getSizeOfColdCode());
+
+ // Now patch the control transfer instructions
+ }
+
+ return code;
+ }
+ else
+ {
+ return dac_cast<PTR_BYTE>(codeRegionInfo.getAddrOfHotCode());
+ }
+ }
+private:
+
+ CQuickBytes buffer;
+};
+
+
+//---------------------------------------------------------------------------------------
+//
+// Called on the helper thread to serialize metadata so it can be read out-of-process.
+//
+// Arguments:
+// pModule - module that needs metadata serialization
+// countBytes - out value, holds the number of bytes which were allocated in the
+// serialized buffer
+//
+// Return Value:
+// A pointer to a serialized buffer of metadata. The caller should free this bufer using
+// DeleteInteropSafe
+//
+// Assumptions:
+// This is called on the helper-thread, or a thread pretending to be the helper-thread.
+// For any synchronous message, the debuggee should be synchronized. The only async
+// messages are Attach and Async-Break.
+//
+//
+//---------------------------------------------------------------------------------------
+BYTE* Debugger::SerializeModuleMetaData(Module * pModule, DWORD * countBytes)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_INFO10000, "Debugger::SMMD called\n"));
+
+ // Do not release the emitter. This is a weak reference.
+ IMetaDataEmit *pEmitter = pModule->GetEmitter();
+ _ASSERTE(pEmitter != NULL);
+
+ HRESULT hr;
+ BYTE* metadataBuffer = NULL;
+ ReleaseHolder<IMDInternalEmit> pInternalEmitter;
+ ULONG originalUpdateMode;
+ hr = pEmitter->QueryInterface(IID_IMDInternalEmit, (void **)&pInternalEmitter);
+ if(FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO10, "Debugger::SMMD pEmitter doesn't support IID_IMDInternalEmit hr=0x%x\n", hr));
+ ThrowHR(hr);
+ }
+ _ASSERTE(pInternalEmitter != NULL);
+
+ hr = pInternalEmitter->SetMDUpdateMode(MDUpdateExtension, &originalUpdateMode);
+ if(FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO10, "Debugger::SMMD SetMDUpdateMode failed hr=0x%x\n", hr));
+ ThrowHR(hr);
+ }
+ _ASSERTE(originalUpdateMode == MDUpdateFull);
+
+ hr = pEmitter->GetSaveSize(cssQuick, countBytes);
+ if(FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO10, "Debugger::SMMD GetSaveSize failed hr=0x%x\n", hr));
+ pInternalEmitter->SetMDUpdateMode(originalUpdateMode, NULL);
+ ThrowHR(hr);
+ }
+
+ EX_TRY
+ {
+ metadataBuffer = new (interopsafe) BYTE[*countBytes];
+ }
+ EX_CATCH
+ {
+ LOG((LF_CORDB, LL_INFO10, "Debugger::SMMD Allocation failed\n"));
+ pInternalEmitter->SetMDUpdateMode(originalUpdateMode, NULL);
+ EX_RETHROW;
+ }
+ EX_END_CATCH(SwallowAllExceptions);
+ _ASSERTE(metadataBuffer != NULL); // allocation would throw first
+
+ // Caller ensures serialization that guarantees that the metadata doesn't grow underneath us.
+ hr = pEmitter->SaveToMemory(metadataBuffer, *countBytes);
+ if(FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO10, "Debugger::SMMD SaveToMemory failed hr=0x%x\n", hr));
+ DeleteInteropSafe(metadataBuffer);
+ pInternalEmitter->SetMDUpdateMode(originalUpdateMode, NULL);
+ ThrowHR(hr);
+ }
+
+ pInternalEmitter->SetMDUpdateMode(originalUpdateMode, NULL);
+ LOG((LF_CORDB, LL_INFO10000, "Debugger::SMMD exiting\n"));
+ return metadataBuffer;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Handle an IPC event from the Debugger.
+//
+// Arguments:
+// event - IPC event to handle.
+//
+// Return Value:
+// True if the event was a continue. Else false.
+//
+// Assumptions:
+// This is called on the helper-thread, or a thread pretending to be the helper-thread.
+// For any synchronous message, the debuggee should be synchronized. The only async
+// messages are Attach and Async-Break.
+//
+// Notes:
+// HandleIPCEvent is called by the RC thread in response to an event
+// from the Debugger Interface. No other IPC events, nor any Runtime
+// events will come in until this method returns. Returns true if this
+// was a Continue event.
+//
+// If this function is called on native debugger helper thread, we will
+// handle everything. However if this is called on managed thread doing
+// helper thread duty, we will fail on operation since we are mainly
+// waiting for CONTINUE message from the RS.
+//
+//
+//---------------------------------------------------------------------------------------
+
+#ifdef _PREFAST_
+#pragma warning(push)
+#pragma warning(disable:21000) // Suppress PREFast warning about overly large function
+#endif
+bool Debugger::HandleIPCEvent(DebuggerIPCEvent * pEvent)
+{
+ CONTRACTL
+ {
+ THROWS;
+ if (g_pEEInterface->GetThread() != NULL) { GC_TRIGGERS; } else { GC_NOTRIGGER; }
+
+ PRECONDITION(ThisIsHelperThreadWorker());
+
+ if (m_stopped)
+ {
+ MODE_COOPERATIVE;
+ }
+ else
+ {
+ MODE_ANY;
+ }
+ }
+ CONTRACTL_END;
+
+ // If we're the temporary helper thread, then we may reject certain operations.
+ bool temporaryHelp = ThisIsTempHelperThread();
+
+
+#ifdef _DEBUG
+ // This reg key allows us to test our unhandled event filter installed in HandleIPCEventWrapper
+ // to make sure it works properly.
+ static int s_fDbgFaultInHandleIPCEvent = -1;
+ if (s_fDbgFaultInHandleIPCEvent == -1)
+ {
+ s_fDbgFaultInHandleIPCEvent = UnsafeGetConfigDWORD(CLRConfig::INTERNAL_DbgFaultInHandleIPCEvent);
+ }
+
+ // If we need to fault, let's generate an access violation.
+ if (s_fDbgFaultInHandleIPCEvent)
+ {
+ *((volatile BYTE *)0) = 0;
+ }
+#endif
+
+ BOOL fSuccess;
+ bool fContinue = false;
+ HRESULT hr = S_OK;
+
+ LOG((LF_CORDB, LL_INFO10000, "D::HIPCE: got %s\n", IPCENames::GetName(pEvent->type)));
+ DbgLog((DebuggerIPCEventType)(pEvent->type & DB_IPCE_TYPE_MASK));
+
+ // As for runtime is considered stopped, it means that managed threads will not
+ // execute anymore managed code. However, these threads may be still running for
+ // unmanaged code. So it is not true that we do not need to hold the lock while processing
+ // synchrnoized event.
+ //
+ // The worst of all, it is the special case where user break point and exception can
+ // be sent as part of attach if debugger was launched by managed app.
+ //
+ DebuggerLockHolder dbgLockHolder(this, FALSE);
+
+ if ((pEvent->type & DB_IPCE_TYPE_MASK) == DB_IPCE_ASYNC_BREAK ||
+ (pEvent->type & DB_IPCE_TYPE_MASK) == DB_IPCE_ATTACHING)
+ {
+ dbgLockHolder.Acquire();
+ }
+ else
+ {
+ _ASSERTE(m_stopped);
+ _ASSERTE(ThreadHoldsLock());
+ }
+
+
+ switch (pEvent->type & DB_IPCE_TYPE_MASK)
+ {
+
+ case DB_IPCE_ATTACHING:
+ // In V3, Attach is atomic, meaning that there isn't a complex handshake back and forth between LS + RS.
+ // the RS sends a single-attaching event and attaches at the first response from the Left-side.
+ StartCanaryThread();
+
+ // In V3 after attaching event was handled we iterate throughout all ADs and made shadow copies of PDBs in the BIN directories.
+ // After all AppDomain, DomainAssembly and modules iteration was available in out-of-proccess model in V4 the code that enables
+ // PDBs to be copied was not called at attach time.
+ // Eliminating PDBs copying side effect is an issue: Dev10 #927143
+ EX_TRY
+ {
+ IterateAppDomainsForPdbs();
+ }
+ EX_CATCH_HRESULT(hr); // ignore failures
+
+ if (m_jitAttachInProgress)
+ {
+ // For jit-attach, mark that we're attached now.
+ // This lets callers to code:Debugger.JitAttach check the flag and
+ // send the jit-attach event just like a normal event.
+ MarkDebuggerAttachedInternal();
+
+ // set the managed attach event so that waiting threads can continue
+ VERIFY(SetEvent(GetAttachEvent()));
+ break;
+ }
+
+ VERIFY(SetEvent(GetAttachEvent()));
+
+ //
+ // For regular (non-jit) attach, fall through to do an async break.
+ //
+
+ case DB_IPCE_ASYNC_BREAK:
+ {
+ if (temporaryHelp)
+ {
+ // Don't support async break on temporary helper thread.
+ // Well, this function does not return HR. So this means that
+ // ASYNC_BREAK event will be catching silently while we are
+ // doing helper thread duty!
+ //
+ hr = CORDBG_E_NOTREADY;
+ }
+ else
+ {
+ // not synchornized. We get debugger lock upon the function entry
+ _ASSERTE(ThreadHoldsLock());
+
+ // Simply trap all Runtime threads if we're not already trying to.
+ if (!m_trappingRuntimeThreads)
+ {
+ // If the RS sent an Async-break, then that's an explicit request.
+ m_RSRequestedSync = TRUE;
+ TrapAllRuntimeThreads(); // Non-blocking...
+ }
+ }
+ break;
+ }
+
+ case DB_IPCE_CONTINUE:
+ {
+ GetCanary()->ClearCache();
+
+ fContinue = ResumeThreads(pEvent->vmAppDomain.GetRawPtr());
+
+ //
+ // Go ahead and release the TSL now that we're continuing. This ensures that we've held
+ // the thread store lock the entire time the Runtime was just stopped.
+ //
+ ThreadSuspend::UnlockThreadStore(FALSE, ThreadSuspend::SUSPEND_FOR_DEBUGGER);
+
+ break;
+ }
+
+ case DB_IPCE_BREAKPOINT_ADD:
+ {
+
+ //
+ // Currently, we can't create a breakpoint before a
+ // function desc is available.
+ // Also, we can't know if a breakpoint is ok
+ // prior to the method being JITted.
+ //
+
+ _ASSERTE(hr == S_OK);
+ DebuggerBreakpoint * pDebuggerBP = NULL;
+
+ DebuggerModule * pDebuggerModule = LookupOrCreateModule(pEvent->BreakpointData.vmDomainFile);
+ Module * pModule = pDebuggerModule->GetRuntimeModule();
+ DebuggerMethodInfo * pDMI = GetOrCreateMethodInfo(pModule, pEvent->BreakpointData.funcMetadataToken);
+ MethodDesc * pMethodDesc = pEvent->BreakpointData.nativeCodeMethodDescToken.UnWrap();
+
+ DebuggerJitInfo * pDJI = NULL;
+ if ((pMethodDesc != NULL) && (pDMI != NULL))
+ {
+ pDJI = pDMI->FindOrCreateInitAndAddJitInfo(pMethodDesc);
+ }
+
+ {
+ // If we haven't been either JITted or EnC'd yet, then
+ // we'll put a patch in by offset, implicitly relative
+ // to the first version of the code.
+
+ pDebuggerBP = new (interopsafe, nothrow) DebuggerBreakpoint(pModule,
+ pEvent->BreakpointData.funcMetadataToken,
+ pEvent->vmAppDomain.GetRawPtr(),
+ pEvent->BreakpointData.offset,
+ !pEvent->BreakpointData.isIL,
+ pEvent->BreakpointData.encVersion,
+ pMethodDesc,
+ pDJI,
+ &fSuccess);
+
+ TRACE_ALLOC(pDebuggerBP);
+
+ if ((pDebuggerBP != NULL) && !fSuccess)
+ {
+ DeleteInteropSafe(pDebuggerBP);
+ pDebuggerBP = NULL;
+ hr = CORDBG_E_UNABLE_TO_SET_BREAKPOINT;
+ }
+ }
+
+ if ((pDebuggerBP == NULL) && !FAILED(hr))
+ {
+ hr = E_OUTOFMEMORY;
+ }
+
+ LOG((LF_CORDB,LL_INFO10000,"\tBP Add: BPTOK:"
+ "0x%x, tok=0x%08x, offset=0x%x, isIL=%d dm=0x%x m=0x%x\n",
+ pDebuggerBP,
+ pEvent->BreakpointData.funcMetadataToken,
+ pEvent->BreakpointData.offset,
+ pEvent->BreakpointData.isIL,
+ pDebuggerModule,
+ pModule));
+
+ //
+ // We're using a two-way event here, so we place the
+ // result event into the _receive_ buffer, not the send
+ // buffer.
+ //
+
+ DebuggerIPCEvent * pIPCResult = m_pRCThread->GetIPCEventReceiveBuffer();
+
+ InitIPCEvent(pIPCResult,
+ DB_IPCE_BREAKPOINT_ADD_RESULT,
+ g_pEEInterface->GetThread(),
+ pEvent->vmAppDomain);
+
+ pIPCResult->BreakpointData.breakpointToken.Set(pDebuggerBP);
+ pIPCResult->hr = hr;
+
+ m_pRCThread->SendIPCReply();
+ }
+ break;
+
+ case DB_IPCE_STEP:
+ {
+ LOG((LF_CORDB,LL_INFO10000, "D::HIPCE: stepIn:0x%x frmTok:0x%x"
+ "StepIn:0x%x RangeIL:0x%x RangeCount:0x%x MapStop:0x%x "
+ "InterceptStop:0x%x AppD:0x%x\n",
+ pEvent->StepData.stepIn,
+ pEvent->StepData.frameToken.GetSPValue(),
+ pEvent->StepData.stepIn,
+ pEvent->StepData.rangeIL,
+ pEvent->StepData.rangeCount,
+ pEvent->StepData.rgfMappingStop,
+ pEvent->StepData.rgfInterceptStop,
+ pEvent->vmAppDomain.GetRawPtr()));
+
+ // <TODO>@todo memory allocation - bad if we're synced</TODO>
+ Thread * pThread = pEvent->StepData.vmThreadToken.GetRawPtr();
+ AppDomain * pAppDomain = pEvent->vmAppDomain.GetRawPtr();
+
+ DebuggerIPCEvent * pIPCResult = m_pRCThread->GetIPCEventReceiveBuffer();
+
+ InitIPCEvent(pIPCResult,
+ DB_IPCE_STEP_RESULT,
+ pThread,
+ pEvent->vmAppDomain);
+
+ if (temporaryHelp)
+ {
+ // Can't step on the temporary helper thread.
+ pIPCResult->hr = CORDBG_E_NOTREADY;
+ }
+ else
+ {
+ DebuggerStepper * pStepper;
+
+ if (pEvent->StepData.IsJMCStop)
+ {
+ pStepper = new (interopsafe, nothrow) DebuggerJMCStepper(pThread,
+ pEvent->StepData.rgfMappingStop,
+ pEvent->StepData.rgfInterceptStop,
+ pAppDomain);
+ }
+ else
+ {
+ pStepper = new (interopsafe, nothrow) DebuggerStepper(pThread,
+ pEvent->StepData.rgfMappingStop,
+ pEvent->StepData.rgfInterceptStop,
+ pAppDomain);
+ }
+
+ if (pStepper == NULL)
+ {
+ pIPCResult->hr = E_OUTOFMEMORY;
+
+ m_pRCThread->SendIPCReply();
+
+ break;
+ }
+ TRACE_ALLOC(pStepper);
+
+ unsigned int cRanges = pEvent->StepData.totalRangeCount;
+
+ _ASSERTE(cRanges == 0 || ((cRanges > 0) && (cRanges == pEvent->StepData.rangeCount)));
+
+ if (!pStepper->Step(pEvent->StepData.frameToken,
+ pEvent->StepData.stepIn,
+ &(pEvent->StepData.range),
+ cRanges,
+ ((cRanges > 0) ? pEvent->StepData.rangeIL : false)))
+ {
+ pIPCResult->hr = E_OUTOFMEMORY;
+
+ m_pRCThread->SendIPCReply();
+
+ DeleteInteropSafe(pStepper);
+ break;
+ }
+
+ pIPCResult->StepData.stepperToken.Set(pStepper);
+
+
+ } // end normal step case.
+
+
+ m_pRCThread->SendIPCReply();
+ }
+ break;
+
+ case DB_IPCE_STEP_OUT:
+ {
+ // <TODO>@todo memory allocation - bad if we're synced</TODO>
+ Thread * pThread = pEvent->StepData.vmThreadToken.GetRawPtr();
+ AppDomain * pAppDomain = pEvent->vmAppDomain.GetRawPtr();
+
+ DebuggerIPCEvent * pIPCResult = m_pRCThread->GetIPCEventReceiveBuffer();
+
+ InitIPCEvent(pIPCResult,
+ DB_IPCE_STEP_RESULT,
+ pThread,
+ pAppDomain);
+
+ if (temporaryHelp)
+ {
+ // Can't step on the temporary helper thread.
+ pIPCResult->hr = CORDBG_E_NOTREADY;
+ }
+ else
+ {
+ DebuggerStepper * pStepper;
+
+ if (pEvent->StepData.IsJMCStop)
+ {
+ pStepper = new (interopsafe, nothrow) DebuggerJMCStepper(pThread,
+ pEvent->StepData.rgfMappingStop,
+ pEvent->StepData.rgfInterceptStop,
+ pAppDomain);
+ }
+ else
+ {
+ pStepper = new (interopsafe, nothrow) DebuggerStepper(pThread,
+ pEvent->StepData.rgfMappingStop,
+ pEvent->StepData.rgfInterceptStop,
+ pAppDomain);
+ }
+
+
+ if (pStepper == NULL)
+ {
+ pIPCResult->hr = E_OUTOFMEMORY;
+ m_pRCThread->SendIPCReply();
+
+ break;
+ }
+
+ TRACE_ALLOC(pStepper);
+
+ // Safe to stack trace b/c we're stopped.
+ StackTraceTicket ticket(pThread);
+
+ pStepper->StepOut(pEvent->StepData.frameToken, ticket);
+
+ pIPCResult->StepData.stepperToken.Set(pStepper);
+ }
+
+ m_pRCThread->SendIPCReply();
+ }
+ break;
+
+ case DB_IPCE_BREAKPOINT_REMOVE:
+ {
+ // <TODO>@todo memory allocation - bad if we're synced</TODO>
+
+ DebuggerBreakpoint * pDebuggerBP = pEvent->BreakpointData.breakpointToken.UnWrap();
+
+ pDebuggerBP->Delete();
+ }
+ break;
+
+ case DB_IPCE_STEP_CANCEL:
+ {
+ // <TODO>@todo memory allocation - bad if we're synced</TODO>
+ LOG((LF_CORDB,LL_INFO10000, "D:HIPCE:Got STEP_CANCEL for stepper 0x%p\n",
+ pEvent->StepData.stepperToken.UnWrap()));
+
+ DebuggerStepper * pStepper = pEvent->StepData.stepperToken.UnWrap();
+
+ pStepper->Delete();
+ }
+ break;
+
+ case DB_IPCE_SET_ALL_DEBUG_STATE:
+ {
+ Thread * pThread = pEvent->SetAllDebugState.vmThreadToken.GetRawPtr();
+ CorDebugThreadState debugState = pEvent->SetAllDebugState.debugState;
+
+ LOG((LF_CORDB,LL_INFO10000,"HandleIPCE: SetAllDebugState: except thread 0x%08x (ID:0x%x) to state 0x%x\n",
+ pThread,
+ (pThread != NULL) ? GetThreadIdHelper(pThread) : 0,
+ debugState));
+
+ if (!g_fProcessDetach)
+ {
+ g_pEEInterface->SetAllDebugState(pThread, debugState);
+ }
+
+ STRESS_LOG1(LF_CORDB,LL_INFO10000,"HandleIPC: Got 0x%x back from SetAllDebugState\n", hr);
+
+ // Just send back an HR.
+ DebuggerIPCEvent * pIPCResult = m_pRCThread->GetIPCEventReceiveBuffer();
+
+ PREFIX_ASSUME(pIPCResult != NULL);
+
+ InitIPCEvent(pIPCResult, DB_IPCE_SET_DEBUG_STATE_RESULT, NULL, NULL);
+
+ pIPCResult->hr = S_OK;
+
+ m_pRCThread->SendIPCReply();
+ }
+ break;
+
+ case DB_IPCE_GET_GCHANDLE_INFO:
+ // Given an unvalidated GC-handle, find out all the info about it to view the object
+ // at the other end
+ {
+ OBJECTHANDLE objectHandle = pEvent->GetGCHandleInfo.GCHandle.GetRawPtr();
+
+ DebuggerIPCEvent * pIPCResult = m_pRCThread->GetIPCEventReceiveBuffer();
+
+ PREFIX_ASSUME(pIPCResult != NULL);
+
+ InitIPCEvent(pIPCResult, DB_IPCE_GET_GCHANDLE_INFO_RESULT, NULL, NULL);
+
+ bool fValid = SUCCEEDED(ValidateGCHandle(objectHandle));
+
+ AppDomain * pAppDomain = NULL;
+
+ if(fValid)
+ {
+ // Get the appdomain
+ ADIndex appDomainIndex = HndGetHandleADIndex(objectHandle);
+ pAppDomain = SystemDomain::GetAppDomainAtIndex(appDomainIndex);
+
+ _ASSERTE(pAppDomain != NULL);
+ }
+
+ pIPCResult->hr = S_OK;
+ pIPCResult->GetGCHandleInfoResult.vmAppDomain.SetRawPtr(pAppDomain);
+ pIPCResult->GetGCHandleInfoResult.fValid = fValid;
+
+ m_pRCThread->SendIPCReply();
+
+ }
+ break;
+
+ case DB_IPCE_GET_BUFFER:
+ {
+ GetAndSendBuffer(m_pRCThread, pEvent->GetBuffer.bufSize);
+ }
+ break;
+
+ case DB_IPCE_RELEASE_BUFFER:
+ {
+ SendReleaseBuffer(m_pRCThread, pEvent->ReleaseBuffer.pBuffer);
+ }
+ break;
+#ifdef EnC_SUPPORTED
+ case DB_IPCE_APPLY_CHANGES:
+ {
+ LOG((LF_ENC, LL_INFO100, "D::HIPCE: DB_IPCE_APPLY_CHANGES 1\n"));
+
+ DebuggerModule * pDebuggerModule = LookupOrCreateModule(pEvent->ApplyChanges.vmDomainFile);
+ //
+ // @todo handle error.
+ //
+ hr = ApplyChangesAndSendResult(pDebuggerModule,
+ pEvent->ApplyChanges.cbDeltaMetadata,
+ (BYTE*) CORDB_ADDRESS_TO_PTR(pEvent->ApplyChanges.pDeltaMetadata),
+ pEvent->ApplyChanges.cbDeltaIL,
+ (BYTE*) CORDB_ADDRESS_TO_PTR(pEvent->ApplyChanges.pDeltaIL));
+
+ LOG((LF_ENC, LL_INFO100, "D::HIPCE: DB_IPCE_APPLY_CHANGES 2\n"));
+ }
+ break;
+#endif // EnC_SUPPORTED
+
+ case DB_IPCE_SET_CLASS_LOAD_FLAG:
+ {
+ DebuggerModule *pDebuggerModule = LookupOrCreateModule(pEvent->SetClassLoad.vmDomainFile);
+
+ _ASSERTE(pDebuggerModule != NULL);
+
+ LOG((LF_CORDB, LL_INFO10000,
+ "D::HIPCE: class load flag is %d for module 0x%p\n",
+ pEvent->SetClassLoad.flag,
+ pDebuggerModule));
+
+ pDebuggerModule->EnableClassLoadCallbacks((BOOL)pEvent->SetClassLoad.flag);
+ }
+ break;
+
+ case DB_IPCE_IS_TRANSITION_STUB:
+ GetAndSendTransitionStubInfo((CORDB_ADDRESS_TYPE*)pEvent->IsTransitionStub.address);
+ break;
+
+ case DB_IPCE_MODIFY_LOGSWITCH:
+ g_pEEInterface->DebuggerModifyingLogSwitch (pEvent->LogSwitchSettingMessage.iLevel,
+ pEvent->LogSwitchSettingMessage.szSwitchName.GetString());
+
+ break;
+
+ case DB_IPCE_ENABLE_LOG_MESSAGES:
+ {
+ bool fOnOff = pEvent->LogSwitchSettingMessage.iLevel ? true : false;
+ EnableLogMessages (fOnOff);
+ }
+ break;
+
+ case DB_IPCE_SET_IP:
+
+ {
+ // This is a synchronous event (reply required)
+ DebuggerIPCEvent * pIPCResult = m_pRCThread->GetIPCEventReceiveBuffer();
+
+ // Don't have an explicit reply msg
+ InitIPCReply(pIPCResult, DB_IPCE_SET_IP);
+
+ if (temporaryHelp)
+ {
+ pIPCResult->hr = CORDBG_E_NOTREADY;
+ }
+ else if (!g_fProcessDetach)
+ {
+ //
+ // Since this pointer is coming from the RS, it may be NULL or something
+ // unexpected in an OOM situation. Quickly just sanity check them.
+ //
+ Thread * pThread = pEvent->SetIP.vmThreadToken.GetRawPtr();
+ Module * pModule = pEvent->SetIP.vmDomainFile.GetRawPtr()->GetModule();
+
+ // Get the DJI for this function
+ DebuggerMethodInfo * pDMI = GetOrCreateMethodInfo(pModule, pEvent->SetIP.mdMethod);
+ DebuggerJitInfo * pDJI = NULL;
+ if (pDMI != NULL)
+ {
+ // In the EnC case, if we look for an older version, we need to find the DJI by starting
+ // address, rather than just by MethodDesc. In the case of generics, we may need to create a DJI, so we
+ pDJI = pDMI->FindJitInfo(pEvent->SetIP.vmMethodDesc.GetRawPtr(),
+ (TADDR)pEvent->SetIP.startAddress);
+ if (pDJI == NULL)
+ {
+ // In the case of other functions, we may need to lazily create a DJI, so we need
+ // FindOrCreate semantics for those.
+ pDJI = pDMI->FindOrCreateInitAndAddJitInfo(pEvent->SetIP.vmMethodDesc.GetRawPtr());
+ }
+ }
+
+ if ((pDJI != NULL) && (pThread != NULL) && (pModule != NULL))
+ {
+ CHECK_IF_CAN_TAKE_HELPER_LOCKS_IN_THIS_SCOPE(&(pIPCResult->hr), GetCanary());
+
+ if (SUCCEEDED(pIPCResult->hr))
+ {
+ pIPCResult->hr = SetIP(pEvent->SetIP.fCanSetIPOnly,
+ pThread,
+ pModule,
+ pEvent->SetIP.mdMethod,
+ pDJI,
+ pEvent->SetIP.offset,
+ pEvent->SetIP.fIsIL
+ );
+ }
+ }
+ else
+ {
+ pIPCResult->hr = E_INVALIDARG;
+ }
+ }
+ else
+ {
+ pIPCResult->hr = S_OK;
+ }
+
+ // Send the result
+ m_pRCThread->SendIPCReply();
+ }
+ break;
+
+ case DB_IPCE_DETACH_FROM_PROCESS:
+ LOG((LF_CORDB, LL_INFO10000, "Detaching from process!\n"));
+
+ // Delete all controllers (remove patches etc.)
+ DebuggerController::DeleteAllControllers();
+ // Note that we'd like to be able to do this assert here
+ // _ASSERTE(DebuggerController::GetNumberOfPatches() == 0);
+ // However controllers may get queued for deletion if there is outstanding
+ // work and so we can't gaurentee the deletion will complete now.
+ // @dbgtodo inspection: This shouldn't be an issue in the complete V3 architecture
+
+ MarkDebuggerUnattachedInternal();
+
+ m_pRCThread->RightSideDetach();
+
+
+ // Clear JMC status
+ {
+ LOG((LF_CORDB, LL_EVERYTHING, "Setting all JMC methods to false:\n"));
+ // On detach, set all DMI's JMC status to false.
+ // We have to do this b/c we clear the DebuggerModules and allocated
+ // new ones on re-attach; and the DMI & DM need to be in sync
+ // (in this case, agreeing that JMC-status = false).
+ // This also syncs the EE modules and disables all JMC probes.
+ DebuggerMethodInfoTable * pMethodInfoTable = g_pDebugger->GetMethodInfoTable();
+
+ if (pMethodInfoTable != NULL)
+ {
+ HASHFIND hashFind;
+ DebuggerDataLockHolder debuggerDataLockHolder(this);
+
+ for (DebuggerMethodInfo * pMethodInfo = pMethodInfoTable->GetFirstMethodInfo(&hashFind);
+ pMethodInfo != NULL;
+ pMethodInfo = pMethodInfoTable->GetNextMethodInfo(&hashFind))
+ {
+ pMethodInfo->SetJMCStatus(false);
+ }
+ }
+ LOG((LF_CORDB, LL_EVERYTHING, "Done clearing JMC methods!\n"));
+ }
+
+ // Clean up the hash of DebuggerModules
+ // This method is overridden to also free all DebuggerModule objects
+ if (m_pModules != NULL)
+ {
+
+ // Removes all DebuggerModules
+ DebuggerDataLockHolder ch(this);
+ m_pModules->Clear();
+
+ }
+
+ // Reply to the detach message before we release any Runtime threads. This ensures that the debugger will get
+ // the detach reply before the process exits if the main thread is near exiting.
+ m_pRCThread->SendIPCReply();
+
+ // Let the process run free now... there is no debugger to bother it anymore.
+ fContinue = ResumeThreads(NULL);
+
+ //
+ // Go ahead and release the TSL now that we're continuing. This ensures that we've held
+ // the thread store lock the entire time the Runtime was just stopped.
+ //
+ ThreadSuspend::UnlockThreadStore(FALSE, ThreadSuspend::SUSPEND_FOR_DEBUGGER);
+ break;
+
+#ifndef DACCESS_COMPILE
+
+ case DB_IPCE_FUNC_EVAL:
+ {
+ // This is a synchronous event (reply required)
+ pEvent = m_pRCThread->GetIPCEventReceiveBuffer();
+
+ Thread * pThread = pEvent->FuncEval.vmThreadToken.GetRawPtr();
+
+ InitIPCEvent(pEvent, DB_IPCE_FUNC_EVAL_SETUP_RESULT, pThread, pThread->GetDomain());
+
+ BYTE * pbArgDataArea = NULL;
+ DebuggerEval * pDebuggerEvalKey = NULL;
+
+ pEvent->hr = FuncEvalSetup(&(pEvent->FuncEval), &pbArgDataArea, &pDebuggerEvalKey);
+
+ // Send the result of how the func eval setup went.
+ pEvent->FuncEvalSetupComplete.argDataArea = PTR_TO_CORDB_ADDRESS(pbArgDataArea);
+ pEvent->FuncEvalSetupComplete.debuggerEvalKey.Set(pDebuggerEvalKey);
+
+ m_pRCThread->SendIPCReply();
+ }
+
+ break;
+
+#endif
+
+ case DB_IPCE_SET_REFERENCE:
+ {
+ // This is a synchronous event (reply required)
+ pEvent = m_pRCThread->GetIPCEventReceiveBuffer();
+
+ InitIPCReply(pEvent, DB_IPCE_SET_REFERENCE_RESULT);
+
+ pEvent->hr = SetReference(pEvent->SetReference.objectRefAddress,
+ pEvent->SetReference.vmObjectHandle,
+ pEvent->SetReference.newReference);
+
+ // Send the result of how the set reference went.
+ m_pRCThread->SendIPCReply();
+ }
+ break;
+
+ case DB_IPCE_SET_VALUE_CLASS:
+ {
+ // This is a synchronous event (reply required)
+ pEvent = m_pRCThread->GetIPCEventReceiveBuffer();
+
+ InitIPCReply(pEvent, DB_IPCE_SET_VALUE_CLASS_RESULT);
+
+ pEvent->hr = SetValueClass(pEvent->SetValueClass.oldData,
+ pEvent->SetValueClass.newData,
+ &pEvent->SetValueClass.type);
+
+ // Send the result of how the set reference went.
+ m_pRCThread->SendIPCReply();
+ }
+ break;
+
+ case DB_IPCE_GET_THREAD_FOR_TASKID:
+ {
+ TASKID taskid = pEvent->GetThreadForTaskId.taskid;
+ Thread *pThread = ThreadStore::GetThreadList(NULL);
+ Thread *pThreadRet = NULL;
+
+ while (pThread != NULL)
+ {
+ if (pThread->GetTaskId() == taskid)
+ {
+ pThreadRet = pThread;
+ break;
+ }
+ pThread = ThreadStore::GetThreadList(pThread);
+ }
+
+ // This is a synchronous event (reply required)
+ pEvent = m_pRCThread->GetIPCEventReceiveBuffer();
+
+ InitIPCReply(pEvent, DB_IPCE_GET_THREAD_FOR_TASKID_RESULT);
+
+ pEvent->GetThreadForTaskIdResult.vmThreadToken.SetRawPtr(pThreadRet);
+ pEvent->hr = S_OK;
+
+ m_pRCThread->SendIPCReply();
+ }
+ break;
+
+ case DB_IPCE_CREATE_HANDLE:
+ {
+ Object * pObject = (Object*)pEvent->CreateHandle.objectToken;
+ OBJECTREF objref = ObjectToOBJECTREF(pObject);
+ AppDomain * pAppDomain = pEvent->vmAppDomain.GetRawPtr();
+ BOOL fStrong = pEvent->CreateHandle.fStrong;
+ OBJECTHANDLE objectHandle;
+
+ // This is a synchronous event (reply required)
+ pEvent = m_pRCThread->GetIPCEventReceiveBuffer();
+
+ InitIPCReply(pEvent, DB_IPCE_CREATE_HANDLE_RESULT);
+
+ {
+ // Handle creation may need to allocate memory.
+ // The API specifically limits the number of handls Cordbg can create,
+ // so we could preallocate and fail allocating anything beyond that.
+ CHECK_IF_CAN_TAKE_HELPER_LOCKS_IN_THIS_SCOPE(&(pEvent->hr), GetCanary());
+
+ if (SUCCEEDED(pEvent->hr))
+ {
+ if (fStrong == TRUE)
+ {
+ // create strong handle
+ objectHandle = pAppDomain->CreateStrongHandle(objref);
+ }
+ else
+ {
+ // create the weak long handle
+ objectHandle = pAppDomain->CreateLongWeakHandle(objref);
+ }
+ pEvent->CreateHandleResult.vmObjectHandle.SetRawPtr(objectHandle);
+ }
+ }
+
+ m_pRCThread->SendIPCReply();
+ break;
+ }
+
+ case DB_IPCE_DISPOSE_HANDLE:
+ {
+ // DISPOSE an object handle
+ OBJECTHANDLE objectHandle = pEvent->DisposeHandle.vmObjectHandle.GetRawPtr();
+
+ if (pEvent->DisposeHandle.fStrong == TRUE)
+ {
+ DestroyStrongHandle(objectHandle);
+ }
+ else
+ {
+ DestroyLongWeakHandle(objectHandle);
+ }
+ break;
+ }
+
+#ifndef DACCESS_COMPILE
+
+ case DB_IPCE_FUNC_EVAL_ABORT:
+ {
+ LOG((LF_CORDB, LL_INFO1000, "D::HIPCE: Got FuncEvalAbort for pDE:%08x\n",
+ pEvent->FuncEvalAbort.debuggerEvalKey.UnWrap()));
+
+ // This is a synchronous event (reply required)
+
+ pEvent = m_pRCThread->GetIPCEventReceiveBuffer();
+ InitIPCReply(pEvent,DB_IPCE_FUNC_EVAL_ABORT_RESULT);
+
+ pEvent->hr = FuncEvalAbort(pEvent->FuncEvalAbort.debuggerEvalKey.UnWrap());
+
+ m_pRCThread->SendIPCReply();
+ }
+ break;
+
+ case DB_IPCE_FUNC_EVAL_RUDE_ABORT:
+ {
+ LOG((LF_CORDB, LL_INFO1000, "D::HIPCE: Got FuncEvalRudeAbort for pDE:%08x\n",
+ pEvent->FuncEvalRudeAbort.debuggerEvalKey.UnWrap()));
+
+ // This is a synchronous event (reply required)
+
+ pEvent = m_pRCThread->GetIPCEventReceiveBuffer();
+
+ InitIPCReply(pEvent, DB_IPCE_FUNC_EVAL_RUDE_ABORT_RESULT);
+
+ pEvent->hr = FuncEvalRudeAbort(pEvent->FuncEvalRudeAbort.debuggerEvalKey.UnWrap());
+
+ m_pRCThread->SendIPCReply();
+ }
+ break;
+
+ case DB_IPCE_FUNC_EVAL_CLEANUP:
+
+ // This is a synchronous event (reply required)
+
+ pEvent = m_pRCThread->GetIPCEventReceiveBuffer();
+
+ InitIPCReply(pEvent,DB_IPCE_FUNC_EVAL_CLEANUP_RESULT);
+
+ pEvent->hr = FuncEvalCleanup(pEvent->FuncEvalCleanup.debuggerEvalKey.UnWrap());
+
+ m_pRCThread->SendIPCReply();
+
+ break;
+
+#endif
+
+ case DB_IPCE_CONTROL_C_EVENT_RESULT:
+ {
+ // store the result of whether the event has been handled by the debugger and
+ // wake up the thread waiting for the result
+ SetDebuggerHandlingCtrlC(pEvent->hr == S_OK);
+ VERIFY(SetEvent(GetCtrlCMutex()));
+ }
+ break;
+
+ // Set the JMC status on invididual methods
+ case DB_IPCE_SET_METHOD_JMC_STATUS:
+ {
+ // Get the info out of the event
+ DebuggerModule * pDebuggerModule = LookupOrCreateModule(pEvent->SetJMCFunctionStatus.vmDomainFile);
+ Module * pModule = pDebuggerModule->GetRuntimeModule();
+
+ bool fStatus = (pEvent->SetJMCFunctionStatus.dwStatus != 0);
+
+ mdMethodDef token = pEvent->SetJMCFunctionStatus.funcMetadataToken;
+
+ // Prepare reply
+ pEvent = m_pRCThread->GetIPCEventReceiveBuffer();
+
+ InitIPCEvent(pEvent, DB_IPCE_SET_METHOD_JMC_STATUS_RESULT, NULL, NULL);
+
+ pEvent->hr = S_OK;
+
+ if (pDebuggerModule->HasAnyOptimizedCode() && fStatus)
+ {
+ // If there's optimized code, then we can't be set JMC status to true.
+ // That's because JMC probes are not injected in optimized code, and we
+ // need a JMC probe to have a JMC function.
+ pEvent->hr = CORDBG_E_CANT_SET_TO_JMC;
+ }
+ else
+ {
+ DebuggerDataLockHolder debuggerDataLockHolder(this);
+ // This may be called on an unjitted method, so we may
+ // have to create the MethodInfo.
+ DebuggerMethodInfo * pMethodInfo = GetOrCreateMethodInfo(pModule, token);
+
+ if (pMethodInfo == NULL)
+ {
+ pEvent->hr = E_OUTOFMEMORY;
+ }
+ else
+ {
+ // Update the storage on the LS
+ pMethodInfo->SetJMCStatus(fStatus);
+ }
+ }
+
+ // Send reply
+ m_pRCThread->SendIPCReply();
+ }
+ break;
+
+ // Get the JMC status on a given function
+ case DB_IPCE_GET_METHOD_JMC_STATUS:
+ {
+ // Get the method
+ DebuggerModule * pDebuggerModule = LookupOrCreateModule(pEvent->SetJMCFunctionStatus.vmDomainFile);
+
+ Module * pModule = pDebuggerModule->GetRuntimeModule();
+
+ mdMethodDef token = pEvent->SetJMCFunctionStatus.funcMetadataToken;
+
+ // Init reply
+ pEvent = m_pRCThread->GetIPCEventReceiveBuffer();
+ InitIPCEvent(pEvent, DB_IPCE_GET_METHOD_JMC_STATUS_RESULT, NULL, NULL);
+
+ //
+ // This may be called on an unjitted method, so we may
+ // have to create the MethodInfo.
+ //
+ DebuggerMethodInfo * pMethodInfo = GetOrCreateMethodInfo(pModule, token);
+
+ if (pMethodInfo == NULL)
+ {
+ pEvent->hr = E_OUTOFMEMORY;
+ }
+ else
+ {
+ bool fStatus = pMethodInfo->IsJMCFunction();
+ pEvent->SetJMCFunctionStatus.dwStatus = fStatus;
+ pEvent->hr = S_OK;
+ }
+
+ m_pRCThread->SendIPCReply();
+ }
+ break;
+
+ case DB_IPCE_SET_MODULE_JMC_STATUS:
+ {
+ // Get data out of event
+ DebuggerModule * pDebuggerModule = LookupOrCreateModule(pEvent->SetJMCFunctionStatus.vmDomainFile);
+
+ bool fStatus = (pEvent->SetJMCFunctionStatus.dwStatus != 0);
+
+ // Prepare reply
+ pEvent = m_pRCThread->GetIPCEventReceiveBuffer();
+
+ InitIPCReply(pEvent, DB_IPCE_SET_MODULE_JMC_STATUS_RESULT);
+
+ pEvent->hr = S_OK;
+
+ if (pDebuggerModule->HasAnyOptimizedCode() && fStatus)
+ {
+ // If there's optimized code, then we can't be set JMC status to true.
+ // That's because JMC probes are not injected in optimized code, and we
+ // need a JMC probe to have a JMC function.
+ pEvent->hr = CORDBG_E_CANT_SET_TO_JMC;
+ }
+ else
+ {
+ g_pDebugger->SetModuleDefaultJMCStatus(pDebuggerModule->GetRuntimeModule(), fStatus);
+ }
+
+
+
+ // Send reply
+ m_pRCThread->SendIPCReply();
+ }
+ break;
+
+
+ case DB_IPCE_INTERCEPT_EXCEPTION:
+ GetAndSendInterceptCommand(pEvent);
+ break;
+
+ case DB_IPCE_RESOLVE_UPDATE_METADATA_1:
+ {
+
+ LOG((LF_CORDB, LL_INFO10000, "D::HIPCE Handling DB_IPCE_RESOLVE_UPDATE_METADATA_1\n"));
+ // This isn't ideal - Making SerializeModuleMetaData not call new is hard,
+ // but the odds of trying to load a module after a thread is stopped w/
+ // the heap lock should be pretty low.
+ // All of the metadata calls can violate this and call new.
+ SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE;
+
+ Module * pModule = pEvent->MetadataUpdateRequest.vmModule.GetRawPtr();
+ LOG((LF_CORDB, LL_INFO100000, "D::HIPCE Got module 0x%x\n", pModule));
+
+ DWORD countBytes = 0;
+
+ // This will allocate memory. Debugger will then copy from here and send a
+ // DB_IPCE_RESOLVE_UPDATE_METADATA_2 to free this memory.
+ BYTE* pData = NULL;
+ EX_TRY
+ {
+ LOG((LF_CORDB, LL_INFO100000, "D::HIPCE Calling SerializeModuleMetaData\n"));
+ pData = SerializeModuleMetaData(pModule, &countBytes);
+
+ }
+ EX_CATCH_HRESULT(hr);
+
+ LOG((LF_CORDB, LL_INFO100000, "D::HIPCE hr is 0x%x\n", hr));
+
+ DebuggerIPCEvent * pResult = m_pRCThread->GetIPCEventReceiveBuffer();
+ InitIPCEvent(pResult, DB_IPCE_RESOLVE_UPDATE_METADATA_1_RESULT, NULL, NULL);
+
+ pResult->MetadataUpdateRequest.pMetadataStart = pData;
+ pResult->MetadataUpdateRequest.nMetadataSize = countBytes;
+ pResult->hr = hr;
+ LOG((LF_CORDB, LL_INFO1000000, "D::HIPCE metadataStart=0x%x, nMetadataSize=0x%x\n", pData, countBytes));
+
+ m_pRCThread->SendIPCReply();
+ LOG((LF_CORDB, LL_INFO1000000, "D::HIPCE reply sent\n"));
+ }
+ break;
+
+ case DB_IPCE_RESOLVE_UPDATE_METADATA_2:
+ {
+ // Delete memory allocated with DB_IPCE_RESOLVE_UPDATE_METADATA_1.
+ BYTE * pData = (BYTE *) pEvent->MetadataUpdateRequest.pMetadataStart;
+ DeleteInteropSafe(pData);
+
+ DebuggerIPCEvent * pResult = m_pRCThread->GetIPCEventReceiveBuffer();
+ InitIPCEvent(pResult, DB_IPCE_RESOLVE_UPDATE_METADATA_2_RESULT, NULL, NULL);
+ pResult->hr = S_OK;
+ m_pRCThread->SendIPCReply();
+ }
+
+ break;
+
+ default:
+ // We should never get an event that we don't know about.
+ CONSISTENCY_CHECK_MSGF(false, ("Unknown Debug-Event on LS:id=0x%08x.", pEvent->type));
+ LOG((LF_CORDB, LL_INFO10000, "Unknown event type: 0x%08x\n",
+ pEvent->type));
+ }
+
+ STRESS_LOG0(LF_CORDB, LL_INFO10000, "D::HIPCE: finished handling event\n");
+
+ // dbgLockHolder goes out of scope - implicit Release
+ return fContinue;
+}
+#ifdef _PREFAST_
+#pragma warning(pop)
+#endif
+
+/*
+ * GetAndSendInterceptCommand
+ *
+ * This function processes an INTERCEPT_EXCEPTION IPC event, sending the appropriate response.
+ *
+ * Parameters:
+ * event - the event to process.
+ *
+ * Returns:
+ * hr - HRESULT.
+ *
+ */
+HRESULT Debugger::GetAndSendInterceptCommand(DebuggerIPCEvent *event)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS_FROM_GETJITINFO;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr = S_OK;
+
+ _ASSERTE((event->type & DB_IPCE_TYPE_MASK) == DB_IPCE_INTERCEPT_EXCEPTION);
+
+ //
+ // Simple state validation first.
+ //
+ Thread *pThread = event->InterceptException.vmThreadToken.GetRawPtr();
+
+ if ((pThread != NULL) &&
+ !m_forceNonInterceptable &&
+ IsInterceptableException(pThread))
+ {
+ ThreadExceptionState* pExState = pThread->GetExceptionState();
+
+ // We can only have one interception going on at any given time.
+ if (!pExState->GetFlags()->DebuggerInterceptInfo())
+ {
+ //
+ // Now start processing the parameters from the event.
+ //
+ FramePointer targetFramePointer = event->InterceptException.frameToken;
+
+ ControllerStackInfo csi;
+
+ // Safe because we're stopped.
+ StackTraceTicket ticket(pThread);
+ csi.GetStackInfo(ticket, pThread, targetFramePointer, NULL);
+
+ if (csi.m_targetFrameFound)
+ {
+ //
+ // If the target frame is below the point where the current exception was
+ // thrown from, then we should reject this interception command. This
+ // can happen in a func-eval during an exception callback, or during a
+ // breakpoint in a filter function. Or it can just be a user error.
+ //
+ CONTEXT* pContext = pExState->GetContextRecord();
+
+ // This is an approximation on IA64, where we should use the caller SP instead of
+ // the current SP. However, if the targetFramePointer is valid, the comparison should
+ // still work. targetFramePointer should be valid because it ultimately comes from a
+ // full stackwalk.
+ FramePointer excepFramePointer = FramePointer::MakeFramePointer(GetSP(pContext));
+
+ if (IsCloserToRoot(excepFramePointer, targetFramePointer))
+ {
+ hr = CORDBG_E_CURRENT_EXCEPTION_IS_OUTSIDE_CURRENT_EXECUTION_SCOPE;
+ goto LSendResponse;
+ }
+
+
+ //
+ // If the instruction that faulted is not in this managed code, at the leaf
+ // frame, then the IP is actually the return address from the managed or
+ // unmanaged function that really did fault. Thus, we actually want the
+ // IP of the call instruction. I fake this by simply subtracting 1 from
+ // the IP, which is close enough approximation for the search below.
+ //
+ if (pExState->GetContextRecord() != NULL)
+ {
+ // If the faulting instruction is not in managed code, then the interception frame
+ // must be non-leaf.
+ if (!g_pEEInterface->IsManagedNativeCode((BYTE *)(GetIP(pExState->GetContextRecord()))))
+ {
+ csi.m_activeFrame.relOffset--;
+ }
+ else
+ {
+ MethodDesc *pMethodDesc = g_pEEInterface->GetNativeCodeMethodDesc(dac_cast<PCODE>(GetIP(pExState->GetContextRecord())));
+
+ // check if the interception frame is the leaf frame
+ if ((pMethodDesc == NULL) ||
+ (pMethodDesc != csi.m_activeFrame.md) ||
+ (GetSP(pExState->GetContextRecord()) != GetRegdisplaySP(&(csi.m_activeFrame.registers))))
+ {
+ csi.m_activeFrame.relOffset--;
+ }
+ }
+ }
+
+ //
+ // Now adjust the IP to be the previous zero-stack depth sequence point.
+ //
+ SIZE_T foundOffset = 0;
+ DebuggerJitInfo *pJitInfo = csi.m_activeFrame.GetJitInfoFromFrame();
+
+ if (pJitInfo != NULL)
+ {
+ ICorDebugInfo::SourceTypes src;
+
+ ULONG relOffset = csi.m_activeFrame.relOffset;
+
+#if defined(WIN64EXCEPTIONS)
+ int funcletIndex = PARENT_METHOD_INDEX;
+
+ // For funclets, we need to make sure that the stack empty sequence point we use is
+ // in the same funclet as the current offset.
+ if (csi.m_activeFrame.IsFuncletFrame())
+ {
+ funcletIndex = pJitInfo->GetFuncletIndex(relOffset, DebuggerJitInfo::GFIM_BYOFFSET);
+ }
+
+ // Refer to the loop using pMap below.
+ DebuggerILToNativeMap* pMap = NULL;
+#endif // WIN64EXCEPTIONS
+
+ for (unsigned int i = 0; i < pJitInfo->GetSequenceMapCount(); i++)
+ {
+ SIZE_T startOffset = pJitInfo->GetSequenceMap()[i].nativeStartOffset;
+
+ if (DbgIsSpecialILOffset(pJitInfo->GetSequenceMap()[i].ilOffset))
+ {
+ LOG((LF_CORDB, LL_INFO10000,
+ "D::HIPCE: not placing breakpoint at special offset 0x%x\n", startOffset));
+ continue;
+ }
+
+ if ((i >= 1) && (startOffset == pJitInfo->GetSequenceMap()[i-1].nativeStartOffset))
+ {
+ LOG((LF_CORDB, LL_INFO10000,
+ "D::HIPCE: not placing redundant breakpoint at duplicate offset 0x%x\n", startOffset));
+ continue;
+ }
+
+ if (startOffset > relOffset)
+ {
+ LOG((LF_CORDB, LL_INFO10000,
+ "D::HIPCE: Stopping scan for breakpoint at offset 0x%x\n", startOffset));
+ continue;
+ }
+
+ src = pJitInfo->GetSequenceMap()[i].source;
+
+ if (!(src & ICorDebugInfo::STACK_EMPTY))
+ {
+ LOG((LF_CORDB, LL_INFO10000, "D::HIPCE: not placing E&C breakpoint at offset "
+ "0x%x b/c not STACK_EMPTY:it's 0x%x\n", startOffset, src));
+ continue;
+ }
+
+ if ((foundOffset < startOffset) && (startOffset <= relOffset)
+#if defined(WIN64EXCEPTIONS)
+ // Check if we are still in the same funclet.
+ && (funcletIndex == pJitInfo->GetFuncletIndex(startOffset, DebuggerJitInfo::GFIM_BYOFFSET))
+#endif // WIN64EXCEPTIONS
+ )
+ {
+ LOG((LF_CORDB, LL_INFO10000, "D::HIPCE: updating breakpoint at native offset 0x%x\n",
+ startOffset));
+ foundOffset = startOffset;
+#if defined(WIN64EXCEPTIONS)
+ // Save the map entry for modification later.
+ pMap = &(pJitInfo->GetSequenceMap()[i]);
+#endif // WIN64EXCEPTIONS
+ }
+ }
+
+#if defined(WIN64EXCEPTIONS)
+ // This is nasty. Starting recently we could have multiple sequence points with the same IL offset
+ // in the SAME funclet/parent method (previously different sequence points with the same IL offset
+ // imply that they are in different funclet/parent method). Fortunately, we only run into this
+ // if we have a loop which throws a range check failed exception. The code for throwing the
+ // exception executes out of line (this is JIT-specific, of course). The following loop makes sure
+ // that when we interecept the exception, we intercept it at the smallest native offset instead
+ // of intercepting it right before we throw the exception.
+ for (/* no initialization */; pMap > pJitInfo->GetSequenceMap() ; pMap--)
+ {
+ if (pMap->ilOffset == (pMap-1)->ilOffset)
+ {
+ foundOffset = (pMap-1)->nativeStartOffset;
+ }
+ else
+ {
+ break;
+ }
+ }
+ _ASSERTE(foundOffset < relOffset);
+#endif // WIN64EXCEPTIONS
+
+ //
+ // Set up a breakpoint on the intercept IP
+ //
+ DebuggerContinuableExceptionBreakpoint *pBreakpoint;
+
+ pBreakpoint = new (interopsafe, nothrow) DebuggerContinuableExceptionBreakpoint(pThread,
+ foundOffset,
+ pJitInfo,
+ csi.m_activeFrame.currentAppDomain
+ );
+
+ if (pBreakpoint != NULL)
+ {
+ //
+ // Set up the VM side of intercepting.
+ //
+ if (pExState->GetDebuggerState()->SetDebuggerInterceptInfo(csi.m_activeFrame.pIJM,
+ pThread,
+ csi.m_activeFrame.MethodToken,
+ csi.m_activeFrame.md,
+ foundOffset,
+#if defined (_TARGET_ARM_ )|| defined (_TARGET_ARM64_ )
+ // ARM requires the caller stack pointer, not the current stack pointer
+ CallerStackFrame::FromRegDisplay(&(csi.m_activeFrame.registers)),
+#else
+ StackFrame::FromRegDisplay(&(csi.m_activeFrame.registers)),
+#endif
+ pExState->GetFlags()
+ ))
+ {
+ //
+ // Make sure no more exception callbacks come thru.
+ //
+ pExState->GetFlags()->SetSentDebugFirstChance();
+ pExState->GetFlags()->SetSentDebugUserFirstChance();
+ pExState->GetFlags()->SetSentDebugUnwindBegin();
+
+ //
+ // Save off this breakpoint, so that if the exception gets unwound before we hit
+ // the breakpoint - the exeception info can call back to remove it.
+ //
+ pExState->GetDebuggerState()->SetDebuggerInterceptContext((void *)pBreakpoint);
+
+ hr = S_OK;
+ }
+ else // VM could not set up for intercept
+ {
+ DeleteInteropSafe(pBreakpoint);
+ hr = E_INVALIDARG;
+ }
+
+ }
+ else // could not allocate for breakpoint
+ {
+ hr = E_OUTOFMEMORY;
+ }
+
+ }
+ else // could not get JitInfo
+ {
+ hr = E_FAIL;
+ }
+
+ }
+ else // target frame not found.
+ {
+ hr = E_INVALIDARG;
+ }
+
+ }
+ else // already set up for an intercept.
+ {
+ hr = CORDBG_E_INTERCEPT_FRAME_ALREADY_SET;
+ }
+
+ }
+ else if (pThread == NULL)
+ {
+ hr = E_INVALIDARG; // pThread is NULL.
+ }
+ else
+ {
+ hr = CORDBG_E_NONINTERCEPTABLE_EXCEPTION;
+ }
+
+LSendResponse:
+
+ //
+ // Prepare reply
+ //
+ event = m_pRCThread->GetIPCEventReceiveBuffer();
+ InitIPCReply(event, DB_IPCE_INTERCEPT_EXCEPTION_RESULT);
+ event->hr = hr;
+
+ //
+ // Send reply
+ //
+ m_pRCThread->SendIPCReply();
+
+ return hr;
+}
+
+// Poll & wait for the real helper thread to come up.
+// It's possible that the helper thread is blocked by DllMain, and so we can't
+// Wait infinite. If this poll does timeout, then it just means we're likely
+// go do helper duty instead of have the real helper do it.
+void Debugger::PollWaitingForHelper()
+{
+
+ LOG((LF_CORDB, LL_INFO10000, "PollWaitingForHelper() start\n"));
+
+ DebuggerIPCControlBlock * pDCB = g_pRCThread->GetDCB();
+
+ PREFIX_ASSUME(pDCB != NULL);
+
+ int nTotalMSToWait = 8 * 1000;
+
+ // Spin waiting for either the real helper thread or a temp. to be ready.
+ // This should never timeout unless the helper is blocked on the loader lock.
+ while (!pDCB->m_helperThreadId && !pDCB->m_temporaryHelperThreadId)
+ {
+ STRESS_LOG1(LF_CORDB,LL_INFO1000, "PollWaitForHelper. %d\n", nTotalMSToWait);
+
+ // If we hold the lock, we'll block the helper thread and this poll is not useful
+ _ASSERTE(!ThreadHoldsLock());
+
+ const DWORD dwTime = 50;
+ ClrSleepEx(dwTime, FALSE);
+ nTotalMSToWait -= dwTime;
+
+ if (nTotalMSToWait <= 0)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "PollWaitingForHelper() timeout\n"));
+ return;
+ }
+ }
+
+ LOG((LF_CORDB, LL_INFO10000, "PollWaitingForHelper() succeed\n"));
+ return;
+}
+
+
+
+
+void Debugger::TypeHandleToBasicTypeInfo(AppDomain *pAppDomain, TypeHandle th, DebuggerIPCE_BasicTypeData *res)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_INFO10000, "D::THTBTI: converting left-side type handle to basic right-side type info, ELEMENT_TYPE: %d.\n", th.GetSignatureCorElementType()));
+ // GetSignatureCorElementType returns E_T_CLASS for E_T_STRING... :-(
+ if (th.IsNull())
+ {
+ res->elementType = ELEMENT_TYPE_VOID;
+ }
+ else if (th.GetMethodTable() == g_pObjectClass)
+ {
+ res->elementType = ELEMENT_TYPE_OBJECT;
+ }
+ else if (th.GetMethodTable() == g_pStringClass)
+ {
+ res->elementType = ELEMENT_TYPE_STRING;
+ }
+ else
+ {
+ res->elementType = th.GetSignatureCorElementType();
+ }
+
+ switch (res->elementType)
+ {
+ case ELEMENT_TYPE_ARRAY:
+ case ELEMENT_TYPE_SZARRAY:
+ case ELEMENT_TYPE_PTR:
+ case ELEMENT_TYPE_FNPTR:
+ case ELEMENT_TYPE_BYREF:
+ res->vmTypeHandle = WrapTypeHandle(th);
+ res->metadataToken = mdTokenNil;
+ res->vmDomainFile.SetRawPtr(NULL);
+ break;
+
+ case ELEMENT_TYPE_CLASS:
+ case ELEMENT_TYPE_VALUETYPE:
+ {
+ res->vmTypeHandle = th.HasInstantiation() ? WrapTypeHandle(th) : VMPTR_TypeHandle::NullPtr();
+ // only set if instantiated
+ res->metadataToken = th.GetCl();
+ DebuggerModule * pDModule = LookupOrCreateModule(th.GetModule(), pAppDomain);
+ res->vmDomainFile.SetRawPtr((pDModule ? pDModule->GetDomainFile() : NULL));
+ break;
+ }
+
+ default:
+ res->vmTypeHandle = VMPTR_TypeHandle::NullPtr();
+ res->metadataToken = mdTokenNil;
+ res->vmDomainFile.SetRawPtr(NULL);
+ break;
+ }
+ return;
+}
+
+void Debugger::TypeHandleToExpandedTypeInfo(AreValueTypesBoxed boxed,
+ AppDomain *pAppDomain,
+ TypeHandle th,
+ DebuggerIPCE_ExpandedTypeData *res)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ if (th.IsNull())
+ {
+ res->elementType = ELEMENT_TYPE_VOID;
+ }
+ else if (th.GetMethodTable() == g_pObjectClass)
+ {
+ res->elementType = ELEMENT_TYPE_OBJECT;
+ }
+ else if (th.GetMethodTable() == g_pStringClass)
+ {
+ res->elementType = ELEMENT_TYPE_STRING;
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO10000, "D::THTETI: converting left-side type handle to expanded right-side type info, ELEMENT_TYPE: %d.\n", th.GetSignatureCorElementType()));
+ // GetSignatureCorElementType returns E_T_CLASS for E_T_STRING... :-(
+ res->elementType = th.GetSignatureCorElementType();
+ }
+
+ switch (res->elementType)
+ {
+ case ELEMENT_TYPE_ARRAY:
+ case ELEMENT_TYPE_SZARRAY:
+ _ASSERTE(th.IsArray());
+ res->ArrayTypeData.arrayRank = th.AsArray()->GetRank();
+ TypeHandleToBasicTypeInfo(pAppDomain,
+ th.AsArray()->GetArrayElementTypeHandle(),
+ &(res->ArrayTypeData.arrayTypeArg));
+ break;
+
+ case ELEMENT_TYPE_PTR:
+ case ELEMENT_TYPE_BYREF:
+ if (boxed == AllBoxed)
+ {
+ res->elementType = ELEMENT_TYPE_CLASS;
+ goto treatAllValuesAsBoxed;
+ }
+ _ASSERTE(th.IsTypeDesc());
+ TypeHandleToBasicTypeInfo(pAppDomain,
+ th.AsTypeDesc()->GetTypeParam(),
+ &(res->UnaryTypeData.unaryTypeArg));
+ break;
+
+ case ELEMENT_TYPE_VALUETYPE:
+ if (boxed == OnlyPrimitivesUnboxed || boxed == AllBoxed)
+ res->elementType = ELEMENT_TYPE_CLASS;
+ // drop through
+
+ case ELEMENT_TYPE_CLASS:
+ {
+treatAllValuesAsBoxed:
+ res->ClassTypeData.typeHandle = th.HasInstantiation() ? WrapTypeHandle(th) : VMPTR_TypeHandle::NullPtr(); // only set if instantiated
+ res->ClassTypeData.metadataToken = th.GetCl();
+ DebuggerModule * pModule = LookupOrCreateModule(th.GetModule(), pAppDomain);
+ res->ClassTypeData.vmDomainFile.SetRawPtr((pModule ? pModule->GetDomainFile() : NULL));
+ _ASSERTE(!res->ClassTypeData.vmDomainFile.IsNull());
+ break;
+ }
+
+ case ELEMENT_TYPE_FNPTR:
+ {
+ if (boxed == AllBoxed)
+ {
+ res->elementType = ELEMENT_TYPE_CLASS;
+ goto treatAllValuesAsBoxed;
+ }
+ res->NaryTypeData.typeHandle = WrapTypeHandle(th);
+ break;
+ }
+ default:
+ // The element type is sufficient, unless the type is effectively a "boxed"
+ // primitive value type...
+ if (boxed == AllBoxed)
+ {
+ res->elementType = ELEMENT_TYPE_CLASS;
+ goto treatAllValuesAsBoxed;
+ }
+ break;
+ }
+ LOG((LF_CORDB, LL_INFO10000, "D::THTETI: converted left-side type handle to expanded right-side type info, res->ClassTypeData.typeHandle = 0x%08x.\n", res->ClassTypeData.typeHandle.GetRawPtr()));
+ return;
+}
+
+
+HRESULT Debugger::BasicTypeInfoToTypeHandle(DebuggerIPCE_BasicTypeData *data, TypeHandle *pRes)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_INFO10000, "D::BTITTH: expanding basic right-side type to left-side type, ELEMENT_TYPE: %d.\n", data->elementType));
+ *pRes = TypeHandle();
+ TypeHandle th;
+ switch (data->elementType)
+ {
+ case ELEMENT_TYPE_ARRAY:
+ case ELEMENT_TYPE_SZARRAY:
+ case ELEMENT_TYPE_PTR:
+ case ELEMENT_TYPE_BYREF:
+ _ASSERTE(!data->vmTypeHandle.IsNull());
+ th = GetTypeHandle(data->vmTypeHandle);
+ break;
+
+ case ELEMENT_TYPE_CLASS:
+ case ELEMENT_TYPE_VALUETYPE:
+ {
+ if (!data->vmTypeHandle.IsNull())
+ {
+ th = GetTypeHandle(data->vmTypeHandle);
+ }
+ else
+ {
+ DebuggerModule *pDebuggerModule = g_pDebugger->LookupOrCreateModule(data->vmDomainFile);
+
+ th = g_pEEInterface->FindLoadedClass(pDebuggerModule->GetRuntimeModule(), data->metadataToken);
+ if (th.IsNull())
+ {
+ LOG((LF_CORDB, LL_INFO10000, "D::ETITTH: class isn't loaded.\n"));
+ return CORDBG_E_CLASS_NOT_LOADED;
+ }
+
+ _ASSERTE(th.GetNumGenericArgs() == 0);
+ }
+ break;
+ }
+
+ case ELEMENT_TYPE_FNPTR:
+ {
+ _ASSERTE(!data->vmTypeHandle.IsNull());
+ th = GetTypeHandle(data->vmTypeHandle);
+ break;
+ }
+
+ default:
+ th = g_pEEInterface->FindLoadedElementType(data->elementType);
+ break;
+ }
+ if (th.IsNull())
+ return CORDBG_E_CLASS_NOT_LOADED;
+ *pRes = th;
+ return S_OK;
+}
+
+// Iterate through the type argument data, creating type handles as we go.
+void Debugger::TypeDataWalk::ReadTypeHandles(unsigned int nTypeArgs, TypeHandle *ppResults)
+{
+ WRAPPER_NO_CONTRACT;
+
+ for (unsigned int i = 0; i < nTypeArgs; i++)
+ ppResults[i] = ReadTypeHandle();
+ }
+
+TypeHandle Debugger::TypeDataWalk::ReadInstantiation(Module *pModule, mdTypeDef tok, unsigned int nTypeArgs)
+{
+ WRAPPER_NO_CONTRACT;
+
+ DWORD dwAllocSize;
+ if (!ClrSafeInt<DWORD>::multiply(nTypeArgs, sizeof(TypeHandle), dwAllocSize))
+ {
+ ThrowHR(COR_E_OVERFLOW);
+ }
+ TypeHandle * inst = (TypeHandle *) _alloca(dwAllocSize);
+ ReadTypeHandles(nTypeArgs, inst) ;
+ TypeHandle th = g_pEEInterface->LoadInstantiation(pModule, tok, nTypeArgs, inst);
+ if (th.IsNull())
+ COMPlusThrow(kArgumentException, W("Argument_InvalidGenericArg"));
+ return th;
+}
+
+TypeHandle Debugger::TypeDataWalk::ReadTypeHandle()
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ }
+ CONTRACTL_END;
+
+ DebuggerIPCE_TypeArgData * data = ReadOne();
+ if (!data)
+ COMPlusThrow(kArgumentException, W("Argument_InvalidGenericArg"));
+
+ LOG((LF_CORDB, LL_INFO10000, "D::ETITTH: expanding right-side type to left-side type, ELEMENT_TYPE: %d.\n", data->data.elementType));
+
+ TypeHandle th;
+ CorElementType et = data->data.elementType;
+ switch (et)
+ {
+ case ELEMENT_TYPE_ARRAY:
+ case ELEMENT_TYPE_SZARRAY:
+ case ELEMENT_TYPE_PTR:
+ case ELEMENT_TYPE_BYREF:
+ if(data->numTypeArgs == 1)
+ {
+ TypeHandle typar = ReadTypeHandle();
+ switch (et)
+ {
+ case ELEMENT_TYPE_ARRAY:
+ case ELEMENT_TYPE_SZARRAY:
+ th = g_pEEInterface->LoadArrayType(data->data.elementType, typar, data->data.ArrayTypeData.arrayRank);
+ break;
+ case ELEMENT_TYPE_PTR:
+ case ELEMENT_TYPE_BYREF:
+ th = g_pEEInterface->LoadPointerOrByrefType(data->data.elementType, typar);
+ break;
+ default:
+ _ASSERTE(0);
+ }
+ }
+ break;
+
+ case ELEMENT_TYPE_CLASS:
+ case ELEMENT_TYPE_VALUETYPE:
+ {
+ DebuggerModule *pDebuggerModule = g_pDebugger->LookupOrCreateModule(data->data.ClassTypeData.vmDomainFile);
+ th = ReadInstantiation(pDebuggerModule->GetRuntimeModule(), data->data.ClassTypeData.metadataToken, data->numTypeArgs);
+ break;
+ }
+
+ case ELEMENT_TYPE_FNPTR:
+ {
+ SIZE_T cbAllocSize;
+ if ((!ClrSafeInt<SIZE_T>::multiply(data->numTypeArgs, sizeof(TypeHandle), cbAllocSize)) ||
+ (cbAllocSize != (size_t)(cbAllocSize)))
+ {
+ _ASSERTE(COR_E_OVERFLOW);
+ cbAllocSize = UINT_MAX;
+ }
+ TypeHandle * inst = (TypeHandle *) _alloca(cbAllocSize);
+ ReadTypeHandles(data->numTypeArgs, inst) ;
+ th = g_pEEInterface->LoadFnptrType(inst, data->numTypeArgs);
+ break;
+ }
+
+ default:
+ th = g_pEEInterface->LoadElementType(data->data.elementType);
+ break;
+ }
+ if (th.IsNull())
+ COMPlusThrow(kArgumentNullException, W("ArgumentNull_Type"));
+ return th;
+
+}
+
+//
+// GetAndSendTransitionStubInfo figures out if an address is a stub
+// address and sends the result back to the right side.
+//
+void Debugger::GetAndSendTransitionStubInfo(CORDB_ADDRESS_TYPE *stubAddress)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_INFO10000, "D::GASTSI: IsTransitionStub. Addr=0x%08x\n", stubAddress));
+
+ bool result = false;
+
+ result = g_pEEInterface->IsStub((const BYTE *)stubAddress);
+
+
+ // If its not a stub, then maybe its an address in mscoree?
+ if (result == false)
+ {
+ result = (IsIPInModule(g_pMSCorEE, (PCODE)stubAddress) == TRUE);
+ }
+
+ // This is a synchronous event (reply required)
+ DebuggerIPCEvent *event = m_pRCThread->GetIPCEventReceiveBuffer();
+ InitIPCEvent(event, DB_IPCE_IS_TRANSITION_STUB_RESULT, NULL, NULL);
+ event->IsTransitionStubResult.isStub = result;
+
+ // Send the result
+ m_pRCThread->SendIPCReply();
+}
+
+/*
+ * A generic request for a buffer in the left-side for use by the right-side
+ *
+ * This is a synchronous event (reply required).
+ */
+HRESULT Debugger::GetAndSendBuffer(DebuggerRCThread* rcThread, ULONG bufSize)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ // This is a synchronous event (reply required)
+ DebuggerIPCEvent* event = rcThread->GetIPCEventReceiveBuffer();
+ PREFIX_ASSUME(event != NULL);
+ InitIPCEvent(event, DB_IPCE_GET_BUFFER_RESULT, NULL, NULL);
+
+ // Allocate the buffer
+ event->GetBufferResult.hr = AllocateRemoteBuffer( bufSize, &event->GetBufferResult.pBuffer );
+
+ // Send the result
+ return rcThread->SendIPCReply();
+}
+
+/*
+ * Allocate a buffer in the left-side for use by the right-side
+ */
+HRESULT Debugger::AllocateRemoteBuffer( ULONG bufSize, void **ppBuffer )
+ {
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ // The call to Append below will call CUnorderedArray, which will call unsafe New.
+ HRESULT hr;
+ CHECK_IF_CAN_TAKE_HELPER_LOCKS_IN_THIS_SCOPE(&hr, GetCanary());
+ if( FAILED(hr) )
+ {
+ return hr;
+ }
+
+ // Actually allocate the buffer
+ BYTE* pBuffer = new (interopsafe, nothrow) BYTE[bufSize];
+
+ LOG((LF_CORDB, LL_EVERYTHING, "D::ARB: new'd 0x%x\n", *ppBuffer));
+
+ // Check for out of memory error
+ if (pBuffer == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ // Track the allocation so we can free it later
+ void **ppNextBlob = GetMemBlobs()->Append();
+ if( ppNextBlob == NULL )
+ {
+ DeleteInteropSafe( pBuffer );
+ return E_OUTOFMEMORY;
+ }
+ *ppNextBlob = pBuffer;
+
+ // Return the allocated memory
+ *ppBuffer = pBuffer;
+ return S_OK;
+}
+
+/*
+ * Used to release a previously-requested buffer
+ *
+ * This is a synchronous event (reply required).
+ */
+HRESULT Debugger::SendReleaseBuffer(DebuggerRCThread* rcThread, void *pBuffer)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB,LL_INFO10000, "D::SRB for buffer 0x%x\n", pBuffer));
+
+ // This is a synchronous event (reply required)
+ DebuggerIPCEvent* event = rcThread->GetIPCEventReceiveBuffer();
+ PREFIX_ASSUME(event != NULL);
+ InitIPCEvent(event, DB_IPCE_RELEASE_BUFFER_RESULT, NULL, NULL);
+
+ _ASSERTE(pBuffer != NULL);
+
+ // Free the memory
+ ReleaseRemoteBuffer(pBuffer, true);
+
+ // Indicate success in reply
+ event->ReleaseBufferResult.hr = S_OK;
+
+ // Send the result
+ return rcThread->SendIPCReply();
+}
+
+
+//
+// Used to delete the buffer previously-requested by the right side.
+// We've factored the code since both the ~Debugger and SendReleaseBuffer
+// methods do this.
+//
+HRESULT Debugger::ReleaseRemoteBuffer(void *pBuffer, bool removeFromBlobList)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_EVERYTHING, "D::RRB: Releasing RS-alloc'd buffer 0x%x\n", pBuffer));
+
+ // Remove the buffer from the blob list if necessary.
+ if (removeFromBlobList)
+ {
+ USHORT cBlobs = GetMemBlobs()->Count();
+ void **rgpBlobs = GetMemBlobs()->Table();
+
+ USHORT i;
+ for (i = 0; i < cBlobs; i++)
+ {
+ if (rgpBlobs[i] == pBuffer)
+ {
+ GetMemBlobs()->DeleteByIndex(i);
+ break;
+ }
+ }
+
+ // We should have found a match. All buffers passed to ReleaseRemoteBuffer
+ // should have been allocated with AllocateRemoteBuffer and not yet freed.
+ _ASSERTE( i < cBlobs );
+ }
+
+ // Delete the buffer. (Need cast for GCC template support)
+ DeleteInteropSafe( (BYTE*)pBuffer );
+
+ return S_OK;
+}
+
+//
+// UnrecoverableError causes the Left Side to enter a state where no more
+// debugging can occur and we leave around enough information for the
+// Right Side to tell what happened.
+//
+void Debugger::UnrecoverableError(HRESULT errorHR,
+ unsigned int errorCode,
+ const char *errorFile,
+ unsigned int errorLine,
+ bool exitThread)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_INFO10,
+ "Unrecoverable error: hr=0x%08x, code=%d, file=%s, line=%d\n",
+ errorHR, errorCode, errorFile, errorLine));
+
+ //
+ // Setting this will ensure that not much else happens...
+ //
+ m_unrecoverableError = TRUE;
+
+ //
+ // Fill out the control block with the error.
+ // in-proc will find out when the function fails
+ //
+ DebuggerIPCControlBlock *pDCB = m_pRCThread->GetDCB();
+
+ PREFIX_ASSUME(pDCB != NULL);
+
+ pDCB->m_errorHR = errorHR;
+ pDCB->m_errorCode = errorCode;
+
+ //
+ // If we're told to, exit the thread.
+ //
+ if (exitThread)
+ {
+ LOG((LF_CORDB, LL_INFO10,
+ "Thread exiting due to unrecoverable error.\n"));
+ ExitThread(errorHR);
+ }
+}
+
+//
+// Callback for IsThreadAtSafePlace's stack walk.
+//
+StackWalkAction Debugger::AtSafePlaceStackWalkCallback(CrawlFrame *pCF,
+ VOID* data)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+
+ PRECONDITION(CheckPointer(pCF));
+ PRECONDITION(CheckPointer(data));
+ }
+ CONTRACTL_END;
+
+ bool *atSafePlace = (bool*)data;
+ LOG((LF_CORDB, LL_INFO100000, "D:AtSafePlaceStackWalkCallback\n"));
+
+ if (pCF->IsFrameless() && pCF->IsActiveFunc())
+ {
+ LOG((LF_CORDB, LL_INFO1000000, "D:AtSafePlaceStackWalkCallback, IsFrameLess() and IsActiveFunc()\n"));
+ if (g_pEEInterface->CrawlFrameIsGcSafe(pCF))
+ {
+ LOG((LF_CORDB, LL_INFO1000000, "D:AtSafePlaceStackWalkCallback - TRUE: CrawlFrameIsGcSafe()\n"));
+ *atSafePlace = true;
+ }
+ }
+ return SWA_ABORT;
+}
+
+//
+// Determine, via a quick one frame stack walk, if a given thread is
+// in a gc safe place.
+//
+bool Debugger::IsThreadAtSafePlaceWorker(Thread *thread)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+
+ PRECONDITION(CheckPointer(thread));
+ }
+ CONTRACTL_END;
+
+ bool atSafePlace = false;
+
+ // Setup our register display.
+ REGDISPLAY rd;
+ CONTEXT *context = g_pEEInterface->GetThreadFilterContext(thread);
+
+ _ASSERTE(!(g_pEEInterface->GetThreadFilterContext(thread) && ISREDIRECTEDTHREAD(thread)));
+ if (context != NULL)
+ {
+ g_pEEInterface->InitRegDisplay(thread, &rd, context, TRUE);
+ }
+ else
+ {
+ CONTEXT ctx;
+ ZeroMemory(&rd, sizeof(rd));
+ ZeroMemory(&ctx, sizeof(ctx));
+#if defined(_TARGET_X86_)
+ rd.ControlPC = ctx.Eip;
+ rd.PCTAddr = (TADDR)&(ctx.Eip);
+#else
+ FillRegDisplay(&rd, &ctx);
+#endif
+
+ if (ISREDIRECTEDTHREAD(thread))
+ {
+ thread->GetFrame()->UpdateRegDisplay(&rd);
+ }
+ }
+
+ // Do the walk. If it fails, we don't care, because we default
+ // atSafePlace to false.
+ g_pEEInterface->StackWalkFramesEx(
+ thread,
+ &rd,
+ Debugger::AtSafePlaceStackWalkCallback,
+ (VOID*)(&atSafePlace),
+ QUICKUNWIND | HANDLESKIPPEDFRAMES |
+ DISABLE_MISSING_FRAME_DETECTION);
+
+#ifdef LOGGING
+ if (!atSafePlace)
+ LOG((LF_CORDB | LF_GC, LL_INFO1000,
+ "Thread 0x%x is not at a safe place.\n",
+ GetThreadIdHelper(thread)));
+#endif
+
+ return atSafePlace;
+}
+
+bool Debugger::IsThreadAtSafePlace(Thread *thread)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+
+ PRECONDITION(CheckPointer(thread));
+ }
+ CONTRACTL_END;
+
+
+ if (m_fShutdownMode)
+ {
+ return true;
+ }
+
+ // <TODO>
+ //
+ // Make sure this fix is evaluated when doing real work for debugging SO handling.
+ //
+ // On the Stack Overflow code path calling IsThreadAtSafePlaceWorker as it is
+ // currently implemented is way too stack intensive. For now we cheat and just
+ // say that if a thread is in the middle of handling a SO it is NOT at a safe
+ // place. This is a reasonably safe assumption to make and hopefully shouldn't
+ // result in deadlocking the debugger.
+ if ( (thread->IsExceptionInProgress()) &&
+ (g_pEEInterface->GetThreadException(thread) == CLRException::GetPreallocatedStackOverflowExceptionHandle()) )
+ {
+ return false;
+ }
+ // </TODO>
+ else
+ {
+ return IsThreadAtSafePlaceWorker(thread);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Get the complete user state flags.
+// This will collect flags both from the EE and from the LS.
+// This is the real implementation of the RS's ICorDebugThread::GetUserState().
+//
+// Parameters:
+// pThread - non-null thread to get state for.
+//
+// Returns: a CorDebugUserState flags enum describing state.
+//-----------------------------------------------------------------------------
+CorDebugUserState Debugger::GetFullUserState(Thread *pThread)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ PRECONDITION(CheckPointer(pThread));
+ }
+ CONTRACTL_END;
+
+ CorDebugUserState state = g_pEEInterface->GetPartialUserState(pThread);
+
+ bool fSafe = IsThreadAtSafePlace(pThread);
+ if (!fSafe)
+ {
+ state = (CorDebugUserState) (state | USER_UNSAFE_POINT);
+ }
+
+ return state;
+}
+
+/******************************************************************************
+ *
+ * Helper for debugger to get an unique thread id
+ * If we are not in Fiber mode, we can safely use OSThreadId
+ * Otherwise, we will use our own unique ID.
+ *
+ * We will return our unique ID when our host is hosting Thread.
+ *
+ *
+ ******************************************************************************/
+DWORD Debugger::GetThreadIdHelper(Thread *pThread)
+{
+ WRAPPER_NO_CONTRACT;
+
+ if (!CLRTaskHosted())
+ {
+ // use the plain old OS Thread ID
+ return pThread->GetOSThreadId();
+ }
+ else
+ {
+ // use our unique thread ID
+ return pThread->GetThreadId();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Called by EnC during remapping to get information about the local vars.
+// EnC will then use this to set values in the new version to their corresponding
+// values from the old version.
+//
+// Returns a pointer to the debugger's copies of the maps. Caller
+// does not own the memory provided via vars outparameter.
+//-----------------------------------------------------------------------------
+void Debugger::GetVarInfo(MethodDesc * fd, // [IN] method of interest
+ void *DebuggerVersionToken, // [IN] which edit version
+ SIZE_T * cVars, // [OUT] size of 'vars'
+ const ICorDebugInfo::NativeVarInfo **vars // [OUT] map telling where local vars are stored
+ )
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS_FROM_GETJITINFO;
+ }
+ CONTRACTL_END;
+
+ DebuggerJitInfo * ji = (DebuggerJitInfo *)DebuggerVersionToken;
+
+ // If we didn't supply a DJI, then we're asking for the most recent version.
+ if (ji == NULL)
+ {
+ ji = GetLatestJitInfoFromMethodDesc(fd);
+ }
+ _ASSERTE(fd == ji->m_fd);
+
+ PREFIX_ASSUME(ji != NULL);
+
+ *vars = ji->GetVarNativeInfo();
+ *cVars = ji->GetVarNativeInfoCount();
+}
+
+#include "openum.h"
+
+#ifdef EnC_SUPPORTED
+
+//---------------------------------------------------------------------------------------
+//
+// Apply an EnC edit to the CLR datastructures and send the result event to the
+// debugger right-side.
+//
+// Arguments:
+// pDebuggerModule - the module in which the edit should occur
+// cbMetadata - the number of bytes in pMetadata
+// pMetadata - pointer to the delta metadata
+// cbIL - the number of bytes in pIL
+// pIL - pointer to the delta IL
+//
+// Return Value:
+//
+// Assumptions:
+//
+// Notes:
+//
+// This is just the first half of processing an EnC request (hot swapping). This updates
+// the metadata and other CLR data structures to reflect the edit, but does not directly
+// affect code which is currently running. In order to achieve on-stack replacement
+// (remap of running code), we mine all old methods with "EnC remap breakpoints"
+// (instances of DebuggerEnCBreakpoint) at many sequence points. When one of those
+// breakpoints is hit, we give the debugger a RemapOpportunity event and give it a
+// chance to remap the execution to the new version of the method.
+//
+
+HRESULT Debugger::ApplyChangesAndSendResult(DebuggerModule * pDebuggerModule,
+ DWORD cbMetadata,
+ BYTE *pMetadata,
+ DWORD cbIL,
+ BYTE *pIL)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ // @todo - if EnC never works w/ interop, caller New on the helper thread may be ok.
+ SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE;
+
+ HRESULT hr = S_OK;
+
+ LOG((LF_ENC, LL_INFO100, "Debugger::ApplyChangesAndSendResult\n"));
+
+ Module *pModule = pDebuggerModule->GetRuntimeModule();
+ if (! pModule->IsEditAndContinueEnabled())
+ {
+ hr = CORDBG_E_ENC_MODULE_NOT_ENC_ENABLED;
+ }
+ else
+ {
+ // Violation with the following call stack:
+ // CONTRACT in MethodTableBuilder::InitMethodDesc
+ // CONTRACT in EEClass::AddMethod
+ // CONTRACT in EditAndContinueModule::AddMethod
+ // CONTRACT in EditAndContinueModule::ApplyEditAndContinue
+ // CONTRACT in EEDbgInterfaceImpl::EnCApplyChanges
+ // VIOLATED--> CONTRACT in Debugger::ApplyChangesAndSendResult
+ CONTRACT_VIOLATION(GCViolation);
+
+ // Tell the VM to apply the edit
+ hr = g_pEEInterface->EnCApplyChanges(
+ (EditAndContinueModule*)pModule, cbMetadata, pMetadata, cbIL, pIL);
+ }
+
+ LOG((LF_ENC, LL_INFO100, "Debugger::ApplyChangesAndSendResult 2\n"));
+
+ DebuggerIPCEvent* event = m_pRCThread->GetIPCEventSendBuffer();
+ InitIPCEvent(event,
+ DB_IPCE_APPLY_CHANGES_RESULT,
+ NULL,
+ NULL);
+
+ event->ApplyChangesResult.hr = hr;
+
+ // Send the result
+ return m_pRCThread->SendIPCEvent();
+}
+
+//
+// This structure is used to hold a list of the sequence points in a function and
+// determine which should have remap breakpoints applied to them for EnC
+//
+class EnCSequencePointHelper
+{
+public:
+ // Calculates remap info given the supplied JitInfo
+ EnCSequencePointHelper(DebuggerJitInfo *pJitInfo);
+ ~EnCSequencePointHelper();
+
+ // Returns true if the specified sequence point (given by it's index in the
+ // sequence point table in the JitInfo) should get an EnC remap breakpoint.
+ BOOL ShouldSetRemapBreakpoint(unsigned int offsetIndex);
+
+private:
+ DebuggerJitInfo *m_pJitInfo;
+
+ DebugOffsetToHandlerInfo *m_pOffsetToHandlerInfo;
+};
+
+//
+// Goes through the list of sequence points for a function and determines whether or not each
+// is a valid Remap Breakpoint location (not in a special offset, must be empty stack, and not in a handler.
+//
+EnCSequencePointHelper::EnCSequencePointHelper(DebuggerJitInfo *pJitInfo)
+ : m_pOffsetToHandlerInfo(NULL),
+ m_pJitInfo(pJitInfo)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ if (m_pJitInfo->GetSequenceMapCount() == 0)
+ {
+ return;
+ }
+
+ // Construct a list of native offsets we may want to place EnC breakpoints at
+ m_pOffsetToHandlerInfo = new DebugOffsetToHandlerInfo[m_pJitInfo->GetSequenceMapCount()];
+ for (unsigned int i = 0; i < m_pJitInfo->GetSequenceMapCount(); i++)
+ {
+ // By default this slot is unused. We want the indexes in m_pOffsetToHandlerInfo
+ // to correspond to the indexes of m_pJitInfo->GetSequenceMapCount, so we rely
+ // on a -1 offset to indicate that a DebuggerOffsetToHandlerInfo is unused.
+ // However, it would be cleaner and permit a simpler API to the EE if we just
+ // had an array mapping the offsets instead.
+ m_pOffsetToHandlerInfo[i].offset = (SIZE_T) -1;
+ m_pOffsetToHandlerInfo[i].isInFilterOrHandler = FALSE;
+
+ SIZE_T offset = m_pJitInfo->GetSequenceMap()[i].nativeStartOffset;
+
+ // Check if this is a "special" IL offset, such as representing the prolog or eppilog,
+ // or other region not directly mapped to native code.
+ if (DbgIsSpecialILOffset(pJitInfo->GetSequenceMap()[i].ilOffset))
+ {
+ LOG((LF_ENC, LL_INFO10000,
+ "D::UF: not placing E&C breakpoint at special offset 0x%x (IL: 0x%x)\n",
+ offset, m_pJitInfo->GetSequenceMap()[i].ilOffset));
+ continue;
+ }
+
+ // Skip duplicate sequence points
+ if (i >=1 && offset == pJitInfo->GetSequenceMap()[i-1].nativeStartOffset)
+ {
+ LOG((LF_ENC, LL_INFO10000,
+ "D::UF: not placing redundant E&C "
+ "breakpoint at duplicate offset 0x%x (IL: 0x%x)\n",
+ offset, m_pJitInfo->GetSequenceMap()[i].ilOffset));
+ continue;
+ }
+
+ // Skip sequence points that aren't due to the evaluation stack being empty
+ // We can only remap at stack-empty points (since we don't have a mapping for
+ // contents of the evaluation stack).
+ if (!(pJitInfo->GetSequenceMap()[i].source & ICorDebugInfo::STACK_EMPTY))
+ {
+ LOG((LF_ENC, LL_INFO10000,
+ "D::UF: not placing E&C breakpoint at offset "
+ "0x%x (IL: 0x%x) b/c not STACK_EMPTY:it's 0x%x\n", offset,
+ m_pJitInfo->GetSequenceMap()[i].ilOffset, pJitInfo->GetSequenceMap()[i].source));
+ continue;
+ }
+
+ // So far this sequence point looks good, so store it's native offset so we can get
+ // EH information about it from the EE.
+ LOG((LF_ENC, LL_INFO10000,
+ "D::UF: possibly placing E&C breakpoint at offset "
+ "0x%x (IL: 0x%x)\n", offset, m_pJitInfo->GetSequenceMap()[i].ilOffset));
+ m_pOffsetToHandlerInfo[i].offset = m_pJitInfo->GetSequenceMap()[i].nativeStartOffset;
+
+ }
+
+ // Ask the EE to fill in the isInFilterOrHandler bit for the native offsets we're interested in
+ g_pEEInterface->DetermineIfOffsetsInFilterOrHandler(
+ (BYTE *)pJitInfo->m_addrOfCode, m_pOffsetToHandlerInfo, m_pJitInfo->GetSequenceMapCount());
+}
+
+EnCSequencePointHelper::~EnCSequencePointHelper()
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ if (m_pOffsetToHandlerInfo)
+ {
+ delete m_pOffsetToHandlerInfo;
+ }
+}
+
+//
+// Returns if we should set a remap breakpoint at a given offset. We only set them at 0-depth stack
+// and not when inside a handler, either finally, filter, or catch
+//
+BOOL EnCSequencePointHelper::ShouldSetRemapBreakpoint(unsigned int offsetIndex)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ CANNOT_TAKE_LOCK;
+ }
+ CONTRACTL_END;
+
+ {
+ // GetSequenceMapCount calls LazyInitBounds() which can eventually
+ // call ExecutionManager::IncrementReader
+ CONTRACT_VIOLATION(TakesLockViolation);
+ _ASSERTE(offsetIndex <= m_pJitInfo->GetSequenceMapCount());
+ }
+
+ // If this slot is unused (offset -1), we excluded it early
+ if (m_pOffsetToHandlerInfo[offsetIndex].offset == (SIZE_T) -1)
+ {
+ return FALSE;
+ }
+
+ // Otherwise, check the isInFilterOrHandler bit
+ if (m_pOffsetToHandlerInfo[offsetIndex].isInFilterOrHandler)
+ {
+ LOG((LF_ENC, LL_INFO10000,
+ "D::UF: not placing E&C breakpoint in filter/handler at offset 0x%x\n",
+ m_pOffsetToHandlerInfo[offsetIndex].offset));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+//-----------------------------------------------------------------------------
+// For each function that's EnC-ed, the EE will call either UpdateFunction
+// (if the function already is loaded + jitted) or Addfunction
+//
+// This is called before the EE updates the MethodDesc, so pMD does not yet
+// point to the version we'll be remapping to.
+//-----------------------------------------------------------------------------
+HRESULT Debugger::UpdateFunction(MethodDesc* pMD, SIZE_T encVersion)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS_FROM_GETJITINFO;
+ PRECONDITION(ThisIsHelperThread()); // guarantees we're serialized.
+ PRECONDITION(IsStopped());
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_INFO10000, "D::UF: updating "
+ "%s::%s to version %d\n", pMD->m_pszDebugClassName, pMD->m_pszDebugMethodName, encVersion));
+
+ // tell the RS that this function has been updated so that it can create new CorDBFunction
+ Module *pModule = g_pEEInterface->MethodDescGetModule(pMD);
+ _ASSERTE(pModule != NULL);
+ mdToken methodDef = pMD->GetMemberDef();
+ SendEnCUpdateEvent(DB_IPCE_ENC_UPDATE_FUNCTION,
+ pModule,
+ methodDef,
+ pMD->GetMethodTable()->GetCl(),
+ encVersion);
+
+ DebuggerMethodInfo *dmi = GetOrCreateMethodInfo(pModule, methodDef);
+ if (dmi == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ // The DMI always holds the most current EnC version number. We always JIT the most
+ // current version of the function, so when we do see a JitBegin we will create a new
+ // dji for it and stash the current version there. We don't want to change the current
+ // jit info because it has to maintain the version for the code it corresponds to.
+ dmi->SetCurrentEnCVersion(encVersion);
+
+ // This is called before the MethodDesc is updated to point to the new function.
+ // So this call will get the most recent old function.
+ DebuggerJitInfo *pJitInfo = GetLatestJitInfoFromMethodDesc(pMD);
+
+ if (pJitInfo == NULL )
+ {
+ LOG((LF_CORDB,LL_INFO10000,"Unable to get DJI by recently "
+ "D::UF: JITted version number (it hasn't been jitted yet),"
+ "which is fine\n"));
+ return S_OK;
+ }
+
+ //
+ // Mine the old version of the method with patches so that we can provide
+ // remap opportunities whenever the old version of the method is executed.
+ //
+
+ if (pJitInfo->m_encBreakpointsApplied)
+ {
+ LOG((LF_CORDB,LL_INFO10000,"D::UF: Breakpoints already applied\n"));
+ return S_OK;
+ }
+
+ LOG((LF_CORDB,LL_INFO10000,"D::UF: Applying breakpoints\n"));
+
+ // We only place the patches if we have jit info for this
+ // function, i.e., its already been jitted. Otherwise, the EE will
+ // pickup the new method on the next JIT anyway.
+
+ ICorDebugInfo::SourceTypes src;
+
+ EnCSequencePointHelper sequencePointHelper(pJitInfo);
+
+ // For each offset in the IL->Native map, set a new EnC breakpoint on the
+ // ones that we know could be remap points.
+ for (unsigned int i = 0; i < pJitInfo->GetSequenceMapCount(); i++)
+ {
+ // Skip if this isn't a valid remap point (eg. is in an exception handler)
+ if (! sequencePointHelper.ShouldSetRemapBreakpoint(i))
+ {
+ continue;
+ }
+
+ SIZE_T offset = pJitInfo->GetSequenceMap()[i].nativeStartOffset;
+
+ LOG((LF_CORDB, LL_INFO10000,
+ "D::UF: placing E&C breakpoint at native offset 0x%x\n",
+ offset));
+
+ DebuggerEnCBreakpoint *bp;
+
+ // Create and activate a new EnC remap breakpoint here in the old version of the method
+ bp = new (interopsafe) DebuggerEnCBreakpoint( offset,
+ pJitInfo,
+ DebuggerEnCBreakpoint::REMAP_PENDING,
+ (AppDomain *)pModule->GetDomain());
+
+ _ASSERTE(bp != NULL);
+ }
+
+ pJitInfo->m_encBreakpointsApplied = true;
+
+ return S_OK;
+}
+
+// Called to update a function that hasn't yet been loaded (and so we don't have a MethodDesc).
+// This may be updating an existing function on a type that hasn't been loaded
+// or adding a new function to a type that hasn't been loaded.
+// We need to notify the debugger so that it can properly track version info.
+HRESULT Debugger::UpdateNotYetLoadedFunction(mdMethodDef token, Module * pModule, SIZE_T encVersion)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_NOTRIGGER;
+
+ PRECONDITION(ThisIsHelperThread());
+ PRECONDITION(ThreadHoldsLock()); // must have lock since we're on helper and stopped.
+ }
+ CONTRACTL_END;
+
+ DebuggerMethodInfo *dmi = GetOrCreateMethodInfo(pModule, token);
+ if (! dmi)
+ {
+ return E_OUTOFMEMORY;
+ }
+ dmi->SetCurrentEnCVersion(encVersion);
+
+
+ // Must tell the RS that this function has been added so that it can create new CorDBFunction.
+ mdTypeDef classToken = 0;
+
+ HRESULT hr = pModule->GetMDImport()->GetParentToken(token, &classToken);
+ if (FAILED(hr))
+ {
+ // We never expect this to actually fail, but just in case it does for some other crazy reason,
+ // we'll return before we AV.
+ CONSISTENCY_CHECK_MSGF(false, ("Class lookup failed:mdToken:0x%08x, pModule=%p. hr=0x%08x\n", token, pModule, hr));
+ return hr;
+ }
+
+ SendEnCUpdateEvent(DB_IPCE_ENC_ADD_FUNCTION, pModule, token, classToken, encVersion);
+
+
+ return S_OK;
+}
+
+// Called to add a new function when the type has been loaded already.
+// This is effectively the same as above, except that we're given a
+// MethodDesc instead of a module and token.
+// This should probably be merged into a single method since the caller
+// should always have a module and token available in both cases.
+HRESULT Debugger::AddFunction(MethodDesc* pMD, SIZE_T encVersion)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_NOTRIGGER;
+
+ PRECONDITION(ThisIsHelperThread());
+ PRECONDITION(ThreadHoldsLock()); // must have lock since we're on helper and stopped.
+ }
+ CONTRACTL_END;
+
+ DebuggerDataLockHolder debuggerDataLockHolder(this);
+
+ LOG((LF_CORDB, LL_INFO10000, "D::AF: adding "
+ "%s::%s to version %d\n", pMD->m_pszDebugClassName, pMD->m_pszDebugMethodName, encVersion));
+
+ _ASSERTE(pMD != NULL);
+ Module *pModule = g_pEEInterface->MethodDescGetModule(pMD);
+ _ASSERTE(pModule != NULL);
+ mdToken methodDef = pMD->GetMemberDef();
+
+ // tell the RS that this function has been added so that it can create new CorDBFunction
+ SendEnCUpdateEvent( DB_IPCE_ENC_ADD_FUNCTION,
+ pModule,
+ methodDef,
+ pMD->GetMethodTable()->GetCl(),
+ encVersion);
+
+ DebuggerMethodInfo *dmi = CreateMethodInfo(pModule, methodDef);
+ if (! dmi)
+ {
+ return E_OUTOFMEMORY;
+ }
+ dmi->SetCurrentEnCVersion(encVersion);
+
+ return S_OK;
+}
+
+// Invoke when a field is added to a class using EnC
+HRESULT Debugger::AddField(FieldDesc* pFD, SIZE_T encVersion)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_INFO10000, "D::AFld: adding "
+ "%8.8d::%8.8d to version %d\n", pFD->GetApproxEnclosingMethodTable()->GetCl(), pFD->GetMemberDef(), encVersion));
+
+ // tell the RS that this field has been added so that it can update it's structures
+ SendEnCUpdateEvent( DB_IPCE_ENC_ADD_FIELD,
+ pFD->GetModule(),
+ pFD->GetMemberDef(),
+ pFD->GetApproxEnclosingMethodTable()->GetCl(),
+ encVersion);
+
+ return S_OK;
+}
+
+//
+// RemapComplete is called when we are just about to resume into
+// the function so that we can setup our breakpoint to trigger
+// a call to the RemapComplete callback once the function is actually
+// on the stack. We need to wait until the function is jitted before
+// we can add the trigger, which doesn't happen until we call
+// ResumeInUpdatedFunction in the VM
+//
+// addr is address within the given function, which we use to determine
+// exact EnC version.
+//
+HRESULT Debugger::RemapComplete(MethodDesc* pMD, TADDR addr, SIZE_T nativeOffset)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS_FROM_GETJITINFO;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(pMD != NULL);
+ _ASSERTE(addr != NULL);
+
+ LOG((LF_CORDB, LL_INFO10000, "D::RC: installed remap complete patch for "
+ "%s::%s to version %d\n", pMD->m_pszDebugClassName, pMD->m_pszDebugMethodName));
+
+ DebuggerMethodInfo *dmi = GetOrCreateMethodInfo(pMD->GetModule(), pMD->GetMemberDef());
+
+ if (dmi == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ DebuggerJitInfo *pJitInfo = GetJitInfo(pMD, (const BYTE *) addr);
+
+ if (pJitInfo == NULL)
+ {
+ _ASSERTE(!"Debugger doesn't handle OOM");
+ return E_OUTOFMEMORY;
+ }
+ _ASSERTE(pJitInfo->m_addrOfCode + nativeOffset == addr);
+
+ DebuggerEnCBreakpoint *bp;
+
+ // Create and activate a new REMAP_COMPLETE EnC breakpoint to let us know when
+ // the EE has completed the remap process.
+ // This will be deleted when the patch is hit.
+ bp = new (interopsafe, nothrow) DebuggerEnCBreakpoint( nativeOffset,
+ pJitInfo,
+ DebuggerEnCBreakpoint::REMAP_COMPLETE,
+ (AppDomain *)pMD->GetModule()->GetDomain());
+ if (bp == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Called by EnC stuff to map an IL offset to a native offset for the given
+// method described by (pMD, nativeFnxStart).
+//
+// pMD - methoddesc for method being remapped
+// ilOffset - incoming offset in old method to remap.
+// nativeFnxStart - address of new function. This can be used to find the DJI
+// for the new method.
+// nativeOffset - outparameter for native linear offset relative to start address.
+//-----------------------------------------------------------------------------
+
+HRESULT Debugger::MapILInfoToCurrentNative(MethodDesc *pMD,
+ SIZE_T ilOffset,
+ TADDR nativeFnxStart,
+ SIZE_T *nativeOffset)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS_FROM_GETJITINFO;
+ PRECONDITION(nativeOffset != NULL);
+ PRECONDITION(CheckPointer(pMD));
+ PRECONDITION(nativeFnxStart != NULL);
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(HasLazyData()); // only used for EnC, should have already inited.
+
+
+ LOG((LF_CORDB, LL_INFO1000000, "D::MILITCN: %s::%s ilOff:0x%x, "
+ ", natFnx:0x%x dji:0x%x\n", pMD->m_pszDebugClassName,
+ pMD->m_pszDebugMethodName, ilOffset, nativeFnxStart));
+
+ *nativeOffset = 0;
+ DebuggerJitInfo *djiTo = GetJitInfo( pMD, (const BYTE *)nativeFnxStart);
+ if (djiTo == NULL)
+ {
+ _ASSERTE(!"No DJI in EnC case: should only happen on oom. Debugger doesn't support OOM.");
+ return E_FAIL;
+ }
+
+ DebuggerJitInfo::ILToNativeOffsetIterator it;
+ djiTo->InitILToNativeOffsetIterator(it, ilOffset);
+ *nativeOffset = it.CurrentAssertOnlyOne(NULL);
+ return S_OK;
+}
+
+#endif // EnC_SUPPORTED
+
+//---------------------------------------------------------------------------------------
+// Hijack worker stub called from asm stub. This can then delegate to other hijacks.
+//
+// Arguments:
+// pContext - context from which we were hijacked. Always non-null.
+// pRecord - exception record if hijacked from an exception event.
+// Else null (if hijacked from a managed IP).
+// reason - hijack reason. Use this to delegate to the proper hijack stub.
+// pData - arbitrary data for the hijack to use. (eg, such as a DebuggerEval object)
+//
+// Returns:
+// This does not return. Instead it restores this threads context to pContext.
+//
+// Assumptions:
+// If hijacked at an exception event, the debugger must have cleared the exception.
+//
+// Notes:
+// The debugger hijacked the thread to get us here via the DacDbi Hijack primitive.
+// This is called from a hand coded asm stub.
+//
+void STDCALL ExceptionHijackWorker(
+ CONTEXT * pContext,
+ EXCEPTION_RECORD * pRecord,
+ EHijackReason::EHijackReason reason,
+ void * pData)
+{
+ STRESS_LOG0(LF_CORDB,LL_INFO100, "D::EHW: Enter ExceptionHijackWorker\n");
+
+ // We could have many different reasons for hijacking. Switch and invoke the proper hijacker.
+ switch(reason)
+ {
+ case EHijackReason::kUnhandledException:
+ STRESS_LOG0(LF_CORDB,LL_INFO10, "D::EHW: Calling g_pDebugger->UnhandledHijackWorker()\n");
+ _ASSERTE(pData == NULL);
+ g_pDebugger->UnhandledHijackWorker(pContext, pRecord);
+ break;
+#ifdef FEATURE_INTEROP_DEBUGGING
+ case EHijackReason::kM2UHandoff:
+ _ASSERTE(pData == NULL);
+ g_pDebugger->M2UHandoffHijackWorker(pContext, pRecord);
+ break;
+ case EHijackReason::kFirstChanceSuspend:
+ _ASSERTE(pData == NULL);
+ g_pDebugger->FirstChanceSuspendHijackWorker(pContext, pRecord);
+ break;
+ case EHijackReason::kGenericHijack:
+ _ASSERTE(pData == NULL);
+ g_pDebugger->GenericHijackFunc();
+ break;
+#endif
+ default:
+ CONSISTENCY_CHECK_MSGF(false, ("Unrecognized Hijack code: %d", reason));
+ }
+
+ // Currently, no Hijack actually returns yet.
+ UNREACHABLE();
+
+ // If we return to this point, then we'll restore ourselves.
+ // We've got the context that we were hijacked from, so we should be able to just
+ // call SetThreadContext on ourself to fix us.
+}
+
+#if defined(WIN64EXCEPTIONS) && !defined(FEATURE_PAL)
+
+#if defined(_TARGET_AMD64_)
+// ----------------------------------------------------------------------------
+// EmptyPersonalityRoutine
+//
+// Description:
+// This personality routine is used to work around a limitation of the OS unwinder when we return
+// ExceptionCollidedUnwind.
+// See code:ExceptionHijackPersonalityRoutine for more information.
+//
+// Arguments:
+// * pExceptionRecord - not used
+// * MemoryStackFp - not used
+// * BackingStoreFp - not used
+// * pContextRecord - not used
+// * pDispatcherContext - not used
+// * GlobalPointer - not used
+//
+// Return Value:
+// Always return ExceptionContinueSearch.
+//
+
+EXCEPTION_DISPOSITION EmptyPersonalityRoutine(IN PEXCEPTION_RECORD pExceptionRecord,
+ IN ULONG64 MemoryStackFp,
+ IN OUT PCONTEXT pContextRecord,
+ IN OUT PDISPATCHER_CONTEXT pDispatcherContext)
+{
+ LIMITED_METHOD_CONTRACT;
+ return ExceptionContinueSearch;
+}
+#endif // _TARGET_AMD64_
+
+//---------------------------------------------------------------------------------------
+// Personality routine for unwinder the assembly hijack stub on 64-bit.
+//
+// Arguments:
+// standard Personality routine signature.
+//
+// Assumptions:
+// This is caleld by the OS exception logic during exception handling.
+//
+// Notes:
+// We just need 1 personality routine for the tiny assembly hijack stub.
+// All the C++ code invoked by the stub is ok.
+//
+// This needs to fetch the original context that this thread was hijacked from
+// (which the hijack pushed onto the stack) and pass that back to the OS. This lets
+// ths OS unwind out of the hijack.
+//
+// This function should only be executed if an unhandled exception is intercepted by a managed debugger.
+// Otherwise there should never be a 2nd pass exception dispatch crossing the hijack stub.
+//
+// The basic idea here is straightforward. The OS does an exception dispatch and hit our hijack stub.
+// Since the hijack stub is not unwindable, we need a personality routine to restore the CONTEXT and
+// tell the OS to continue the dispatch with that CONTEXT by returning ExceptionCollidedUnwind.
+//
+// However, empricially, the OS expects that when we return ExceptionCollidedUnwind, the function
+// represented by the CONTEXT has a personality routine. The OS will actually AV if we return a NULL
+// personality routine.
+//
+// On AMD64, we work around this by using an empty personality routine.
+
+EXTERN_C EXCEPTION_DISPOSITION
+ExceptionHijackPersonalityRoutine(IN PEXCEPTION_RECORD pExceptionRecord
+ WIN64_ARG(IN ULONG64 MemoryStackFp)
+ NOT_WIN64_ARG(IN ULONG32 MemoryStackFp),
+ IN OUT PCONTEXT pContextRecord,
+ IN OUT PDISPATCHER_CONTEXT pDispatcherContext
+ )
+{
+#if defined(_TARGET_AMD64_)
+ CONTEXT * pHijackContext = NULL;
+
+ // Get the 1st parameter (the Context) from hijack worker.
+ // EstablisherFrame points to the stack slot 8 bytes above the
+ // return address to the ExceptionHijack. This would contain the
+ // parameters passed to ExceptionHijackWorker, which is marked
+ // STDCALL, but the x64 calling convention lets the
+ // ExceptionHijackWorker use that stack space, resulting in the
+ // context being overwritten. Instead, we get the context from the
+ // previous stack frame, which contains the arguments to
+ // ExceptionHijack, placed there by the debugger in
+ // DacDbiInterfaceImpl::Hijack. This works because ExceptionHijack
+ // allocates exactly 4 stack slots.
+ pHijackContext = *reinterpret_cast<CONTEXT **>(pDispatcherContext->EstablisherFrame + 0x20);
+
+ // This copies pHijackContext into pDispatcherContext, which the OS can then
+ // use to walk the stack.
+ FixupDispatcherContext(pDispatcherContext, pHijackContext, pContextRecord, (PEXCEPTION_ROUTINE)EmptyPersonalityRoutine);
+#else
+ _ASSERTE(!"NYI - ExceptionHijackPersonalityRoutine()");
+#endif
+
+ // Returning ExceptionCollidedUnwind will cause the OS to take our new context record and
+ // dispatcher context and restart the exception dispatching on this call frame, which is
+ // exactly the behavior we want.
+ return ExceptionCollidedUnwind;
+}
+#endif // WIN64EXCEPTIONS && !FEATURE_PAL
+
+
+// UEF Prototype from excep.cpp
+LONG InternalUnhandledExceptionFilter_Worker(EXCEPTION_POINTERS *pExceptionInfo);
+
+//---------------------------------------------------------------------------------------
+// Hijack for a 2nd-chance exception. Will invoke the CLR's UEF.
+//
+// Arguments:
+// pContext - context that this thread was hijacked from.
+// pRecord - exception record of the exception that this was hijacked at.
+// pData - random data.
+// Notes:
+// When under a native-debugger, the OS does not invoking the Unhandled Exception Filter (UEF).
+// It dispatches a 2nd-chance Exception event instead.
+// However, the CLR's UEF does lots of useful work (like dispatching the 2nd-chance managed exception,
+// allowing func-eval on 2nd-chance, and allowing intercepting unhandled exceptions).
+// So we'll emulate the OS behavior here by invoking the CLR's UEF directly.
+//
+void Debugger::UnhandledHijackWorker(CONTEXT * pContext, EXCEPTION_RECORD * pRecord)
+{
+ CONTRACTL
+ {
+ // The ultimate protection shield is that this hijack can be executed under the same circumstances
+ // as a top-level UEF that pinvokes into managed code
+ // - That means we're GC-triggers safe
+ // - that means that we can crawl the stack. (1st-pass EH logic ensures this).
+ // We need to be GC-triggers because this may invoke a func-eval.
+ GC_TRIGGERS;
+
+ // Don't throw out of a hijack! There's nobody left to catch this.
+ NOTHROW;
+
+ // We expect to always be in preemptive here by the time we get this unhandled notification.
+ // We know this is true because a native UEF is preemptive.
+ // More detail:
+ // 1) If we got here from a software exception (eg, Throw from C#), then the jit helper
+ // toggled us to preemptive before calling RaiseException().
+ // 2) If we got here from a hardware exception in managed code, then the 1st-pass already did
+ // some magic to get us into preemptive. On x86, this is magic. On 64-bit, it did some magic
+ // to push a Faulting-Exception-Frame and rethrow the exception as a software exception.
+ MODE_PREEMPTIVE;
+
+
+ PRECONDITION(CheckPointer(pContext));
+ PRECONDITION(CheckPointer(pRecord));
+ }
+ CONTRACTL_END;
+
+ EXCEPTION_POINTERS exceptionInfo;
+ exceptionInfo.ContextRecord = pContext;
+ exceptionInfo.ExceptionRecord = pRecord;
+
+ // Snag the Runtime thread. Since we're hijacking a managed exception, we should always have one.
+ Thread * pThread = g_pEEInterface->GetThread();
+ (void)pThread; //prevent "unused variable" error from GCC
+ _ASSERTE(pThread != NULL);
+
+ BOOL fSOException = FALSE;
+
+ if ((pRecord != NULL) &&
+ (pRecord->ExceptionCode == STATUS_STACK_OVERFLOW))
+ {
+ fSOException = TRUE;
+ }
+
+ // because we hijack here during jit attach invoked by the OS we need to make sure that the debugger is completely
+ // attached before continuing. If we ever hijacked here when an attach was not in progress this function returns
+ // immediately so no problems there.
+ WaitForDebuggerAttach();
+ PostJitAttach();
+
+ // On Win7 WatsonLastChance returns CONTINUE_SEARCH for unhandled exceptions execpt stack overflow, and
+ // lets OS launch debuggers for us. Before the unhandled exception reaches the OS, CLR UEF has already
+ // processed this unhandled exception. Thus, we should not call into CLR UEF again if it is the case.
+ if (RunningOnWin7() &&
+ pThread &&
+ (pThread->HasThreadStateNC(Thread::TSNC_ProcessedUnhandledException) ||
+ pThread->HasThreadStateNC(Thread::TSNC_AppDomainContainUnhandled) ||
+ fSOException))
+ {
+
+ FrameWithCookie<FaultingExceptionFrame> fef;
+#if defined(WIN64EXCEPTIONS)
+ *((&fef)->GetGSCookiePtr()) = GetProcessGSCookie();
+#endif // WIN64EXCEPTIONS
+ if ((pContext != NULL) && fSOException)
+ {
+ GCX_COOP(); // Must be cooperative to modify frame chain.
+
+ // EEPolicy::HandleFatalStackOverflow pushes a FaultingExceptionFrame on the stack after SO
+ // exception. Our hijack code runs in the exception context, and overwrites the stack space
+ // after SO excpetion, so this frame was popped out before invoking RaiseFailFast. We need to
+ // put it back here for running func-eval code.
+ // This cumbersome code should be removed once SO synchronization is moved to be completely
+ // out-of-process.
+ fef.InitAndLink(pContext);
+ }
+
+ STRESS_LOG0(LF_CORDB, LL_INFO10, "D::EHW: Calling NotifyDebuggerLastChance\n");
+ NotifyDebuggerLastChance(pThread, &exceptionInfo, TRUE);
+
+ // Continuing from a second chance managed exception causes the process to exit.
+ TerminateProcess(GetCurrentProcess(), 0);
+ }
+
+ // Since this is a unhandled managed exception:
+ // - we always have a Thread* object.
+ // - we always have a throwable
+ // - we executed through the 1st-pass of the EH logic. This means the 1st-pass could do work
+ // to enforce certain invariants (like the ones listed here, or ensuring the thread can be crawled)
+
+ // Need to call the CLR's UEF. This will do all the key work including:
+ // - send the managed 2nd-chance exception event.
+ // - deal with synchronization.
+ // - allow func-evals.
+ // - deal with interception.
+
+ // If intercepted, then this never returns. It will manually invoke the unwinders and fix the context.
+
+ // InternalUnhandledExceptionFilter_Worker has a throws contract, but should not be throwing in any
+ // conditions we care about. This hijack should never throw, so catch everything.
+ HRESULT hrIgnore;
+ EX_TRY
+ {
+ InternalUnhandledExceptionFilter_Worker(&exceptionInfo);
+ }
+ EX_CATCH_HRESULT(hrIgnore);
+
+ // Continuing from a second chance managed exception causes the process to exit.
+ TerminateProcess(GetCurrentProcess(), 0);
+}
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+//
+// This is the handler function that is put in place of a thread's top-most SEH handler function when it is hijacked by
+// the Right Side during an unmanaged first chance exception.
+//
+typedef EXCEPTION_DISPOSITION (__cdecl *SEHHandler)(EXCEPTION_RECORD *pExceptionRecord,
+ EXCEPTION_REGISTRATION_RECORD *pEstablisherFrame,
+ CONTEXT *pContext,
+ void *DispatcherContext);
+#define DOSPEW 0
+
+#if DOSPEW
+#define SPEW(s) s
+#else
+#define SPEW(s)
+#endif
+
+
+
+
+//-----------------------------------------------------------------------------
+// Hijack when we have a M2U handoff.
+// This happens when we do a step-out from Managed-->Unmanaged, and so we hit a managed patch in Native code.
+// This also happens when a managed stepper does a step-in to unmanaged code.
+// Since we're in native code, there's no CPFH, and so we have to hijack.
+// @todo- could this be removed? Step-out to native is illegal in v2.0, and do existing
+// CLR filters catch the step-in patch?
+// @dbgtodo controller/stepping - this will be completely unneeded in V3 when all stepping is oop
+//-----------------------------------------------------------------------------
+VOID Debugger::M2UHandoffHijackWorker(CONTEXT *pContext,
+ EXCEPTION_RECORD *pExceptionRecord)
+{
+ // We must use a static contract here because the function does not return normally
+ STATIC_CONTRACT_NOTHROW;
+ STATIC_CONTRACT_GC_TRIGGERS; // from sending managed event
+ STATIC_CONTRACT_MODE_PREEMPTIVE; // we're in umanaged code.
+ SO_NOT_MAINLINE_FUNCTION;
+
+
+ LOG((LF_CORDB, LL_INFO1000, "D::M2UHHW: Context=0x%p exception record=0x%p\n",
+ pContext, pExceptionRecord));
+
+ // We should only be here for a BP
+ _ASSERTE(pExceptionRecord->ExceptionCode == STATUS_BREAKPOINT);
+
+ // Get the current runtime thread. This is only an optimized TLS access.
+ // Since we're coming off a managed-step, we should always have a thread.
+ Thread *pEEThread = g_pEEInterface->GetThread();
+ _ASSERTE(pEEThread != NULL);
+
+ _ASSERTE(!pEEThread->GetInteropDebuggingHijacked());
+ pEEThread->SetInteropDebuggingHijacked(TRUE);
+
+ //win32 has a weird property where EIP points after the BP in the debug event
+ //so we are adjusting it to point at the BP
+ CORDbgAdjustPCForBreakInstruction((DT_CONTEXT*)pContext);
+ LOG((LF_CORDB, LL_INFO1000, "D::M2UHHW: Context ip set to 0x%p\n", GetIP(pContext)));
+
+ _ASSERTE(!ISREDIRECTEDTHREAD(pEEThread));
+
+ // Don't bother setting FilterContext here because we already pass it to FirstChanceNativeException.
+ // Shortcut right to our dispatch native exception logic, there may be no COMPlusFrameHandler in place!
+ EX_TRY
+ {
+ LOG((LF_CORDB, LL_INFO1000, "D::M2UHHW: Calling FirstChanceNativeException\n"));
+ bool okay;
+ okay = g_pDebugger->FirstChanceNativeException(pExceptionRecord,
+ pContext,
+ pExceptionRecord->ExceptionCode,
+ pEEThread);
+ _ASSERTE(okay == true);
+ LOG((LF_CORDB, LL_INFO1000, "D::M2UHHW: FirstChanceNativeException returned\n"));
+ }
+ EX_CATCH
+ {
+ // It would be really bad if somebody threw here. We're actually outside of managed code,
+ // so there's not a lot we can do besides just swallow the exception and hope for the best.
+ LOG((LF_CORDB, LL_INFO1000, "D::M2UHHW: ERROR! FirstChanceNativeException threw an exception\n"));
+ }
+ EX_END_CATCH(SwallowAllExceptions);
+
+ _ASSERTE(!ISREDIRECTEDTHREAD(pEEThread));
+ _ASSERTE(pEEThread->GetInteropDebuggingHijacked());
+ pEEThread->SetInteropDebuggingHijacked(FALSE);
+
+ // This signal will be received by the RS and it will use SetThreadContext
+ // to clear away the entire hijack frame. This function does not return.
+ LOG((LF_CORDB, LL_INFO1000, "D::M2UHHW: Flaring hijack complete\n"));
+ SignalHijackComplete();
+
+ _ASSERTE(!"UNREACHABLE");
+}
+
+//-----------------------------------------------------------------------------
+// This hijack is run after receiving an IB event that we don't know how the
+// debugger will want to continue. Under the covers we clear the event and divert
+// execution here where we block until the debugger decides whether or not to clear
+// the event. At that point we exit this hijack and the LS diverts execution back
+// to the offending instruction.
+// We don't know:
+// - whether we have an EE-thread?
+// - how we're going to continue this (handled / not-handled).
+//
+// But we do know that:
+// - this exception does not belong to the CLR.
+// - this thread is not in cooperative mode.
+//-----------------------------------------------------------------------------
+LONG Debugger::FirstChanceSuspendHijackWorker(CONTEXT *pContext,
+ EXCEPTION_RECORD *pExceptionRecord)
+{
+ // if we aren't set up to do interop debugging this function should just bail out
+ if(m_pRCThread == NULL)
+ return EXCEPTION_CONTINUE_SEARCH;
+
+ DebuggerIPCControlBlock *pDCB = m_pRCThread->GetDCB();
+ if(pDCB == NULL)
+ return EXCEPTION_CONTINUE_SEARCH;
+
+ if (!pDCB->m_rightSideIsWin32Debugger)
+ return EXCEPTION_CONTINUE_SEARCH;
+
+ // at this point we know that there is an interop debugger attached. This makes it safe to send
+ // flares
+#if DOSPEW
+ DWORD tid = GetCurrentThreadId();
+#endif
+
+ SPEW(fprintf(stderr, "0x%x D::FCHF: in first chance hijack filter.\n", tid));
+ SPEW(fprintf(stderr, "0x%x D::FCHF: pExceptionRecord=0x%p (%d), pContext=0x%p (%d)\n", tid, pExceptionRecord, sizeof(EXCEPTION_RECORD),
+ pContext, sizeof(CONTEXT)));
+#if defined(_TARGET_AMD64_)
+ SPEW(fprintf(stderr, "0x%x D::FCHF: code=0x%08x, addr=0x%p, Rip=0x%p, Rsp=0x%p, EFlags=0x%08x\n",
+ tid, pExceptionRecord->ExceptionCode, pExceptionRecord->ExceptionAddress, pContext->Rip, pContext->Rsp,
+ pContext->EFlags));
+#elif defined(_TARGET_X86_)
+ SPEW(fprintf(stderr, "0x%x D::FCHF: code=0x%08x, addr=0x%08x, Eip=0x%08x, Esp=0x%08x, EFlags=0x%08x\n",
+ tid, pExceptionRecord->ExceptionCode, pExceptionRecord->ExceptionAddress, pContext->Eip, pContext->Esp,
+ pContext->EFlags));
+
+#endif
+
+
+ // This memory is used as IPC during the hijack. We will place a pointer to this in
+ // either the EEThreadPtr or the EEDebuggerWord and then the RS can write info into
+ // the memory
+ DebuggerIPCFirstChanceData fcd;
+ // accessing through the volatile pointer to fend off some potential compiler optimizations.
+ // If the debugger changes that data from OOP we need to see those updates
+ volatile DebuggerIPCFirstChanceData* pFcd = &fcd;
+
+
+ {
+ // Hijack filters are always in the can't stop range.
+ // The RS knows this b/c it knows which threads it hijacked.
+ // Bump up the CS counter so that any further calls in the LS can see this too.
+ // (This makes places where we assert that we're in a CS region happy).
+ CantStopHolder hCantStop;
+
+ // Get the current runtime thread. This is only an optimized TLS access.
+ Thread *pEEThread = g_pEEInterface->GetThread();
+
+ // Is that really a ptr to a Thread? If the low bit is set or it its NULL then we don't have an EE Thread. If we
+ // have a EE Thread, then we know the original handler now. If not, we have to wait for the Right Side to fixup our
+ // handler chain once we've notified it that the exception does not belong to the runtime. Note: if we don't have an
+ // EE thread, then the exception never belongs to the Runtime.
+ bool hasEEThread = false;
+ if ((pEEThread != NULL) && !(((UINT_PTR)pEEThread) & 0x01))
+ {
+ SPEW(fprintf(stderr, "0x%x D::FCHF: Has EE thread.\n", tid));
+ hasEEThread = true;
+ }
+
+ // Hook up the memory so RS can get to it
+ fcd.pLeftSideContext.Set((DT_CONTEXT*)pContext);
+ fcd.action = HIJACK_ACTION_EXIT_UNHANDLED;
+ fcd.debugCounter = 0;
+ if(hasEEThread)
+ {
+ SPEW(fprintf(stderr, "0x%x D::FCHF: Set Debugger word to 0x%p.\n", tid, pFcd));
+ g_pEEInterface->SetThreadDebuggerWord(pEEThread, (VOID*) pFcd);
+ }
+ else
+ {
+ // this shouldn't be re-entrant
+ _ASSERTE(pEEThread == NULL);
+
+ SPEW(fprintf(stderr, "0x%x D::FCHF: EEThreadPtr word to 0x%p.\n", tid, (BYTE*)pFcd + 1));
+ g_pEEInterface->SetEEThreadPtr((void*) ((BYTE*)pFcd + 1));
+ }
+
+ // Signal the RS to tell us what to do
+ SPEW(fprintf(stderr, "0x%x D::FCHF: Signaling hijack started.\n", tid));
+ SignalHijackStarted();
+ SPEW(fprintf(stderr, "0x%x D::FCHF: Signaling hijack started complete. DebugCounter=0x%x\n", tid, pFcd->debugCounter));
+
+ if(pFcd->action == HIJACK_ACTION_WAIT)
+ {
+ // This exception does NOT belong to the CLR.
+ // If we belong to the CLR, then we either:
+ // - were a M2U transition, in which case we should be in a different Hijack
+ // - were a CLR exception in CLR code, in which case we should have continued and let the inproc handlers get it.
+ SPEW(fprintf(stderr, "0x%x D::FCHF: exception does not belong to the Runtime, hasEEThread=%d, pContext=0x%p\n",
+ tid, hasEEThread, pContext));
+
+ if(hasEEThread)
+ {
+ _ASSERTE(!pEEThread->GetInteropDebuggingHijacked()); // hijack is not re-entrant.
+ pEEThread->SetInteropDebuggingHijacked(TRUE);
+
+ // Setting the FilterContext must be done in cooperative mode (since it's like pushing a Frame onto the Frame chain).
+ // Thus we have a violation. We don't really need the filter context specifically here, we're just using
+ // it for legacy purposes as a way to stash the context of the original exception (that this thread was hijacked from).
+ // @todo - use another way to store the context indepedent of the Filter context.
+ CONTRACT_VIOLATION(ModeViolation);
+ _ASSERTE(g_pEEInterface->GetThreadFilterContext(pEEThread) == NULL);
+ g_pEEInterface->SetThreadFilterContext(pEEThread, pContext);
+ }
+
+ // Wait for the continue. We may / may not have an EE Thread for this, (and we're definitely
+ // not doing fiber-mode debugging), so just use a raw win32 API, and not some fancy fiber-safe call.
+ SPEW(fprintf(stderr, "0x%x D::FCHF: waiting for continue.\n", tid));
+
+ DWORD ret = WaitForSingleObject(g_pDebugger->m_pRCThread->GetDCB()->m_leftSideUnmanagedWaitEvent,
+ INFINITE);
+
+ SPEW(fprintf(stderr, "0x%x D::FCHF: waiting for continue complete.\n", tid));
+ if (ret != WAIT_OBJECT_0)
+ {
+ SPEW(fprintf(stderr, "0x%x D::FCHF: wait failed!\n", tid));
+ }
+
+ if(hasEEThread)
+ {
+ _ASSERTE(pEEThread->GetInteropDebuggingHijacked());
+ pEEThread->SetInteropDebuggingHijacked(FALSE);
+ _ASSERTE(!ISREDIRECTEDTHREAD(pEEThread));
+
+ // See violation above.
+ CONTRACT_VIOLATION(ModeViolation);
+ g_pEEInterface->SetThreadFilterContext(pEEThread, NULL);
+ _ASSERTE(g_pEEInterface->GetThreadFilterContext(pEEThread) == NULL);
+ }
+ }
+
+ SPEW(fprintf(stderr, "0x%x D::FCHF: signaling HijackComplete.\n", tid));
+ SignalHijackComplete();
+ SPEW(fprintf(stderr, "0x%x D::FCHF: done signaling HijackComplete. DebugCounter=0x%x\n", tid, pFcd->debugCounter));
+
+ // we should know what we are about to do now
+ _ASSERTE(pFcd->action != HIJACK_ACTION_WAIT);
+
+ // cleanup from above
+ if (hasEEThread)
+ {
+ SPEW(fprintf(stderr, "0x%x D::FCHF: set debugger word = NULL.\n", tid));
+ g_pEEInterface->SetThreadDebuggerWord(pEEThread, (VOID*) NULL);
+ }
+ else
+ {
+ SPEW(fprintf(stderr, "0x%x D::FCHF: set EEThreadPtr = NULL.\n", tid));
+ g_pEEInterface->SetEEThreadPtr(NULL);
+ }
+
+ } // end can't stop region
+
+ if(pFcd->action == HIJACK_ACTION_EXIT_HANDLED)
+ {
+ SPEW(fprintf(stderr, "0x%x D::FCHF: exiting with CONTINUE_EXECUTION\n", tid));
+ return EXCEPTION_CONTINUE_EXECUTION;
+ }
+ else
+ {
+ SPEW(fprintf(stderr, "0x%x D::FCHF: exiting with CONTINUE_SEARCH\n", tid));
+ _ASSERTE(pFcd->action == HIJACK_ACTION_EXIT_UNHANDLED);
+ return EXCEPTION_CONTINUE_SEARCH;
+ }
+}
+
+#if defined(_TARGET_X86_) || defined(_TARGET_AMD64_)
+void GenericHijackFuncHelper()
+{
+#if DOSPEW
+ DWORD tid = GetCurrentThreadId();
+#endif
+ // Hijack filters are always in the can't stop range.
+ // The RS knows this b/c it knows which threads it hijacked.
+ // Bump up the CS counter so that any further calls in the LS can see this too.
+ // (This makes places where we assert that we're in a CS region happy).
+ CantStopHolder hCantStop;
+
+ SPEW(fprintf(stderr, "0x%x D::GHF: in generic hijack.\n", tid));
+
+ // There is no need to setup any context pointer or interact with the Right Side in anyway. We simply wait for
+ // the continue event to be set.
+ SPEW(fprintf(stderr, "0x%x D::GHF: waiting for continue.\n", tid));
+
+ // If this thread has an EE thread and that EE thread has preemptive gc disabled, then mark that there is a
+ // thread at an unsafe place and enable pgc. This will allow us to sync even with this thread hijacked.
+ bool disabled = false;
+
+ Thread *pEEThread = g_pEEInterface->GetThread();
+
+ if ((pEEThread != NULL) && !(((UINT_PTR)pEEThread) & 0x01))
+ {
+ disabled = g_pEEInterface->IsPreemptiveGCDisabled();
+ _ASSERTE(!disabled);
+
+ _ASSERTE(!pEEThread->GetInteropDebuggingHijacked());
+ pEEThread->SetInteropDebuggingHijacked(TRUE);
+ }
+
+ DWORD ret = WaitForSingleObject(g_pRCThread->GetDCB()->m_leftSideUnmanagedWaitEvent,
+ INFINITE);
+
+ if (ret != WAIT_OBJECT_0)
+ {
+ SPEW(fprintf(stderr, "0x%x D::GHF: wait failed!\n", tid));
+ }
+
+ // Get the continue type. Non-zero means that the exception was not cleared by the Right Side and therefore has
+ // not been handled. Zero means that the exception has been cleared. (Presumably, the debugger altered the
+ // thread's context before clearing the exception, so continuing will give a different result.)
+ DWORD continueType = 0;
+
+ pEEThread = g_pEEInterface->GetThread();
+
+ if (((UINT_PTR)pEEThread) & 0x01)
+ {
+ // There is no EE Thread for this thread, so we null out the TLS word so we don't confuse the Runtime.
+ continueType = 1;
+ g_pEEInterface->SetEEThreadPtr(NULL);
+ pEEThread = NULL;
+ }
+ else if (pEEThread)
+ {
+ // We've got a Thread ptr, so get the continue type out of the thread's debugger word.
+ continueType = (DWORD) g_pEEInterface->GetThreadDebuggerWord(pEEThread);
+
+ _ASSERTE(pEEThread->GetInteropDebuggingHijacked());
+ pEEThread->SetInteropDebuggingHijacked(FALSE);
+ }
+
+ SPEW(fprintf(stderr, "0x%x D::GHF: continued with %d.\n", tid, continueType));
+
+ if (continueType)
+ {
+ SPEW(fprintf(stderr, "0x%x D::GHF: calling ExitProcess\n", tid));
+
+ // Continuing from a second chance exception without clearing the exception causes the process to
+ // exit. Note: the continue type will only be non-zero if this hijack was setup for a second chance
+ // exception. If the hijack was setup for another type of debug event, then we'll never get here.
+ //
+ // We explicitly terminate the process directly instead of going through any escalation policy because:
+ // 1) that's what a native-only debugger would do. Interop and Native-only should be the same.
+ // 2) there's no CLR escalation policy anyways for *native* unhandled exceptions.
+ // 3) The escalation policy may do lots of extra confusing work (like fire MDAs) that can only cause
+ // us grief.
+ TerminateProcess(GetCurrentProcess(), 0);
+ }
+
+ SPEW(fprintf(stderr, "0x%x D::GHF: signaling continue...\n", tid));
+}
+#endif
+
+
+//
+// This is the function that a thread is hijacked to by the Right Side during a variety of debug events. This function
+// must be naked.
+//
+#if defined(_TARGET_X86_)
+__declspec(naked)
+#endif // defined (_x86_)
+void Debugger::GenericHijackFunc(void)
+{
+#if defined(_TARGET_X86_) || defined(_TARGET_AMD64_)
+
+#if defined(_TARGET_X86_)
+ _asm
+ {
+ push ebp
+ mov ebp,esp
+ sub esp,__LOCAL_SIZE
+ }
+#endif
+ // We can't have C++ classes w/ dtors in a declspec naked, so just have call into a helper.
+ GenericHijackFuncHelper();
+
+#if defined(_TARGET_X86_)
+ _asm
+ {
+ mov esp,ebp
+ pop ebp
+ }
+#endif
+
+ // This signals the Right Side that this thread is ready to have its context restored.
+ ExceptionNotForRuntime();
+
+#else
+ _ASSERTE(!"@todo - port GenericHijackFunc");
+#endif // defined (_x86_)
+
+ _ASSERTE(!"Should never get here (Debugger::GenericHijackFunc)");
+}
+
+
+
+
+//#ifdef _TARGET_X86_
+//
+// This is the function that is called when we determine that a first chance exception hijack has
+// begun and memory is prepared for the RS to tell the LS what to do
+//
+void Debugger::SignalHijackStarted(void)
+{
+ WRAPPER_NO_CONTRACT;
+
+#if defined(_TARGET_X86_) || defined(_TARGET_AMD64_)
+ SignalHijackStartedFlare();
+#else
+ _ASSERTE(!"@todo - port the flares to the platform your running on.");
+#endif
+}
+
+//
+// This is the function that is called when we determine that a first chance exception really belongs to the Runtime,
+// and that that exception is due to a managed->unmanaged transition. This notifies the Right Side of this and the Right
+// Side fixes up the thread's execution state from there, making sure to remember that it needs to continue to hide the
+// hijack state of the thread.
+//
+void Debugger::ExceptionForRuntimeHandoffStart(void)
+{
+ WRAPPER_NO_CONTRACT;
+
+#if defined(_TARGET_X86_) || defined(_TARGET_AMD64_)
+ ExceptionForRuntimeHandoffStartFlare();
+#else
+ _ASSERTE(!"@todo - port the flares to the platform your running on.");
+#endif
+
+}
+
+//
+// This is the function that is called when the original handler returns after we've determined that an exception was
+// due to a managed->unmanaged transition. This notifies the Right Side of this and the Right Side fixes up the thread's
+// execution state from there, making sure to turn off its flag indicating that the thread's hijack state should still
+// be hidden.
+//
+void Debugger::ExceptionForRuntimeHandoffComplete(void)
+{
+ WRAPPER_NO_CONTRACT;
+
+#if defined(_TARGET_X86_) || defined(_TARGET_AMD64_)
+ ExceptionForRuntimeHandoffCompleteFlare();
+#else
+ _ASSERTE(!"@todo - port the flares to the platform your running on.");
+#endif
+
+}
+
+//
+// This signals the RS that a hijack function is ready to return. This will cause the RS to restore
+// the thread context
+//
+void Debugger::SignalHijackComplete(void)
+{
+ WRAPPER_NO_CONTRACT;
+
+#if defined(_TARGET_X86_) || defined(_TARGET_AMD64_)
+ SignalHijackCompleteFlare();
+#else
+ _ASSERTE(!"@todo - port the flares to the platform your running on.");
+#endif
+
+}
+
+//
+// This is the function that is called when we determine that a first chance exception does not belong to the
+// Runtime. This notifies the Right Side of this and the Right Side fixes up the thread's execution state from there.
+//
+void Debugger::ExceptionNotForRuntime(void)
+{
+ WRAPPER_NO_CONTRACT;
+
+#if defined(_TARGET_X86_) || defined(_TARGET_AMD64_)
+ ExceptionNotForRuntimeFlare();
+#else
+ _ASSERTE(!"@todo - port the flares to the platform your running on.");
+#endif
+}
+
+//
+// This is the function that is called when we want to send a sync complete event to the Right Side when it is the Win32
+// debugger of this process. This notifies the Right Side of this and the Right Side fixes up the thread's execution
+// state from there.
+//
+void Debugger::NotifyRightSideOfSyncComplete(void)
+{
+ WRAPPER_NO_CONTRACT;
+ STRESS_LOG0(LF_CORDB, LL_INFO100000, "D::NRSOSC: Sending flare...\n");
+#if defined(_TARGET_X86_) || defined(_TARGET_AMD64_)
+ NotifyRightSideOfSyncCompleteFlare();
+#else
+ _ASSERTE(!"@todo - port the flares to the platform your running on.");
+#endif
+ STRESS_LOG0(LF_CORDB, LL_INFO100000, "D::NRSOSC: Flare sent\n");
+}
+
+#endif // FEATURE_INTEROP_DEBUGGING
+
+/******************************************************************************
+ *
+ ******************************************************************************/
+bool Debugger::GetILOffsetFromNative (MethodDesc *pFunc, const BYTE *pbAddr,
+ DWORD nativeOffset, DWORD *ilOffset)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS_FROM_GETJITINFO;
+ }
+ CONTRACTL_END;
+
+ if (!HasLazyData())
+ {
+ DebuggerLockHolder dbgLockHolder(this);
+ // This is an entry path into the debugger, so make sure we're inited.
+ LazyInit();
+ }
+
+ // Sometimes we'll get called w/ an instantiating stub MD.
+ if (pFunc->IsWrapperStub())
+ {
+ pFunc = pFunc->GetWrappedMethodDesc();
+ }
+
+ DebuggerJitInfo *jitInfo =
+ GetJitInfo(pFunc, (const BYTE *)pbAddr);
+
+ if (jitInfo != NULL)
+ {
+ CorDebugMappingResult map;
+ DWORD whichIDontCare;
+
+ *ilOffset = jitInfo->MapNativeOffsetToIL(
+ nativeOffset,
+ &map,
+ &whichIDontCare);
+
+ return true;
+ }
+
+ return false;
+}
+
+/******************************************************************************
+ *
+ ******************************************************************************/
+DWORD Debugger::GetHelperThreadID(void )
+{
+ LIMITED_METHOD_CONTRACT;
+
+ return m_pRCThread->GetDCB()
+ ->m_temporaryHelperThreadId;
+}
+
+
+// HRESULT Debugger::InsertToMethodInfoList(): Make sure
+// that there's only one head of the the list of DebuggerMethodInfos
+// for the (implicitly) given MethodDef/Module pair.
+HRESULT
+Debugger::InsertToMethodInfoList( DebuggerMethodInfo *dmi )
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB,LL_INFO10000,"D:IAHOL DMI: dmi:0x%08x\n", dmi));
+
+ HRESULT hr = S_OK;
+
+ _ASSERTE(dmi != NULL);
+
+ _ASSERTE(HasDebuggerDataLock());
+
+ // CHECK_DJI_TABLE_DEBUGGER;
+
+ hr = CheckInitMethodInfoTable();
+
+ if (FAILED(hr)) {
+ return (hr);
+ }
+
+ DebuggerMethodInfo *dmiPrev = m_pMethodInfos->GetMethodInfo(dmi->m_module, dmi->m_token);
+
+ _ASSERTE((dmiPrev == NULL) || ((dmi->m_token == dmiPrev->m_token) && (dmi->m_module == dmiPrev->m_module)));
+
+ LOG((LF_CORDB,LL_INFO10000,"D:IAHOL: current head of dmi list:0x%08x\n",dmiPrev));
+
+ if (dmiPrev != NULL)
+ {
+ dmi->m_prevMethodInfo = dmiPrev;
+ dmiPrev->m_nextMethodInfo = dmi;
+
+ _ASSERTE(dmi->m_module != NULL);
+ hr = m_pMethodInfos->OverwriteMethodInfo(dmi->m_module,
+ dmi->m_token,
+ dmi,
+ FALSE);
+
+ LOG((LF_CORDB,LL_INFO10000,"D:IAHOL: DMI version 0x%04x for token 0x%08x\n",
+ dmi->GetCurrentEnCVersion(),dmi->m_token));
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_EVERYTHING, "AddMethodInfo being called in D:IAHOL\n"));
+ hr = m_pMethodInfos->AddMethodInfo(dmi->m_module,
+ dmi->m_token,
+ dmi);
+ }
+#ifdef _DEBUG
+ dmiPrev = m_pMethodInfos->GetMethodInfo(dmi->m_module, dmi->m_token);
+ LOG((LF_CORDB,LL_INFO10000,"D:IAHOL: new head of dmi list:0x%08x\n",
+ dmiPrev));
+#endif //_DEBUG
+
+ // DebuggerDataLockHolder out of scope - release implied
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// Helper to get an SString through the IPC buffer.
+// We do this by putting the SString data into a LS_RS_buffer object,
+// and then the RS reads it out as soon as it's queued.
+// It's very very important that the SString's buffer is around while we send the event.
+// So we pass the SString by reference in case there's an implicit conversion (because
+// we don't want to do the conversion on a temporary object and then lose that object).
+//-----------------------------------------------------------------------------
+void SetLSBufferFromSString(Ls_Rs_StringBuffer * pBuffer, SString & str)
+{
+ // Copy string contents (+1 for null terminator) into a LS_RS_Buffer.
+ // Then the RS can pull it out as a null-terminated string.
+ pBuffer->SetLsData(
+ (BYTE*) str.GetUnicode(),
+ (str.GetCount() +1)* sizeof(WCHAR)
+ );
+}
+
+//*************************************************************
+// structure that we to marshal MDA Notification event data.
+//*************************************************************
+struct SendMDANotificationParams
+{
+ Thread * m_pThread; // may be NULL. Lets us send on behalf of other threads.
+
+ // Pass SStrings by ptr in case to guarantee that they're shared (in case we internally modify their storage).
+ SString * m_szName;
+ SString * m_szDescription;
+ SString * m_szXML;
+ CorDebugMDAFlags m_flags;
+
+ SendMDANotificationParams(
+ Thread * pThread, // may be NULL. Lets us send on behalf of other threads.
+ SString * szName,
+ SString * szDescription,
+ SString * szXML,
+ CorDebugMDAFlags flags
+ ) :
+ m_pThread(pThread),
+ m_szName(szName),
+ m_szDescription(szDescription),
+ m_szXML(szXML),
+ m_flags(flags)
+ {
+ LIMITED_METHOD_CONTRACT;
+ }
+
+};
+
+//-----------------------------------------------------------------------------
+// Actually send the MDA event. (Could be on any thread)
+// Parameters:
+// params - data to initialize the IPC event.
+//-----------------------------------------------------------------------------
+void Debugger::SendRawMDANotification(
+ SendMDANotificationParams * params
+)
+{
+ // Send the unload assembly event to the Right Side.
+ DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer();
+
+ Thread * pThread = params->m_pThread;
+ AppDomain *pAppDomain = (pThread != NULL) ? pThread->GetDomain() : NULL;
+
+ InitIPCEvent(ipce,
+ DB_IPCE_MDA_NOTIFICATION,
+ pThread,
+ pAppDomain);
+
+ SetLSBufferFromSString(&ipce->MDANotification.szName, *(params->m_szName));
+ SetLSBufferFromSString(&ipce->MDANotification.szDescription, *(params->m_szDescription));
+ SetLSBufferFromSString(&ipce->MDANotification.szXml, *(params->m_szXML));
+ ipce->MDANotification.dwOSThreadId = GetCurrentThreadId();
+ ipce->MDANotification.flags = params->m_flags;
+
+ m_pRCThread->SendIPCEvent();
+}
+
+//-----------------------------------------------------------------------------
+// Send an MDA notification. This ultimately translates to an ICorDebugMDA object on the Right-Side.
+// Called by EE to send a MDA debug event. This will block on the debug event
+// until the RS continues us.
+// Debugger may or may not be attached. If bAttached, then this
+// will trigger a jitattach as well.
+// See MDA documentation for what szName, szDescription + szXML should look like.
+// The debugger just passes them through.
+//
+// Parameters:
+// pThread - thread for debug event. May be null.
+// szName - short name of MDA.
+// szDescription - full description of MDA.
+// szXML - xml string for MDA.
+// bAttach - do a JIT-attach
+//-----------------------------------------------------------------------------
+void Debugger::SendMDANotification(
+ Thread * pThread, // may be NULL. Lets us send on behalf of other threads.
+ SString * szName,
+ SString * szDescription,
+ SString * szXML,
+ CorDebugMDAFlags flags,
+ BOOL bAttach
+)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ PREFIX_ASSUME(szName != NULL);
+ PREFIX_ASSUME(szDescription != NULL);
+ PREFIX_ASSUME(szXML != NULL);
+
+ // Note: we normally don't send events like this when there is an unrecoverable error. However,
+ // if a host attempts to setup fiber mode on a thread, then we'll set an unrecoverable error
+ // and use an MDA to 1) tell the user and 2) get the Right Side to notice the unrecoverable error.
+ // Therefore, we'll go ahead and send a MDA event if the unrecoverable error is
+ // CORDBG_E_CANNOT_DEBUG_FIBER_PROCESS.
+ DebuggerIPCControlBlock *pDCB = m_pRCThread->GetDCB();
+
+
+ // If the MDA is ocuring very early in startup before the DCB is setup, then bail.
+ if (pDCB == NULL)
+ {
+ return;
+ }
+
+ if (CORDBUnrecoverableError(this) && (pDCB->m_errorHR != CORDBG_E_CANNOT_DEBUG_FIBER_PROCESS))
+ {
+ return;
+ }
+
+ // Validate flags. Make sure that folks don't start passing flags that we don't handle.
+ // If pThread != current thread, caller should either pass in MDA_FLAG_SLIP or guarantee
+ // that pThread is not slipping.
+ _ASSERTE((flags & ~(MDA_FLAG_SLIP)) == 0);
+
+ // Helper thread should not be triggering MDAs. The helper thread is executing code in a very constrained
+ // and controlled region and shouldn't be able to do anything dangerous.
+ // If we revise this in the future, we should probably just post the event to the RS w/ use the MDA_FLAG_SLIP flag,
+ // and then not bother suspending the runtime. The RS will get it on its next event.
+ // The jit-attach logic below assumes we're not on the helper. (If we are on the helper, then a debugger should already
+ // be attached)
+ if (ThisIsHelperThreadWorker())
+ {
+ CONSISTENCY_CHECK_MSGF(false, ("MDA '%s' fired on *helper* thread.\r\nDesc:%s",
+ szName->GetUnicode(), szDescription->GetUnicode()
+ ));
+
+ // If for some reason we're wrong about the assert above, we'll just ignore the MDA (rather than potentially deadlock)
+ return;
+ }
+
+ // Public entry point into the debugger. May cause a jit-attach, so we may need to be lazily-init.
+ if (!HasLazyData())
+ {
+ DebuggerLockHolder dbgLockHolder(this);
+ // This is an entry path into the debugger, so make sure we're inited.
+ LazyInit();
+ }
+
+
+ // Cases:
+ // 1) Debugger already attached, send event normally (ignore severity)
+ // 2) No debugger attached, Non-severe probe - ignore.
+ // 3) No debugger attached, Severe-probe - do a jit-attach.
+ bool fTryJitAttach = bAttach == TRUE;
+
+ // Check case #2 - no debugger, and no jit-attach. Early opt out.
+ if (!CORDebuggerAttached() && !fTryJitAttach)
+ {
+ return;
+ }
+
+ if (pThread == NULL)
+ {
+ // If there's no thread object, then we're not blocking after the event,
+ // and thus this probe may slip.
+ flags = (CorDebugMDAFlags) (flags | MDA_FLAG_SLIP);
+ }
+
+ {
+ GCX_PREEMP_EEINTERFACE_TOGGLE_IFTHREAD();
+
+ // For "Severe" probes, we'll do a jit attach dialog
+ if (fTryJitAttach)
+ {
+ // May return:
+ // - S_OK if we do a jit-attach,
+ // - S_FALSE if a debugger is already attached.
+ // - Error in other cases..
+
+ JitAttach(pThread, NULL, TRUE, FALSE);
+ }
+
+ // Debugger may be attached now...
+ if (CORDebuggerAttached())
+ {
+ SendMDANotificationParams params(pThread, szName, szDescription, szXML, flags);
+
+ // Non-attach case. Send like normal event.
+ // This includes if someone launch the debugger during the meantime.
+ // just send the event
+ SENDIPCEVENT_BEGIN(this, pThread);
+
+ // Send Log message event to the Right Side
+ SendRawMDANotification(&params);
+
+ // Stop all Runtime threads
+ // Even if we don't have a managed thead object, this will catch us at the next good spot.
+ TrapAllRuntimeThreads();
+
+ // Let other Runtime threads handle their events.
+ SENDIPCEVENT_END;
+ }
+ } // end of GCX_PREEMP_EEINTERFACE_TOGGLE()
+}
+
+//*************************************************************
+// This method sends a log message over to the right side for the debugger to log it.
+//
+// The CLR doesn't assign any semantics to the level or cateogory values.
+// The BCL has a level convention (LoggingLevels enum), but this isn't exposed publicly,
+// so we shouldn't base our behavior on it in any way.
+//*************************************************************
+void Debugger::SendLogMessage(int iLevel,
+ SString * pSwitchName,
+ SString * pMessage)
+{
+ CONTRACTL
+ {
+ GC_TRIGGERS;
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_INFO10000, "D::SLM: Sending log message.\n"));
+
+ // Send the message only if the debugger is attached to this appdomain.
+ // Note the the debugger may detach at any time, so we'll have to check
+ // this again after we get the lock.
+ AppDomain *pAppDomain = g_pEEInterface->GetThread()->GetDomain();
+
+ if (!CORDebuggerAttached())
+ {
+ return;
+ }
+
+ Thread *pThread = g_pEEInterface->GetThread();
+ SENDIPCEVENT_BEGIN(this, pThread);
+
+ // Send Log message event to the Right Side
+ SendRawLogMessage(
+ pThread,
+ pAppDomain,
+ iLevel,
+ pSwitchName,
+ pMessage);
+
+ // Stop all Runtime threads
+ TrapAllRuntimeThreads();
+
+ // Let other Runtime threads handle their events.
+ SENDIPCEVENT_END;
+}
+
+
+//*************************************************************
+//
+// Helper function to just send LogMessage event. Can be called on either
+// helper thread or managed thread.
+//
+//*************************************************************
+void Debugger::SendRawLogMessage(
+ Thread *pThread,
+ AppDomain *pAppDomain,
+ int iLevel,
+ SString * pCategory,
+ SString * pMessage
+)
+{
+ DebuggerIPCEvent* ipce;
+
+
+ // We should have hold debugger lock
+ // This can happen on either native helper thread or managed thread
+ _ASSERTE(ThreadHoldsLock());
+
+ // It's possible that the debugger dettached while we were waiting
+ // for our lock. Check again and abort the event if it did.
+ if (!CORDebuggerAttached())
+ {
+ return;
+ }
+
+ ipce = m_pRCThread->GetIPCEventSendBuffer();
+
+ // Send a LogMessage event to the Right Side
+ InitIPCEvent(ipce,
+ DB_IPCE_FIRST_LOG_MESSAGE,
+ pThread,
+ pAppDomain);
+
+ ipce->FirstLogMessage.iLevel = iLevel;
+ ipce->FirstLogMessage.szCategory.SetString(pCategory->GetUnicode());
+ SetLSBufferFromSString(&ipce->FirstLogMessage.szContent, *pMessage);
+
+ m_pRCThread->SendIPCEvent();
+}
+
+
+// This function sends a message to the right side informing it about
+// the creation/modification of a LogSwitch
+void Debugger::SendLogSwitchSetting(int iLevel,
+ int iReason,
+ __in_z LPCWSTR pLogSwitchName,
+ __in_z LPCWSTR pParentSwitchName)
+{
+ CONTRACTL
+ {
+ MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT;
+ MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_INFO1000, "D::SLSS: Sending log switch message switch=%S parent=%S.\n",
+ pLogSwitchName, pParentSwitchName));
+
+ // Send the message only if the debugger is attached to this appdomain.
+ if (!CORDebuggerAttached())
+ {
+ return;
+ }
+
+ Thread *pThread = g_pEEInterface->GetThread();
+ SENDIPCEVENT_BEGIN(this, pThread);
+
+ if (CORDebuggerAttached())
+ {
+ DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer();
+ InitIPCEvent(ipce,
+ DB_IPCE_LOGSWITCH_SET_MESSAGE,
+ pThread,
+ pThread->GetDomain());
+
+ ipce->LogSwitchSettingMessage.iLevel = iLevel;
+ ipce->LogSwitchSettingMessage.iReason = iReason;
+
+
+ ipce->LogSwitchSettingMessage.szSwitchName.SetString(pLogSwitchName);
+
+ if (pParentSwitchName == NULL)
+ {
+ pParentSwitchName = W("");
+ }
+
+ ipce->LogSwitchSettingMessage.szParentSwitchName.SetString(pParentSwitchName);
+
+ m_pRCThread->SendIPCEvent();
+
+ // Stop all Runtime threads
+ TrapAllRuntimeThreads();
+ }
+ else
+ {
+ LOG((LF_CORDB,LL_INFO1000, "D::SLSS: Skipping SendIPCEvent because RS detached."));
+ }
+
+ SENDIPCEVENT_END;
+}
+
+// send a custom debugger notification to the RS
+// Arguments:
+// input: pThread - thread on which the notification occurred
+// pDomain - domain file for the domain in which the notification occurred
+// classToken - metadata token for the type of the notification object
+void Debugger::SendCustomDebuggerNotification(Thread * pThread,
+ DomainFile * pDomain,
+ mdTypeDef classToken)
+{
+ CONTRACTL
+ {
+ GC_TRIGGERS;
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_INFO10000, "D::SLM: Sending log message.\n"));
+
+ // Send the message only if the debugger is attached to this appdomain.
+ // Note the the debugger may detach at any time, so we'll have to check
+ // this again after we get the lock.
+ if (!CORDebuggerAttached())
+ {
+ return;
+ }
+
+ Thread *curThread = g_pEEInterface->GetThread();
+ SENDIPCEVENT_BEGIN(this, curThread);
+
+ if (CORDebuggerAttached())
+ {
+ DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer();
+ InitIPCEvent(ipce,
+ DB_IPCE_CUSTOM_NOTIFICATION,
+ curThread,
+ curThread->GetDomain());
+
+ VMPTR_DomainFile vmDomainFile = VMPTR_DomainFile::MakePtr(pDomain);
+
+ ipce->CustomNotification.classToken = classToken;
+ ipce->CustomNotification.vmDomainFile = vmDomainFile;
+
+
+ m_pRCThread->SendIPCEvent();
+
+ // Stop all Runtime threads
+ TrapAllRuntimeThreads();
+ }
+ else
+ {
+ LOG((LF_CORDB,LL_INFO1000, "D::SCDN: Skipping SendIPCEvent because RS detached."));
+ }
+
+ SENDIPCEVENT_END;
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// Add the AppDomain to the list stored in the IPC block. It adds the id and
+// the name.
+//
+// Arguments:
+// pAppDomain - The runtime app domain object to add.
+//
+// Return Value:
+// S_OK on success, else detailed error code.
+//
+HRESULT Debugger::AddAppDomainToIPC(AppDomain *pAppDomain)
+{
+ CONTRACTL
+ {
+ MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT;
+ GC_TRIGGERS;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr = S_OK;
+ LPCWSTR szName = NULL;
+
+ LOG((LF_CORDB, LL_INFO100, "D::AADTIPC: Executing AADTIPC for AppDomain 0x%08x (0x%x).\n",
+ pAppDomain,
+ pAppDomain->GetId().m_dwId));
+
+ STRESS_LOG2(LF_CORDB, LL_INFO10000, "D::AADTIPC: AddAppDomainToIPC:%#08x, %#08x\n",
+ pAppDomain, pAppDomain->GetId().m_dwId);
+
+
+
+ _ASSERTE(m_pAppDomainCB->m_iTotalSlots > 0);
+ _ASSERTE(m_pAppDomainCB->m_rgListOfAppDomains != NULL);
+
+ {
+ //
+ // We need to synchronize this routine with the attach logic. The "normal"
+ // attach case uses the HelperThread and TrapAllRuntimeThreads to synchronize
+ // the runtime before sending any of the events (including AppDomainCreates)
+ // to the right-side. Thus, we can synchronize with this case by forcing us
+ // to go co-operative. If we were already co-op, then the helper thread will
+ // wait to start the attach until all co-op threads are paused. If we were
+ // pre-emptive, then going co-op will suspend us until the HelperThread finishes.
+ //
+ // The second case is under the IPC event for ATTACHING, which is where there are
+ // zero app domains, so it is considered an 'early attach' case. To synchronize
+ // with this we have to grab and hold the AppDomainDB lock.
+ //
+
+ GCX_COOP();
+
+ // Lock the list
+ if (!m_pAppDomainCB->Lock())
+ {
+ return E_FAIL;
+ }
+
+ // Get a free entry from the list
+ AppDomainInfo *pAppDomainInfo = m_pAppDomainCB->GetFreeEntry();
+
+ // Function returns NULL if the list is full and a realloc failed.
+ if (!pAppDomainInfo)
+ {
+ hr = E_OUTOFMEMORY;
+ goto LErrExit;
+ }
+
+ // copy the ID
+ pAppDomainInfo->m_id = pAppDomain->GetId().m_dwId;
+
+ // Now set the AppDomainName.
+
+ /*
+ * TODO :
+ *
+ * Make sure that returning NULL here does not result in a catastrophic
+ * failure.
+ *
+ * GetFriendlyNameNoThrow may call SetFriendlyName, which may call
+ * UpdateAppDomainEntryInIPC. There is no recursive death, however, because
+ * the AppDomainInfo object does not contain a pointer to the app domain
+ * yet.
+ */
+ szName = pAppDomain->GetFriendlyNameForDebugger();
+ pAppDomainInfo->SetName(szName);
+
+ // Save on to the appdomain pointer
+ pAppDomainInfo->m_pAppDomain = pAppDomain;
+
+ // bump the used slot count
+ m_pAppDomainCB->m_iNumOfUsedSlots++;
+
+LErrExit:
+ // UnLock the list
+ m_pAppDomainCB->Unlock();
+
+ // Send event to debugger if one is attached.
+ if (CORDebuggerAttached())
+ {
+ SendCreateAppDomainEvent(pAppDomain);
+ }
+ }
+
+ return hr;
+}
+
+
+/******************************************************************************
+ * Remove the AppDomain from the list stored in the IPC block and send an ExitAppDomain
+ * event to the debugger if attached.
+ ******************************************************************************/
+HRESULT Debugger::RemoveAppDomainFromIPC (AppDomain *pAppDomain)
+{
+ CONTRACTL
+ {
+ MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT;
+ MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT;
+ SO_INTOLERANT;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr = E_FAIL;
+
+ LOG((LF_CORDB, LL_INFO100, "D::RADFIPC: Executing RADFIPC for AppDomain 0x%08x (0x%x).\n",
+ pAppDomain,
+ pAppDomain->GetId().m_dwId));
+
+ // if none of the slots are occupied, then simply return.
+ if (m_pAppDomainCB->m_iNumOfUsedSlots == 0)
+ return hr;
+
+ // Lock the list
+ if (!m_pAppDomainCB->Lock())
+ return (E_FAIL);
+
+
+ // Look for the entry
+ AppDomainInfo *pADInfo = m_pAppDomainCB->FindEntry(pAppDomain);
+
+ // Shouldn't be trying to remove an appdomain that was never added
+ if (!pADInfo)
+ {
+ // We'd like to assert this, but there is a small window where we may have
+ // called AppDomain::Init (and so it's fair game to call Stop, and hence come here),
+ // but not yet published the app domain.
+ // _ASSERTE(!"D::RADFIPC: trying to remove an AppDomain that was never added");
+ hr = (E_FAIL);
+ goto ErrExit;
+ }
+
+ // Release the entry
+ m_pAppDomainCB->FreeEntry(pADInfo);
+
+ErrExit:
+ // UnLock the list
+ m_pAppDomainCB->Unlock();
+
+ // send event to debugger if one is attached
+ if (CORDebuggerAttached())
+ {
+ SendExitAppDomainEvent(pAppDomain);
+ }
+
+ return hr;
+}
+
+/******************************************************************************
+ * Update the AppDomain in the list stored in the IPC block.
+ ******************************************************************************/
+HRESULT Debugger::UpdateAppDomainEntryInIPC(AppDomain *pAppDomain)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ if (GetThread()) { GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);}
+ SO_INTOLERANT;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr = S_OK;
+ LPCWSTR szName = NULL;
+
+ LOG((LF_CORDB, LL_INFO100,
+ "D::UADEIIPC: Executing UpdateAppDomainEntryInIPC ad:0x%x.\n",
+ pAppDomain));
+
+ // if none of the slots are occupied, then simply return.
+ if (m_pAppDomainCB->m_iNumOfUsedSlots == 0)
+ return (E_FAIL);
+
+ // Lock the list
+ if (!m_pAppDomainCB->Lock())
+ return (E_FAIL);
+
+ // Look up the info entry
+ AppDomainInfo *pADInfo = m_pAppDomainCB->FindEntry(pAppDomain);
+
+ if (!pADInfo)
+ {
+ hr = E_FAIL;
+ goto ErrExit;
+ }
+
+ // Update the name only if new name is non-null
+ szName = pADInfo->m_pAppDomain->GetFriendlyNameForDebugger();
+ pADInfo->SetName(szName);
+
+ LOG((LF_CORDB, LL_INFO100,
+ "D::UADEIIPC: New name:%ls (AD:0x%x)\n", pADInfo->m_szAppDomainName,
+ pAppDomain));
+
+ErrExit:
+ // UnLock the list
+ m_pAppDomainCB->Unlock();
+
+ return hr;
+}
+
+HRESULT Debugger::CopyModulePdb(Module* pRuntimeModule)
+{
+ CONTRACTL
+ {
+ THROWS;
+ MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT;
+ SO_NOT_MAINLINE;
+
+ PRECONDITION(ThisIsHelperThread());
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ if (!pRuntimeModule->IsVisibleToDebugger())
+ {
+ return S_OK;
+ }
+
+ HRESULT hr = S_OK;
+#ifdef FEATURE_FUSION
+ //
+ // Populate the pdb to fusion cache.
+ //
+ if (pRuntimeModule->IsIStream() == FALSE)
+ {
+ SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE;
+
+ EX_TRY
+ {
+ pRuntimeModule->FusionCopyPDBs(pRuntimeModule->GetPath());
+ }
+ EX_CATCH_HRESULT(hr); // ignore failures
+ }
+#endif // FEATURE_FUSION
+
+ return hr;
+}
+
+/******************************************************************************
+ * When attaching to a process, this is called to enumerate all of the
+ * AppDomains currently in the process and allow modules pdbs to be copied over to the shadow dir maintaining out V2 in-proc behaviour.
+ ******************************************************************************/
+HRESULT Debugger::IterateAppDomainsForPdbs()
+{
+ CONTRACTL
+ {
+ THROWS;
+ MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT;
+ SO_NOT_MAINLINE;
+
+ PRECONDITION(ThisIsHelperThread());
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ STRESS_LOG0(LF_CORDB, LL_INFO100, "Entered function IterateAppDomainsForPdbs()\n");
+ HRESULT hr = S_OK;
+
+ // Lock the list
+ if (!m_pAppDomainCB->Lock())
+ return (E_FAIL);
+
+ // Iterate through the app domains
+ AppDomainInfo *pADInfo = m_pAppDomainCB->FindFirst();
+
+ while (pADInfo)
+ {
+ STRESS_LOG3(LF_CORDB, LL_INFO100, "Iterating over domain %#08x AD:%#08x %ls\n", pADInfo->m_pAppDomain->GetId().m_dwId, pADInfo->m_pAppDomain, pADInfo->m_szAppDomainName);
+
+ AppDomain::AssemblyIterator i;
+ i = pADInfo->m_pAppDomain->IterateAssembliesEx((AssemblyIterationFlags)(kIncludeLoaded | kIncludeLoading | kIncludeExecution));
+ CollectibleAssemblyHolder<DomainAssembly *> pDomainAssembly;
+ while (i.Next(pDomainAssembly.This()))
+ {
+ if (!pDomainAssembly->IsVisibleToDebugger())
+ continue;
+
+ DomainAssembly::ModuleIterator j = pDomainAssembly->IterateModules(kModIterIncludeLoading);
+ while (j.Next())
+ {
+ DomainFile * pDomainFile = j.GetDomainFile();
+ if (!pDomainFile->ShouldNotifyDebugger())
+ continue;
+
+ Module* pRuntimeModule = pDomainFile->GetModule();
+ CopyModulePdb(pRuntimeModule);
+ }
+ if (pDomainAssembly->ShouldNotifyDebugger())
+ {
+ CopyModulePdb(pDomainAssembly->GetModule());
+ }
+ }
+
+ // Get the next appdomain in the list
+ pADInfo = m_pAppDomainCB->FindNext(pADInfo);
+ }
+
+ // Unlock the list
+ m_pAppDomainCB->Unlock();
+
+ STRESS_LOG0(LF_CORDB, LL_INFO100, "Exiting function IterateAppDomainsForPdbs\n");
+
+ return hr;
+}
+
+
+/******************************************************************************
+ *
+ ******************************************************************************/
+HRESULT Debugger::InitAppDomainIPC(void)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_NOTRIGGER;
+ SO_INTOLERANT;
+
+ PRECONDITION(CheckPointer(m_pAppDomainCB));
+ }
+ CONTRACTL_END;
+
+ // Ensure that if we throw here, the Terminate will get called and cleanup all resources.
+ // This will make Init an atomic operation - it either fully inits or fully fails.
+ class EnsureCleanup
+ {
+ Debugger * m_pThis;
+
+ public:
+ EnsureCleanup(Debugger * pThis)
+ {
+ m_pThis = pThis;
+ }
+
+ void SupressCleanup()
+ {
+ m_pThis = NULL;
+ }
+
+ ~EnsureCleanup()
+ {
+ if (m_pThis != NULL)
+ {
+ m_pThis->TerminateAppDomainIPC();
+ }
+ }
+ } hEnsureCleanup(this);
+
+ DWORD dwStrLen = 0;
+ SString szExeName;
+ int i;
+
+ // all fields in the object can be zero initialized.
+ // If we throw, before fully initializing this, then cleanup won't try to free
+ // uninited values.
+ ZeroMemory(m_pAppDomainCB, sizeof(*m_pAppDomainCB));
+
+ // Fix for issue: whidbey 143061
+ // We are creating the mutex as hold, when we unlock, the EndThreadAffinity in
+ // hosting case will be unbalanced.
+ // Ideally, I would like to fix this by creating mutex not-held and call Lock method.
+ // This way, when we clean up the OOM, (as you can tell, we never release the mutex in
+ // some error cases), we can change it to holder class.
+ //
+ Thread::BeginThreadAffinity();
+
+ // Create a mutex to allow the Left and Right Sides to properly
+ // synchronize. The Right Side will spin until m_hMutex is valid,
+ // then it will acquire it before accessing the data.
+ HandleHolder hMutex(WszCreateMutex(NULL, TRUE/*hold*/, NULL));
+ if (hMutex == NULL)
+ {
+ ThrowLastError();
+ }
+ if (!m_pAppDomainCB->m_hMutex.SetLocal(hMutex))
+ {
+ ThrowLastError();
+ }
+ hMutex.SuppressRelease();
+
+ m_pAppDomainCB->m_iSizeInBytes = INITIAL_APP_DOMAIN_INFO_LIST_SIZE *
+ sizeof (AppDomainInfo);
+
+ // Number of slots in AppDomainListElement array
+ m_pAppDomainCB->m_rgListOfAppDomains = new AppDomainInfo[INITIAL_APP_DOMAIN_INFO_LIST_SIZE];
+ _ASSERTE(m_pAppDomainCB->m_rgListOfAppDomains != NULL); // throws on oom
+
+
+ m_pAppDomainCB->m_iTotalSlots = INITIAL_APP_DOMAIN_INFO_LIST_SIZE;
+
+ // Initialize each AppDomainListElement
+ for (i = 0; i < INITIAL_APP_DOMAIN_INFO_LIST_SIZE; i++)
+ {
+ m_pAppDomainCB->m_rgListOfAppDomains[i].FreeEntry();
+ }
+
+ // also initialize the process name
+ dwStrLen = WszGetModuleFileName(NULL,
+ szExeName);
+
+
+ // If we couldn't get the name, then use a nice default.
+ if (dwStrLen == 0)
+ {
+ szExeName.Set(W("<NoProcessName>"));
+ dwStrLen = szExeName.GetCount();
+ }
+
+ // If we got the name, copy it into a buffer. dwStrLen is the
+ // count of characters in the name, not including the null
+ // terminator.
+ m_pAppDomainCB->m_szProcessName = new WCHAR[dwStrLen + 1];
+ _ASSERTE(m_pAppDomainCB->m_szProcessName != NULL); // throws on oom
+
+ wcscpy_s(m_pAppDomainCB->m_szProcessName, dwStrLen + 1, szExeName);
+
+ // Add 1 to the string length so the Right Side will copy out the
+ // null terminator, too.
+ m_pAppDomainCB->m_iProcessNameLengthInBytes = (dwStrLen + 1) * sizeof(WCHAR);
+
+ if (m_pAppDomainCB->m_hMutex != NULL)
+ {
+ m_pAppDomainCB->Unlock();
+ }
+
+ hEnsureCleanup.SupressCleanup();
+ return S_OK;
+}
+
+/******************************************************************************
+ * Unitialize the AppDomain IPC block
+ * Returns:
+ * S_OK -if fully unitialized
+ * E_FAIL - if we can't get ownership of the block, and thus no unitialization
+ * work is done.
+ ******************************************************************************/
+HRESULT Debugger::TerminateAppDomainIPC(void)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ SO_INTOLERANT;
+ }
+ CONTRACTL_END;
+
+ // If we have no AppDomain block, then we can consider it's already terminated.
+ if (m_pAppDomainCB == NULL)
+ return S_OK;
+
+ HRESULT hr = S_OK;
+
+ // Lock the list
+ // If there's no mutex, then we're in a partially created state.
+ // This means InitAppDomainIPC failed halfway through. But we're still thread safe
+ // since other threads can't access us if we don't have the mutex.
+ if ((m_pAppDomainCB->m_hMutex != NULL) && !m_pAppDomainCB->Lock())
+ {
+ // The callers don't check our return value, we may want to know when we can't gracefully clean up
+ LOG((LF_CORDB, LL_INFO10, "Debugger::TerminateAppDomainIPC: Failed to get AppDomain IPC lock, not cleaning up.\n"));
+
+ // If the lock is valid, but we can't get it, then we can't really
+ // uninitialize since someone else is using the block.
+ return (E_FAIL);
+ }
+
+ // The shared IPC segment could still be around after the debugger
+ // object has been destroyed during process shutdown. So, reset
+ // the UsedSlots count to 0 so that any out of process clients
+ // enumeratingthe app domains in this process see 0 AppDomains.
+ m_pAppDomainCB->m_iNumOfUsedSlots = 0;
+ m_pAppDomainCB->m_iTotalSlots = 0;
+
+ // Now delete the memory alloacted for AppDomainInfo array
+ delete [] m_pAppDomainCB->m_rgListOfAppDomains;
+ m_pAppDomainCB->m_rgListOfAppDomains = NULL;
+
+ delete [] m_pAppDomainCB->m_szProcessName;
+ m_pAppDomainCB->m_szProcessName = NULL;
+ m_pAppDomainCB->m_iProcessNameLengthInBytes = 0;
+
+ // Set the mutex handle to NULL.
+ // If the Right Side acquires the mutex, it will verify
+ // that the handle is still not NULL. If it is, then it knows it
+ // really lost.
+ RemoteHANDLE m = m_pAppDomainCB->m_hMutex;
+ m_pAppDomainCB->m_hMutex.m_hLocal = NULL;
+
+ // And bring us back to a fully unintialized state.
+ ZeroMemory(m_pAppDomainCB, sizeof(*m_pAppDomainCB));
+
+ // We're done. release and close the mutex. Note that this must be done
+ // after we clear it out above to ensure there is no race condition.
+ if( m != NULL )
+ {
+ VERIFY(ReleaseMutex(m));
+ m.Close();
+ }
+
+ return hr;
+}
+
+
+#ifndef DACCESS_COMPILE
+
+//
+// FuncEvalSetup sets up a function evaluation for the given method on the given thread.
+//
+HRESULT Debugger::FuncEvalSetup(DebuggerIPCE_FuncEvalInfo *pEvalInfo,
+ BYTE **argDataArea,
+ DebuggerEval **debuggerEvalKey)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ SO_NOT_MAINLINE;
+ }
+ CONTRACTL_END;
+
+ Thread *pThread = pEvalInfo->vmThreadToken.GetRawPtr();
+
+
+ //
+ // If TS_AbortRequested (which may have been set by a pending FuncEvalAbort),
+ // we will not be able to do a new func-eval
+ //
+ // <TODO>@TODO: Remember the current value of m_State, reset m_State as appropriate,
+ // do the new func-eval, and then set m_State to the original value</TODO>
+ if (pThread->m_State & Thread::TS_AbortRequested)
+ return CORDBG_E_FUNC_EVAL_BAD_START_POINT;
+
+ if (g_fProcessDetach)
+ return CORDBG_E_FUNC_EVAL_BAD_START_POINT;
+
+ // If there is no guard page on this thread, then we've taken a stack overflow exception and can't run managed
+ // code on this thread. Therefore, we can't do a func eval on this thread.
+ if (!pThread->DetermineIfGuardPagePresent())
+ {
+ return CORDBG_E_ILLEGAL_IN_STACK_OVERFLOW;
+ }
+
+ bool fInException = pEvalInfo->evalDuringException;
+
+ // The thread has to be at a GC safe place for now, just in case the func eval causes a collection. Processing an
+ // exception also counts as a "safe place." Eventually, we'd like to have to avoid this check and eval anyway, but
+ // that's a way's off...
+ if (!fInException && !g_pDebugger->IsThreadAtSafePlace(pThread))
+ return CORDBG_E_ILLEGAL_AT_GC_UNSAFE_POINT;
+
+ // For now, we assume that the target thread must be stopped in managed code due to a single step or a
+ // breakpoint. Being stopped while sending a first or second chance exception is also valid, and there may or may
+ // not be a filter context when we do a func eval from such places. This will loosen over time, eventually allowing
+ // threads that are stopped anywhere in managed code to perform func evals.
+ CONTEXT *filterContext = GetManagedStoppedCtx(pThread);
+
+ if (filterContext == NULL && !fInException)
+ {
+ return CORDBG_E_ILLEGAL_AT_GC_UNSAFE_POINT;
+ }
+
+ // Create a DebuggerEval to hold info about this eval while its in progress. Constructor copies the thread's
+ // CONTEXT.
+ DebuggerEval *pDE = new (interopsafe, nothrow) DebuggerEval(filterContext, pEvalInfo, fInException);
+
+ if (pDE == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+ else if (!pDE->Init())
+ {
+ // We fail to change the m_breakpointInstruction field to PAGE_EXECUTE_READWRITE permission.
+ return E_FAIL;
+ }
+
+ SIZE_T argDataAreaSize = 0;
+
+ argDataAreaSize += pEvalInfo->genericArgsNodeCount * sizeof(DebuggerIPCE_TypeArgData);
+
+ if ((pEvalInfo->funcEvalType == DB_IPCE_FET_NORMAL) ||
+ (pEvalInfo->funcEvalType == DB_IPCE_FET_NEW_OBJECT) ||
+ (pEvalInfo->funcEvalType == DB_IPCE_FET_NEW_OBJECT_NC))
+ argDataAreaSize += pEvalInfo->argCount * sizeof(DebuggerIPCE_FuncEvalArgData);
+ else if (pEvalInfo->funcEvalType == DB_IPCE_FET_NEW_STRING)
+ argDataAreaSize += pEvalInfo->stringSize;
+ else if (pEvalInfo->funcEvalType == DB_IPCE_FET_NEW_ARRAY)
+ argDataAreaSize += pEvalInfo->arrayRank * sizeof(SIZE_T);
+
+ if (argDataAreaSize > 0)
+ {
+ pDE->m_argData = new (interopsafe, nothrow) BYTE[argDataAreaSize];
+
+ if (pDE->m_argData == NULL)
+ {
+ DeleteInteropSafeExecutable(pDE);
+ return E_OUTOFMEMORY;
+ }
+
+ // Pass back the address of the argument data area so the right side can write to it for us.
+ *argDataArea = pDE->m_argData;
+ }
+
+ // Set the thread's IP (in the filter context) to our hijack function if we're stopped due to a breakpoint or single
+ // step.
+ if (!fInException)
+ {
+ _ASSERTE(filterContext != NULL);
+
+ ::SetIP(filterContext, (UINT_PTR)GetEEFuncEntryPoint(::FuncEvalHijack));
+
+ // Don't be fooled into thinking you can push things onto the thread's stack now. If the thread is stopped at a
+ // breakpoint or from a single step, then its really suspended in the SEH filter. ESP in the thread's CONTEXT,
+ // therefore, points into the middle of the thread's current stack. So we pass things we need in the hijack in
+ // the thread's registers.
+
+ // Set the first argument to point to the DebuggerEval.
+#if defined(_TARGET_X86_)
+ filterContext->Eax = (DWORD)pDE;
+#elif defined(_TARGET_AMD64_)
+#ifdef UNIX_AMD64_ABI
+ filterContext->Rdi = (SIZE_T)pDE;
+#else // UNIX_AMD64_ABI
+ filterContext->Rcx = (SIZE_T)pDE;
+#endif // !UNIX_AMD64_ABI
+#elif defined(_TARGET_ARM_)
+ filterContext->R0 = (DWORD)pDE;
+#elif defined(_TARGET_ARM64_)
+ filterContext->X0 = (SIZE_T)pDE;
+#else
+ PORTABILITY_ASSERT("Debugger::FuncEvalSetup is not implemented on this platform.");
+#endif
+
+ //
+ // To prevent GCs until the func-eval gets a chance to run, we increment the counter here.
+ // We only need to do this if we have changed the filter CONTEXT, since the stack will be unwalkable
+ // in this case.
+ //
+ g_pDebugger->IncThreadsAtUnsafePlaces();
+ }
+ else
+ {
+ HRESULT hr = CheckInitPendingFuncEvalTable();
+
+ if (FAILED(hr))
+ {
+ DeleteInteropSafeExecutable(pDE); // Note this runs the destructor for DebuggerEval, which releases its internal buffers
+ return (hr);
+ }
+ // If we're in an exception, then add a pending eval for this thread. This will cause us to perform the func
+ // eval when the user continues the process after the current exception event.
+ GetPendingEvals()->AddPendingEval(pDE->m_thread, pDE);
+ }
+
+
+ // Return that all went well. Tracing the stack at this point should not show that the func eval is setup, but it
+ // will show a wrong IP, so it shouldn't be done.
+ *debuggerEvalKey = pDE;
+
+ LOG((LF_CORDB, LL_INFO100000, "D:FES for pDE:%08x evalType:%d on thread %#x, id=0x%x\n",
+ pDE, pDE->m_evalType, pThread, GetThreadIdHelper(pThread)));
+
+ return S_OK;
+}
+
+//
+// FuncEvalSetupReAbort sets up a function evaluation specifically to rethrow a ThreadAbortException on the given
+// thread.
+//
+HRESULT Debugger::FuncEvalSetupReAbort(Thread *pThread, Thread::ThreadAbortRequester requester)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ SO_NOT_MAINLINE;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_INFO1000,
+ "D::FESRA: performing reabort on thread %#x, id=0x%x\n",
+ pThread, GetThreadIdHelper(pThread)));
+
+ // The thread has to be at a GC safe place. It should be, since this is only done in response to a previous eval
+ // completing with a ThreadAbortException.
+ if (!g_pDebugger->IsThreadAtSafePlace(pThread))
+ return CORDBG_E_ILLEGAL_AT_GC_UNSAFE_POINT;
+
+ // Grab the filter context.
+ CONTEXT *filterContext = GetManagedStoppedCtx(pThread);
+
+ if (filterContext == NULL)
+ {
+ return CORDBG_E_ILLEGAL_AT_GC_UNSAFE_POINT;
+ }
+
+ // Create a DebuggerEval to hold info about this eval while its in progress. Constructor copies the thread's
+ // CONTEXT.
+ DebuggerEval *pDE = new (interopsafe, nothrow) DebuggerEval(filterContext, pThread, requester);
+
+ if (pDE == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+ else if (!pDE->Init())
+ {
+ // We fail to change the m_breakpointInstruction field to PAGE_EXECUTE_READWRITE permission.
+ return E_FAIL;
+ }
+
+ // Set the thread's IP (in the filter context) to our hijack function.
+ _ASSERTE(filterContext != NULL);
+
+ ::SetIP(filterContext, (UINT_PTR)GetEEFuncEntryPoint(::FuncEvalHijack));
+
+#ifdef _TARGET_X86_ // reliance on filterContext->Eip & Eax
+ // Set EAX to point to the DebuggerEval.
+ filterContext->Eax = (DWORD)pDE;
+#elif defined(_TARGET_AMD64_)
+ // Set RCX to point to the DebuggerEval.
+ filterContext->Rcx = (SIZE_T)pDE;
+#elif defined(_TARGET_ARM_)
+ filterContext->R0 = (DWORD)pDE;
+#elif defined(_TARGET_ARM64_)
+ filterContext->X0 = (SIZE_T)pDE;
+#else
+ PORTABILITY_ASSERT("FuncEvalSetupReAbort (Debugger.cpp) is not implemented on this platform.");
+#endif
+
+ // Now clear the bit requesting a re-abort
+ pThread->ResetThreadStateNC(Thread::TSNC_DebuggerReAbort);
+
+ g_pDebugger->IncThreadsAtUnsafePlaces();
+
+ // Return that all went well. Tracing the stack at this point should not show that the func eval is setup, but it
+ // will show a wrong IP, so it shouldn't be done.
+
+ return S_OK;
+}
+
+//
+// FuncEvalAbort: Does a gentle abort of a func-eval already in progress.
+// Because this type of abort waits for the thread to get to a good state,
+// it may never return, or may time out.
+//
+
+//
+// Wait at most 0.5 seconds.
+//
+#define FUNC_EVAL_DEFAULT_TIMEOUT_VALUE 500
+
+HRESULT
+Debugger::FuncEvalAbort(
+ DebuggerEval *debuggerEvalKey
+ )
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ DebuggerEval *pDE = (DebuggerEval*) debuggerEvalKey;
+ HRESULT hr = S_OK;
+ CHECK_IF_CAN_TAKE_HELPER_LOCKS_IN_THIS_SCOPE(&hr, GetCanary());
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+
+ if (pDE->m_aborting == DebuggerEval::FE_ABORT_NONE)
+ {
+ // Remember that we're aborting this func eval.
+ pDE->m_aborting = DebuggerEval::FE_ABORT_NORMAL;
+
+ LOG((LF_CORDB, LL_INFO1000,
+ "D::FEA: performing UserAbort on thread %#x, id=0x%x\n",
+ pDE->m_thread, GetThreadIdHelper(pDE->m_thread)));
+
+ if (!g_fProcessDetach && !pDE->m_completed)
+ {
+ //
+ // Perform a stop on the thread that the eval is running on.
+ // This will cause a ThreadAbortException to be thrown on the thread.
+ //
+ EX_TRY
+ {
+ hr = pDE->m_thread->UserAbort(Thread::TAR_FuncEval, EEPolicy::TA_Safe, (DWORD)FUNC_EVAL_DEFAULT_TIMEOUT_VALUE, Thread::UAC_Normal);
+ if (hr == HRESULT_FROM_WIN32(ERROR_TIMEOUT))
+ {
+ hr = S_OK;
+ }
+ }
+ EX_CATCH
+ {
+ _ASSERTE(!"Unknown exception from UserAbort(), not expected");
+ }
+ EX_END_CATCH(EX_RETHROW);
+
+ }
+
+ LOG((LF_CORDB, LL_INFO1000, "D::FEA: UserAbort complete.\n"));
+ }
+
+ return hr;
+}
+
+//
+// FuncEvalRudeAbort: Does a rude abort of a func-eval in progress. This
+// leaves the thread in an undetermined state.
+//
+HRESULT
+Debugger::FuncEvalRudeAbort(
+ DebuggerEval *debuggerEvalKey
+ )
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_NOTRIGGER;
+ SO_NOT_MAINLINE;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr = S_OK;
+ CHECK_IF_CAN_TAKE_HELPER_LOCKS_IN_THIS_SCOPE(&hr, GetCanary());
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+
+ DebuggerEval *pDE = debuggerEvalKey;
+
+
+ if (!(pDE->m_aborting & DebuggerEval::FE_ABORT_RUDE))
+ {
+ //
+ // Remember that we're aborting this func eval.
+ //
+ pDE->m_aborting = (DebuggerEval::FUNC_EVAL_ABORT_TYPE)(pDE->m_aborting | DebuggerEval::FE_ABORT_RUDE);
+
+ LOG((LF_CORDB, LL_INFO1000,
+ "D::FEA: performing RudeAbort on thread %#x, id=0x%x\n",
+ pDE->m_thread, Debugger::GetThreadIdHelper(pDE->m_thread)));
+
+ if (!g_fProcessDetach && !pDE->m_completed)
+ {
+ //
+ // Perform a stop on the thread that the eval is running on.
+ // This will cause a ThreadAbortException to be thrown on the thread.
+ //
+ EX_TRY
+ {
+ hr = pDE->m_thread->UserAbort(Thread::TAR_FuncEval, EEPolicy::TA_Rude, (DWORD)FUNC_EVAL_DEFAULT_TIMEOUT_VALUE, Thread::UAC_Normal);
+ if (hr == HRESULT_FROM_WIN32(ERROR_TIMEOUT))
+ {
+ hr = S_OK;
+ }
+ }
+ EX_CATCH
+ {
+ _ASSERTE(!"Unknown exception from UserAbort(), not expected");
+ EX_RETHROW;
+ }
+ EX_END_CATCH(RethrowTerminalExceptions);
+ }
+
+ LOG((LF_CORDB, LL_INFO1000, "D::FEA: RudeAbort complete.\n"));
+ }
+
+ return hr;
+}
+
+//
+// FuncEvalCleanup cleans up after a function evaluation is released.
+//
+HRESULT Debugger::FuncEvalCleanup(DebuggerEval *debuggerEvalKey)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ DebuggerEval *pDE = debuggerEvalKey;
+
+ _ASSERTE(pDE->m_completed);
+
+ LOG((LF_CORDB, LL_INFO1000, "D::FEC: pDE:%08x 0x%08x, id=0x%x\n",
+ pDE, pDE->m_thread, GetThreadIdHelper(pDE->m_thread)));
+
+ DeleteInteropSafeExecutable(pDE->m_bpInfoSegment);
+ DeleteInteropSafe(pDE);
+
+ return S_OK;
+}
+
+#endif // ifndef DACCESS_COMPILE
+
+//
+// SetReference sets an object reference for the Right Side,
+// respecting the write barrier for references that are in the heap.
+//
+HRESULT Debugger::SetReference(void *objectRefAddress,
+ VMPTR_OBJECTHANDLE vmObjectHandle,
+ void *newReference)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr = S_OK;
+
+ hr = ValidateObject((Object *)newReference);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+
+ // If the object ref isn't in a handle, then go ahead and use
+ // SetObjectReference.
+ if (vmObjectHandle.IsNull())
+ {
+ OBJECTREF *dst = (OBJECTREF*)objectRefAddress;
+ OBJECTREF src = *((OBJECTREF*)&newReference);
+
+ SetObjectReferenceUnchecked(dst, src);
+ }
+ else
+ {
+
+ // If the object reference to set is inside of a handle, then
+ // fixup the handle.
+ OBJECTHANDLE h = vmObjectHandle.GetRawPtr();
+ OBJECTREF src = *((OBJECTREF*)&newReference);
+ HndAssignHandle(h, src);
+ }
+
+ return S_OK;
+}
+
+//
+// SetValueClass sets a value class for the Right Side, respecting the write barrier for references that are embedded
+// within in the value class.
+//
+HRESULT Debugger::SetValueClass(void *oldData, void *newData, DebuggerIPCE_BasicTypeData * type)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr = S_OK;
+
+ TypeHandle th;
+ hr = BasicTypeInfoToTypeHandle(type, &th);
+
+ if (FAILED(hr))
+ return CORDBG_E_CLASS_NOT_LOADED;
+
+ // Update the value class.
+ CopyValueClassUnchecked(oldData, newData, th.GetMethodTable());
+
+ // Free the buffer that is holding the new data. This is a buffer that was created in response to a GET_BUFFER
+ // message, so we release it with ReleaseRemoteBuffer.
+ ReleaseRemoteBuffer((BYTE*)newData, true);
+
+ return hr;
+}
+
+/******************************************************************************
+ *
+ ******************************************************************************/
+HRESULT Debugger::SetILInstrumentedCodeMap(MethodDesc *fd,
+ BOOL fStartJit,
+ ULONG32 cILMapEntries,
+ COR_IL_MAP rgILMapEntries[])
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS_FROM_GETJITINFO;
+ }
+ CONTRACTL_END;
+
+ if (!HasLazyData())
+ {
+ DebuggerLockHolder dbgLockHolder(this);
+ // This is an entry path into the debugger, so make sure we're inited.
+ LazyInit();
+ }
+
+ DebuggerMethodInfo * dmi = GetOrCreateMethodInfo(fd->GetModule(), fd->GetMemberDef());
+ if (dmi == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ dmi->SetInstrumentedILMap(rgILMapEntries, cILMapEntries);
+
+ return S_OK;
+}
+
+//
+// EarlyHelperThreadDeath handles the case where the helper
+// thread has been ripped out from underneath of us by
+// ExitProcess or TerminateProcess. These calls are bad, whacking
+// all threads except the caller in the process. This can happen, for
+// instance, when an app calls ExitProcess. All threads are wacked,
+// the main thread calls all DLL main's, and the EE starts shutting
+// down in its DLL main with the helper thread terminated.
+//
+void Debugger::EarlyHelperThreadDeath(void)
+{
+ WRAPPER_NO_CONTRACT;
+
+ if (m_pRCThread)
+ m_pRCThread->EarlyHelperThreadDeath();
+}
+
+//
+// This tells the debugger that shutdown of the in-proc debugging services has begun. We need to know this during
+// managed/unmanaged debugging so we can stop doing certian things to the process (like hijacking threads.)
+//
+void Debugger::ShutdownBegun(void)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ SO_INTOLERANT;
+ }
+ CONTRACTL_END;
+
+
+ // Shouldn't be Debugger-stopped if we're shutting down.
+ // However, shutdown can occur in preemptive mode. Thus if the RS does an AsyncBreak late
+ // enough, then the LS will appear to be stopped but may still shutdown.
+ // Since the debuggee can exit asynchronously at any time (eg, suppose somebody forcefully
+ // kills it with taskman), this doesn't introduce a new case.
+ // That aside, it would be great to be able to assert this:
+ //_ASSERTE(!IsStopped());
+
+ if (m_pRCThread != NULL)
+ {
+ DebuggerIPCControlBlock *dcb = m_pRCThread->GetDCB();
+
+ if ((dcb != NULL) && (dcb->m_rightSideIsWin32Debugger))
+ dcb->m_shutdownBegun = true;
+ }
+}
+
+/*
+ * LockDebuggerForShutdown
+ *
+ * This routine is used during shutdown to tell the in-process portion of the
+ * debugger to synchronize with any threads that are currently using the
+ * debugging facilities such that no more threads will run debugging services.
+ *
+ * This is accomplished by transitioning the debugger lock in to a state where
+ * it will block all threads, except for the finalizer, shutdown, and helper thread.
+ */
+void Debugger::LockDebuggerForShutdown(void)
+{
+#ifndef DACCESS_COMPILE
+
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ SO_INTOLERANT;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ DebuggerLockHolder dbgLockHolder(this);
+
+ // Shouldn't be Debugger-stopped if we're shutting down.
+ // However, shutdown can occur in preemptive mode. Thus if the RS does an AsyncBreak late
+ // enough, then the LS will appear to be stopped but may still shutdown.
+ // Since the debuggee can exit asynchronously at any time (eg, suppose somebody forcefully
+ // kills it with taskman), this doesn't introduce a new case.
+ // That aside, it would be great to be able to assert this:
+ //_ASSERTE(!IsStopped());
+
+ // After setting this flag, nonspecial threads will not be able to
+ // take the debugger lock.
+ m_fShutdownMode = true;
+
+ m_ignoreThreadDetach = TRUE;
+#else
+ DacNotImpl();
+#endif
+}
+
+
+/*
+ * DisableDebugger
+ *
+ * This routine is used by the EE to inform the debugger that it should block all
+ * threads from executing as soon as it can. Any thread entering the debugger can
+ * block infinitely, as well.
+ *
+ * This is accomplished by transitioning the debugger lock into a mode where it will
+ * block all threads infinitely rather than taking the lock.
+ *
+ */
+void Debugger::DisableDebugger(void)
+{
+#ifndef DACCESS_COMPILE
+
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ SO_INTOLERANT;
+ PRECONDITION(ThisMaybeHelperThread());
+ }
+ CONTRACTL_END;
+
+ m_fDisabled = true;
+
+ CORDBDebuggerSetUnrecoverableError(this, CORDBG_E_DEBUGGING_DISABLED, false);
+
+#else
+ DacNotImpl();
+#endif
+}
+
+
+/****************************************************************************
+ * This will perform the duties of the helper thread if none already exists.
+ * This is called in the case that the loader lock is held and so no new
+ * threads can be spun up to be the helper thread, so the existing thread
+ * must be the helper thread until a new one can spin up.
+ * This is also called in the shutdown case (g_fProcessDetach==true) and our
+ * helper may have already been blown away.
+ ***************************************************************************/
+void Debugger::DoHelperThreadDuty()
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ THROWS;
+ WRAPPER(GC_TRIGGERS);
+ }
+ CONTRACTL_END;
+
+ // This should not be a real helper thread.
+ _ASSERTE(!IsDbgHelperSpecialThread());
+ _ASSERTE(ThreadHoldsLock());
+
+ // We may be here in the shutdown case (only if the shutdown started after we got here).
+ // We'll get killed randomly anyways, so not much we can do.
+
+ // These assumptions are based off us being called from TART.
+ _ASSERTE(ThreadStore::HoldingThreadStore() || g_fProcessDetach); // got this from TART
+ _ASSERTE(m_trappingRuntimeThreads); // We're only called from TART.
+ _ASSERTE(!m_stopped); // we haven't sent the sync-complete yet.
+
+ // Can't have 2 threads doing helper duty.
+ _ASSERTE(m_pRCThread->GetDCB()->m_temporaryHelperThreadId == 0);
+
+ LOG((LF_CORDB, LL_INFO1000,
+ "D::SSCIPCE: helper thread is not ready, doing helper "
+ "thread duty...\n"));
+
+ // We're the temporary helper thread now.
+ DWORD dwMyTID = GetCurrentThreadId();
+ m_pRCThread->GetDCB()->m_temporaryHelperThreadId = dwMyTID;
+
+ // Make sure the helper thread has something to wait on while
+ // we're trying to be the helper thread.
+ VERIFY(ResetEvent(m_pRCThread->GetHelperThreadCanGoEvent()));
+
+ // We have not sent the sync-complete flare yet.
+
+ // Now that we've synchronized, we'll eventually send the sync-complete. But we're currently within the
+ // scope of sombody already sending an event. So unlock from that event so that we can send the sync-complete.
+ // Don't release the debugger lock
+ //
+ UnlockFromEventSending(NULL);
+
+ // We are the temporary helper thread. We will not deal with everything! But just pump for
+ // continue.
+ //
+ m_pRCThread->TemporaryHelperThreadMainLoop();
+
+ // We do not need to relock it since we never release it.
+ LockForEventSending(NULL);
+ _ASSERTE(ThreadHoldsLock());
+
+
+ STRESS_LOG1(LF_CORDB, LL_INFO1000,
+ "D::SSCIPCE: done doing helper thread duty. "
+ "Current helper thread id=0x%x\n",
+ m_pRCThread->GetDCB()->m_helperThreadId);
+
+ // We're not the temporary helper thread anymore.
+ _ASSERTE(m_pRCThread->GetDCB()->m_temporaryHelperThreadId == dwMyTID);
+ m_pRCThread->GetDCB()->m_temporaryHelperThreadId = 0;
+
+ // Let the helper thread go if its waiting on us.
+ VERIFY(SetEvent(m_pRCThread->GetHelperThreadCanGoEvent()));
+}
+
+
+
+// This function is called from the EE to notify the right side
+// whenever the name of a thread or AppDomain changes
+//
+// Notes:
+// This just sends a ping event to notify that the name has been changed.
+// It does not send the actual updated name. Instead, the debugger can query for the name.
+//
+// For an AppDomain name change:
+// - pAppDoamin != NULL
+// - name retrieved via ICorDebugAppDomain::GetName
+//
+// For a Thread name change:
+// - pAppDomain == NULL, pThread != NULL
+// - name retrieved via a func-eval of Thread::get_Name
+HRESULT Debugger::NameChangeEvent(AppDomain *pAppDomain, Thread *pThread)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT;
+ MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT;
+ }
+ CONTRACTL_END;
+
+ // Don't try to send one of these if the thread really isn't setup
+ // yet. This can happen when initially setting up an app domain,
+ // before the appdomain create event has been sent. Since the app
+ // domain create event hasn't been sent yet in this case, its okay
+ // to do this...
+ if (g_pEEInterface->GetThread() == NULL)
+ return S_OK;
+
+ // Skip if thread doesn't yet have native ID.
+ // This can easily happen if an app sets Thread.Name before it calls Thread.Start.
+ // Since this is just a ping-event, it's ignorable. The debugger can query the thread name at Thread.Start in this case.
+ // This emulates whidbey semantics.
+ if (pThread != NULL)
+ {
+ if (pThread->GetOSThreadId() == 0)
+ {
+ return S_OK;
+ }
+ }
+
+ LOG((LF_CORDB, LL_INFO1000, "D::NCE: Sending NameChangeEvent 0x%x 0x%x\n",
+ pAppDomain, pThread));
+
+ Thread *curThread = g_pEEInterface->GetThread();
+ SENDIPCEVENT_BEGIN(this, curThread);
+
+ if (CORDebuggerAttached())
+ {
+
+ DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer();
+ InitIPCEvent(ipce,
+ DB_IPCE_NAME_CHANGE,
+ curThread,
+ curThread->GetDomain());
+
+
+ if (pAppDomain)
+ {
+ ipce->NameChange.eventType = APP_DOMAIN_NAME_CHANGE;
+ ipce->NameChange.vmAppDomain.SetRawPtr(pAppDomain);
+ }
+ else
+ {
+ // Thread Name
+ ipce->NameChange.eventType = THREAD_NAME_CHANGE;
+ _ASSERTE (pThread);
+ ipce->NameChange.vmThread.SetRawPtr(pThread);
+ }
+
+ m_pRCThread->SendIPCEvent();
+
+ // Stop all Runtime threads
+ TrapAllRuntimeThreads();
+ }
+ else
+ {
+ LOG((LF_CORDB,LL_INFO1000, "D::NCE: Skipping SendIPCEvent because RS detached."));
+ }
+
+ SENDIPCEVENT_END;
+
+ return S_OK;
+
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Send an event to the RS indicating that there's a Ctrl-C or Ctrl-Break.
+//
+// Arguments:
+// dwCtrlType - represents the type of the event (Ctrl-C or Ctrl-Break)
+//
+// Return Value:
+// Return TRUE if the event has been handled by the debugger.
+//
+
+BOOL Debugger::SendCtrlCToDebugger(DWORD dwCtrlType)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT;
+ MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_INFO1000, "D::SCCTD: Sending CtrlC Event 0x%x\n", dwCtrlType));
+
+ // Prevent other Runtime threads from handling events.
+ Thread *pThread = g_pEEInterface->GetThread();
+ SENDIPCEVENT_BEGIN(this, pThread);
+
+ if (CORDebuggerAttached())
+ {
+ DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer();
+ InitIPCEvent(ipce,
+ DB_IPCE_CONTROL_C_EVENT,
+ pThread,
+ NULL);
+
+ // The RS doesn't do anything with dwCtrlType
+ m_pRCThread->SendIPCEvent();
+
+ // Stop all Runtime threads
+ TrapAllRuntimeThreads();
+ }
+ else
+ {
+ LOG((LF_CORDB,LL_INFO1000, "D::SCCTD: Skipping SendIPCEvent because RS detached."));
+ }
+
+ SENDIPCEVENT_END;
+
+ // now wait for notification from the right side about whether or not
+ // the out-of-proc debugger is handling ControlC events.
+ WaitForSingleObjectHelper(GetCtrlCMutex(), INFINITE);
+
+ return GetDebuggerHandlingCtrlC();
+}
+
+// Allows the debugger to keep an up to date list of special threads
+HRESULT Debugger::UpdateSpecialThreadList(DWORD cThreadArrayLength,
+ DWORD *rgdwThreadIDArray)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ _ASSERTE(g_pRCThread != NULL);
+
+ DebuggerIPCControlBlock *pIPC = g_pRCThread->GetDCB();
+ _ASSERTE(pIPC);
+
+ if (!pIPC)
+ return (E_FAIL);
+
+ // Save the thread list information, and mark the dirty bit so
+ // the right side knows.
+ pIPC->m_specialThreadList = rgdwThreadIDArray;
+ pIPC->m_specialThreadListLength = cThreadArrayLength;
+ pIPC->m_specialThreadListDirty = true;
+
+ return (S_OK);
+}
+
+// Updates the pointer for the debugger services
+void Debugger::SetIDbgThreadControl(IDebuggerThreadControl *pIDbgThreadControl)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+ if (m_pIDbgThreadControl)
+ m_pIDbgThreadControl->Release();
+
+ m_pIDbgThreadControl = pIDbgThreadControl;
+
+ if (m_pIDbgThreadControl)
+ m_pIDbgThreadControl->AddRef();
+}
+
+//
+// If a thread is Win32 suspended right after hitting a breakpoint instruction, but before the OS has transitioned the
+// thread over to the user-level exception dispatching logic, then we may see the IP pointing after the breakpoint
+// instruction. There are times when the Runtime will use the IP to try to determine what code as run in the prolog or
+// epilog, most notably when unwinding a frame. If the thread is suspended in such a case, then the unwind will believe
+// that the instruction that the breakpoint replaced has really been executed, which is not true. This confuses the
+// unwinding logic. This function is called from Thread::HandledJITCase() to help us recgonize when this may have
+// happened and allow us to skip the unwind and abort the HandledJITCase.
+//
+// The criteria is this:
+//
+// 1) If a debugger is attached.
+//
+// 2) If the instruction before the IP is a breakpoint instruction.
+//
+// 3) If the IP is in the prolog or epilog of a managed function.
+//
+BOOL Debugger::IsThreadContextInvalid(Thread *pThread)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ BOOL invalid = FALSE;
+
+ // Get the thread context.
+ CONTEXT ctx;
+ ctx.ContextFlags = CONTEXT_CONTROL;
+ BOOL success = pThread->GetThreadContext(&ctx);
+
+ if (success)
+ {
+ // Check single-step flag
+ if (IsSSFlagEnabled(reinterpret_cast<DT_CONTEXT *>(&ctx) ARM_ARG(pThread)))
+ {
+ // Can't hijack a thread whose SS-flag is set. This could lead to races
+ // with the thread taking the SS-exception.
+ // The debugger's controller filters will poll for GC to avoid starvation.
+ STRESS_LOG0(LF_CORDB, LL_EVERYTHING, "HJC - Hardware trace flag applied\n");
+ return TRUE;
+ }
+ }
+
+ if (success)
+ {
+#ifdef _TARGET_X86_
+ // Grab Eip - 1
+ LPVOID address = (((BYTE*)GetIP(&ctx)) - 1);
+
+ EX_TRY
+ {
+ // Use AVInRuntimeImplOkHolder.
+ AVInRuntimeImplOkayHolder AVOkay;
+
+ // Is it a breakpoint?
+ if (AddressIsBreakpoint((CORDB_ADDRESS_TYPE*)address))
+ {
+ size_t prologSize; // Unused...
+ if (g_pEEInterface->IsInPrologOrEpilog((BYTE*)GetIP(&ctx), &prologSize))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "D::ITCI: thread is after a BP and in prolog or epilog.\n"));
+ invalid = TRUE;
+ }
+ }
+ }
+ EX_CATCH
+ {
+ // If we fault trying to read the byte before EIP, then we know that its not a breakpoint.
+ // Do nothing. The default return value is FALSE.
+ }
+ EX_END_CATCH(SwallowAllExceptions);
+#else // _TARGET_X86_
+ // Non-x86 can detect whether the thread is suspended after an exception is hit but before
+ // the kernel has dispatched the exception to user mode by trap frame reporting.
+ // See Thread::IsContextSafeToRedirect().
+#endif // _TARGET_X86_
+ }
+ else
+ {
+ // If we can't get the context, then its definetly invalid... ;)
+ LOG((LF_CORDB, LL_INFO1000, "D::ITCI: couldn't get thread's context!\n"));
+ invalid = TRUE;
+ }
+
+ return invalid;
+}
+
+
+// notification when a SQL connection begins
+void Debugger::CreateConnection(CONNID dwConnectionId, __in_z WCHAR *wzName)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT;
+ MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB,LL_INFO1000, "D::CreateConnection %d\n.", dwConnectionId));
+
+ if (CORDBUnrecoverableError(this))
+ return;
+
+ Thread *pThread = g_pEEInterface->GetThread();
+ SENDIPCEVENT_BEGIN(this, pThread);
+
+ if (CORDebuggerAttached())
+ {
+ DebuggerIPCEvent* ipce;
+
+ // Send a update module syns event to the Right Side.
+ ipce = m_pRCThread->GetIPCEventSendBuffer();
+ InitIPCEvent(ipce, DB_IPCE_CREATE_CONNECTION,
+ pThread,
+ NULL);
+ ipce->CreateConnection.connectionId = dwConnectionId;
+ _ASSERTE(wzName != NULL);
+ ipce->CreateConnection.wzConnectionName.SetString(wzName);
+
+ m_pRCThread->SendIPCEvent();
+ }
+ else
+ {
+ LOG((LF_CORDB,LL_INFO1000, "D::CreateConnection: Skipping SendIPCEvent because RS detached."));
+ }
+
+ // Stop all Runtime threads if we actually sent an event
+ if (CORDebuggerAttached())
+ {
+ TrapAllRuntimeThreads();
+ }
+
+ SENDIPCEVENT_END;
+}
+
+// notification when a SQL connection ends
+void Debugger::DestroyConnection(CONNID dwConnectionId)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT;
+ MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB,LL_INFO1000, "D::DestroyConnection %d\n.", dwConnectionId));
+
+ if (CORDBUnrecoverableError(this))
+ return;
+
+ Thread *thread = g_pEEInterface->GetThread();
+ // Note that the debugger lock is reentrant, so we may or may not hold it already.
+ SENDIPCEVENT_BEGIN(this, thread);
+
+ // Send a update module syns event to the Right Side.
+ DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer();
+ InitIPCEvent(ipce, DB_IPCE_DESTROY_CONNECTION,
+ thread,
+ NULL);
+ ipce->ConnectionChange.connectionId = dwConnectionId;
+
+ // IPC event is now initialized, so we can send it over.
+ SendSimpleIPCEventAndBlock();
+
+ // This will block on the continue
+ SENDIPCEVENT_END;
+
+}
+
+// notification for SQL connection changes
+void Debugger::ChangeConnection(CONNID dwConnectionId)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT;
+ MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB,LL_INFO1000, "D::ChangeConnection %d\n.", dwConnectionId));
+
+ if (CORDBUnrecoverableError(this))
+ return;
+
+ Thread *pThread = g_pEEInterface->GetThread();
+ SENDIPCEVENT_BEGIN(this, pThread);
+
+ if (CORDebuggerAttached())
+ {
+ DebuggerIPCEvent* ipce;
+
+ // Send a update module syns event to the Right Side.
+ ipce = m_pRCThread->GetIPCEventSendBuffer();
+ InitIPCEvent(ipce, DB_IPCE_CHANGE_CONNECTION,
+ pThread,
+ NULL);
+ ipce->ConnectionChange.connectionId = dwConnectionId;
+ m_pRCThread->SendIPCEvent();
+ }
+ else
+ {
+ LOG((LF_CORDB,LL_INFO1000, "D::ChangeConnection: Skipping SendIPCEvent because RS detached."));
+ }
+
+ // Stop all Runtime threads if we actually sent an event
+ if (CORDebuggerAttached())
+ {
+ TrapAllRuntimeThreads();
+ }
+
+ SENDIPCEVENT_END;
+}
+
+
+//
+// Are we the helper thread?
+// Some important things about running on the helper thread:
+// - there's only 1, so guaranteed to be thread-safe.
+// - we'll never run managed code.
+// - therefore, Never GC.
+// - It listens for events from the RS.
+// - It's the only thread to send a sync complete.
+//
+bool ThisIsHelperThreadWorker(void)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ SO_TOLERANT;
+ }
+ CONTRACTL_END;
+
+ // This can
+ Thread * pThread;
+ pThread = GetThreadNULLOk();
+
+ // First check for a real helper thread. This will do a FLS access.
+ bool fIsHelperThread = !!IsDbgHelperSpecialThread();
+ if (fIsHelperThread)
+ {
+ // If we're on the real helper thread, we never run managed code
+ // and so we'd better not have an EE thread object.
+ _ASSERTE((pThread == NULL) || !"The helper thread should not being running managed code.\n"
+ "Are you running managed code inside the dllmain? If so, your scenario is invalid and this"
+ "assert is only the tip of the iceberg.\n");
+ return true;
+ }
+
+ // Even if we're not on the real helper thread, we may still be on a thread
+ // pretending to be the helper. (Helper Duty, etc).
+ DWORD id = GetCurrentThreadId();
+
+ // Check for temporary helper thread.
+ if (ThisIsTempHelperThread(id))
+ {
+ return true;
+ }
+
+ return false;
+}
+
+//
+// Make call to the static method.
+// This is exposed to the contracts susbsystem so that the helper thread can call
+// things on MODE_COOPERATIVE.
+//
+bool Debugger::ThisIsHelperThread(void)
+{
+ WRAPPER_NO_CONTRACT;
+
+ return ThisIsHelperThreadWorker();
+}
+
+// Check if we're the temporary helper thread. Have 2 forms of this, 1 that assumes the current
+// thread (but has the overhead of an extra call to GetCurrentThreadId() if we laready know the tid.
+bool ThisIsTempHelperThread()
+{
+ WRAPPER_NO_CONTRACT;
+
+ DWORD id = GetCurrentThreadId();
+ return ThisIsTempHelperThread(id);
+}
+
+bool ThisIsTempHelperThread(DWORD tid)
+{
+ WRAPPER_NO_CONTRACT;
+
+ // If helper thread class isn't created, then there's no helper thread.
+ // No one is doing helper thread duty either.
+ // It's also possible we're in a shutdown case and have already deleted the
+ // data for the helper thread.
+ if (g_pRCThread != NULL)
+ {
+ // May be the temporary helper thread...
+ DebuggerIPCControlBlock * pBlock = g_pRCThread->GetDCB();
+ if (pBlock != NULL)
+ {
+ DWORD idTemp = pBlock->m_temporaryHelperThreadId;
+
+ if (tid == idTemp)
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+
+}
+
+
+// This function is called when host call ICLRSecurityAttributeManager::setDacl.
+// It will redacl our SSE, RSEA, RSER events.
+HRESULT Debugger::ReDaclEvents(PSECURITY_DESCRIPTOR securityDescriptor)
+{
+ WRAPPER_NO_CONTRACT;
+
+ return m_pRCThread->ReDaclEvents(securityDescriptor);
+}
+
+/* static */
+void Debugger::AcquireDebuggerDataLock(Debugger *pDebugger)
+{
+ WRAPPER_NO_CONTRACT;
+
+ if (!g_fProcessDetach)
+ {
+ pDebugger->GetDebuggerDataLock()->Enter();
+ }
+}
+
+/* static */
+void Debugger::ReleaseDebuggerDataLock(Debugger *pDebugger)
+{
+ WRAPPER_NO_CONTRACT;
+
+ if (!g_fProcessDetach)
+ {
+ pDebugger->GetDebuggerDataLock()->Leave();
+ }
+}
+
+
+#else // DACCESS_COMPILE
+
+// determine whether the LS holds the data lock. If it does, we will assume the locked data is in an
+// inconsistent state and will throw an exception. The DAC will execute this if we are executing code
+// that takes the lock.
+// Arguments: input: pDebugger - the LS debugger data structure
+/* static */
+void Debugger::AcquireDebuggerDataLock(Debugger *pDebugger)
+{
+ SUPPORTS_DAC;
+
+ if (pDebugger->GetDebuggerDataLock()->GetEnterCount() != 0)
+ {
+ ThrowHR(CORDBG_E_PROCESS_NOT_SYNCHRONIZED);
+ }
+}
+
+void Debugger::ReleaseDebuggerDataLock(Debugger *pDebugger)
+{
+}
+#endif // DACCESS_COMPILE
+
+/* ------------------------------------------------------------------------ *
+ * Functions for DebuggerHeap executable memory allocations
+ * ------------------------------------------------------------------------ */
+
+DebuggerHeapExecutableMemoryAllocator::~DebuggerHeapExecutableMemoryAllocator()
+{
+ while (m_pages != NULL)
+ {
+ DebuggerHeapExecutableMemoryPage *temp = m_pages->GetNextPage();
+
+ // Free this page
+ INDEBUG(BOOL ret =) VirtualFree(m_pages, 0, MEM_RELEASE);
+ ASSERT(ret == TRUE);
+
+ m_pages = temp;
+ }
+
+ ASSERT(m_pages == NULL);
+}
+
+void* DebuggerHeapExecutableMemoryAllocator::Allocate(DWORD numberOfBytes)
+{
+ if (numberOfBytes > DBG_MAX_EXECUTABLE_ALLOC_SIZE)
+ {
+ ASSERT(!"Allocating more than DBG_MAX_EXECUTABLE_ALLOC_SIZE at once is unsupported and breaks our assumptions.");
+ return NULL;
+ }
+
+ if (numberOfBytes == 0)
+ {
+ // Should we allocate anything in this case?
+ ASSERT(!"Allocate called with 0 for numberOfBytes!");
+ return NULL;
+ }
+
+ CrstHolder execMemAllocCrstHolder(&m_execMemAllocMutex);
+
+ int chunkToUse = -1;
+ DebuggerHeapExecutableMemoryPage *pageToAllocateOn = NULL;
+ for (DebuggerHeapExecutableMemoryPage *currPage = m_pages; currPage != NULL; currPage = currPage->GetNextPage())
+ {
+ if (CheckPageForAvailability(currPage, &chunkToUse))
+ {
+ pageToAllocateOn = currPage;
+ break;
+ }
+ }
+
+ if (pageToAllocateOn == NULL)
+ {
+ // No existing page had availability, so create a new page and use that.
+ pageToAllocateOn = AddNewPage();
+ if (pageToAllocateOn == NULL)
+ {
+ ASSERT(!"Call to AddNewPage failed!");
+ return NULL;
+ }
+
+ if (!CheckPageForAvailability(pageToAllocateOn, &chunkToUse))
+ {
+ ASSERT(!"No availability on new page?");
+ return NULL;
+ }
+ }
+
+ return ChangePageUsage(pageToAllocateOn, chunkToUse, ChangePageUsageAction::ALLOCATE);
+}
+
+int DebuggerHeapExecutableMemoryAllocator::Free(void* addr)
+{
+ ASSERT(addr != NULL);
+
+ CrstHolder execMemAllocCrstHolder(&m_execMemAllocMutex);
+
+ DebuggerHeapExecutableMemoryPage *pageToFreeIn = static_cast<DebuggerHeapExecutableMemoryChunk*>(addr)->data.startOfPage;
+
+ if (pageToFreeIn == NULL)
+ {
+ ASSERT(!"Couldn't locate page in which to free!");
+ return -1;
+ }
+
+ int chunkNum = static_cast<DebuggerHeapExecutableMemoryChunk*>(addr)->data.chunkNumber;
+
+ // Sanity check: assert that the address really represents the start of a chunk.
+ ASSERT(((uint64_t)addr - (uint64_t)pageToFreeIn) % 64 == 0);
+
+ ChangePageUsage(pageToFreeIn, chunkNum, ChangePageUsageAction::FREE);
+
+ return 0;
+}
+
+DebuggerHeapExecutableMemoryPage* DebuggerHeapExecutableMemoryAllocator::AddNewPage()
+{
+ void* newPageAddr = VirtualAlloc(NULL, sizeof(DebuggerHeapExecutableMemoryPage), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
+
+ DebuggerHeapExecutableMemoryPage *newPage = new (newPageAddr) DebuggerHeapExecutableMemoryPage;
+ newPage->SetNextPage(m_pages);
+
+ // Add the new page to the linked list of pages
+ m_pages = newPage;
+ return newPage;
+}
+
+bool DebuggerHeapExecutableMemoryAllocator::CheckPageForAvailability(DebuggerHeapExecutableMemoryPage* page, /* _Out_ */ int* chunkToUse)
+{
+ uint64_t occupancy = page->GetPageOccupancy();
+ bool available = occupancy != UINT64_MAX;
+
+ if (!available)
+ {
+ if (chunkToUse)
+ {
+ *chunkToUse = -1;
+ }
+
+ return false;
+ }
+
+ if (chunkToUse)
+ {
+ // Start i at 62 because first chunk is reserved
+ for (int i = 62; i >= 0; i--)
+ {
+ uint64_t mask = ((uint64_t)1 << i);
+ if ((mask & occupancy) == 0)
+ {
+ *chunkToUse = 64 - i - 1;
+ break;
+ }
+ }
+ }
+
+ return true;
+}
+
+void* DebuggerHeapExecutableMemoryAllocator::ChangePageUsage(DebuggerHeapExecutableMemoryPage* page, int chunkNumber, ChangePageUsageAction action)
+{
+ ASSERT(action == ChangePageUsageAction::ALLOCATE || action == ChangePageUsageAction::FREE);
+
+ uint64_t mask = (uint64_t)0x1 << (64 - chunkNumber - 1);
+
+ uint64_t prevOccupancy = page->GetPageOccupancy();
+ uint64_t newOccupancy = (action == ChangePageUsageAction::ALLOCATE) ? (prevOccupancy | mask) : (prevOccupancy ^ mask);
+ page->SetPageOccupancy(newOccupancy);
+
+ return page->GetPointerToChunk(chunkNumber);
+}
+
+/* ------------------------------------------------------------------------ *
+ * DebuggerHeap impl
+ * ------------------------------------------------------------------------ */
+
+DebuggerHeap::DebuggerHeap()
+{
+#ifdef USE_INTEROPSAFE_HEAP
+ m_hHeap = NULL;
+#endif
+ m_fExecutable = FALSE;
+}
+
+DebuggerHeap::~DebuggerHeap()
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ Destroy();
+}
+
+void DebuggerHeap::Destroy()
+{
+#ifdef USE_INTEROPSAFE_HEAP
+ if (IsInit())
+ {
+ ::HeapDestroy(m_hHeap);
+ m_hHeap = NULL;
+ }
+#endif
+#ifdef FEATURE_PAL
+ if (m_execMemAllocator != NULL)
+ {
+ delete m_execMemAllocator;
+ }
+#endif
+}
+
+bool DebuggerHeap::IsInit()
+{
+ LIMITED_METHOD_CONTRACT;
+#ifdef USE_INTEROPSAFE_HEAP
+ return m_hHeap != NULL;
+#else
+ return true;
+#endif
+}
+
+HRESULT DebuggerHeap::Init(BOOL fExecutable)
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ // Have knob catch if we don't want to lazy init the debugger.
+ _ASSERTE(!g_DbgShouldntUseDebugger);
+ m_fExecutable = fExecutable;
+
+#ifdef USE_INTEROPSAFE_HEAP
+ // If already inited, then we're done.
+ // We normally don't double-init. However, we may oom between when we allocate the heap and when we do other initialization.
+ // We don't worry about backout code to free the heap. Rather, we'll just leave it alive and nop if we try to allocate it again.
+ if (IsInit())
+ {
+ return S_OK;
+ }
+
+#ifndef HEAP_CREATE_ENABLE_EXECUTE
+#define HEAP_CREATE_ENABLE_EXECUTE 0x00040000 // winnt create heap with executable pages
+#endif
+
+ // Create a standard, grow-able, thread-safe heap.
+ DWORD dwFlags = ((fExecutable == TRUE)? HEAP_CREATE_ENABLE_EXECUTE : 0);
+ m_hHeap = ::HeapCreate(dwFlags, 0, 0);
+ if (m_hHeap == NULL)
+ {
+ return HRESULT_FROM_GetLastError();
+ }
+#endif
+
+#ifdef FEATURE_PAL
+ m_execMemAllocator = new (nothrow) DebuggerHeapExecutableMemoryAllocator();
+ ASSERT(m_execMemAllocator != NULL);
+ if (m_execMemAllocator == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+#endif
+
+ return S_OK;
+}
+
+// Only use canaries on x86 b/c they throw of alignment on Ia64.
+#if defined(_DEBUG) && defined(_TARGET_X86_)
+#define USE_INTEROPSAFE_CANARY
+#endif
+
+#ifdef USE_INTEROPSAFE_CANARY
+// Small header to to prefix interop-heap blocks.
+// This lets us enforce that we don't delete interopheap data from a non-interop heap.
+struct InteropHeapCanary
+{
+ ULONGLONG m_canary;
+
+ // Raw address - this is what the heap alloc + free routines use.
+ // User address - this is what the user sees after we adjust the raw address for the canary
+
+ // Given a raw address to an allocated block, get the canary + mark it.
+ static InteropHeapCanary * GetFromRawAddr(void * pStart)
+ {
+ _ASSERTE(pStart != NULL);
+ InteropHeapCanary * p = (InteropHeapCanary*) pStart;
+ p->Mark();
+ return p;
+ }
+
+ // Get the raw address from this canary.
+ void * GetRawAddr()
+ {
+ return (void*) this;
+ }
+
+ // Get a canary from a start address.
+ static InteropHeapCanary * GetFromUserAddr(void * pStart)
+ {
+ _ASSERTE(pStart != NULL);
+ InteropHeapCanary * p = ((InteropHeapCanary*) pStart)-1;
+ p->Check();
+ return p;
+ }
+ void * GetUserAddr()
+ {
+ this->Check();
+ return (void*) (this + 1);
+ }
+
+protected:
+ void Check()
+ {
+ CONSISTENCY_CHECK_MSGF((m_canary == kInteropHeapCookie),
+ ("Using InteropSafe delete on non-interopsafe allocated memory.\n"));
+ }
+ void Mark()
+ {
+ m_canary = kInteropHeapCookie;
+ }
+ static const ULONGLONG kInteropHeapCookie = 0x12345678;
+};
+#endif // USE_INTEROPSAFE_CANARY
+
+void *DebuggerHeap::Alloc(DWORD size)
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+#ifdef USE_INTEROPSAFE_CANARY
+ // Make sure we allocate enough space for the canary at the start.
+ size += sizeof(InteropHeapCanary);
+#endif
+
+ void *ret;
+#ifdef USE_INTEROPSAFE_HEAP
+ _ASSERTE(m_hHeap != NULL);
+ ret = ::HeapAlloc(m_hHeap, HEAP_ZERO_MEMORY, size);
+#else // USE_INTEROPSAFE_HEAP
+
+ bool allocateOnHeap = true;
+ HANDLE hExecutableHeap = NULL;
+
+#ifdef FEATURE_PAL
+ if (m_fExecutable)
+ {
+ allocateOnHeap = false;
+ ret = m_execMemAllocator->Allocate(size);
+ }
+ else
+ {
+ hExecutableHeap = ClrGetProcessHeap();
+ }
+#else // FEATURE_PAL
+ hExecutableHeap = ClrGetProcessExecutableHeap();
+#endif
+
+ if (allocateOnHeap)
+ {
+ if (hExecutableHeap == NULL)
+ {
+ return NULL;
+ }
+
+ ret = ClrHeapAlloc(hExecutableHeap, NULL, S_SIZE_T(size));
+ }
+
+#endif // USE_INTEROPSAFE_HEAP
+
+#ifdef USE_INTEROPSAFE_CANARY
+ if (ret == NULL)
+ {
+ return NULL;
+ }
+ InteropHeapCanary * pCanary = InteropHeapCanary::GetFromRawAddr(ret);
+ ret = pCanary->GetUserAddr();
+#endif
+
+ return ret;
+}
+
+// Realloc memory.
+// If this fails, the original memory is still valid.
+void *DebuggerHeap::Realloc(void *pMem, DWORD newSize, DWORD oldSize)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(pMem != NULL);
+ _ASSERTE(newSize != 0);
+ _ASSERTE(oldSize != 0);
+
+#if defined(USE_INTEROPSAFE_HEAP) && !defined(USE_INTEROPSAFE_CANARY) && !defined(FEATURE_PAL)
+ // No canaries in this case.
+ // Call into realloc.
+ void *ret;
+
+ _ASSERTE(m_hHeap != NULL);
+ ret = ::HeapReAlloc(m_hHeap, HEAP_ZERO_MEMORY, pMem, newSize);
+#else
+ // impl Realloc on top of alloc & free.
+ void *ret;
+
+ ret = this->Alloc(newSize);
+ if (ret == NULL)
+ {
+ // Not supposed to free original memory in failure condition.
+ return NULL;
+ }
+
+ memcpy(ret, pMem, oldSize);
+ this->Free(pMem);
+#endif
+
+ return ret;
+}
+
+void DebuggerHeap::Free(void *pMem)
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+#ifdef USE_INTEROPSAFE_CANARY
+ // Check for canary
+
+ if (pMem != NULL)
+ {
+ InteropHeapCanary * pCanary = InteropHeapCanary::GetFromUserAddr(pMem);
+ pMem = pCanary->GetRawAddr();
+ }
+#endif
+
+#ifdef USE_INTEROPSAFE_HEAP
+ if (pMem != NULL)
+ {
+ _ASSERTE(m_hHeap != NULL);
+ ::HeapFree(m_hHeap, 0, pMem);
+ }
+#else
+ if (pMem != NULL)
+ {
+#ifndef FEATURE_PAL
+ HANDLE hProcessExecutableHeap = ClrGetProcessExecutableHeap();
+ _ASSERTE(hProcessExecutableHeap != NULL);
+ ClrHeapFree(hProcessExecutableHeap, NULL, pMem);
+#else // !FEATURE_PAL
+ if(!m_fExecutable)
+ {
+ HANDLE hProcessHeap = ClrGetProcessHeap();
+ _ASSERTE(hProcessHeap != NULL);
+ ClrHeapFree(hProcessHeap, NULL, pMem);
+ }
+ else
+ {
+ INDEBUG(int ret =) m_execMemAllocator->Free(pMem);
+ _ASSERTE(ret == 0);
+ }
+#endif // !FEATURE_PAL
+ }
+#endif
+}
+
+#ifndef DACCESS_COMPILE
+
+
+// Undef this so we can call them from the EE versions.
+#undef UtilMessageBoxVA
+
+// Message box API for the left side of the debugger. This API handles calls from the
+// debugger helper thread as well as from normal EE threads. It is the only one that
+// should be used from inside the debugger left side.
+int Debugger::MessageBox(
+ UINT uText, // Resource Identifier for Text message
+ UINT uCaption, // Resource Identifier for Caption
+ UINT uType, // Style of MessageBox
+ BOOL displayForNonInteractive, // Display even if the process is running non interactive
+ BOOL showFileNameInTitle, // Flag to show FileName in Caption
+ ...) // Additional Arguments
+{
+ CONTRACTL
+ {
+ MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT;
+ MODE_PREEMPTIVE;
+ NOTHROW;
+
+ PRECONDITION(ThisMaybeHelperThread());
+ }
+ CONTRACTL_END;
+
+ va_list marker;
+ va_start(marker, showFileNameInTitle);
+
+ // Add the MB_TASKMODAL style to indicate that the dialog should be displayed on top of the windows
+ // owned by the current thread and should prevent interaction with them until dismissed.
+ uType |= MB_TASKMODAL;
+
+ int result = UtilMessageBoxVA(NULL, uText, uCaption, uType, displayForNonInteractive, showFileNameInTitle, marker);
+ va_end( marker );
+
+ return result;
+}
+
+// Redefine this to an error just in case code is added after this point in the file.
+#define UtilMessageBoxVA __error("Use g_pDebugger->MessageBox from inside the left side of the debugger")
+
+#else // DACCESS_COMPILE
+void
+Debugger::EnumMemoryRegions(CLRDataEnumMemoryFlags flags)
+{
+ DAC_ENUM_VTHIS();
+ SUPPORTS_DAC;
+ _ASSERTE(m_rgHijackFunction != NULL);
+
+ if ( flags != CLRDATA_ENUM_MEM_TRIAGE)
+ {
+ if (m_pMethodInfos.IsValid())
+ {
+ m_pMethodInfos->EnumMemoryRegions(flags);
+ }
+
+ DacEnumMemoryRegion(dac_cast<TADDR>(m_pLazyData),
+ sizeof(DebuggerLazyInit));
+ }
+
+ // Needed for stack walking from an initial native context. If the debugger can find the
+ // on-disk image of clr.dll, then this is not necessary.
+ DacEnumMemoryRegion(dac_cast<TADDR>(m_rgHijackFunction), sizeof(MemoryRange)*kMaxHijackFunctions);
+}
+
+
+// This code doesn't hang out in Frame/TransitionFrame/FuncEvalFrame::EnumMemoryRegions() like it would
+// for other normal VM objects because we don't want to have code in VM directly referencing LS types.
+// Frames.h's FuncEvalFrame simply does a forward decl of DebuggerEval and gets away with it because it
+// never does anything but a cast of a TADDR.
+void
+Debugger::EnumMemoryRegionsIfFuncEvalFrame(CLRDataEnumMemoryFlags flags, Frame * pFrame)
+{
+ SUPPORTS_DAC;
+
+ if ((pFrame != NULL) && (pFrame->GetFrameType() == Frame::TYPE_FUNC_EVAL))
+ {
+ FuncEvalFrame * pFEF = dac_cast<PTR_FuncEvalFrame>(pFrame);
+ DebuggerEval * pDE = pFEF->GetDebuggerEval();
+
+ if (pDE != NULL)
+ {
+ DacEnumMemoryRegion(dac_cast<TADDR>(pDE), sizeof(DebuggerEval), true);
+
+ if (pDE->m_debuggerModule != NULL)
+ DacEnumMemoryRegion(dac_cast<TADDR>(pDE->m_debuggerModule), sizeof(DebuggerModule), true);
+ }
+ }
+}
+
+#endif // #ifdef DACCESS_COMPILE
+
+#ifndef DACCESS_COMPILE
+void Debugger::StartCanaryThread()
+{
+ // we need to already have the rcthread running and the pointer stored
+ _ASSERTE(m_pRCThread != NULL && g_pRCThread == m_pRCThread);
+ _ASSERTE(m_pRCThread->GetDCB() != NULL);
+ _ASSERTE(GetCanary() != NULL);
+
+ GetCanary()->Init();
+}
+#endif // DACCESS_COMPILE
+
+#endif //DEBUGGING_SUPPORTED
diff --git a/src/debug/ee/debugger.h b/src/debug/ee/debugger.h
new file mode 100644
index 0000000000..6368647946
--- /dev/null
+++ b/src/debug/ee/debugger.h
@@ -0,0 +1,3981 @@
+// 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.
+//*****************************************************************************
+// File: debugger.h
+//
+
+//
+// Header file for Runtime Controller classes of the COM+ Debugging Services.
+//
+//*****************************************************************************
+
+#ifndef DEBUGGER_H_
+#define DEBUGGER_H_
+
+#include <windows.h>
+
+#include <utilcode.h>
+
+#include <metahost.h>
+
+#if defined(_DEBUG) && !defined(DACCESS_COMPILE)
+#define LOGGING
+#endif
+
+#include <log.h>
+
+#include "cor.h"
+#include "corpriv.h"
+#include "daccess.h"
+
+#include "common.h"
+#include "winwrap.h"
+#include "threads.h"
+#include "frames.h"
+
+#include "appdomain.hpp"
+#include "eedbginterface.h"
+#include "dbginterface.h"
+#include "corhost.h"
+
+
+#include "corjit.h"
+#include <dbgmeta.h> // <TODO>need to rip this out of here...</TODO>
+
+#include "frameinfo.h"
+
+#include "dllimportcallback.h"
+
+#include "canary.h"
+
+#undef ASSERT
+#define CRASH(x) _ASSERTE(!x)
+#define ASSERT(x) _ASSERTE(x)
+
+
+#ifndef TRACE_MEMORY
+#define TRACE_MEMORY 0
+#endif
+
+#if TRACE_MEMORY
+#define TRACE_ALLOC(p) LOG((LF_CORDB, LL_INFO10000, \
+ "--- Allocated %x at %s:%d\n", p, __FILE__, __LINE__));
+#define TRACE_FREE(p) LOG((LF_CORDB, LL_INFO10000, \
+ "--- Freed %x at %s:%d\n", p, __FILE__, __LINE__));
+#else
+#define TRACE_ALLOC(p)
+#define TRACE_FREE(p)
+#endif
+
+typedef CUnorderedArray<void*,11> UnorderedPtrArray;
+
+/* ------------------------------------------------------------------------ *
+ * Forward class declarations
+ * ------------------------------------------------------------------------ */
+
+class DebuggerFrame;
+class DebuggerModule;
+class DebuggerModuleTable;
+class Debugger;
+class DebuggerBreakpoint;
+class DebuggerPendingFuncEvalTable;
+class DebuggerRCThread;
+class DebuggerStepper;
+class DebuggerMethodInfo;
+class DebuggerJitInfo;
+class DebuggerMethodInfoTable;
+struct DebuggerControllerPatch;
+class DebuggerEval;
+class DebuggerControllerQueue;
+class DebuggerController;
+class Crst;
+
+typedef CUnorderedArray<DebuggerControllerPatch *, 17> PATCH_UNORDERED_ARRAY;
+template<class T> void DeleteInteropSafe(T *p);
+template<class T> void DeleteInteropSafeExecutable(T *p);
+
+typedef VPTR(class Debugger) PTR_Debugger;
+typedef DPTR(struct DebuggerILToNativeMap) PTR_DebuggerILToNativeMap;
+typedef DPTR(class DebuggerMethodInfo) PTR_DebuggerMethodInfo;
+typedef VPTR(class DebuggerMethodInfoTable) PTR_DebuggerMethodInfoTable;
+typedef DPTR(class DebuggerJitInfo) PTR_DebuggerJitInfo;
+typedef DPTR(class DebuggerEval) PTR_DebuggerEval;
+typedef DPTR(struct DebuggerIPCControlBlock) PTR_DebuggerIPCControlBlock;
+
+
+/* ------------------------------------------------------------------------ *
+ * Global variables
+ * ------------------------------------------------------------------------ */
+
+GPTR_DECL(Debugger, g_pDebugger);
+GPTR_DECL(EEDebugInterface, g_pEEInterface);
+#ifndef FEATURE_PAL
+GVAL_DECL(HANDLE, g_hContinueStartupEvent);
+#endif
+extern DebuggerRCThread *g_pRCThread;
+
+//---------------------------------------------------------------------------------------
+// Holder to ensure our calls to IncThreadsAtUnsafePlaces and DecThreadsAtUnsafePlaces
+class AtSafePlaceHolder
+{
+public:
+ AtSafePlaceHolder(Thread * pThread);
+
+ // Clear the holder.
+ ~AtSafePlaceHolder();
+
+ // True if the holder is acquired.
+ bool IsAtUnsafePlace();
+
+ // Clear the holder (call DecThreadsAtUnsafePlaces if needed)
+ void Clear();
+
+private:
+ // If this is non-null, then the holder incremented the unsafe counter and it needs
+ // to decrement it.
+ Thread * m_pThreadAtUnsafePlace;
+};
+
+
+template<BOOL COOPERATIVE, BOOL TOGGLE, BOOL IFTHREAD>
+class GCHolderEEInterface
+{
+public:
+ DEBUG_NOINLINE GCHolderEEInterface();
+ DEBUG_NOINLINE ~GCHolderEEInterface();
+};
+
+#ifndef DACCESS_COMPILE
+template<BOOL TOGGLE, BOOL IFTHREAD>
+class GCHolderEEInterface<TRUE, TOGGLE, IFTHREAD>
+{
+private:
+ bool startInCoop;
+
+public:
+ DEBUG_NOINLINE GCHolderEEInterface()
+ {
+ SCAN_SCOPE_BEGIN;
+ STATIC_CONTRACT_MODE_COOPERATIVE;
+
+ if (IFTHREAD && g_pEEInterface->GetThread() == NULL)
+ {
+ return;
+ }
+
+ startInCoop = false;
+
+ if (g_pEEInterface->IsPreemptiveGCDisabled())
+ {
+ // we're starting in COOP, no need to switch
+ startInCoop = true;
+ }
+ else
+ {
+ // we're starting in PREEMP, need to switch to COOP
+ startInCoop = false;
+ g_pEEInterface->DisablePreemptiveGC();
+ }
+ };
+
+ DEBUG_NOINLINE ~GCHolderEEInterface()
+ {
+ SCAN_SCOPE_END;
+
+ if (IFTHREAD && g_pEEInterface->GetThread() == NULL)
+ {
+ return;
+ }
+
+ _ASSERT(g_pEEInterface->IsPreemptiveGCDisabled());
+
+ if (TOGGLE)
+ {
+ // We're in COOP, toggle to PREEMPTIVE and back to COOP
+ // for synch purposes.
+ g_pEEInterface->EnablePreemptiveGC();
+ g_pEEInterface->DisablePreemptiveGC();
+
+ // If we started in PREEMPTIVE switch back
+ if (!startInCoop)
+ {
+ g_pEEInterface->EnablePreemptiveGC();
+ }
+ }
+ else
+ {
+ // If we started in PREEMPTIVE switch back
+ if (!startInCoop)
+ {
+ g_pEEInterface->EnablePreemptiveGC();
+ }
+ }
+ };
+};
+
+template<BOOL TOGGLE, BOOL IFTHREAD>
+class GCHolderEEInterface<FALSE, TOGGLE, IFTHREAD>
+{
+private:
+ bool startInCoop;
+ bool conditional;
+
+ void EnterInternal(bool bStartInCoop, bool bConditional)
+ {
+ startInCoop = bStartInCoop;
+ conditional = bConditional;
+
+ if (!conditional || (IFTHREAD && g_pEEInterface->GetThread() == NULL))
+ {
+ return;
+ }
+
+ if (g_pEEInterface->IsPreemptiveGCDisabled())
+ {
+ // we're starting in COOP, we need to switch to PREEMP
+ startInCoop = true;
+ g_pEEInterface->EnablePreemptiveGC();
+ }
+ else
+ {
+ // We're starting in PREEMP, no need to switch
+ startInCoop = false;
+ }
+ }
+
+ void LeaveInternal()
+ {
+ if (!conditional || (IFTHREAD && g_pEEInterface->GetThread() == NULL))
+ {
+ return;
+ }
+
+ _ASSERTE(!g_pEEInterface->IsPreemptiveGCDisabled());
+
+ if (TOGGLE)
+ {
+ // Explicitly toggle to COOP for eventin
+ g_pEEInterface->DisablePreemptiveGC();
+
+ // If we started in PREEMPTIVE switch back to PREEMPTIVE
+ if (!startInCoop)
+ {
+ g_pEEInterface->EnablePreemptiveGC();
+ }
+ }
+ else
+ {
+ // If we started in COOP, flip back to COOP at the end of the
+ // scope, if we started in preemptive we should be fine.
+ if (startInCoop)
+ {
+ g_pEEInterface->DisablePreemptiveGC();
+ }
+ }
+ }
+
+public:
+ DEBUG_NOINLINE GCHolderEEInterface()
+ {
+ SCAN_SCOPE_BEGIN;
+ STATIC_CONTRACT_MODE_PREEMPTIVE;
+
+ this->EnterInternal(false, true);
+ }
+
+ DEBUG_NOINLINE GCHolderEEInterface(bool bConditional)
+ {
+ SCAN_SCOPE_BEGIN;
+ if (bConditional)
+ {
+ STATIC_CONTRACT_MODE_PREEMPTIVE;
+ }
+
+ this->EnterInternal(false, bConditional);
+ }
+
+ DEBUG_NOINLINE ~GCHolderEEInterface()
+ {
+ SCAN_SCOPE_END;
+
+ this->LeaveInternal();
+ };
+};
+#endif //DACCESS_COMPILE
+
+#define GCX_COOP_EEINTERFACE() \
+ GCHolderEEInterface<TRUE, FALSE, FALSE> __gcCoop_onlyOneAllowedPerScope
+
+#define GCX_PREEMP_EEINTERFACE() \
+ GCHolderEEInterface<FALSE, FALSE, FALSE> __gcCoop_onlyOneAllowedPerScope
+
+#define GCX_COOP_EEINTERFACE_TOGGLE() \
+ GCHolderEEInterface<TRUE, TRUE, FALSE> __gcCoop_onlyOneAllowedPerScope
+
+#define GCX_PREEMP_EEINTERFACE_TOGGLE() \
+ GCHolderEEInterface<FALSE, TRUE, FALSE> __gcCoop_onlyOneAllowedPerScope
+
+#define GCX_PREEMP_EEINTERFACE_TOGGLE_IFTHREAD() \
+ GCHolderEEInterface<FALSE, TRUE, TRUE> __gcCoop_onlyOneAllowedPerScope
+
+#define GCX_PREEMP_EEINTERFACE_TOGGLE_COND(cond) \
+ GCHolderEEInterface<FALSE, TRUE, FALSE> __gcCoop_onlyOneAllowedPerScope((cond))
+
+#define GCX_PREEMP_EEINTERFACE_TOGGLE_IFTHREAD_COND(cond) \
+ GCHolderEEInterface<FALSE, TRUE, TRUE> __gcCoop_onlyOneAllowedPerScope((cond))
+
+
+
+// There are still some APIs that call new that we call from the helper thread.
+// These are unsafe operations, so we wrap them here. Each of these is a potential hang.
+inline DWORD UnsafeGetConfigDWORD_DontUse_(LPCWSTR name, DWORD defValue)
+{
+ SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE;
+ return REGUTIL::GetConfigDWORD_DontUse_(name, defValue);
+}
+
+inline DWORD UnsafeGetConfigDWORD(const CLRConfig::ConfigDWORDInfo & info)
+{
+ SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE;
+ return CLRConfig::GetConfigValue(info);
+}
+
+#define FILE_DEBUG INDEBUG(__FILE__) NOT_DEBUG(NULL)
+#define LINE_DEBUG INDEBUG(__LINE__) NOT_DEBUG(0)
+
+#define CORDBDebuggerSetUnrecoverableWin32Error(__d, __code, __w) \
+ ((__d)->UnrecoverableError(HRESULT_FROM_WIN32(GetLastError()), \
+ (__code), FILE_DEBUG, LINE_DEBUG, (__w)), \
+ HRESULT_FROM_GetLastError())
+
+#define CORDBDebuggerSetUnrecoverableError(__d, __hr, __w) \
+ (__d)->UnrecoverableError((__hr), \
+ (__hr), FILE_DEBUG, LINE_DEBUG, (__w))
+
+#define CORDBUnrecoverableError(__d) ((__d)->m_unrecoverableError == TRUE)
+
+/* ------------------------------------------------------------------------ *
+ * Helpers used for contract preconditions.
+ * ------------------------------------------------------------------------ */
+
+
+bool ThisIsHelperThreadWorker(void);
+bool ThisIsTempHelperThread();
+bool ThisIsTempHelperThread(DWORD tid);
+
+#ifdef _DEBUG
+
+// Functions can be split up into 3 categories:
+// 1.) Functions that must run on the helper thread.
+// Returns true if this is the helper thread (or the thread
+// doing helper-threadduty).
+
+// 2.) Functions that can't run on the helper thread.
+// This is just !ThisIsHelperThread();
+
+// 3.) Functions that may or may not run on the helper thread.
+// Note this is trivially true, but it's presences means that
+// we're not case #1 or #2, so it's still valuable.
+inline bool ThisMaybeHelperThread() { return true; }
+
+#endif
+
+
+// These are methods for transferring information between a REGDISPLAY and
+// a DebuggerREGDISPLAY.
+extern void CopyREGDISPLAY(REGDISPLAY* pDst, REGDISPLAY* pSrc);
+extern void SetDebuggerREGDISPLAYFromREGDISPLAY(DebuggerREGDISPLAY* pDRD, REGDISPLAY* pRD);
+
+//
+// PUSHED_REG_ADDR gives us NULL if the register still lives in the thread's context, or it gives us the address
+// of where the register was pushed for this frame.
+//
+// This macro is used in CopyREGDISPLAY() and SetDebuggerREGDISPLAYFromREGDISPLAY(). We really should make
+// DebuggerREGDISPLAY to be a class with these two methods, but unfortunately, the RS has no notion of REGDISPLAY.
+inline LPVOID PushedRegAddr(REGDISPLAY* pRD, LPVOID pAddr)
+{
+ LIMITED_METHOD_CONTRACT;
+
+#if defined(_TARGET_AMD64_)
+ if ( ((UINT_PTR)(pAddr) >= (UINT_PTR)pRD->pCurrentContextPointers) &&
+ ((UINT_PTR)(pAddr) <= ((UINT_PTR)pRD->pCurrentContextPointers + sizeof(_KNONVOLATILE_CONTEXT_POINTERS))) )
+#else
+ if ( ((UINT_PTR)(pAddr) >= (UINT_PTR)pRD->pContext) &&
+ ((UINT_PTR)(pAddr) <= ((UINT_PTR)pRD->pContext + sizeof(T_CONTEXT))) )
+#endif
+ return NULL;
+
+ // (Microsoft 2/9/07 - putting this in an else clause confuses gcc for some reason, so I've moved
+ // it to here)
+ return pAddr;
+}
+
+bool HandleIPCEventWrapper(Debugger* pDebugger, DebuggerIPCEvent *e);
+
+HRESULT ValidateObject(Object *objPtr);
+
+//-----------------------------------------------------------------------------
+// Execution control needs several ways to get at the context of a thread
+// stopped in mangaged code (stepping, setip, func-eval).
+// We want to abstract away a few things:
+// - active: this thread is stopped at a patch
+// - inactive: this threads was managed suspended somewhere in jitted code
+// because of some other active thread.
+//
+// In general, execution control operations administered from the helper thread
+// can occur on any managed thread (active or inactive).
+// Intermediate triggers (eg, TriggerPatch) only occur on an active thread.
+//
+// Viewing the context in terms of Active vs. Inactive lets us abstract away
+// filter context, redirected context, and interop hijacks.
+//-----------------------------------------------------------------------------
+
+// Get the context for a thread stopped (perhaps temporarily) in managed code.
+// The process may be live or stopped.
+// This thread could be 'active' (stopped at patch) or inactive.
+// This context should always be in managed code and this context can be manipulated
+// for execution control (setip, single-step, func-eval, etc)
+// Returns NULL if not available.
+CONTEXT * GetManagedStoppedCtx(Thread * pThread);
+
+// Get the context for a thread live in or around managed code.
+// Caller guarantees this is active.
+// This ctx is just for a 'live' thread. This means that the ctx may include
+// from a M2U hijack or from a Native patch (like .
+// Never NULL.
+CONTEXT * GetManagedLiveCtx(Thread * pThread);
+
+
+#undef UtilMessageBoxCatastrophic
+#undef UtilMessageBoxCatastrophicNonLocalized
+#undef UtilMessageBoxCatastrophicVA
+#undef UtilMessageBoxCatastrophicNonLocalizedVA
+#undef UtilMessageBox
+#undef UtilMessageBoxNonLocalized
+#undef UtilMessageBoxVA
+#undef UtilMessageBoxNonLocalizedVA
+#undef WszMessageBox
+#define UtilMessageBoxCatastrophic __error("Use g_pDebugger->MessageBox from inside the left side of the debugger")
+#define UtilMessageBoxCatastrophicNonLocalized __error("Use g_pDebugger->MessageBox from inside the left side of the debugger")
+#define UtilMessageBoxCatastrophicVA __error("Use g_pDebugger->MessageBox from inside the left side of the debugger")
+#define UtilMessageBoxCatastrophicNonLocalizedVA __error("Use g_pDebugger->MessageBox from inside the left side of the debugger")
+#define UtilMessageBox __error("Use g_pDebugger->MessageBox from inside the left side of the debugger")
+#define UtilMessageBoxNonLocalized __error("Use g_pDebugger->MessageBox from inside the left side of the debugger")
+#define UtilMessageBoxVA __error("Use g_pDebugger->MessageBox from inside the left side of the debugger")
+#define UtilMessageBoxNonLocalizedVA __error("Use g_pDebugger->MessageBox from inside the left side of the debugger")
+#define WszMessageBox __error("Use g_pDebugger->MessageBox from inside the left side of the debugger")
+
+
+/* ------------------------------------------------------------------------ *
+ * Module classes
+ * ------------------------------------------------------------------------ */
+
+// Once a module / appdomain is unloaded, all Right-side objects (such as breakpoints)
+// in that appdomain will get neutered and will thus be prevented from accessing
+// the unloaded appdomain.
+//
+// @dbgtodo jmc - This is now purely relegated to the LS. Eventually completely get rid of this
+// by moving fields off to Module or getting rid of the fields completely.
+typedef DPTR(class DebuggerModule) PTR_DebuggerModule;
+class DebuggerModule
+{
+ public:
+ DebuggerModule(Module * pRuntimeModule, DomainFile * pDomainFile, AppDomain * pAppDomain);
+
+ // Do we have any optimized code in the module?
+ // JMC-probes aren't emitted in optimized code,
+ bool HasAnyOptimizedCode();
+
+ // If the debugger updates things to allow/disallow optimized code, then we have to track that.
+ void MarkAllowedOptimizedCode();
+ void UnmarkAllowedOptimizedCode();
+
+
+ BOOL ClassLoadCallbacksEnabled(void);
+ void EnableClassLoadCallbacks(BOOL f);
+
+ AppDomain* GetAppDomain();
+
+ Module * GetRuntimeModule();
+
+
+ // <TODO> (8/12/2002)
+ // Currently we create a new DebuggerModules for each appdomain a shared
+ // module lives in. We then pretend there aren't any shared modules.
+ // This is bad. We need to move away from this.
+ // Once we stop lying, then every module will be it's own PrimaryModule. :)
+ //
+ // Currently, Module* is 1:n w/ DebuggerModule.
+ // We add a notion of PrimaryModule so that:
+ // Module* is 1:1 w/ DebuggerModule::GetPrimaryModule();
+ // This should help transition towards exposing shared modules.
+ // If the Runtime module is shared, then this gives a common DM.
+ // If the runtime module is not shared, then this is an identity function.
+ //
+ // The runtime has the notion of "DomainFile", which is 1:1 with DebuggerModule
+ // and thus 1:1 with CordbModule. The CordbModule hash table on the RS now uses
+ // the DomainFile as the key instead of DebuggerModule. This is a temporary
+ // workaround to facilitate the removal of DebuggerModule.
+ // </TODO>
+ DebuggerModule * GetPrimaryModule();
+ DomainFile * GetDomainFile()
+ {
+ LIMITED_METHOD_DAC_CONTRACT;
+ return m_pRuntimeDomainFile;
+ }
+
+ // Called by DebuggerModuleTable to set our primary module
+ void SetPrimaryModule(DebuggerModule * pPrimary);
+
+ void SetCanChangeJitFlags(bool fCanChangeJitFlags);
+
+ private:
+ BOOL m_enableClassLoadCallbacks;
+
+ // First step in moving away from hiding shared modules.
+ DebuggerModule* m_pPrimaryModule;
+
+ PTR_Module m_pRuntimeModule;
+ PTR_DomainFile m_pRuntimeDomainFile;
+
+ AppDomain* m_pAppDomain;
+
+ bool m_fHasOptimizedCode;
+
+ void PickPrimaryModule();
+
+ // Can we change jit flags on the module?
+ // This is true during the Module creation
+ bool m_fCanChangeJitFlags;
+
+
+};
+
+/* ------------------------------------------------------------------------ *
+ * Hash to hold pending func evals by thread id
+ * ------------------------------------------------------------------------ */
+
+struct DebuggerPendingFuncEval
+{
+ FREEHASHENTRY entry;
+ PTR_Thread pThread;
+ PTR_DebuggerEval pDE;
+};
+
+typedef DPTR(struct DebuggerPendingFuncEval) PTR_DebuggerPendingFuncEval;
+
+/* ------------------------------------------------------------------------ *
+ * DebuggerRCThread class -- the Runtime Controller thread.
+ * ------------------------------------------------------------------------ */
+
+#define DRCT_CONTROL_EVENT 0
+#define DRCT_RSEA 1
+#define DRCT_FAVORAVAIL 2
+#define DRCT_COUNT_INITIAL 3
+
+#define DRCT_DEBUGGER_EVENT 3
+#define DRCT_COUNT_FINAL 4
+
+
+
+
+
+
+// Canary is used as way to have a runtime failure for the SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE
+// contract violation.
+// Have a macro which checks the canary and then uses the Suppress macro.
+// We need this check to be a macro in order to chain to the Suppress_allocation macro.
+#define CHECK_IF_CAN_TAKE_HELPER_LOCKS_IN_THIS_SCOPE(pHR, pCanary) \
+ { \
+ HelperCanary * __pCanary = (pCanary); \
+ if (!__pCanary->AreLocksAvailable()) { \
+ (*pHR) = CORDBG_E_HELPER_MAY_DEADLOCK; \
+ } else { \
+ (*pHR) = S_OK; \
+ } \
+ } \
+ SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE \
+ ; \
+
+
+// Mechanics for cross-thread call to helper thread (called "Favor").
+class HelperThreadFavor
+{
+ // Only let RCThread access these fields.
+ friend class DebuggerRCThread;
+
+ HelperThreadFavor();
+ // No dtor because we intentionally leak all shutdown.
+ void Init();
+
+protected:
+ // Stuff for having the helper thread do function calls for a thread
+ // that blew its stack
+ FAVORCALLBACK m_fpFavor;
+ void *m_pFavorData;
+ HANDLE m_FavorReadEvent;
+ Crst m_FavorLock;
+
+ HANDLE m_FavorAvailableEvent;
+};
+
+
+// The *LazyInit classes represents storage that the debugger doesn't need until after it has started up.
+// This is effectively an extension to the debugger class; but for perf reasons, we only
+// want to instantiate it if we're actually debugging.
+
+// Fields that are a logical extension of RCThread
+class RCThreadLazyInit
+{
+ // Only let RCThread access these fields.
+ friend class DebuggerRCThread;
+
+public:
+ RCThreadLazyInit() { }
+ ~RCThreadLazyInit() { }
+
+ void Init() { }
+protected:
+
+
+
+ HelperCanary m_Canary;
+};
+
+// Fields that are a logical extension of Debugger
+class DebuggerLazyInit
+{
+ friend class Debugger;
+public:
+ DebuggerLazyInit();
+ ~DebuggerLazyInit();
+
+protected:
+ void Init();
+
+ DebuggerPendingFuncEvalTable *m_pPendingEvals;
+
+ // The "debugger data lock" is a very small leaf lock used to protect debugger internal data structures (such
+ // as DJIs, DMIs, module table). It is a GC-unsafe-anymode lock and so it can't trigger a GC while being held.
+ // It also can't issue any callbacks into the EE or anycode that it does not directly control.
+ // This is a separate lock from the the larger Debugger-lock / Controller lock, which allows regions under those
+ // locks to access debugger datastructures w/o blocking each other.
+ Crst m_DebuggerDataLock;
+ HANDLE m_CtrlCMutex;
+ HANDLE m_exAttachEvent;
+ HANDLE m_exUnmanagedAttachEvent;
+
+ BOOL m_DebuggerHandlingCtrlC;
+
+ // Used by MapAndBindFunctionBreakpoints. Note that this is thread-safe
+ // only b/c we access it from within the DebuggerController::Lock
+ SIZE_T_UNORDERED_ARRAY m_BPMappingDuplicates;
+
+ UnorderedPtrArray m_pMemBlobs;
+
+ // Hang RCThread fields off DebuggerLazyInit to avoid an extra pointer.
+ RCThreadLazyInit m_RCThread;
+};
+typedef DPTR(DebuggerLazyInit) PTR_DebuggerLazyInit;
+
+class DebuggerRCThread
+{
+public:
+ DebuggerRCThread(Debugger * pDebugger);
+ virtual ~DebuggerRCThread();
+ void CloseIPCHandles();
+
+ //
+ // You create a new instance of this class, call Init() to set it up,
+ // then call Start() start processing events. Stop() terminates the
+ // thread and deleting the instance cleans all the handles and such
+ // up.
+ //
+ HRESULT Init(void);
+ HRESULT Start(void);
+ HRESULT AsyncStop(void);
+
+ //
+ // These are used by this thread to send IPC events to the Debugger
+ // Interface side.
+ //
+ DebuggerIPCEvent* GetIPCEventSendBuffer()
+ {
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+#ifdef LOGGING
+ if(IsRCThreadReady()) {
+ LOG((LF_CORDB, LL_EVERYTHING, "RCThread is ready\n"));
+ }
+#endif
+
+ _ASSERTE(m_pDCB != NULL);
+ // In case this turns into a continuation event
+ GetRCThreadSendBuffer()->next = NULL;
+ LOG((LF_CORDB,LL_EVERYTHING, "GIPCESBuffer: got event 0x%x\n", GetRCThreadSendBuffer()));
+
+ return GetRCThreadSendBuffer();
+ }
+
+ DebuggerIPCEvent *GetIPCEventSendBufferContinuation(
+ DebuggerIPCEvent *eventCur)
+ {
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ PRECONDITION(eventCur != NULL);
+ PRECONDITION(eventCur->next == NULL);
+ }
+ CONTRACTL_END;
+
+ DebuggerIPCEvent *dipce = (DebuggerIPCEvent *) new (nothrow) BYTE [CorDBIPC_BUFFER_SIZE];
+ dipce->next = NULL;
+
+ LOG((LF_CORDB,LL_INFO1000000, "About to GIPCESBC 0x%x\n",dipce));
+
+ if (dipce != NULL)
+ {
+ eventCur->next = dipce;
+ }
+#ifdef _DEBUG
+ else
+ {
+ _ASSERTE( !"GetIPCEventSendBufferContinuation failed to allocate mem!" );
+ }
+#endif //_DEBUG
+
+ return dipce;
+ }
+
+ // Send an IPCEvent once we're ready for sending. This should be done inbetween
+ // SENDIPCEVENT_BEGIN & SENDIPCEVENT_END. See definition of SENDIPCEVENT_BEGIN
+ // for usage pattern
+ HRESULT SendIPCEvent();
+
+ HRESULT EnsureRuntimeOffsetsInit(IpcTarget i); // helper function for SendIPCEvent
+ void NeedRuntimeOffsetsReInit(IpcTarget i);
+
+ DebuggerIPCEvent* GetIPCEventReceiveBuffer()
+ {
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+ _ASSERTE(m_pDCB != NULL);
+
+ return GetRCThreadReceiveBuffer();
+ }
+
+ HRESULT SendIPCReply();
+
+ //
+ // Handle Favors - get the Helper thread to do a function call for us
+ // because our thread can't (eg, we don't have the stack space)
+ // DoFavor will call (*fp)(pData) and block until fp returns.
+ // pData can store parameters, return value, and a this ptr (if we
+ // need to call a member function)
+ //
+ void DoFavor(FAVORCALLBACK fp, void * pData);
+
+ //
+ // Convience routines
+ //
+ PTR_DebuggerIPCControlBlock GetDCB()
+ {
+ LIMITED_METHOD_DAC_CONTRACT;
+ // This may be called before we init or after we shutdown.
+
+ return m_pDCB;
+ }
+
+ void WatchForStragglers(void);
+
+ HRESULT SetupRuntimeOffsets(DebuggerIPCControlBlock *pDCB);
+
+ bool HandleRSEA();
+ void MainLoop();
+ void TemporaryHelperThreadMainLoop();
+
+ HANDLE GetHelperThreadCanGoEvent(void) {LIMITED_METHOD_CONTRACT; return m_helperThreadCanGoEvent; }
+
+ void EarlyHelperThreadDeath(void);
+
+ void RightSideDetach(void);
+
+ //
+ //
+ //
+ void ThreadProc(void);
+ static DWORD WINAPI ThreadProcStatic(LPVOID parameter);
+ static DWORD WINAPI ThreadProcRemote(LPVOID parameter);
+
+ DWORD GetRCThreadId()
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ return m_pDCB->m_helperThreadId;
+ }
+
+ // Return true if the Helper Thread up & initialized.
+ bool IsRCThreadReady();
+
+ HRESULT ReDaclEvents(PSECURITY_DESCRIPTOR securityDescriptor);
+private:
+
+ // The transport based communication protocol keeps the send and receive buffers outside of the DCB
+ // to keep the DCB size down (since we send it over the wire).
+ DebuggerIPCEvent * GetRCThreadReceiveBuffer()
+ {
+#if defined(FEATURE_DBGIPC_TRANSPORT_VM)
+ return reinterpret_cast<DebuggerIPCEvent *>(&m_receiveBuffer[0]);
+#else
+ return reinterpret_cast<DebuggerIPCEvent *>(&m_pDCB->m_receiveBuffer[0]);
+#endif
+ }
+
+ // The transport based communication protocol keeps the send and receive buffers outside of the DCB
+ // to keep the DCB size down (since we send it over the wire).
+ DebuggerIPCEvent * GetRCThreadSendBuffer()
+ {
+#if defined(FEATURE_DBGIPC_TRANSPORT_VM)
+ return reinterpret_cast<DebuggerIPCEvent *>(&m_sendBuffer[0]);
+#else // FEATURE_DBGIPC_TRANSPORT_VM
+ return reinterpret_cast<DebuggerIPCEvent *>(&m_pDCB->m_sendBuffer[0]);
+#endif // FEATURE_DBGIPC_TRANSPORT_VM
+ }
+
+ FAVORCALLBACK GetFavorFnPtr() { return m_favorData.m_fpFavor; }
+ void * GetFavorData() { return m_favorData.m_pFavorData; }
+
+ void SetFavorFnPtr(FAVORCALLBACK fp, void * pData)
+ {
+ m_favorData.m_fpFavor = fp;
+ m_favorData.m_pFavorData = pData;
+ }
+ Crst * GetFavorLock() { return &m_favorData.m_FavorLock; }
+
+ HANDLE GetFavorReadEvent() { return m_favorData.m_FavorReadEvent; }
+ HANDLE GetFavorAvailableEvent() { return m_favorData.m_FavorAvailableEvent; }
+
+ HelperThreadFavor m_favorData;
+
+
+ HelperCanary * GetCanary() { return &GetLazyData()->m_Canary; }
+
+
+ friend class Debugger;
+ HRESULT VerifySecurityOnRSCreatedEvents(HANDLE sse, HANDLE lsea, HANDLE lser);
+ Debugger* m_debugger;
+
+ // IPC_TARGET_* define default targets - if we ever want to do
+ // multiple right sides, we'll have to switch to a OUTOFPROC + iTargetProcess scheme
+ PTR_DebuggerIPCControlBlock m_pDCB;
+
+#ifdef FEATURE_DBGIPC_TRANSPORT_VM
+ // These buffers move here out of the DebuggerIPCControlBlock since the block is not shared memory when
+ // using the transport, but we do send its contents over the wire (and these buffers would greatly impact
+ // the number of bytes sent without being useful in any way).
+ BYTE m_receiveBuffer[CorDBIPC_BUFFER_SIZE];
+ BYTE m_sendBuffer[CorDBIPC_BUFFER_SIZE];
+#endif // FEATURE_DBGIPC_TRANSPORT_VM
+
+ HANDLE m_thread;
+ bool m_run;
+
+ HANDLE m_threadControlEvent;
+ HANDLE m_helperThreadCanGoEvent;
+ bool m_rgfInitRuntimeOffsets[IPC_TARGET_COUNT];
+ bool m_fDetachRightSide;
+
+ RCThreadLazyInit * GetLazyData();
+#ifdef _DEBUG
+ // Tracking to ensure that the helper thread only calls New() on the interop-safe heap.
+ // We need a very light-weight way to track the helper b/c we need to check everytime somebody
+ // calls operator new, which may occur during shutdown paths.
+ static EEThreadId s_DbgHelperThreadId;
+
+ friend void AssertAllocationAllowed();
+
+public:
+ // The OS ThreadId of the helper as determined from the CreateThread call.
+ DWORD m_DbgHelperThreadOSTid;
+private:
+#endif
+
+};
+
+typedef DPTR(DebuggerRCThread) PTR_DebuggerRCThread;
+
+/* ------------------------------------------------------------------------ *
+ * Debugger Method Info struct and hash table
+ * ------------------------------------------------------------------------ */
+
+// class DebuggerMethodInfo: Struct to hold all the information
+// necessary for a given function.
+//
+// m_module, m_token: Method that this DMI applies to
+//
+const bool bOriginalToInstrumented = true;
+const bool bInstrumentedToOriginal = false;
+
+class DebuggerMethodInfo
+{
+ // This is the most recent version of the function based on the latest update and is
+ // set in UpdateFunction. When a function is jitted, the version is copied from here
+ // and stored in the corresponding DebuggerJitInfo structure so can always know the
+ // version of a particular jitted function.
+ SIZE_T m_currentEnCVersion;
+
+public:
+ PTR_Module m_module;
+ mdMethodDef m_token;
+
+ PTR_DebuggerMethodInfo m_prevMethodInfo;
+ PTR_DebuggerMethodInfo m_nextMethodInfo;
+
+
+ // Enumerate DJIs
+ // Expected usage:
+ // DMI.InitDJIIterator(&it);
+ // while(!it.IsAtEnd()) {
+ // f(it.Current()); it.Next();
+ // }
+ class DJIIterator
+ {
+ friend class DebuggerMethodInfo;
+
+ DebuggerJitInfo* m_pCurrent;
+ Module* m_pLoaderModuleFilter;
+ public:
+ DJIIterator();
+
+ bool IsAtEnd();
+ DebuggerJitInfo * Current();
+ void Next(BOOL fFirst = FALSE);
+
+ };
+
+ // Ensure the DJI cache is completely up to date. (This is heavy weight).
+ void CreateDJIsForNativeBlobs(AppDomain * pAppDomain, Module * pModuleFilter = NULL);
+
+ // Get an iterator for all native blobs (accounts for Generics, Enc, + Prejiiting).
+ // Must be stopped when we do this. This could be heavy weight.
+ // This will call CreateDJIsForNativeBlobs() to ensure we have all DJIs available.
+ // You may optionally pass pLoaderModuleFilter to restrict the DJIs iterated to
+ // exist only on MethodDescs whose loader module matches the filter (pass NULL not
+ // to filter by loader module).
+ void IterateAllDJIs(AppDomain * pAppDomain, Module * pLoaderModuleFilter, DJIIterator * pEnum);
+
+private:
+ // The linked list of JIT's of this version of the method. This will ALWAYS
+ // contain one element except for code in generic classes or generic methods,
+ // which may get JITted more than once under different type instantiations.
+ //
+ // We find the appropriate JitInfo by searching the list (nearly always this
+ // will return the first element of course).
+ //
+ // The JitInfos contain back pointers to this MethodInfo. They should never be associated
+ // with any other MethodInfo.
+ //
+ // USE ACCESSOR FUNCTION GetLatestJitInfo(), as it does lazy init of this field.
+ //
+
+ PTR_DebuggerJitInfo m_latestJitInfo;
+
+public:
+
+ PTR_DebuggerJitInfo GetLatestJitInfo(MethodDesc *fd);
+
+ DebuggerJitInfo * GetLatestJitInfo_NoCreate();
+
+
+ // Find the DJI corresponding to the specified MD and native start address.
+ DebuggerJitInfo * FindJitInfo(MethodDesc * pMD, TADDR addrNativeStartAddr);
+
+ // Creating the Jit-infos.
+ DebuggerJitInfo *FindOrCreateInitAndAddJitInfo(MethodDesc* fd);
+ DebuggerJitInfo *CreateInitAndAddJitInfo(MethodDesc* fd, TADDR startAddr);
+
+
+ void DeleteJitInfo(DebuggerJitInfo *dji);
+ void DeleteJitInfoList(void);
+
+ // Return true iff this has been jitted.
+ // Since we can create DMIs freely, a DMI's existence doesn't mean that the method was jitted.
+ bool HasJitInfos();
+
+ // Return true iff this has been EnCed since the last time the function was jitted.
+ bool HasMoreRecentEnCVersion();
+
+
+ // Return true iif this is a JMC function, else false.
+ bool IsJMCFunction();
+ void SetJMCStatus(bool fStatus);
+
+
+ DebuggerMethodInfo(Module *module, mdMethodDef token);
+ ~DebuggerMethodInfo();
+
+ // A profiler can remap the IL. We track the "instrumented" IL map here.
+ void SetInstrumentedILMap(COR_IL_MAP * pMap, SIZE_T cEntries);
+ bool HasInstrumentedILMap() {return m_fHasInstrumentedILMap; }
+
+ // TranslateToInstIL will take offOrig, and translate it to the
+ // correct IL offset if this code happens to be instrumented
+ ULONG32 TranslateToInstIL(const InstrumentedILOffsetMapping * pMapping, ULONG32 offOrig, bool fOrigToInst);
+
+
+ // We don't always have a debugger module. (Ex: we're tracking debug info,
+ // but no debugger's attached). So this may return NULL alot.
+ // If we can, we should use the RuntimeModule when ever possible.
+ DebuggerModule* GetPrimaryModule();
+
+ // We always have a runtime module.
+ Module * GetRuntimeModule();
+
+ // Set the latest EnC version number for this method
+ // This doesn't mean we have a DJI for this version yet.
+ void SetCurrentEnCVersion(SIZE_T currentEnCVersion)
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ _ASSERTE(currentEnCVersion >= CorDB_DEFAULT_ENC_FUNCTION_VERSION);
+ m_currentEnCVersion = currentEnCVersion;
+ }
+
+ SIZE_T GetCurrentEnCVersion()
+ {
+ LIMITED_METHOD_CONTRACT;
+ SUPPORTS_DAC;
+
+ return m_currentEnCVersion;
+ }
+
+#ifdef DACCESS_COMPILE
+ void EnumMemoryRegions(CLRDataEnumMemoryFlags flags);
+#endif
+
+protected:
+ // JMC info. Each method can have its own JMC setting.
+ bool m_fJMCStatus;
+
+ // "Instrumented" IL map set by the profiler.
+ // @dbgtodo execution control - remove this when we do execution control from out-of-proc
+ bool m_fHasInstrumentedILMap;
+};
+
+// ------------------------------------------------------------------------ *
+// Executable code memory management for the debugger heap.
+//
+// Rather than allocating memory that needs to be executable on the process heap (which
+// is forbidden on some flavors of SELinux and is generally a bad idea), we use the
+// allocator below. It will handle allocating and managing the executable memory in a
+// different part of the address space (not on the heap).
+// ------------------------------------------------------------------------ */
+
+#define DBG_MAX_EXECUTABLE_ALLOC_SIZE 48
+
+// Forward declaration
+struct DebuggerHeapExecutableMemoryPage;
+
+// ------------------------------------------------------------------------ */
+// DebuggerHeapExecutableMemoryChunk
+//
+// Each DebuggerHeapExecutableMemoryPage is divided into 64 of these chunks.
+// The first chunk is a BookkeepingChunk used for bookkeeping information
+// for the page, and the remaining ones are DataChunks and are handed out
+// by the allocator when it allocates memory.
+// ------------------------------------------------------------------------ */
+union DECLSPEC_ALIGN(64) DebuggerHeapExecutableMemoryChunk {
+
+ struct DataChunk
+ {
+ char data[DBG_MAX_EXECUTABLE_ALLOC_SIZE];
+
+ DebuggerHeapExecutableMemoryPage *startOfPage;
+
+ // The chunk number within the page.
+ uint8_t chunkNumber;
+
+ } data;
+
+ struct BookkeepingChunk
+ {
+ DebuggerHeapExecutableMemoryPage *nextPage;
+
+ uint64_t pageOccupancy;
+
+ } bookkeeping;
+
+ char _alignpad[64];
+};
+
+static_assert(sizeof(DebuggerHeapExecutableMemoryChunk) == 64, "DebuggerHeapExecutableMemoryChunk is expect to be 64 bytes.");
+
+// ------------------------------------------------------------------------ */
+// DebuggerHeapExecutableMemoryPage
+//
+// We allocate the size of DebuggerHeapExecutableMemoryPage each time we need
+// more memory and divide each page into DebuggerHeapExecutableMemoryChunks for
+// use. The pages are self describing; the first chunk contains information
+// about which of the other chunks are used/free as well as a pointer to
+// the next page.
+// ------------------------------------------------------------------------ */
+struct DECLSPEC_ALIGN(4096) DebuggerHeapExecutableMemoryPage
+{
+ inline DebuggerHeapExecutableMemoryPage* GetNextPage()
+ {
+ return chunks[0].bookkeeping.nextPage;
+ }
+
+ inline void SetNextPage(DebuggerHeapExecutableMemoryPage* nextPage)
+ {
+ chunks[0].bookkeeping.nextPage = nextPage;
+ }
+
+ inline uint64_t GetPageOccupancy() const
+ {
+ return chunks[0].bookkeeping.pageOccupancy;
+ }
+
+ inline void SetPageOccupancy(uint64_t newOccupancy)
+ {
+ // Can't unset first bit of occupancy!
+ ASSERT((newOccupancy & 0x8000000000000000) != 0);
+
+ chunks[0].bookkeeping.pageOccupancy = newOccupancy;
+ }
+
+ inline void* GetPointerToChunk(int chunkNum) const
+ {
+ return (char*)this + chunkNum * sizeof(DebuggerHeapExecutableMemoryChunk);
+ }
+
+ DebuggerHeapExecutableMemoryPage()
+ {
+ SetPageOccupancy(0x8000000000000000); // only the first bit is set.
+ for (uint8_t i = 1; i < sizeof(chunks)/sizeof(chunks[0]); i++)
+ {
+ ASSERT(i != 0);
+ chunks[i].data.startOfPage = this;
+ chunks[i].data.chunkNumber = i;
+ }
+ }
+
+private:
+ DebuggerHeapExecutableMemoryChunk chunks[64];
+};
+
+// ------------------------------------------------------------------------ */
+// DebuggerHeapExecutableMemoryAllocator class
+// Handles allocation and freeing (and all necessary bookkeeping) for
+// executable memory that the DebuggerHeap class needs. This is especially
+// useful on systems (like SELinux) where having executable code on the
+// heap is explicity disallowed for security reasons.
+// ------------------------------------------------------------------------ */
+
+class DebuggerHeapExecutableMemoryAllocator
+{
+public:
+ DebuggerHeapExecutableMemoryAllocator()
+ : m_pages(NULL)
+ , m_execMemAllocMutex(CrstDebuggerHeapExecMemLock, (CrstFlags)(CRST_UNSAFE_ANYMODE | CRST_REENTRANCY | CRST_DEBUGGER_THREAD))
+ { }
+
+ ~DebuggerHeapExecutableMemoryAllocator();
+
+ void* Allocate(DWORD numberOfBytes);
+ int Free(void* addr);
+
+private:
+ enum class ChangePageUsageAction {ALLOCATE, FREE};
+
+ DebuggerHeapExecutableMemoryPage* AddNewPage();
+ bool CheckPageForAvailability(DebuggerHeapExecutableMemoryPage* page, /* _Out_ */ int* chunkToUse);
+ void* ChangePageUsage(DebuggerHeapExecutableMemoryPage* page, int chunkNumber, ChangePageUsageAction action);
+
+private:
+ // Linked list of pages that have been allocated
+ DebuggerHeapExecutableMemoryPage* m_pages;
+ Crst m_execMemAllocMutex;
+};
+
+// ------------------------------------------------------------------------ *
+// DebuggerHeap class
+// For interop debugging, we need a heap that:
+// - does not take any outside looks
+// - returns memory which could be executed.
+// ------------------------------------------------------------------------ */
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ #define USE_INTEROPSAFE_HEAP
+#endif
+
+class DebuggerHeap
+{
+public:
+ DebuggerHeap();
+ ~DebuggerHeap();
+
+ bool IsInit();
+ void Destroy();
+ HRESULT Init(BOOL fExecutable);
+
+ void *Alloc(DWORD size);
+ void *Realloc(void *pMem, DWORD newSize, DWORD oldSize);
+ void Free(void *pMem);
+
+
+protected:
+#ifdef USE_INTEROPSAFE_HEAP
+ HANDLE m_hHeap;
+#endif
+ BOOL m_fExecutable;
+
+private:
+ DebuggerHeapExecutableMemoryAllocator *m_execMemAllocator;
+};
+
+class DebuggerJitInfo;
+
+#if defined(WIN64EXCEPTIONS)
+const int PARENT_METHOD_INDEX = -1;
+#endif // WIN64EXCEPTIONS
+
+class CodeRegionInfo
+{
+public:
+ CodeRegionInfo() :
+ m_addrOfHotCode(NULL),
+ m_addrOfColdCode(NULL),
+ m_sizeOfHotCode(0),
+ m_sizeOfColdCode(0)
+ {
+ WRAPPER_NO_CONTRACT;
+ SUPPORTS_DAC;
+ }
+
+ static CodeRegionInfo GetCodeRegionInfo(DebuggerJitInfo * dji,
+ MethodDesc * md = NULL,
+ PTR_CORDB_ADDRESS_TYPE addr = PTR_NULL);
+
+ // Fills in the CodeRegoinInfo fields from the start address.
+ void InitializeFromStartAddress(PCODE addr)
+ {
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ SUPPORTS_DAC;
+ }
+ CONTRACTL_END;
+
+ m_addrOfHotCode = addr;
+ g_pEEInterface->GetMethodRegionInfo(addr,
+ &m_addrOfColdCode,
+ (size_t *) &m_sizeOfHotCode,
+ (size_t *) &m_sizeOfColdCode);
+ }
+
+ // Converts an offset within a method to a code address
+ PCODE OffsetToAddress(SIZE_T offset)
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ if (m_addrOfHotCode != NULL)
+ {
+ if (offset < m_sizeOfHotCode)
+ {
+ return m_addrOfHotCode + offset;
+ }
+ else
+ {
+ _ASSERTE(m_addrOfColdCode);
+ _ASSERTE(offset <= m_sizeOfHotCode + m_sizeOfColdCode);
+
+ return m_addrOfColdCode + (offset - m_sizeOfHotCode);
+ }
+ }
+ else
+ {
+ return NULL;
+ }
+ }
+
+ // Converts a code address to an offset within the method
+ SIZE_T AddressToOffset(const BYTE *addr)
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ PCODE address = (PCODE)addr;
+
+ if ((address >= m_addrOfHotCode) &&
+ (address < m_addrOfHotCode + m_sizeOfHotCode))
+ {
+ return address - m_addrOfHotCode;
+ }
+ else if ((address >= m_addrOfColdCode) &&
+ (address < m_addrOfColdCode + m_sizeOfColdCode))
+ {
+ return address - m_addrOfColdCode + m_sizeOfHotCode;
+ }
+
+ _ASSERTE(!"addressToOffset called with invalid address");
+ return NULL;
+ }
+
+ // Determines whether the address lies within the method
+ bool IsMethodAddress(const BYTE *addr)
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ PCODE address = (PCODE)addr;
+ return (((address >= m_addrOfHotCode) &&
+ (address < m_addrOfHotCode + m_sizeOfHotCode)) ||
+ ((address >= m_addrOfColdCode) &&
+ (address < m_addrOfColdCode + m_sizeOfColdCode)));
+ }
+
+ // Determines whether the offset is in the hot section
+ bool IsOffsetHot(SIZE_T offset)
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ return (offset < m_sizeOfHotCode);
+ }
+
+ PCODE getAddrOfHotCode() {LIMITED_METHOD_DAC_CONTRACT; return m_addrOfHotCode;}
+ PCODE getAddrOfColdCode() {LIMITED_METHOD_DAC_CONTRACT; return m_addrOfColdCode;}
+ SIZE_T getSizeOfHotCode() {LIMITED_METHOD_DAC_CONTRACT; return m_sizeOfHotCode;}
+ SIZE_T getSizeOfColdCode() {LIMITED_METHOD_DAC_CONTRACT; return m_sizeOfColdCode;}
+ SIZE_T getSizeOfTotalCode(){LIMITED_METHOD_DAC_CONTRACT; return m_sizeOfHotCode + m_sizeOfColdCode; }
+
+private:
+
+ PCODE m_addrOfHotCode;
+ PCODE m_addrOfColdCode;
+ SIZE_T m_sizeOfHotCode;
+ SIZE_T m_sizeOfColdCode;
+};
+
+/* ------------------------------------------------------------------------ *
+ * Debugger JIT Info struct
+ * ------------------------------------------------------------------------ */
+
+// class DebuggerJitInfo: Struct to hold all the JIT information
+// necessary for a given function.
+// - DJIs are 1:1 w/ native codeblobs. They're almost 1:1 w/ Native Method Descs.
+// except that a MethodDesc only refers to the most recent EnC version of a method.
+// - If 2 DJIs are different, they refer to different code-blobs.
+// - DJIs are lazily created, and so you can't safely enumerate them b/c
+// you can't rely on whether they're created or not.
+
+
+//
+// MethodDesc* m_fd: MethodDesc of the method that this DJI applies to
+//
+// CORDB_ADDRESS m_addrOfCode: Address of the code. This will be read by
+// the right side (via ReadProcessMemory) to grab the actual native start
+// address of the jitted method.
+//
+// SIZE_T m_sizeOfCode: Pseudo-private variable: use the GetSkzeOfCode
+// method to get this value.
+//
+// bool m_jitComplete: Set to true once JITComplete has been called.
+//
+// DebuggerILToNativeMap* m_sequenceMap: This is the sequence map, which
+// is actually a collection of IL-Native pairs, where each IL corresponds
+// to a line of source code. Each pair is refered to as a sequence map point.
+//
+// SIZE_T m_lastIL: last nonEPILOG instruction
+//
+// unsigned int m_sequenceMapCount: Count of the DebuggerILToNativeMaps
+// in m_sequenceMap.
+//
+// bool m_sequenceMapSorted: Set to true once m_sequenceMapSorted is sorted
+// into ascending IL order (Debugger::setBoundaries, SortMap).
+//
+
+class DebuggerJitInfo
+{
+public:
+ PTR_MethodDesc m_fd;
+
+ // Loader module is used to control life-time of DebufferJitInfo. Ideally, we would refactor the code to use LoaderAllocator here
+ // instead because of it is what the VM actually uses to track the life time. It would make the debugger interface less chatty.
+ PTR_Module m_pLoaderModule;
+
+ bool m_jitComplete;
+
+#ifdef EnC_SUPPORTED
+ // If this is true, then we've plastered the method with DebuggerEncBreakpoints
+ // and the method has been EnC'd
+ bool m_encBreakpointsApplied;
+#endif //EnC_SUPPORTED
+
+ PTR_DebuggerMethodInfo m_methodInfo;
+
+ CORDB_ADDRESS m_addrOfCode;
+ SIZE_T m_sizeOfCode;
+
+ CodeRegionInfo m_codeRegionInfo;
+
+ PTR_DebuggerJitInfo m_prevJitInfo;
+ PTR_DebuggerJitInfo m_nextJitInfo;
+
+protected:
+ // The jit maps are lazy-initialized.
+ // They are always sorted.
+ ULONG m_lastIL;
+ PTR_DebuggerILToNativeMap m_sequenceMap;
+ unsigned int m_sequenceMapCount;
+ PTR_DebuggerILToNativeMap m_callsiteMap;
+ unsigned int m_callsiteMapCount;
+ bool m_sequenceMapSorted;
+
+ PTR_NativeVarInfo m_varNativeInfo;
+ unsigned int m_varNativeInfoCount;
+
+ bool m_fAttemptInit;
+
+#ifndef DACCESS_COMPILE
+ void LazyInitBounds();
+#else
+ void LazyInitBounds() { LIMITED_METHOD_DAC_CONTRACT; }
+#endif
+
+public:
+ unsigned int GetSequenceMapCount()
+ {
+ SUPPORTS_DAC;
+
+ LazyInitBounds();
+ return m_sequenceMapCount;
+ }
+
+ //@todo: this method could return NULL, but some callers are not handling the case
+ PTR_DebuggerILToNativeMap GetSequenceMap()
+ {
+ SUPPORTS_DAC;
+
+ LazyInitBounds();
+ return m_sequenceMap;
+ }
+
+ unsigned int GetCallsiteMapCount()
+ {
+ SUPPORTS_DAC;
+
+ LazyInitBounds();
+ return m_callsiteMapCount;
+ }
+
+ PTR_DebuggerILToNativeMap GetCallSiteMap()
+ {
+ SUPPORTS_DAC;
+
+ LazyInitBounds();
+ return m_callsiteMap;
+ }
+
+ PTR_NativeVarInfo GetVarNativeInfo()
+ {
+ SUPPORTS_DAC;
+
+ LazyInitBounds();
+ return m_varNativeInfo;
+ }
+
+ unsigned int GetVarNativeInfoCount()
+ {
+ SUPPORTS_DAC;
+
+ LazyInitBounds();
+ return m_varNativeInfoCount;
+ }
+
+
+ // The version number of this jitted code
+ SIZE_T m_encVersion;
+
+#if defined(WIN64EXCEPTIONS)
+ DWORD *m_rgFunclet;
+ int m_funcletCount;
+#endif // WIN64EXCEPTIONS
+
+#ifndef DACCESS_COMPILE
+
+ DebuggerJitInfo(DebuggerMethodInfo *minfo, MethodDesc *fd);
+ ~DebuggerJitInfo();
+
+#endif // #ifdef DACCESS_COMPILE
+
+ class ILToNativeOffsetIterator;
+
+ // Usage of ILToNativeOffsetIterator:
+ //
+ // ILToNativeOffsetIterator it;
+ // dji->InitILToNativeOffsetIterator(&it, ilOffset);
+ // while (!it.IsAtEnd())
+ // {
+ // nativeOffset = it.Current(&fExact);
+ // it.Next();
+ // }
+ struct ILOffset
+ {
+ friend class DebuggerJitInfo;
+ friend class DebuggerJitInfo::ILToNativeOffsetIterator;
+
+ private:
+ SIZE_T m_ilOffset;
+#ifdef WIN64EXCEPTIONS
+ int m_funcletIndex;
+#endif
+ };
+
+ struct NativeOffset
+ {
+ friend class DebuggerJitInfo;
+ friend class DebuggerJitInfo::ILToNativeOffsetIterator;
+
+ private:
+ SIZE_T m_nativeOffset;
+ BOOL m_fExact;
+ };
+
+ class ILToNativeOffsetIterator
+ {
+ friend class DebuggerJitInfo;
+
+ public:
+ ILToNativeOffsetIterator();
+
+ bool IsAtEnd();
+ SIZE_T Current(BOOL* pfExact);
+ SIZE_T CurrentAssertOnlyOne(BOOL* pfExact);
+ void Next();
+
+ private:
+ void Init(DebuggerJitInfo* dji, SIZE_T ilOffset);
+
+ DebuggerJitInfo* m_dji;
+ ILOffset m_currentILOffset;
+ NativeOffset m_currentNativeOffset;
+ };
+
+ void InitILToNativeOffsetIterator(ILToNativeOffsetIterator &it, SIZE_T ilOffset);
+
+ DebuggerILToNativeMap *MapILOffsetToMapEntry(SIZE_T ilOffset, BOOL *exact=NULL, BOOL fWantFirst = TRUE);
+ void MapILRangeToMapEntryRange(SIZE_T ilStartOffset, SIZE_T ilEndOffset,
+ DebuggerILToNativeMap **start,
+ DebuggerILToNativeMap **end);
+ NativeOffset MapILOffsetToNative(ILOffset ilOffset);
+
+ // MapSpecialToNative maps a CordDebugMappingResult to a native
+ // offset so that we can get the address of the prolog & epilog. which
+ // determines which epilog or prolog, if there's more than one.
+ SIZE_T MapSpecialToNative(CorDebugMappingResult mapping,
+ SIZE_T which,
+ BOOL *pfAccurate);
+#if defined(WIN64EXCEPTIONS)
+ void MapSpecialToNative(int funcletIndex, DWORD* pPrologEndOffset, DWORD* pEpilogStartOffset);
+ SIZE_T MapILOffsetToNativeForSetIP(SIZE_T offsetILTo, int funcletIndexFrom, EHRangeTree* pEHRT, BOOL* pExact);
+#endif // _WIN64
+
+ // MapNativeOffsetToIL Takes a given nativeOffset, and maps it back
+ // to the corresponding IL offset, which it returns. If mapping indicates
+ // that a the native offset corresponds to a special region of code (for
+ // example, the epilog), then the return value will be specified by
+ // ICorDebugILFrame::GetIP (see cordebug.idl)
+ DWORD MapNativeOffsetToIL(SIZE_T nativeOffsetToMap,
+ CorDebugMappingResult *mapping,
+ DWORD *which,
+ BOOL skipPrologs=FALSE);
+
+ // If a method has multiple copies of code (because of EnC or code-pitching),
+ // this returns the DJI corresponding to 'pbAddr'
+ DebuggerJitInfo *GetJitInfoByAddress(const BYTE *pbAddr );
+
+ void Init(TADDR newAddress);
+
+#if defined(WIN64EXCEPTIONS)
+ enum GetFuncletIndexMode
+ {
+ GFIM_BYOFFSET,
+ GFIM_BYADDRESS,
+ };
+
+ void InitFuncletAddress();
+ DWORD GetFuncletOffsetByIndex(int index);
+ int GetFuncletIndex(CORDB_ADDRESS offset, GetFuncletIndexMode mode);
+ int GetFuncletCount() {return m_funcletCount;}
+#endif // WIN64EXCEPTIONS
+
+ void SetVars(ULONG32 cVars, ICorDebugInfo::NativeVarInfo *pVars);
+ void SetBoundaries(ULONG32 cMap, ICorDebugInfo::OffsetMapping *pMap);
+
+ ICorDebugInfo::SourceTypes GetSrcTypeFromILOffset(SIZE_T ilOffset);
+
+#ifdef DACCESS_COMPILE
+ void EnumMemoryRegions(CLRDataEnumMemoryFlags flags);
+#endif
+
+ // Debug support
+ CHECK Check() const;
+ CHECK Invariant() const;
+};
+
+#if !defined(DACCESS_COMPILE)
+// @dbgtodo Microsoft inspection: get rid of this class when IPC events are eliminated. It's been copied to
+// dacdbistructures
+/*
+ * class MapSortIL: A template class that will sort an array of DebuggerILToNativeMap.
+ * This class is intended to be instantiated on the stack / in temporary storage, and used to reorder the sequence map.
+ */
+class MapSortIL : public CQuickSort<DebuggerILToNativeMap>
+{
+ public:
+ //Constructor
+ MapSortIL(DebuggerILToNativeMap *map,
+ int count)
+ : CQuickSort<DebuggerILToNativeMap>(map, count) {}
+
+ inline int CompareInternal(DebuggerILToNativeMap *first,
+ DebuggerILToNativeMap *second)
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ if (first->nativeStartOffset == second->nativeStartOffset)
+ return 0;
+ else if (first->nativeStartOffset < second->nativeStartOffset)
+ return -1;
+ else
+ return 1;
+ }
+
+ //Comparison operator
+ int Compare(DebuggerILToNativeMap *first,
+ DebuggerILToNativeMap *second)
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ const DWORD call_inst = (DWORD)ICorDebugInfo::CALL_INSTRUCTION;
+
+ //PROLOGs go first
+ if (first->ilOffset == (ULONG) ICorDebugInfo::PROLOG
+ && second->ilOffset == (ULONG) ICorDebugInfo::PROLOG)
+ {
+ return CompareInternal(first, second);
+ } else if (first->ilOffset == (ULONG) ICorDebugInfo::PROLOG)
+ {
+ return -1;
+ } else if (second->ilOffset == (ULONG) ICorDebugInfo::PROLOG)
+ {
+ return 1;
+ }
+ // call_instruction goes at the very very end of the table.
+ else if ((first->source & call_inst) == call_inst
+ && (second->source & call_inst) == call_inst)
+ {
+ return CompareInternal(first, second);
+ } else if ((first->source & call_inst) == call_inst)
+ {
+ return 1;
+ } else if ((second->source & call_inst) == call_inst)
+ {
+ return -1;
+ }
+ //NO_MAPPING go last
+ else if (first->ilOffset == (ULONG) ICorDebugInfo::NO_MAPPING
+ && second->ilOffset == (ULONG) ICorDebugInfo::NO_MAPPING)
+ {
+ return CompareInternal(first, second);
+ } else if (first->ilOffset == (ULONG) ICorDebugInfo::NO_MAPPING)
+ {
+ return 1;
+ } else if (second->ilOffset == (ULONG) ICorDebugInfo::NO_MAPPING)
+ {
+ return -1;
+ }
+ //EPILOGs go next-to-last
+ else if (first->ilOffset == (ULONG) ICorDebugInfo::EPILOG
+ && second->ilOffset == (ULONG) ICorDebugInfo::EPILOG)
+ {
+ return CompareInternal(first, second);
+ } else if (first->ilOffset == (ULONG) ICorDebugInfo::EPILOG)
+ {
+ return 1;
+ } else if (second->ilOffset == (ULONG) ICorDebugInfo::EPILOG)
+ {
+ return -1;
+ }
+ //normal offsets compared otherwise
+ else if (first->ilOffset < second->ilOffset)
+ return -1;
+ else if (first->ilOffset == second->ilOffset)
+ return CompareInternal(first, second);
+ else
+ return 1;
+ }
+};
+
+/*
+ * class MapSortNative: A template class that will sort an array of DebuggerILToNativeMap by the nativeStartOffset field.
+ * This class is intended to be instantiated on the stack / in temporary storage, and used to reorder the sequence map.
+ */
+class MapSortNative : public CQuickSort<DebuggerILToNativeMap>
+{
+ public:
+ //Constructor
+ MapSortNative(DebuggerILToNativeMap *map,
+ int count)
+ : CQuickSort<DebuggerILToNativeMap>(map, count)
+ {
+ WRAPPER_NO_CONTRACT;
+ }
+
+
+ //Returns -1,0,or 1 if first's nativeStartOffset is less than, equal to, or greater than second's
+ int Compare(DebuggerILToNativeMap *first,
+ DebuggerILToNativeMap *second)
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ if (first->nativeStartOffset < second->nativeStartOffset)
+ return -1;
+ else if (first->nativeStartOffset == second->nativeStartOffset)
+ return 0;
+ else
+ return 1;
+ }
+};
+#endif //!DACCESS_COMPILE
+
+/* ------------------------------------------------------------------------ *
+ * Import flares from assembly file
+ * We rely on flares having unique addresses, and so we need to keeps them
+ * from getting folded by the linker (Since they are identical code).
+ * ------------------------------------------------------------------------ */
+
+extern "C" void __stdcall SignalHijackStartedFlare(void);
+extern "C" void __stdcall ExceptionForRuntimeHandoffStartFlare(void);
+extern "C" void __stdcall ExceptionForRuntimeHandoffCompleteFlare(void);
+extern "C" void __stdcall SignalHijackCompleteFlare(void);
+extern "C" void __stdcall ExceptionNotForRuntimeFlare(void);
+extern "C" void __stdcall NotifyRightSideOfSyncCompleteFlare(void);
+extern "C" void __stdcall NotifySecondChanceReadyForDataFlare(void);
+
+/* ------------------------------------------------------------------------ *
+ * Debugger class
+ * ------------------------------------------------------------------------ */
+
+
+// Forward declare some parameter marshalling structs
+struct ShouldAttachDebuggerParams;
+struct EnsureDebuggerAttachedParams;
+struct SendMDANotificationParams;
+
+// class Debugger: This class implements DebugInterface to provide
+// the hooks to the Runtime directly.
+//
+
+class Debugger : public DebugInterface
+{
+ VPTR_VTABLE_CLASS(Debugger, DebugInterface);
+public:
+
+#ifndef DACCESS_COMPILE
+ Debugger();
+ virtual ~Debugger();
+#else
+ virtual ~Debugger() {}
+#endif
+
+ // If 0, then not yet initialized. If non-zero, then LS is initialized.
+ LONG m_fLeftSideInitialized;
+
+ // This flag controls the window where SetDesiredNGENCompilerFlags is allowed,
+ // which is until Debugger::StartupPhase2 is complete. Typically it would be
+ // set during the CreateProcess debug event but it could be set other times such
+ // as module load for clr.dll.
+ SVAL_DECL(BOOL, s_fCanChangeNgenFlags);
+
+ friend class DebuggerLazyInit;
+#ifdef TEST_DATA_CONSISTENCY
+ friend class DataTest;
+#endif
+
+ // Checks if the JitInfos table has been allocated, and if not does so.
+ HRESULT inline CheckInitMethodInfoTable();
+ HRESULT inline CheckInitModuleTable();
+ HRESULT CheckInitPendingFuncEvalTable();
+
+#ifndef DACCESS_COMPILE
+ DWORD GetRCThreadId()
+ {
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ if (m_pRCThread)
+ return m_pRCThread->GetRCThreadId();
+ else
+ return 0;
+ }
+#endif
+
+ //
+ // Methods exported from the Runtime Controller to the Runtime.
+ // (These are the methods specified by DebugInterface.)
+ //
+ HRESULT Startup(void);
+
+ HRESULT StartupPhase2(Thread * pThread);
+
+ void InitializeLazyDataIfNecessary();
+
+ void LazyInit(); // will throw
+ HRESULT LazyInitWrapper(); // calls LazyInit and converts to HR.
+
+ // Helper on startup to notify debugger
+ void RaiseStartupNotification();
+
+ // Send a raw managed debug event over the managed pipeline.
+ void SendRawEvent(const DebuggerIPCEvent * pManagedEvent);
+
+ // Message box API for the left side of the debugger. This API handles calls from the
+ // debugger helper thread as well as from normal EE threads. It is the only one that
+ // should be used from inside the debugger left side.
+ int MessageBox(
+ UINT uText, // Resource Identifier for Text message
+ UINT uCaption, // Resource Identifier for Caption
+ UINT uType, // Style of MessageBox
+ BOOL displayForNonInteractive, // Display even if the process is running non interactive
+ BOOL showFileNameInTitle, // Flag to show FileName in Caption
+ ...); // Additional Arguments
+
+ void SetEEInterface(EEDebugInterface* i);
+ void StopDebugger(void);
+ BOOL IsStopped(void)
+ {
+ LIMITED_METHOD_CONTRACT;
+ // implements DebugInterface but also is called internally
+ return m_stopped;
+ }
+
+
+
+ void ThreadCreated(Thread* pRuntimeThread);
+ void ThreadStarted(Thread* pRuntimeThread);
+ void DetachThread(Thread *pRuntimeThread);
+
+ BOOL SuspendComplete();
+
+ void LoadModule(Module* pRuntimeModule,
+ LPCWSTR pszModuleName,
+ DWORD dwModuleName,
+ Assembly *pAssembly,
+ AppDomain *pAppDomain,
+ DomainFile * pDomainFile,
+ BOOL fAttaching);
+ void LoadModuleFinished(Module* pRuntimeModule, AppDomain * pAppDomain);
+ DebuggerModule * AddDebuggerModule(DomainFile * pDomainFile);
+
+
+ void UnloadModule(Module* pRuntimeModule,
+ AppDomain *pAppDomain);
+ void DestructModule(Module *pModule);
+
+ void RemoveModuleReferences(Module * pModule);
+
+
+ void SendUpdateModuleSymsEventAndBlock(Module * pRuntimeModule, AppDomain * pAppDomain);
+ void SendRawUpdateModuleSymsEvent(Module * pRuntimeModule, AppDomain * pAppDomain);
+
+ BOOL LoadClass(TypeHandle th,
+ mdTypeDef classMetadataToken,
+ Module* classModule,
+ AppDomain *pAppDomain);
+ void UnloadClass(mdTypeDef classMetadataToken,
+ Module* classModule,
+ AppDomain *pAppDomain);
+
+ void SendClassLoadUnloadEvent (mdTypeDef classMetadataToken,
+ DebuggerModule *classModule,
+ Assembly *pAssembly,
+ AppDomain *pAppDomain,
+ BOOL fIsLoadEvent);
+ BOOL SendSystemClassLoadUnloadEvent (mdTypeDef classMetadataToken,
+ Module *classModule,
+ BOOL fIsLoadEvent);
+
+ void SendCatchHandlerFound(Thread *pThread,
+ FramePointer fp,
+ SIZE_T nOffset,
+ DWORD dwFlags);
+
+ LONG NotifyOfCHFFilter(EXCEPTION_POINTERS* pExceptionPointers, PVOID pCatchStackAddr);
+
+
+ bool FirstChanceNativeException(EXCEPTION_RECORD *exception,
+ T_CONTEXT *context,
+ DWORD code,
+ Thread *thread);
+
+ bool IsJMCMethod(Module* pModule, mdMethodDef tkMethod);
+
+ int GetMethodEncNumber(MethodDesc * pMethod);
+
+
+ bool FirstChanceManagedException(Thread *pThread, SIZE_T currentIP, SIZE_T currentSP);
+
+ void FirstChanceManagedExceptionCatcherFound(Thread *pThread,
+ MethodDesc *pMD, TADDR pMethodAddr,
+ BYTE *currentSP,
+ EE_ILEXCEPTION_CLAUSE *pEHClause);
+
+ LONG LastChanceManagedException(EXCEPTION_POINTERS * pExceptionInfo,
+ Thread *pThread,
+ BOOL jitAttachRequested);
+
+ void ManagedExceptionUnwindBegin(Thread *pThread);
+
+ void DeleteInterceptContext(void *pContext);
+
+ void ExceptionFilter(MethodDesc *fd, TADDR pMethodAddr, SIZE_T offset, BYTE *pStack);
+ void ExceptionHandle(MethodDesc *fd, TADDR pMethodAddr, SIZE_T offset, BYTE *pStack);
+
+ int NotifyUserOfFault(bool userBreakpoint, DebuggerLaunchSetting dls);
+
+ SIZE_T GetArgCount(MethodDesc* md, BOOL *fVarArg = NULL);
+
+ void FuncEvalComplete(Thread *pThread, DebuggerEval *pDE);
+
+ DebuggerMethodInfo *CreateMethodInfo(Module *module, mdMethodDef md);
+ void JITComplete(MethodDesc* fd, TADDR newAddress);
+
+ HRESULT RequestFavor(FAVORCALLBACK fp, void * pData);
+
+#ifdef EnC_SUPPORTED
+ HRESULT UpdateFunction(MethodDesc* pFD, SIZE_T encVersion);
+ HRESULT AddFunction(MethodDesc* md, SIZE_T enCVersion);
+ HRESULT UpdateNotYetLoadedFunction(mdMethodDef token, Module * pModule, SIZE_T enCVersion);
+
+ HRESULT AddField(FieldDesc* fd, SIZE_T enCVersion);
+ HRESULT RemapComplete(MethodDesc *pMd, TADDR addr, SIZE_T nativeOffset);
+
+ HRESULT MapILInfoToCurrentNative(MethodDesc *pMD,
+ SIZE_T ilOffset,
+ TADDR nativeFnxStart,
+ SIZE_T *nativeOffset);
+#endif // EnC_SUPPORTED
+
+ void GetVarInfo(MethodDesc * fd, // [IN] method of interest
+ void *DebuggerVersionToken, // [IN] which edit version
+ SIZE_T * cVars, // [OUT] size of 'vars'
+ const ICorDebugInfo::NativeVarInfo **vars // [OUT] map telling where local vars are stored
+ );
+
+ void getBoundariesHelper(MethodDesc * ftn,
+ unsigned int *cILOffsets, DWORD **pILOffsets);
+ void getBoundaries(MethodDesc * ftn,
+ unsigned int *cILOffsets, DWORD **pILOffsets,
+ ICorDebugInfo::BoundaryTypes* implictBoundaries);
+
+ void getVars(MethodDesc * ftn,
+ ULONG32 *cVars, ICorDebugInfo::ILVarInfo **vars,
+ bool *extendOthers);
+
+ DebuggerMethodInfo *GetOrCreateMethodInfo(Module *pModule, mdMethodDef token);
+
+ PTR_DebuggerMethodInfoTable GetMethodInfoTable() { return m_pMethodInfos; }
+
+ // Gets the DJI for 'fd'
+ // If 'pbAddr' is non-NULL and if the method has multiple copies of code
+ // (because of EnC or code-pitching), this returns the DJI corresponding
+ // to 'pbAddr'
+ DebuggerJitInfo *GetJitInfo(MethodDesc *fd, const BYTE *pbAddr, DebuggerMethodInfo **pMethInfo = NULL);
+
+ // Several ways of getting a DJI. DJIs are 1:1 w/ Native Code blobs.
+ // Caller must guarantee good parameters.
+ // DJIs can be lazily created; so the only way these will fail is in an OOM case.
+ DebuggerJitInfo *GetJitInfoFromAddr(TADDR addr);
+
+ // EnC trashes the methoddesc to point to the latest version. Thus given a method-desc,
+ // we can get the most recent DJI.
+ DebuggerJitInfo *GetLatestJitInfoFromMethodDesc(MethodDesc * pMethodDesc);
+
+
+ HRESULT GetILToNativeMapping(MethodDesc *pMD, ULONG32 cMap, ULONG32 *pcMap,
+ COR_DEBUG_IL_TO_NATIVE_MAP map[]);
+
+ HRESULT GetILToNativeMappingIntoArrays(
+ MethodDesc * pMD,
+ USHORT cMapMax,
+ USHORT * pcMap,
+ UINT ** prguiILOffset,
+ UINT ** prguiNativeOffset);
+
+ PRD_TYPE GetPatchedOpcode(CORDB_ADDRESS_TYPE *ip);
+ BOOL CheckGetPatchedOpcode(CORDB_ADDRESS_TYPE *address, /*OUT*/ PRD_TYPE *pOpcode);
+
+ void TraceCall(const BYTE *address);
+
+ bool ThreadsAtUnsafePlaces(void);
+
+
+ void PollWaitingForHelper();
+
+ void IncThreadsAtUnsafePlaces(void)
+ {
+ LIMITED_METHOD_CONTRACT;
+ InterlockedIncrement(&m_threadsAtUnsafePlaces);
+ }
+
+ void DecThreadsAtUnsafePlaces(void)
+ {
+ LIMITED_METHOD_CONTRACT;
+ InterlockedDecrement(&m_threadsAtUnsafePlaces);
+ }
+
+ static StackWalkAction AtSafePlaceStackWalkCallback(CrawlFrame *pCF,
+ VOID* data);
+ bool IsThreadAtSafePlaceWorker(Thread *thread);
+ bool IsThreadAtSafePlace(Thread *thread);
+
+ CorDebugUserState GetFullUserState(Thread *pThread);
+
+
+ void Terminate();
+ void Continue();
+
+ bool HandleIPCEvent(DebuggerIPCEvent* event);
+
+ DebuggerModule * LookupOrCreateModule(VMPTR_DomainFile vmDomainFile);
+ DebuggerModule * LookupOrCreateModule(DomainFile * pDomainFile);
+ DebuggerModule * LookupOrCreateModule(Module * pModule, AppDomain * pAppDomain);
+
+ HRESULT GetAndSendInterceptCommand(DebuggerIPCEvent *event);
+
+ //HRESULT GetAndSendJITFunctionData(DebuggerRCThread* rcThread,
+ // mdMethodDef methodToken,
+ // void* functionModuleToken);
+ HRESULT GetFuncData(mdMethodDef funcMetadataToken,
+ DebuggerModule* pDebuggerModule,
+ SIZE_T nVersion,
+ DebuggerIPCE_FuncData *data);
+
+
+ // The following four functions convert between type handles and the data that is
+ // shipped for types to and from the right-side.
+ //
+ // I'm heading toward getting rid of the first two - they are almost never used.
+ static HRESULT ExpandedTypeInfoToTypeHandle(DebuggerIPCE_ExpandedTypeData *data,
+ unsigned int genericArgsCount,
+ DebuggerIPCE_BasicTypeData *genericArgs,
+ TypeHandle *pRes);
+ static HRESULT BasicTypeInfoToTypeHandle(DebuggerIPCE_BasicTypeData *data,
+ TypeHandle *pRes);
+ void TypeHandleToBasicTypeInfo(AppDomain *pAppDomain,
+ TypeHandle th,
+ DebuggerIPCE_BasicTypeData *res);
+
+ // TypeHandleToExpandedTypeInfo returns different DebuggerIPCE_ExpandedTypeData objects
+ // depending on whether the object value that the TypeData corresponds to is
+ // boxed or not. Different parts of the API transfer objects in slightly different ways.
+ // AllBoxed:
+ // For GetAndSendObjectData all values are boxed,
+ //
+ // StructsBoxed:
+ // When returning results from FuncEval only "true" structs
+ // get boxed, i.e. primitives are unboxed.
+ //
+ // NoSpecialBoxing:
+ // TypeHandleToExpandedTypeInfo is also used to report type parameters,
+ // and in this case none of the types are considered boxed (
+ enum AreValueTypesBoxed { NoValueTypeBoxing, OnlyPrimitivesUnboxed, AllBoxed };
+
+ void TypeHandleToExpandedTypeInfo(AreValueTypesBoxed boxed,
+ AppDomain *pAppDomain,
+ TypeHandle th,
+ DebuggerIPCE_ExpandedTypeData *res);
+
+ class TypeDataWalk
+ {
+ DebuggerIPCE_TypeArgData *m_curdata;
+ unsigned int m_remaining;
+
+ public:
+ TypeDataWalk(DebuggerIPCE_TypeArgData *pData, unsigned int nData)
+ {
+ m_curdata = pData;
+ m_remaining = nData;
+ }
+
+
+ // These are for type arguments in the funceval case.
+ // They throw COMPLUS exceptions if they fail, so can only be used during funceval.
+ void ReadTypeHandles(unsigned int nTypeArgs, TypeHandle *pRes);
+ TypeHandle ReadInstantiation(Module *pModule, mdTypeDef tok, unsigned int nTypeArgs);
+ TypeHandle ReadTypeHandle();
+
+ BOOL Finished() { LIMITED_METHOD_CONTRACT; return m_remaining == 0; }
+ DebuggerIPCE_TypeArgData *ReadOne() { LIMITED_METHOD_CONTRACT; if (m_remaining) { m_remaining--; return m_curdata++; } else return NULL; }
+
+ };
+
+
+
+ HRESULT GetMethodDescData(MethodDesc *pFD,
+ DebuggerJitInfo *pJITInfo,
+ DebuggerIPCE_JITFuncData *data);
+
+ void GetAndSendTransitionStubInfo(CORDB_ADDRESS_TYPE *stubAddress);
+
+ void SendBreakpoint(Thread *thread, T_CONTEXT *context,
+ DebuggerBreakpoint *breakpoint);
+
+ void SendStep(Thread *thread, T_CONTEXT *context,
+ DebuggerStepper *stepper,
+ CorDebugStepReason reason);
+
+ void LockAndSendEnCRemapEvent(DebuggerJitInfo * dji, SIZE_T currentIP, SIZE_T *resumeIP);
+ void LockAndSendEnCRemapCompleteEvent(MethodDesc *pFD);
+ void SendEnCUpdateEvent(DebuggerIPCEventType eventType,
+ Module * pModule,
+ mdToken memberToken,
+ mdTypeDef classToken,
+ SIZE_T enCVersion);
+ void LockAndSendBreakpointSetError(PATCH_UNORDERED_ARRAY * listUnbindablePatches);
+
+ // helper for SendException
+ void SendExceptionEventsWorker(
+ Thread * pThread,
+ bool firstChance,
+ bool fIsInterceptable,
+ bool continuable,
+ SIZE_T currentIP,
+ FramePointer framePointer,
+ bool atSafePlace);
+
+ // Main function to send an exception event, handle jit-attach if needed, etc
+ HRESULT SendException(Thread *pThread,
+ bool fFirstChance,
+ SIZE_T currentIP,
+ SIZE_T currentSP,
+ bool fContinuable,
+ bool fAttaching,
+ bool fForceNonInterceptable,
+ EXCEPTION_POINTERS * pExceptionInfo);
+
+ // Top-level function to handle sending a user-breakpoint, jit-attach, sync, etc.
+ void SendUserBreakpoint(Thread * thread);
+
+ // Send the user breakpoint and block waiting for a continue.
+ void SendUserBreakpointAndSynchronize(Thread * pThread);
+
+ // Just send the actual event.
+ void SendRawUserBreakpoint(Thread *thread);
+
+
+
+ void SendInterceptExceptionComplete(Thread *thread);
+
+ HRESULT AttachDebuggerForBreakpoint(Thread *thread,
+ __in_opt WCHAR *wszLaunchReason);
+
+
+ void ThreadIsSafe(Thread *thread);
+
+ void UnrecoverableError(HRESULT errorHR,
+ unsigned int errorCode,
+ const char *errorFile,
+ unsigned int errorLine,
+ bool exitThread);
+
+ BOOL IsSynchronizing(void)
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ return m_trappingRuntimeThreads;
+ }
+
+ //
+ // The debugger mutex is used to protect any "global" Left Side
+ // data structures. The RCThread takes it when handling a Right
+ // Side event, and Runtime threads take it when processing
+ // debugger events.
+ //
+#ifdef _DEBUG
+ int m_mutexCount;
+#endif
+
+ // Helper function
+ HRESULT AttachDebuggerForBreakpointOnHelperThread(Thread *pThread);
+
+ // helper function to send Exception IPC event and Exception_CallBack2 event
+ HRESULT SendExceptionHelperAndBlock(
+ Thread *pThread,
+ OBJECTHANDLE exceptionHandle,
+ bool continuable,
+ FramePointer framePointer,
+ SIZE_T nOffset,
+ CorDebugExceptionCallbackType eventType,
+ DWORD dwFlags);
+
+
+ // Helper function to send out LogMessage only. Can be either on helper thread or manager thread.
+ void SendRawLogMessage(
+ Thread *pThread,
+ AppDomain *pAppDomain,
+ int iLevel,
+ SString * pCategory,
+ SString * pMessage);
+
+
+ // Helper function to send MDA notification
+ void SendRawMDANotification(SendMDANotificationParams * params);
+ static void SendMDANotificationOnHelperThreadProxy(SendMDANotificationParams * params);
+
+ // Returns a bitfield reflecting the managed debugging state at the time of
+ // the jit attach.
+ CLR_DEBUGGING_PROCESS_FLAGS GetAttachStateFlags();
+
+ // Records that this thread is about to trigger jit attach and
+ // resolves the race for which thread gets to trigger it
+ BOOL PreJitAttach(BOOL willSendManagedEvent, BOOL willLaunchDebugger, BOOL explicitUserRequest);
+
+ // Blocks until the debugger completes jit attach
+ void WaitForDebuggerAttach();
+
+ // Cleans up after jit attach is complete
+ void PostJitAttach();
+
+ // Main worker function to initiate, handle, and wait for a Jit-attach.
+ void JitAttach(Thread * pThread, EXCEPTION_POINTERS * pExceptionInfo, BOOL willSendManagedEvent, BOOL explicitUserRequest);
+
+private:
+ void DoNotCallDirectlyPrivateLock(void);
+ void DoNotCallDirectlyPrivateUnlock(void);
+
+ // This function gets the jit debugger launched and waits for the native attach to complete
+ // Make sure you called PreJitAttach and it returned TRUE before you call this
+ HRESULT LaunchJitDebuggerAndNativeAttach(Thread * pThread, EXCEPTION_POINTERS * pExceptionInfo);
+
+ // Helper to serialize metadata that has been updated by the profiler into
+ // a buffer so that it can be read out-of-proc
+ BYTE* SerializeModuleMetaData(Module * pModule, DWORD * countBytes);
+
+ /// Wrapps fusion Module FusionCopyPDBs.
+ HRESULT CopyModulePdb(Module* pRuntimeModule);
+
+ // When attaching to a process, this is called to enumerate all of the
+ // AppDomains currently in the process and allow modules pdbs to be copied over to the shadow dir maintaining out V2 in-proc behaviour.
+ HRESULT IterateAppDomainsForPdbs();
+
+#ifndef DACCESS_COMPILE
+public:
+ // Helper function to initialize JDI structure
+ void InitDebuggerLaunchJitInfo(Thread * pThread, EXCEPTION_POINTERS * pExceptionInfo);
+
+ // Helper function to retrieve JDI structure
+ JIT_DEBUG_INFO * GetDebuggerLaunchJitInfo(void);
+
+private:
+ static JIT_DEBUG_INFO s_DebuggerLaunchJitInfo;
+ static EXCEPTION_RECORD s_DebuggerLaunchJitInfoExceptionRecord;
+ static CONTEXT s_DebuggerLaunchJitInfoContext;
+
+ static void AcquireDebuggerLock(Debugger *c)
+ {
+ WRAPPER_NO_CONTRACT;
+ c->DoNotCallDirectlyPrivateLock();
+ }
+
+ static void ReleaseDebuggerLock(Debugger *c)
+ {
+ WRAPPER_NO_CONTRACT;
+ c->DoNotCallDirectlyPrivateUnlock();
+ }
+#else // DACCESS_COMPILE
+ static void AcquireDebuggerLock(Debugger *c);
+ static void ReleaseDebuggerLock(Debugger *c);
+#endif // DACCESS_COMPILE
+
+
+public:
+ // define type for DebuggerLockHolder
+ typedef DacHolder<Debugger *, Debugger::AcquireDebuggerLock, Debugger::ReleaseDebuggerLock> DebuggerLockHolder;
+
+ void LockForEventSending(DebuggerLockHolder *dbgLockHolder);
+ void UnlockFromEventSending(DebuggerLockHolder *dbgLockHolder);
+ void SyncAllThreads(DebuggerLockHolder *dbgLockHolder);
+ void SendSyncCompleteIPCEvent();
+
+ // Helper for sending a single pre-baked IPC event and blocking on the continue.
+ // See definition of SENDIPCEVENT_BEGIN for usage pattern.
+ void SendSimpleIPCEventAndBlock();
+
+ void SendCreateProcess(DebuggerLockHolder * pDbgLockHolder);
+
+ void IncrementClassLoadCallbackCount(void)
+ {
+ LIMITED_METHOD_CONTRACT;
+ InterlockedIncrement(&m_dClassLoadCallbackCount);
+ }
+
+ void DecrementClassLoadCallbackCount(void)
+ {
+ LIMITED_METHOD_CONTRACT;
+ _ASSERTE(m_dClassLoadCallbackCount > 0);
+ InterlockedDecrement(&m_dClassLoadCallbackCount);
+ }
+
+
+#ifdef _DEBUG_IMPL
+ bool ThreadHoldsLock(void)
+ {
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ if (g_fProcessDetach)
+ return true;
+
+ BEGIN_GETTHREAD_ALLOWED;
+ if (g_pEEInterface->GetThread())
+ {
+ return (GetThreadIdHelper(g_pEEInterface->GetThread()) == m_mutexOwner);
+ }
+ else
+ {
+ return (GetCurrentThreadId() == m_mutexOwner);
+ }
+ END_GETTHREAD_ALLOWED;
+ }
+#endif // _DEBUG_IMPL
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ static VOID M2UHandoffHijackWorker(
+ T_CONTEXT *pContext,
+ EXCEPTION_RECORD *pExceptionRecord);
+
+ LONG FirstChanceSuspendHijackWorker(
+ T_CONTEXT *pContext,
+ EXCEPTION_RECORD *pExceptionRecord);
+ static void GenericHijackFunc(void);
+ static void SecondChanceHijackFunc(void);
+ static void SecondChanceHijackFuncWorker(void);
+ static void SignalHijackStarted(void);
+ static void ExceptionForRuntimeHandoffStart(void);
+ static void ExceptionForRuntimeHandoffComplete(void);
+ static void SignalHijackComplete(void);
+ static void ExceptionNotForRuntime(void);
+ static void NotifyRightSideOfSyncComplete(void);
+ static void NotifySecondChanceReadyForData(void);
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ void UnhandledHijackWorker(T_CONTEXT * pContext, EXCEPTION_RECORD * pRecord);
+
+ //
+ // InsertToMethodInfoList puts the given DMI onto the DMI list.
+ //
+ HRESULT InsertToMethodInfoList(DebuggerMethodInfo *dmi);
+
+
+ // MapBreakpoints will map any and all breakpoints (except EnC
+ // patches) from previous versions of the method into the current version.
+ HRESULT MapAndBindFunctionPatches( DebuggerJitInfo *pJiNew,
+ MethodDesc * fd,
+ CORDB_ADDRESS_TYPE * addrOfCode);
+
+ // MPTDJI takes the given patch (and djiFrom, if you've got it), and
+ // does the IL mapping forwards to djiTo. Returns
+ // CORDBG_E_CODE_NOT_AVAILABLE if there isn't a mapping, which means that
+ // no patch was placed.
+ HRESULT MapPatchToDJI(DebuggerControllerPatch *dcp, DebuggerJitInfo *djiTo);
+
+ HRESULT LaunchDebuggerForUser(Thread * pThread, EXCEPTION_POINTERS * pExceptionInfo,
+ BOOL useManagedBPForManagedAttach, BOOL explicitUserRequest);
+
+ void SendLogMessage (int iLevel,
+ SString * pSwitchName,
+ SString * pMessage);
+
+ void SendLogSwitchSetting (int iLevel,
+ int iReason,
+ __in_z LPCWSTR pLogSwitchName,
+ __in_z LPCWSTR pParentSwitchName);
+
+ bool IsLoggingEnabled (void)
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ if (m_LoggingEnabled)
+ return true;
+ return false;
+ }
+
+ // send a custom debugger notification to the RS
+ void SendCustomDebuggerNotification(Thread * pThread, DomainFile * pDomain, mdTypeDef classToken);
+
+ // Send an MDA notification. This ultimately translates to an ICorDebugMDA object on the Right-Side.
+ void SendMDANotification(
+ Thread * pThread, // may be NULL. Lets us send on behalf of other threads.
+ SString * szName,
+ SString * szDescription,
+ SString * szXML,
+ CorDebugMDAFlags flags,
+ BOOL bAttach
+ );
+
+
+ void EnableLogMessages (bool fOnOff) {LIMITED_METHOD_CONTRACT; m_LoggingEnabled = fOnOff;}
+ bool GetILOffsetFromNative (MethodDesc *PFD, const BYTE *pbAddr,
+ DWORD nativeOffset, DWORD *ilOffset);
+
+ DWORD GetHelperThreadID(void );
+
+
+ HRESULT SetIP( bool fCanSetIPOnly,
+ Thread *thread,
+ Module *module,
+ mdMethodDef mdMeth,
+ DebuggerJitInfo* dji,
+ SIZE_T offsetILTo,
+ BOOL fIsIL);
+
+ // Helper routines used by Debugger::SetIP
+
+ // If we have a varargs function, we can't set the IP (we don't know how to pack/unpack the arguments), so if we
+ // call SetIP with fCanSetIPOnly = true, we need to check for that.
+ BOOL IsVarArgsFunction(unsigned int nEntries, PTR_NativeVarInfo varNativeInfo);
+
+ HRESULT ShuffleVariablesGet(DebuggerJitInfo *dji,
+ SIZE_T offsetFrom,
+ T_CONTEXT *pCtx,
+ SIZE_T **prgVal1,
+ SIZE_T **prgVal2,
+ BYTE ***prgpVCs);
+
+ HRESULT ShuffleVariablesSet(DebuggerJitInfo *dji,
+ SIZE_T offsetTo,
+ T_CONTEXT *pCtx,
+ SIZE_T **prgVal1,
+ SIZE_T **prgVal2,
+ BYTE **rgpVCs);
+
+ HRESULT GetVariablesFromOffset(MethodDesc *pMD,
+ UINT varNativeInfoCount,
+ ICorDebugInfo::NativeVarInfo *varNativeInfo,
+ SIZE_T offsetFrom,
+ T_CONTEXT *pCtx,
+ SIZE_T *rgVal1,
+ SIZE_T *rgVal2,
+ UINT uRgValSize, // number of element of the preallocated rgVal1 and rgVal2
+ BYTE ***rgpVCs);
+
+ HRESULT SetVariablesAtOffset(MethodDesc *pMD,
+ UINT varNativeInfoCount,
+ ICorDebugInfo::NativeVarInfo *varNativeInfo,
+ SIZE_T offsetTo,
+ T_CONTEXT *pCtx,
+ SIZE_T *rgVal1,
+ SIZE_T *rgVal2,
+ BYTE **rgpVCs);
+
+ BOOL IsThreadContextInvalid(Thread *pThread);
+
+ // notification for SQL fiber debugging support
+ void CreateConnection(CONNID dwConnectionId, __in_z WCHAR *wzName);
+ void DestroyConnection(CONNID dwConnectionId);
+ void ChangeConnection(CONNID dwConnectionId);
+
+ //
+ // This function is used to identify the helper thread.
+ //
+ bool ThisIsHelperThread(void);
+
+ HRESULT ReDaclEvents(PSECURITY_DESCRIPTOR securityDescriptor);
+
+#ifdef DACCESS_COMPILE
+ virtual void EnumMemoryRegions(CLRDataEnumMemoryFlags flags);
+ virtual void EnumMemoryRegionsIfFuncEvalFrame(CLRDataEnumMemoryFlags flags, Frame * pFrame);
+#endif
+
+ BOOL ShouldAutoAttach();
+ BOOL FallbackJITAttachPrompt();
+ HRESULT SetFiberMode(bool isFiberMode);
+
+ HRESULT AddAppDomainToIPC (AppDomain *pAppDomain);
+ HRESULT RemoveAppDomainFromIPC (AppDomain *pAppDomain);
+ HRESULT UpdateAppDomainEntryInIPC (AppDomain *pAppDomain);
+
+ void SendCreateAppDomainEvent(AppDomain * pAppDomain);
+ void SendExitAppDomainEvent (AppDomain *pAppDomain);
+
+ // Notify the debugger that an assembly has been loaded
+ void LoadAssembly(DomainAssembly * pDomainAssembly);
+
+ // Notify the debugger that an assembly has been unloaded
+ void UnloadAssembly(DomainAssembly * pDomainAssembly);
+
+ HRESULT FuncEvalSetup(DebuggerIPCE_FuncEvalInfo *pEvalInfo, BYTE **argDataArea, DebuggerEval **debuggerEvalKey);
+ HRESULT FuncEvalSetupReAbort(Thread *pThread, Thread::ThreadAbortRequester requester);
+ HRESULT FuncEvalAbort(DebuggerEval *debuggerEvalKey);
+ HRESULT FuncEvalRudeAbort(DebuggerEval *debuggerEvalKey);
+ HRESULT FuncEvalCleanup(DebuggerEval *debuggerEvalKey);
+
+ HRESULT SetReference(void *objectRefAddress, VMPTR_OBJECTHANDLE vmObjectHandle, void *newReference);
+ HRESULT SetValueClass(void *oldData, void *newData, DebuggerIPCE_BasicTypeData *type);
+
+ HRESULT SetILInstrumentedCodeMap(MethodDesc *fd,
+ BOOL fStartJit,
+ ULONG32 cILMapEntries,
+ COR_IL_MAP rgILMapEntries[]);
+
+ void EarlyHelperThreadDeath(void);
+
+ void ShutdownBegun(void);
+
+ void LockDebuggerForShutdown(void);
+
+ void DisableDebugger(void);
+
+ // Pid of the left side process that this Debugger instance is in.
+ DWORD GetPid(void) { return m_processId; }
+
+ HRESULT NameChangeEvent(AppDomain *pAppDomain, Thread *pThread);
+
+ // send an event to the RS indicating that there's a Ctrl-C or Ctrl-Break
+ BOOL SendCtrlCToDebugger(DWORD dwCtrlType);
+
+ // Allows the debugger to keep an up to date list of special threads
+ HRESULT UpdateSpecialThreadList(DWORD cThreadArrayLength, DWORD *rgdwThreadIDArray);
+
+ // Updates the pointer for the debugger services
+ void SetIDbgThreadControl(IDebuggerThreadControl *pIDbgThreadControl);
+
+#ifndef DACCESS_COMPILE
+ static void AcquireDebuggerDataLock(Debugger *pDebugger);
+
+ static void ReleaseDebuggerDataLock(Debugger *pDebugger);
+
+#else // DACCESS_COMPILE
+ // determine whether the LS holds the data lock. If it does, we will assume the locked data is in an
+ // inconsistent state and will throw an exception. The DAC will execute this if we are executing code
+ // that takes the lock.
+ static void AcquireDebuggerDataLock(Debugger *pDebugger);
+
+ // unimplemented--nothing to do here
+ static void ReleaseDebuggerDataLock(Debugger *pDebugger);
+
+#endif // DACCESS_COMPILE
+
+ // define type for DebuggerDataLockHolder
+ typedef DacHolder<Debugger *, Debugger::AcquireDebuggerDataLock, Debugger::ReleaseDebuggerDataLock> DebuggerDataLockHolder;
+
+#ifdef _DEBUG
+ // Use for asserts
+ bool HasDebuggerDataLock()
+ {
+ // If no lazy data yet, then can't possibly have the debugger-data lock.
+ if (!g_pDebugger->HasLazyData())
+ {
+ return false;
+ }
+ return (g_pDebugger->GetDebuggerDataLock()->OwnedByCurrentThread()) != 0;
+ }
+#endif
+
+
+ // For Just-My-Code (aka Just-User-Code).
+ // The jit injects probes in debuggable managed methods that look like:
+ // if (*pFlag != 0) call JIT_DbgIsJustMyCode.
+ // pFlag is unique per-method constant determined by GetJMCFlagAddr.
+ // JIT_DbgIsJustMyCode will get the ip & fp and call OnMethodEnter.
+
+ // pIP is an ip within the method, right after the prolog.
+#ifndef DACCESS_COMPILE
+ virtual void OnMethodEnter(void * pIP);
+ virtual DWORD* GetJMCFlagAddr(Module * pModule);
+#endif
+
+ // GetJMCFlagAddr provides a unique flag for each module. UpdateModuleJMCFlag
+ // will go through all modules with user-code and set their flag to fStatus.
+ void UpdateAllModuleJMCFlag(bool fStatus);
+ void UpdateModuleJMCFlag(Module * pRuntime, bool fStatus);
+
+ // Set the default JMC status of the specified module. This function
+ // also finds all the DMIs in the specified module and update their
+ // JMC status as well.
+ void SetModuleDefaultJMCStatus(Module * pRuntimeModule, bool fStatus);
+
+#ifndef DACCESS_COMPILE
+ static DWORD GetThreadIdHelper(Thread *pThread);
+#endif // DACCESS_COMPILE
+
+private:
+ DebuggerJitInfo *GetJitInfoWorker(MethodDesc *fd, const BYTE *pbAddr, DebuggerMethodInfo **pMethInfo);
+
+ // Save the necessary information for the debugger to recognize an IP in one of the thread redirection
+ // functions.
+ void InitializeHijackFunctionAddress();
+
+ void InitDebugEventCounting();
+ void DoHelperThreadDuty();
+
+ typedef enum
+ {
+ ATTACH_YES,
+ ATTACH_NO,
+ ATTACH_TERMINATE
+ } ATTACH_ACTION;
+
+ // Returns true if the debugger is not attached and DbgJITDebugLaunchSetting
+ // is set to either ATTACH_DEBUGGER or ASK_USER and the user request attaching.
+ ATTACH_ACTION ShouldAttachDebugger(bool fIsUserBreakpoint);
+ ATTACH_ACTION ShouldAttachDebuggerProxy(bool fIsUserBreakpoint);
+ friend void ShouldAttachDebuggerStub(ShouldAttachDebuggerParams * p);
+ friend struct ShouldAttachDebuggerParams;
+
+ void TrapAllRuntimeThreads();
+ void ReleaseAllRuntimeThreads(AppDomain *pAppDomain);
+
+#ifndef DACCESS_COMPILE
+ // @dbgtodo inspection - eventually, all replies should be removed because requests will be DAC-ized.
+ // Do not call this function unless you are getting ThreadId from RS
+ void InitIPCReply(DebuggerIPCEvent *ipce,
+ DebuggerIPCEventType type)
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ _ASSERTE(ipce != NULL);
+ ipce->type = type;
+ ipce->hr = S_OK;
+
+ ipce->processId = m_processId;
+ // AppDomain, Thread, are already initialized
+ }
+
+ void InitIPCEvent(DebuggerIPCEvent *ipce,
+ DebuggerIPCEventType type,
+ Thread *pThread,
+ AppDomain* pAppDomain)
+ {
+ WRAPPER_NO_CONTRACT;
+
+ InitIPCEvent(ipce, type, pThread, VMPTR_AppDomain::MakePtr(pAppDomain));
+ }
+
+ // Let this function to figure out the unique Id that we will use for Thread.
+ void InitIPCEvent(DebuggerIPCEvent *ipce,
+ DebuggerIPCEventType type,
+ Thread *pThread,
+ VMPTR_AppDomain vmAppDomain)
+ {
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(ipce != NULL);
+ ipce->type = type;
+ ipce->hr = S_OK;
+ ipce->processId = m_processId;
+ ipce->vmAppDomain = vmAppDomain;
+ ipce->vmThread.SetRawPtr(pThread);
+ }
+
+ void InitIPCEvent(DebuggerIPCEvent *ipce,
+ DebuggerIPCEventType type)
+ {
+ WRAPPER_NO_CONTRACT;
+
+ _ASSERTE((type == DB_IPCE_SYNC_COMPLETE) ||
+ (type == DB_IPCE_TEST_CRST) ||
+ (type == DB_IPCE_TEST_RWLOCK));
+
+ Thread *pThread = g_pEEInterface->GetThread();
+ AppDomain *pAppDomain = NULL;
+
+ if (pThread)
+ {
+ pAppDomain = pThread->GetDomain();
+ }
+
+ InitIPCEvent(ipce,
+ type,
+ pThread,
+ VMPTR_AppDomain::MakePtr(pAppDomain));
+ }
+#endif // DACCESS_COMPILE
+
+ HRESULT GetFunctionInfo(Module *pModule,
+ mdToken functionToken,
+ BYTE **pCodeStart,
+ unsigned int *pCodeSize,
+ mdToken *pLocalSigToken);
+
+ // Allocate a buffer and send it to the right side
+ HRESULT GetAndSendBuffer(DebuggerRCThread* rcThread, ULONG bufSize);
+
+ // Allocate a buffer in the left-side for use by the right-side
+ HRESULT AllocateRemoteBuffer( ULONG bufSize, void **ppBuffer );
+
+ // Releases a previously requested remote bufer and send reply
+ HRESULT SendReleaseBuffer(DebuggerRCThread* rcThread, void *pBuffer);
+
+public:
+ // Release previously requested remmote buffer
+ HRESULT ReleaseRemoteBuffer(void *pBuffer, bool removeFromBlobList);
+
+private:
+#ifdef EnC_SUPPORTED
+ // Apply an EnC edit and send the result event to the RS
+ HRESULT ApplyChangesAndSendResult(DebuggerModule * pDebuggerModule,
+ DWORD cbMetadata,
+ BYTE *pMetadata,
+ DWORD cbIL,
+ BYTE *pIL);
+#endif // EnC_SUPPORTED
+
+ bool GetCompleteDebuggerLaunchString(SString * pStrArgsBuf);
+
+ // Launch a debugger for jit-attach
+ void EnsureDebuggerAttached(Thread * pThread, EXCEPTION_POINTERS * pExceptionInfo, BOOL willSendManagedEvent, BOOL explicitUserRequest);
+ HRESULT EDAHelper(PROCESS_INFORMATION * pProcessInfo);
+ HRESULT EDAHelperProxy(PROCESS_INFORMATION * pProcessInfo);
+ friend void EDAHelperStub(EnsureDebuggerAttachedParams * p);
+ DebuggerLaunchSetting GetDbgJITDebugLaunchSetting();
+
+public:
+ HRESULT InitAppDomainIPC(void);
+ HRESULT TerminateAppDomainIPC(void);
+
+ bool ResumeThreads(AppDomain* pAppDomain);
+
+ static DWORD WaitForSingleObjectHelper(HANDLE handle, DWORD dwMilliseconds);
+
+ void ProcessAnyPendingEvals(Thread *pThread);
+
+ bool HasLazyData();
+ RCThreadLazyInit * GetRCThreadLazyData();
+
+ // The module table is lazy init, and may be NULL. Callers must check.
+ DebuggerModuleTable * GetModuleTable();
+
+ DebuggerHeap *GetInteropSafeHeap();
+ DebuggerHeap *GetInteropSafeHeap_NoThrow();
+ DebuggerHeap *GetInteropSafeExecutableHeap();
+ DebuggerHeap *GetInteropSafeExecutableHeap_NoThrow();
+ DebuggerLazyInit *GetLazyData();
+ HelperCanary * GetCanary();
+ void MarkDebuggerAttachedInternal();
+ void MarkDebuggerUnattachedInternal();
+
+ HANDLE GetAttachEvent() { return GetLazyData()->m_exAttachEvent; }
+
+private:
+#ifndef DACCESS_COMPILE
+ void StartCanaryThread();
+#endif
+ DebuggerPendingFuncEvalTable *GetPendingEvals() { return GetLazyData()->m_pPendingEvals; }
+ SIZE_T_UNORDERED_ARRAY * GetBPMappingDuplicates() { return &GetLazyData()->m_BPMappingDuplicates; }
+ HANDLE GetUnmanagedAttachEvent() { return GetLazyData()->m_exUnmanagedAttachEvent; }
+ BOOL GetDebuggerHandlingCtrlC() { return GetLazyData()->m_DebuggerHandlingCtrlC; }
+ void SetDebuggerHandlingCtrlC(BOOL f) { GetLazyData()->m_DebuggerHandlingCtrlC = f; }
+ HANDLE GetCtrlCMutex() { return GetLazyData()->m_CtrlCMutex; }
+ UnorderedPtrArray* GetMemBlobs() { return &GetLazyData()->m_pMemBlobs; }
+
+
+ PTR_DebuggerRCThread m_pRCThread;
+ DWORD m_processId; // our pid
+ BOOL m_trappingRuntimeThreads;
+ BOOL m_stopped;
+ BOOL m_unrecoverableError;
+ BOOL m_ignoreThreadDetach;
+ PTR_DebuggerMethodInfoTable m_pMethodInfos;
+
+
+ // This is the main debugger lock. It is a large lock and used to synchronize complex operations
+ // such as sending IPC events, debugger sycnhronization, and attach / detach.
+ // The debugger effectively can't make any radical state changes without holding this lock.
+ //
+ //
+ Crst m_mutex; // The main debugger lock.
+
+ // Flag to track if the debugger Crst needs to go into "Shutdown for Finalizer" mode.
+ // This means that only special shutdown threads (helper / finalizer / shutdown) can
+ // take the lock, and all others will just block forever if they take it.
+ bool m_fShutdownMode;
+
+ //
+ // Flag to track if the VM has told the debugger that it should block all threads
+ // as soon as possible as it goes thru the debugger. As of this writing, this is
+ // done via the debugger Crst, anyone attempting to take the lock will block forever.
+ //
+ bool m_fDisabled;
+
+#ifdef _DEBUG
+ // Ownership tracking for debugging.
+ DWORD m_mutexOwner;
+
+ // Tid that last called LockForEventSending.
+ DWORD m_tidLockedForEventSending;
+#endif
+ LONG m_threadsAtUnsafePlaces;
+ Volatile<BOOL> m_jitAttachInProgress;
+
+ // True if after the jit attach we plan to send a managed non-catchup
+ // debug event
+ BOOL m_attachingForManagedEvent;
+ BOOL m_launchingDebugger;
+ BOOL m_userRequestedDebuggerLaunch;
+
+ BOOL m_LoggingEnabled;
+ AppDomainEnumerationIPCBlock *m_pAppDomainCB;
+
+ LONG m_dClassLoadCallbackCount;
+
+ // Lazily initialized array of debugger modules
+ // @dbgtodo module - eventually, DebuggerModule should go away,
+ // and all such information should be stored in either the VM's module class or in the RS.
+ DebuggerModuleTable *m_pModules;
+
+ // DacDbiInterfaceImpl needs to be able to write to private fields in the debugger class.
+ friend class DacDbiInterfaceImpl;
+
+ // Set OOP by RS to request a sync after a debug event.
+ // Clear by LS when we sync.
+ Volatile<BOOL> m_RSRequestedSync;
+
+ // send first chance/handler found callbacks for exceptions outside of JMC to the LS
+ Volatile<BOOL> m_sendExceptionsOutsideOfJMC;
+
+ // represents different thead redirection functions recognized by the debugger
+ enum HijackFunction
+ {
+ kUnhandledException = 0,
+ kRedirectedForGCThreadControl,
+ kRedirectedForDbgThreadControl,
+ kRedirectedForUserSuspend,
+ kRedirectedForYieldTask,
+#if defined(HAVE_GCCOVER) && defined(_TARGET_AMD64_)
+ kRedirectedForGCStress,
+#endif // HAVE_GCCOVER && _TARGET_AMD64_
+ kMaxHijackFunctions,
+ };
+
+ // static array storing the range of the thread redirection functions
+ static MemoryRange s_hijackFunction[kMaxHijackFunctions];
+
+ // Currently DAC doesn't support static array members. This field is used to work around this limitation.
+ ARRAY_PTR_MemoryRange m_rgHijackFunction;
+
+public:
+
+
+ IDebuggerThreadControl *m_pIDbgThreadControl;
+
+
+ // Sometimes we force all exceptions to be non-interceptable.
+ // There are currently three cases where we set this field to true:
+ //
+ // 1) NotifyOfCHFFilter()
+ // - If the CHF filter is the first handler we encounter in the first pass, then there is no
+ // managed stack frame at which we can intercept the exception anyway.
+ //
+ // 2) LastChanceManagedException()
+ // - If Watson is launched for an unhandled exception, then the exception cannot be intercepted.
+ //
+ // 3) SecondChanceHijackFuncWorker()
+ // - The RS hijack the thread to this function to prevent the OS from killing the process at
+ // the end of the first pass. (When a debugger is attached, the OS does not run a second pass.)
+ // This function ensures that the debugger gets a second chance notification.
+ BOOL m_forceNonInterceptable;
+
+ // When we are doing an early attach, the RS shim should not queue all the fake attach events for
+ // the process, the appdomain, and the thread. Otherwise we'll get duplicate events when these
+ // entities are actually created. This flag is used to mark whether we are doing an early attach.
+ // There are still time windows where we can get duplicate events, but this flag closes down the
+ // most common scenario.
+ SVAL_DECL(BOOL, s_fEarlyAttach);
+
+private:
+ Crst * GetDebuggerDataLock() { SUPPORTS_DAC; return &GetLazyData()-> m_DebuggerDataLock; }
+
+ // This is lazily inititalized. It's just a wrapper around a handle so we embed it here.
+ DebuggerHeap m_heap;
+ DebuggerHeap m_executableHeap;
+
+ PTR_DebuggerLazyInit m_pLazyData;
+
+
+ // A list of all defines that affect layout of MD types
+ typedef enum _Target_Defines
+ {
+ DEFINE__DEBUG = 1,
+ } _Target_Defines;
+
+ // A bitfield that has bits set at build time corresponding
+ // to which defines are active
+ static const int _defines = 0
+#ifdef _DEBUG
+ | DEFINE__DEBUG
+#endif
+ ;
+
+public:
+ DWORD m_defines;
+ DWORD m_mdDataStructureVersion;
+};
+
+
+
+extern "C" {
+void STDCALL FuncEvalHijack(void);
+void * STDCALL FuncEvalHijackWorker(DebuggerEval *pDE);
+
+void STDCALL ExceptionHijack(void);
+void STDCALL ExceptionHijackEnd(void);
+void STDCALL ExceptionHijackWorker(T_CONTEXT * pContext, EXCEPTION_RECORD * pRecord, EHijackReason::EHijackReason reason, void * pData);
+
+void RedirectedHandledJITCaseForGCThreadControl_Stub();
+void RedirectedHandledJITCaseForGCThreadControl_StubEnd();
+
+void RedirectedHandledJITCaseForDbgThreadControl_Stub();
+void RedirectedHandledJITCaseForDbgThreadControl_StubEnd();
+
+void RedirectedHandledJITCaseForUserSuspend_Stub();
+void RedirectedHandledJITCaseForUserSuspend_StubEnd();
+
+void RedirectedHandledJITCaseForYieldTask_Stub();
+void RedirectedHandledJITCaseForYieldTask_StubEnd();
+#if defined(HAVE_GCCOVER) && defined(_TARGET_AMD64_)
+void RedirectedHandledJITCaseForGCStress_Stub();
+void RedirectedHandledJITCaseForGCStress_StubEnd();
+#endif // HAVE_GCCOVER && _TARGET_AMD64_
+};
+
+
+// CNewZeroData is the allocator used by the all the hash tables that the helper thread could possibly alter. It uses
+// the interop safe allocator.
+class CNewZeroData
+{
+public:
+#ifndef DACCESS_COMPILE
+ static BYTE *Alloc(int iSize, int iMaxSize)
+ {
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ PRECONDITION(g_pDebugger != NULL);
+ }
+ CONTRACTL_END;
+
+ DebuggerHeap * pHeap = g_pDebugger->GetInteropSafeHeap_NoThrow();
+ if (pHeap == NULL)
+ {
+ return NULL;
+ }
+
+ BYTE *pb = (BYTE *) pHeap->Alloc(iSize);
+ if (pb == NULL)
+ {
+ return NULL;
+ }
+
+ memset(pb, 0, iSize);
+ return pb;
+ }
+ static void Free(BYTE *pPtr, int iSize)
+ {
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ PRECONDITION(g_pDebugger != NULL);
+ }
+ CONTRACTL_END;
+
+
+ DebuggerHeap * pHeap = g_pDebugger->GetInteropSafeHeap_NoThrow();
+ _ASSERTE(pHeap != NULL); // should already exist
+
+ pHeap->Free(pPtr);
+ }
+ static BYTE *Grow(BYTE *&pPtr, int iCurSize)
+ {
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ PRECONDITION(g_pDebugger != NULL);
+ }
+ CONTRACTL_END;
+
+ void *p;
+
+ DebuggerHeap* pHeap = g_pDebugger->GetInteropSafeHeap_NoThrow();
+ _ASSERTE(pHeap != NULL); // should already exist
+
+ PREFIX_ASSUME( iCurSize >= 0 );
+ S_UINT32 iNewSize = S_UINT32( iCurSize ) + S_UINT32( GrowSize(iCurSize) );
+ if( iNewSize.IsOverflow() )
+ {
+ return NULL;
+ }
+ p = pHeap->Realloc(pPtr, iNewSize.Value(), iCurSize);
+ if (p == NULL)
+ {
+ return NULL;
+ }
+
+ memset((BYTE*)p+iCurSize, 0, GrowSize(iCurSize));
+ return (pPtr = (BYTE *)p);
+ }
+
+ // A hashtable may recycle memory. We need to zero it out again.
+ static void Clean(BYTE * pData, int iSize)
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ memset(pData, 0, iSize);
+ }
+#endif // DACCESS_COMPILE
+
+ static int RoundSize(int iSize)
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ return (iSize);
+ }
+ static int GrowSize(int iCurSize)
+ {
+ LIMITED_METHOD_CONTRACT;
+ int newSize = (3 * iCurSize) / 2;
+ return (newSize < 256) ? 256 : newSize;
+ }
+};
+
+class DebuggerPendingFuncEvalTable : private CHashTableAndData<CNewZeroData>
+{
+ public:
+ virtual ~DebuggerPendingFuncEvalTable() = default;
+
+ private:
+
+ BOOL Cmp(SIZE_T k1, const HASHENTRY * pc2)
+ {
+ LIMITED_METHOD_DAC_CONTRACT;
+
+#if defined(DACCESS_COMPILE)
+ // This function hasn't been tested yet in the DAC build. Make sure the DACization is correct.
+ DacNotImpl();
+#endif // DACCESS_COMPILE
+
+ Thread * pThread1 = reinterpret_cast<Thread *>(k1);
+ Thread * pThread2 = dac_cast<PTR_DebuggerPendingFuncEval>(const_cast<HASHENTRY *>(pc2))->pThread;
+
+ return (pThread1 != pThread2);
+ }
+
+ ULONG HASH(Thread* pThread)
+ {
+ LIMITED_METHOD_CONTRACT;
+ return (ULONG)((SIZE_T)pThread); // only use low 32-bits if 64-bit
+ }
+
+
+ SIZE_T KEY(Thread * pThread)
+ {
+ LIMITED_METHOD_CONTRACT;
+ return (SIZE_T)pThread;
+ }
+
+ public:
+
+#ifndef DACCESS_COMPILE
+ DebuggerPendingFuncEvalTable() : CHashTableAndData<CNewZeroData>(11)
+ {
+ WRAPPER_NO_CONTRACT;
+
+ SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE;
+ NewInit(11, sizeof(DebuggerPendingFuncEval), 11);
+ }
+
+ void AddPendingEval(Thread *pThread, DebuggerEval *pDE)
+ {
+ WRAPPER_NO_CONTRACT;
+
+ _ASSERTE((pThread != NULL) && (pDE != NULL));
+
+ DebuggerPendingFuncEval *pfe = (DebuggerPendingFuncEval*)Add(HASH(pThread));
+ pfe->pThread = pThread;
+ pfe->pDE = pDE;
+ }
+
+ void RemovePendingEval(Thread* pThread)
+ {
+ WRAPPER_NO_CONTRACT;
+
+ _ASSERTE(pThread != NULL);
+
+ DebuggerPendingFuncEval *entry = (DebuggerPendingFuncEval*)Find(HASH(pThread), KEY(pThread));
+ Delete(HASH(pThread), (HASHENTRY*)entry);
+ }
+
+#endif // #ifndef DACCESS_COMPILE
+
+ DebuggerPendingFuncEval *GetPendingEval(Thread* pThread)
+ {
+ WRAPPER_NO_CONTRACT;
+
+ DebuggerPendingFuncEval *entry = (DebuggerPendingFuncEval*)Find(HASH(pThread), KEY(pThread));
+ return entry;
+ }
+};
+
+struct DebuggerModuleEntry
+{
+ FREEHASHENTRY entry;
+ PTR_DebuggerModule module;
+};
+
+typedef DPTR(struct DebuggerModuleEntry) PTR_DebuggerModuleEntry;
+
+class DebuggerModuleTable : private CHashTableAndData<CNewZeroData>
+{
+#ifdef DACCESS_COMPILE
+ public:
+ virtual ~DebuggerModuleTable() = default;
+#endif
+
+ private:
+
+ BOOL Cmp(SIZE_T k1, const HASHENTRY * pc2)
+ {
+ LIMITED_METHOD_DAC_CONTRACT;
+
+#if defined(DACCESS_COMPILE)
+ // This function hasn't been tested yet in the DAC build. Make sure the DACization is correct.
+ DacNotImpl();
+#endif // DACCESS_COMPILE
+
+ Module * pModule1 = reinterpret_cast<Module *>(k1);
+ Module * pModule2 =
+ dac_cast<PTR_DebuggerModuleEntry>(const_cast<HASHENTRY *>(pc2))->module->GetRuntimeModule();
+
+ return (pModule1 != pModule2);
+ }
+
+ ULONG HASH(Module* module)
+ {
+ LIMITED_METHOD_CONTRACT;
+ return (ULONG)((SIZE_T)module); // only use low 32-bits if 64-bit
+ }
+
+ SIZE_T KEY(Module * pModule)
+ {
+ LIMITED_METHOD_CONTRACT;
+ return (SIZE_T)pModule;
+ }
+
+#ifdef _DEBUG
+ bool ThreadHoldsLock();
+#endif
+
+public:
+
+#ifndef DACCESS_COMPILE
+
+ DebuggerModuleTable();
+ virtual ~DebuggerModuleTable();
+
+ void AddModule(DebuggerModule *module);
+
+ void RemoveModule(Module* module, AppDomain *pAppDomain);
+
+
+ void Clear();
+
+ //
+ // RemoveModules removes any module loaded into the given appdomain from the hash. This is used when we send an
+ // ExitAppdomain event to ensure that there are no leftover modules in the hash. This can happen when we have shared
+ // modules that aren't properly accounted for in the CLR. We miss sending UnloadModule events for those modules, so
+ // we clean them up with this method.
+ //
+ void RemoveModules(AppDomain *pAppDomain);
+#endif // #ifndef DACCESS_COMPILE
+
+ DebuggerModule *GetModule(Module* module);
+
+ // We should never look for a NULL Module *
+ DebuggerModule *GetModule(Module* module, AppDomain* pAppDomain);
+ DebuggerModule *GetFirstModule(HASHFIND *info);
+ DebuggerModule *GetNextModule(HASHFIND *info);
+};
+
+// struct DebuggerMethodInfoKey: Key for each of the method info hash table entries.
+// Module * m_pModule: This and m_token make up the key
+// mdMethodDef m_token: This and m_pModule make up the key
+//
+// Note: This is used for hashing, so the structure must be totally blittable.
+typedef DPTR(struct DebuggerMethodInfoKey) PTR_DebuggerMethodInfoKey;
+struct DebuggerMethodInfoKey
+{
+ PTR_Module pModule;
+ mdMethodDef token;
+} ;
+
+// struct DebuggerMethodInfoEntry: Entry for the JIT info hash table.
+// FREEHASHENTRY entry: Needed for use by the hash table
+// DebuggerMethodInfo * ji: The actual DebuggerMethodInfo to
+// hash. Note that DMI's will be hashed by MethodDesc.
+typedef DPTR(struct DebuggerMethodInfoEntry) PTR_DebuggerMethodInfoEntry;
+struct DebuggerMethodInfoEntry
+{
+ FREEHASHENTRY entry;
+ DebuggerMethodInfoKey key;
+ SIZE_T nVersion;
+ SIZE_T nVersionLastRemapped;
+ PTR_DebuggerMethodInfo mi;
+
+#ifdef DACCESS_COMPILE
+ void EnumMemoryRegions(CLRDataEnumMemoryFlags flags);
+#endif
+};
+
+// class DebuggerMethodInfoTable: Hash table to hold all the non-JIT related
+// info for each method we see. The JIT infos live in a seperate table
+// keyed by MethodDescs - there may be multiple
+// JITted realizations of each MethodDef, e.g. under different generic
+// assumptions. Hangs off of the Debugger object.
+// INVARIANT: There is only one DebuggerMethodInfo per method
+// in the table. Note that DMI's will be hashed by MethodDesc.
+//
+class DebuggerMethodInfoTable : private CHashTableAndData<CNewZeroData>
+{
+ VPTR_BASE_CONCRETE_VTABLE_CLASS(DebuggerMethodInfoTable);
+
+ public:
+ virtual ~DebuggerMethodInfoTable() = default;
+
+ private:
+ BOOL Cmp(SIZE_T k1, const HASHENTRY * pc2)
+ {
+ LIMITED_METHOD_DAC_CONTRACT;
+
+ // This is the inverse of the KEY() function.
+ DebuggerMethodInfoKey * pDjik = reinterpret_cast<DebuggerMethodInfoKey *>(k1);
+
+ DebuggerMethodInfoEntry * pDjie = dac_cast<PTR_DebuggerMethodInfoEntry>(const_cast<HASHENTRY *>(pc2));
+
+ return (pDjik->pModule != pDjie->key.pModule) ||
+ (pDjik->token != pDjie->key.token);
+ }
+
+ ULONG HASH(DebuggerMethodInfoKey* pDjik)
+ {
+ LIMITED_METHOD_DAC_CONTRACT;
+ return HashPtr( pDjik->token, pDjik->pModule );
+ }
+
+ SIZE_T KEY(DebuggerMethodInfoKey * pDjik)
+ {
+ // This is casting a host pointer to a SIZE_T. So that key is restricted to the host address space.
+ // This key is just passed to Cmp(), which will cast it back to a DebuggerMethodInfoKey*.
+ LIMITED_METHOD_DAC_CONTRACT;
+ return (SIZE_T)pDjik;
+ }
+
+//#define _DEBUG_DMI_TABLE
+
+#ifdef _DEBUG_DMI_TABLE
+public:
+ ULONG CheckDmiTable();
+
+#define CHECK_DMI_TABLE (CheckDmiTable())
+#define CHECK_DMI_TABLE_DEBUGGER (m_pMethodInfos->CheckDmiTable())
+
+#else
+
+#define CHECK_DMI_TABLE
+#define CHECK_DMI_TABLE_DEBUGGER
+
+#endif // _DEBUG_DMI_TABLE
+
+ public:
+
+#ifndef DACCESS_COMPILE
+
+ DebuggerMethodInfoTable();
+
+ HRESULT AddMethodInfo(Module *pModule,
+ mdMethodDef token,
+ DebuggerMethodInfo *mi);
+
+ HRESULT OverwriteMethodInfo(Module *pModule,
+ mdMethodDef token,
+ DebuggerMethodInfo *mi,
+ BOOL fOnlyIfNull);
+
+ // pModule is being unloaded - remove any entries that belong to it. Why?
+ // (a) Correctness: the module can be reloaded at the same address,
+ // which will cause accidental matches with our hashtable (indexed by
+ // {Module*,mdMethodDef}
+ // (b) Perf: don't waste the memory!
+ void ClearMethodsOfModule(Module *pModule);
+ void DeleteEntryDMI(DebuggerMethodInfoEntry *entry);
+
+#endif // #ifndef DACCESS_COMPILE
+
+ DebuggerMethodInfo *GetMethodInfo(Module *pModule, mdMethodDef token);
+ DebuggerMethodInfo *GetFirstMethodInfo(HASHFIND *info);
+ DebuggerMethodInfo *GetNextMethodInfo(HASHFIND *info);
+
+#ifdef DACCESS_COMPILE
+ void EnumMemoryRegions(CLRDataEnumMemoryFlags flags);
+#endif
+};
+
+class DebuggerEvalBreakpointInfoSegment
+{
+public:
+ // DebuggerEvalBreakpointInfoSegment contains just the breakpoint
+ // instruction and a pointer to the associated DebuggerEval. It makes
+ // it easy to go from the instruction to the corresponding DebuggerEval
+ // object. It has been separated from the rest of the DebuggerEval
+ // because it needs to be in a section of memory that's executable,
+ // while the rest of DebuggerEval does not. By having it separate, we
+ // don't need to have the DebuggerEval contents in executable memory.
+ BYTE m_breakpointInstruction[CORDbg_BREAK_INSTRUCTION_SIZE];
+ DebuggerEval *m_associatedDebuggerEval;
+
+ DebuggerEvalBreakpointInfoSegment(DebuggerEval* dbgEval)
+ : m_associatedDebuggerEval(dbgEval)
+ {
+ ASSERT(dbgEval != NULL);
+ }
+};
+
+/* ------------------------------------------------------------------------ *
+ * DebuggerEval class
+ *
+ * Note that arguments get passsed in a block allocated when
+ * the func-eval is set up. The setup phase passes the total count of arguments.
+ *
+ * In some situations type arguments must also be passed, e.g.
+ * when performing a "newarr" operation or calling a generic function with a
+ * "funceval". In the setup phase we pass a count of the number of
+ * nodes in the "flattened" type expressions for the type arguments, if any.
+ * e.g. for calls to non-generic code this is 0.
+ * - for "newobj List<int>" this is 1: there is one type argument "int".
+ * - for "newobj Dict<string,int>" this is 2: there are two
+ * type arguments "string" and "int".
+ * - for "newobj Dict<string,List<int>>" this is 3: there are two
+ type arguments but the second contains two nodes (one for List and one for int).
+ * The type argument will get placed in the allocated argument block,
+ * the order being determined by the order they occur in the tree, i.e.
+ * left-to-right, top-to-bottom in the type expressions tree, e.g. for
+ * type arguments <string,List<int>> you get string followed by List followed by int.
+ * ------------------------------------------------------------------------ */
+
+class DebuggerEval
+{
+public:
+
+ //
+ // Used as a bit field.
+ //
+ enum FUNC_EVAL_ABORT_TYPE
+ {
+ FE_ABORT_NONE = 0,
+ FE_ABORT_NORMAL = 1,
+ FE_ABORT_RUDE = 2
+ };
+
+ T_CONTEXT m_context;
+ Thread *m_thread;
+ DebuggerIPCE_FuncEvalType m_evalType;
+ mdMethodDef m_methodToken;
+ mdTypeDef m_classToken;
+ ADID m_appDomainId; // Safe even if AD unloaded
+ PTR_DebuggerModule m_debuggerModule; // Only valid if AD is still around
+ RSPTR_CORDBEVAL m_funcEvalKey;
+ bool m_successful; // Did the eval complete successfully
+ Debugger::AreValueTypesBoxed m_retValueBoxing; // Is the return value boxed?
+ unsigned int m_argCount;
+ unsigned int m_genericArgsCount;
+ unsigned int m_genericArgsNodeCount;
+ SIZE_T m_stringSize;
+ BYTE *m_argData;
+ MethodDesc *m_md;
+ PCODE m_targetCodeAddr;
+ ARG_SLOT m_result[NUMBER_RETURNVALUE_SLOTS];
+ TypeHandle m_resultType;
+ SIZE_T m_arrayRank;
+ FUNC_EVAL_ABORT_TYPE m_aborting; // Has an abort been requested, and what type.
+ bool m_aborted; // Was this eval aborted
+ bool m_completed; // Is the eval complete - successfully or by aborting
+ bool m_evalDuringException;
+ bool m_rethrowAbortException;
+ Thread::ThreadAbortRequester m_requester; // For aborts, what kind?
+ VMPTR_OBJECTHANDLE m_vmObjectHandle;
+ TypeHandle m_ownerTypeHandle;
+ DebuggerEvalBreakpointInfoSegment* m_bpInfoSegment;
+
+ DebuggerEval(T_CONTEXT * pContext, DebuggerIPCE_FuncEvalInfo * pEvalInfo, bool fInException);
+
+ // This constructor is only used when setting up an eval to re-abort a thread.
+ DebuggerEval(T_CONTEXT * pContext, Thread * pThread, Thread::ThreadAbortRequester requester);
+
+ bool Init()
+ {
+ _ASSERTE(DbgIsExecutable(&m_bpInfoSegment->m_breakpointInstruction, sizeof(m_bpInfoSegment->m_breakpointInstruction)));
+ return true;
+ }
+
+ // The m_argData buffer holds both the type arg data (for generics) and the main argument data.
+ //
+ // For DB_IPCE_FET_NEW_STRING it holds the data specifying the string to create.
+ DebuggerIPCE_TypeArgData *GetTypeArgData()
+ {
+ LIMITED_METHOD_CONTRACT;
+ return (DebuggerIPCE_TypeArgData *) (m_argData);
+ }
+
+ DebuggerIPCE_FuncEvalArgData *GetArgData()
+ {
+ LIMITED_METHOD_CONTRACT;
+ return (DebuggerIPCE_FuncEvalArgData*) (m_argData + m_genericArgsNodeCount * sizeof(DebuggerIPCE_TypeArgData));
+ }
+
+ WCHAR *GetNewStringArgData()
+ {
+ LIMITED_METHOD_CONTRACT;
+ _ASSERTE(m_evalType == DB_IPCE_FET_NEW_STRING);
+ return (WCHAR*)m_argData;
+ }
+
+ ~DebuggerEval()
+ {
+ WRAPPER_NO_CONTRACT;
+
+ // Clean up any temporary buffers used to send the argument type information. These were allocated
+ // in respnse to a GET_BUFFER message
+ DebuggerIPCE_FuncEvalArgData *argData = GetArgData();
+ for (unsigned int i = 0; i < m_argCount; i++)
+ {
+ if (argData[i].fullArgType != NULL)
+ {
+ _ASSERTE(g_pDebugger != NULL);
+ g_pDebugger->ReleaseRemoteBuffer((BYTE*)argData[i].fullArgType, true);
+ }
+ }
+
+ // Clean up the array of argument information. This was allocated as part of Func Eval setup.
+ if (m_argData)
+ {
+ DeleteInteropSafe(m_argData);
+ }
+
+#ifdef _DEBUG
+ // Set flags to strategic values in case we access deleted memory.
+ m_completed = false;
+ m_rethrowAbortException = true;
+#endif
+ }
+};
+
+/* ------------------------------------------------------------------------ *
+ * New/delete overrides to use the debugger's private heap
+ * ------------------------------------------------------------------------ */
+
+class InteropSafe {};
+#define interopsafe (*(InteropSafe*)NULL)
+
+class InteropSafeExecutable {};
+#define interopsafeEXEC (*(InteropSafeExecutable*)NULL)
+
+#ifndef DACCESS_COMPILE
+inline void * __cdecl operator new(size_t n, const InteropSafe&)
+{
+ CONTRACTL
+ {
+ THROWS; // throw on OOM
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(g_pDebugger != NULL);
+ void *result = g_pDebugger->GetInteropSafeHeap()->Alloc((DWORD)n);
+ if (result == NULL) {
+ ThrowOutOfMemory();
+ }
+ return result;
+}
+
+inline void * __cdecl operator new[](size_t n, const InteropSafe&)
+{
+ CONTRACTL
+ {
+ THROWS; // throw on OOM
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+ _ASSERTE(g_pDebugger != NULL);
+ void *result = g_pDebugger->GetInteropSafeHeap()->Alloc((DWORD)n);
+ if (result == NULL) {
+ ThrowOutOfMemory();
+ }
+ return result;
+}
+
+inline void * __cdecl operator new(size_t n, const InteropSafe&, const NoThrow&) throw()
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(g_pDebugger != NULL);
+ DebuggerHeap * pHeap = g_pDebugger->GetInteropSafeHeap_NoThrow();
+ if (pHeap == NULL)
+ {
+ return NULL;
+ }
+ void *result = pHeap->Alloc((DWORD)n);
+ return result;
+}
+
+inline void * __cdecl operator new[](size_t n, const InteropSafe&, const NoThrow&) throw()
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(g_pDebugger != NULL);
+ DebuggerHeap * pHeap = g_pDebugger->GetInteropSafeHeap_NoThrow();
+ if (pHeap == NULL)
+ {
+ return NULL;
+ }
+ void *result = pHeap->Alloc((DWORD)n);
+ return result;
+}
+
+// Note: there is no C++ syntax for manually invoking this, but if a constructor throws an exception I understand that
+// this delete operator will be invoked automatically to destroy the object.
+inline void __cdecl operator delete(void *p, const InteropSafe&)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ if (p != NULL)
+ {
+ _ASSERTE(g_pDebugger != NULL);
+ DebuggerHeap * pHeap = g_pDebugger->GetInteropSafeHeap_NoThrow();
+ _ASSERTE(pHeap != NULL); // should have had heap around if we're deleting
+ pHeap->Free(p);
+ }
+}
+
+// Note: there is no C++ syntax for manually invoking this, but if a constructor throws an exception I understand that
+// this delete operator will be invoked automatically to destroy the object.
+inline void __cdecl operator delete[](void *p, const InteropSafe&)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ if (p != NULL)
+ {
+ _ASSERTE(g_pDebugger != NULL);
+ DebuggerHeap * pHeap = g_pDebugger->GetInteropSafeHeap_NoThrow();
+ _ASSERTE(pHeap != NULL); // should have had heap around if we're deleting
+
+ pHeap->Free(p);
+ }
+}
+
+//
+// Interop safe delete to match the interop safe new's above. There is no C++ syntax for actually invoking those interop
+// safe delete operators above, so we use this method to accomplish the same thing.
+//
+template<class T> void DeleteInteropSafe(T *p)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ // Don't stop a thread that may hold the Interop-safe heap lock.
+ // It may be in preemptive, but it's still "inside" the CLR and so inside the "Can't-Stop-Region"
+ CantStopHolder hHolder;
+
+ if (p != NULL)
+ {
+ p->~T();
+
+ _ASSERTE(g_pDebugger != NULL);
+ DebuggerHeap * pHeap = g_pDebugger->GetInteropSafeHeap_NoThrow();
+ _ASSERTE(pHeap != NULL); // should have had heap around if we're deleting
+
+ pHeap->Free(p);
+ }
+}
+
+inline void * __cdecl operator new(size_t n, const InteropSafeExecutable&)
+{
+ CONTRACTL
+ {
+ THROWS; // throw on OOM
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(g_pDebugger != NULL);
+ void *result = g_pDebugger->GetInteropSafeExecutableHeap()->Alloc((DWORD)n);
+ if (result == NULL) {
+ ThrowOutOfMemory();
+ }
+ return result;
+}
+
+inline void * __cdecl operator new(size_t n, const InteropSafeExecutable&, const NoThrow&) throw()
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(g_pDebugger != NULL);
+ DebuggerHeap * pHeap = g_pDebugger->GetInteropSafeExecutableHeap_NoThrow();
+ if (pHeap == NULL)
+ {
+ return NULL;
+ }
+ void *result = pHeap->Alloc((DWORD)n);
+ return result;
+}
+
+// Note: there is no C++ syntax for manually invoking this, but if a constructor throws an exception I understand that
+// this delete operator will be invoked automatically to destroy the object.
+inline void __cdecl operator delete(void *p, const InteropSafeExecutable&)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ if (p != NULL)
+ {
+ _ASSERTE(g_pDebugger != NULL);
+ DebuggerHeap * pHeap = g_pDebugger->GetInteropSafeExecutableHeap_NoThrow();
+ _ASSERTE(pHeap != NULL); // should have had heap around if we're deleting
+ pHeap->Free(p);
+ }
+}
+
+//
+// Interop safe delete to match the interop safe new's above. There is no C++ syntax for actually invoking those interop
+// safe delete operators above, so we use this method to accomplish the same thing.
+//
+template<class T> void DeleteInteropSafeExecutable(T *p)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ // Don't stop a thread that may hold the Interop-safe heap lock.
+ // It may be in preemptive, but it's still "inside" the CLR and so inside the "Can't-Stop-Region"
+ CantStopHolder hHolder;
+
+ if (p != NULL)
+ {
+ p->~T();
+
+ _ASSERTE(g_pDebugger != NULL);
+ DebuggerHeap * pHeap = g_pDebugger->GetInteropSafeExecutableHeap_NoThrow();
+ _ASSERTE(pHeap != NULL); // should have had heap around if we're deleting
+
+ pHeap->Free(p);
+ }
+}
+#endif // DACCESS_COMPILE
+
+
+#if _DEBUG
+#define DBG_RUNTIME_MAX ((DB_IPCE_RUNTIME_LAST&0xff)+1)
+#define DBG_DEBUGGER_MAX ((DB_IPCE_DEBUGGER_LAST&0xff)+1)
+
+#define DbgLog(event) DbgLogHelper(event)
+void DbgLogHelper(DebuggerIPCEventType event);
+#else
+#define DbgLog(event)
+#endif // _DEBUG
+
+//-----------------------------------------------------------------------------
+// Helpers for cleanup
+// These are various utility functions, mainly where we factor out code.
+//-----------------------------------------------------------------------------
+void GetPidDecoratedName(__out_ecount(cBufSizeInChars) WCHAR * pBuf,
+ int cBufSizeInChars,
+ const WCHAR * pPrefix);
+
+// Specify type of Win32 event
+enum EEventResetType {
+ kManualResetEvent = TRUE,
+ kAutoResetEvent = FALSE
+};
+
+HANDLE CreateWin32EventOrThrow(
+ LPSECURITY_ATTRIBUTES lpEventAttributes,
+ EEventResetType eType,
+ BOOL bInitialState
+);
+
+HANDLE OpenWin32EventOrThrow(
+ DWORD dwDesiredAccess,
+ BOOL bInheritHandle,
+ LPCWSTR lpName
+);
+
+// @todo - should this be moved into where we defined IPCWriterInterface?
+// Holder for security Attribute
+// Old code:
+// hr = g_pIPCManagerInterface->GetSecurityAttributes(GetCurrentProcessId(), &pSA);
+// .... foo(pSa)...
+// g_pIPCManagerInterface->DestroySecurityAttributes(pSA);
+//
+// new code:
+// {
+// SAHolder x(g_pIPCManagerInterface, GetCurrentProcessId());
+// .... foo(x.GetSA()) ..
+// } // calls dtor
+class IPCHostSecurityAttributeHolder
+{
+public:
+ IPCHostSecurityAttributeHolder(DWORD pid);
+ ~IPCHostSecurityAttributeHolder();
+
+ SECURITY_ATTRIBUTES * GetHostSA();
+
+protected:
+ SECURITY_ATTRIBUTES *m_pSA; // the resource we're protecting.
+};
+
+#define SENDIPCEVENT_RAW_BEGIN_EX(pDbgLockHolder, gcxStmt) \
+ { \
+ Debugger::DebuggerLockHolder *__pDbgLockHolder = pDbgLockHolder; \
+ gcxStmt; \
+ g_pDebugger->LockForEventSending(__pDbgLockHolder);
+
+#define SENDIPCEVENT_RAW_END_EX \
+ g_pDebugger->UnlockFromEventSending(__pDbgLockHolder); \
+ }
+
+#define SENDIPCEVENT_RAW_BEGIN(pDbgLockHolder) \
+ SENDIPCEVENT_RAW_BEGIN_EX(pDbgLockHolder, GCX_PREEMP_EEINTERFACE_TOGGLE_COND(CORDebuggerAttached()))
+
+#define SENDIPCEVENT_RAW_END SENDIPCEVENT_RAW_END_EX
+
+// Suspend-aware SENDIPCEVENT macros:
+// Check whether __thread has been suspended by the debugger via SetDebugState().
+// If this thread has been suspended, it shouldn't send any event to the RS because the
+// debugger may not be expecting it. Instead, just leave the lock and retry.
+// When we leave, we'll enter coop mode first and get suspended if a suspension is in progress.
+// Afterwards, we'll transition back into preemptive mode, and we'll block because this thread
+// has been suspended by the debugger (see code:Thread::RareEnablePreemptiveGC).
+#define SENDIPCEVENT_BEGIN_EX(pDebugger, thread, gcxStmt) \
+ { \
+ FireEtwDebugIPCEventStart(); \
+ bool __fRetry = true; \
+ do \
+ { \
+ { \
+ Debugger::DebuggerLockHolder __dbgLockHolder(pDebugger, FALSE); \
+ Debugger::DebuggerLockHolder *__pDbgLockHolder = &__dbgLockHolder; \
+ gcxStmt; \
+ g_pDebugger->LockForEventSending(__pDbgLockHolder); \
+ /* Check if the thread has been suspended by the debugger via SetDebugState(). */ \
+ if (thread != NULL && thread->HasThreadStateNC(Thread::TSNC_DebuggerUserSuspend)) \
+ { \
+ /* Just leave the lock and retry (see comment above for explanation */ \
+ } \
+ else \
+ { \
+ __fRetry = false; \
+
+#define SENDIPCEVENT_END_EX \
+ ; \
+ } \
+ g_pDebugger->UnlockFromEventSending(__pDbgLockHolder); \
+ } /* ~gcxStmt & ~DebuggerLockHolder */ \
+ } while (__fRetry); \
+ FireEtwDebugIPCEventEnd(); \
+ }
+
+
+// The typical SENDIPCEVENT - toggles the GC mode...
+#define SENDIPCEVENT_BEGIN(pDebugger, thread) \
+ SENDIPCEVENT_BEGIN_EX(pDebugger, thread, GCX_PREEMP_EEINTERFACE_TOGGLE_IFTHREAD_COND(CORDebuggerAttached()))
+
+// Convenience macro to match SENDIPCEVENT_BEGIN
+#define SENDIPCEVENT_END SENDIPCEVENT_END_EX
+
+
+// Use this if you need to access the DebuggerLockHolder set up by SENDIPCEVENT_BEGIN.
+// This is valid only between the SENDIPCEVENT_BEGIN / SENDIPCEVENT_END macros
+#define SENDIPCEVENT_PtrDbgLockHolder __pDbgLockHolder
+
+
+// Common contract for sending events.
+// Used inbetween SENDIPCEVENT_BEGIN & _END.
+//
+// Can't GC trigger b/c if we're sycning we'll deadlock:
+// - We'll block at the GC toggle (b/c we're syncing).
+// - But we're holding the LockForEventSending "lock", so we'll block the helper trying to send a
+// SuspendComplete
+//
+// @todo- we could also assert that:
+// - m_tidLockedForEventSending = GetCurrentThreadId();
+#define SENDEVENT_CONTRACT_ITEMS \
+ GC_NOTRIGGER; \
+ MODE_PREEMPTIVE; \
+ PRECONDITION(g_pDebugger->ThreadHoldsLock()); \
+ PRECONDITION(!g_pDebugger->IsStopped()); \
+
+
+//-----------------------------------------------------------------------------
+// Sample usage for sending IPC _Notification_ events.
+// This is different then SendIPCReply (which is used to reply to events
+// initiated by the RS).
+//-----------------------------------------------------------------------------
+
+// Thread *pThread = g_pEEInterface->GetThread();
+// SENDIPCEVENT_BEGIN(g_pDebugger, pThread); // or use "this" if inside a Debugger method
+// _ASSERTE(ThreadHoldsLock()); // we now hold the debugger lock.
+// // debugger may have detached while we were blocked above.
+//
+// if (CORDebuggerAttached()) {
+// // Send as many IPC events as we wish.
+// SendIPCEvent(....);
+// SendIPCEvent(....);
+// SendIPCEvent(....);
+//
+// if (we sent an event) {
+// TrapAllRuntimeThreads();
+// }
+// }
+//
+// // We block here while the debugger responds to the event.
+// SENDIPCEVENT_END;
+
+// Or if we just want to send a single IPC event and block, we can do this:
+//
+// < ... Init IPC Event ...>
+// SendSimpleIPCEventAndBlock(); <-- this will block
+//
+// Note we don't have to call SENDIPCEVENT_BEGIN / END in this case.
+
+// @todo - further potential cleanup to the IPC sending:
+// - Make SendIPCEvent + TrapAllRuntimeThreads check for CORDebuggerAttached() so that we
+// can always call them after SENDIPCEVENT_BEGIN
+// - Assert that SendIPCEVent is only called inbetween a Begin/End pair
+// - count if we actually send any IPCEvents inbetween a Begin/End pair, and then have
+// SendIPCEvent_END call TrapAllRuntimeThreads automatically for us.
+
+
+// Include all of the inline stuff now.
+#include "debugger.inl"
+
+
+//
+//
+//
+// The below contract defines should only be used (A) if they apply, and (B) they are the LEAST
+// definitive for the function you are contracting. The below defines represent the baseline contract
+// for each case.
+//
+// e.g. If a function FOO() throws, always, you should use THROWS, not any of the below.
+//
+//
+//
+#if _DEBUG
+
+#define MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT \
+ if (!m_pRCThread->IsRCThreadReady()) { THROWS; } else { NOTHROW; }
+
+#define MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT \
+ if (!m_pRCThread->IsRCThreadReady() || (GetThread() != NULL)) { GC_TRIGGERS; } else { GC_NOTRIGGER; }
+
+#define GC_TRIGGERS_FROM_GETJITINFO if (GetThreadNULLOk() != NULL) { GC_TRIGGERS; } else { GC_NOTRIGGER; }
+
+//
+// The DebuggerDataLock lock is UNSAFE_ANYMODE, which means that we cannot
+// take a GC while someone is holding it. Unfortunately this means that
+// we cannot contract for a "possible" GC trigger statically, and must
+// rely on runtime coverage to find any code path that may cause a GC.
+//
+#define CALLED_IN_DEBUGGERDATALOCK_HOLDER_SCOPE_MAY_GC_TRIGGERS_CONTRACT WRAPPER(GC_TRIGGERS)
+
+#else
+
+#define MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT
+#define MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT
+#define CALLED_IN_DEBUGGERDATALOCK_HOLDER_SCOPE_MAY_GC_TRIGGERS_CONTRACT
+
+#define GC_TRIGGERS_FROM_GETJITINFO
+
+#endif
+
+// Returns true if the specified IL offset has a special meaning (eg. prolog, etc.)
+bool DbgIsSpecialILOffset(DWORD offset);
+
+#if !defined(_TARGET_X86_)
+void FixupDispatcherContext(T_DISPATCHER_CONTEXT* pDispatcherContext, T_CONTEXT* pContext, T_CONTEXT* pOriginalContext, PEXCEPTION_ROUTINE pUnwindPersonalityRoutine = NULL);
+#endif
+
+#endif /* DEBUGGER_H_ */
diff --git a/src/debug/ee/debugger.inl b/src/debug/ee/debugger.inl
new file mode 100644
index 0000000000..dbd5479a69
--- /dev/null
+++ b/src/debug/ee/debugger.inl
@@ -0,0 +1,303 @@
+// 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.
+//*****************************************************************************
+// File: debugger.inl
+//
+
+//
+// Inline definitions for the Left-Side of the CLR debugging services
+// This is logically part of the header file.
+//
+//*****************************************************************************
+
+#ifndef DEBUGGER_INL_
+#define DEBUGGER_INL_
+
+//=============================================================================
+// Inlined methods for Debugger.
+//=============================================================================
+inline bool Debugger::HasLazyData()
+{
+ LIMITED_METHOD_CONTRACT;
+ return (m_pLazyData != NULL);
+}
+inline RCThreadLazyInit *Debugger::GetRCThreadLazyData()
+{
+ LIMITED_METHOD_CONTRACT;
+ return &(GetLazyData()->m_RCThread);
+}
+
+inline DebuggerLazyInit *Debugger::GetLazyData()
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ _ASSERTE(m_pLazyData != NULL);
+ return m_pLazyData;
+}
+
+inline DebuggerModuleTable * Debugger::GetModuleTable()
+{
+ LIMITED_METHOD_CONTRACT;
+
+ return m_pModules;
+}
+
+
+//=============================================================================
+// Inlined methods for DebuggerModule.
+//=============================================================================
+
+
+//-----------------------------------------------------------------------------
+// Constructor for a Debugger-Module.
+// @dbgtodo inspection - get rid of this entire class as we move things out-of-proc.
+//-----------------------------------------------------------------------------
+inline DebuggerModule::DebuggerModule(Module * pRuntimeModule,
+ DomainFile * pDomainFile,
+ AppDomain * pAppDomain) :
+ m_enableClassLoadCallbacks(FALSE),
+ m_pPrimaryModule(NULL),
+ m_pRuntimeModule(pRuntimeModule),
+ m_pRuntimeDomainFile(pDomainFile),
+ m_pAppDomain(pAppDomain)
+{
+ LOG((LF_CORDB,LL_INFO10000, "DM::DM this:0x%x Module:0x%x DF:0x%x AD:0x%x\n",
+ this, pRuntimeModule, pDomainFile, pAppDomain));
+
+ // Pick a primary module.
+ // Arguably, this could be in DebuggerModuleTable::AddModule
+ PickPrimaryModule();
+
+
+ // Do we have any optimized code?
+ DWORD dwDebugBits = pRuntimeModule->GetDebuggerInfoBits();
+ m_fHasOptimizedCode = CORDebuggerAllowJITOpts(dwDebugBits);
+
+ // Dynamic modules must receive ClassLoad callbacks in order to receive metadata updates as the module
+ // evolves. So we force this on here and refuse to change it for all dynamic modules.
+ if (pRuntimeModule->IsReflection())
+ {
+ EnableClassLoadCallbacks(TRUE);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Returns true if we have any optimized code in the module.
+//
+// Notes:
+// JMC-probes aren't emitted in optimized code.
+// <TODO> Life would be nice if the Jit tracked this. </TODO>
+//-----------------------------------------------------------------------------
+inline bool DebuggerModule::HasAnyOptimizedCode()
+{
+ LIMITED_METHOD_CONTRACT;
+ Module * pModule = this->GetPrimaryModule()->GetRuntimeModule();
+ DWORD dwDebugBits = pModule->GetDebuggerInfoBits();
+ return CORDebuggerAllowJITOpts(dwDebugBits);
+}
+
+//-----------------------------------------------------------------------------
+// Return true if we've enabled class-load callbacks.
+//-----------------------------------------------------------------------------
+inline BOOL DebuggerModule::ClassLoadCallbacksEnabled(void)
+{
+ return m_enableClassLoadCallbacks;
+}
+
+//-----------------------------------------------------------------------------
+// Set whether we should enable class-load callbacks for this module.
+//-----------------------------------------------------------------------------
+inline void DebuggerModule::EnableClassLoadCallbacks(BOOL f)
+{
+ if (m_enableClassLoadCallbacks != f)
+ {
+ if (f)
+ {
+ _ASSERTE(g_pDebugger != NULL);
+ g_pDebugger->IncrementClassLoadCallbackCount();
+ }
+ else
+ {
+ _ASSERTE(g_pDebugger != NULL);
+ g_pDebugger->DecrementClassLoadCallbackCount();
+ }
+
+ m_enableClassLoadCallbacks = f;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Return the appdomain that this module exists in.
+//-----------------------------------------------------------------------------
+inline AppDomain* DebuggerModule::GetAppDomain()
+{
+ return m_pAppDomain;
+}
+
+//-----------------------------------------------------------------------------
+// Return the EE module that this module corresponds to.
+//-----------------------------------------------------------------------------
+inline Module * DebuggerModule::GetRuntimeModule()
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return m_pRuntimeModule;
+}
+
+//-----------------------------------------------------------------------------
+// <TODO> (8/12/2002)
+// Currently we create a new DebuggerModules for each appdomain a shared
+// module lives in. We then pretend there aren't any shared modules.
+// This is bad. We need to move away from this.
+// Once we stop lying, then every module will be it's own PrimaryModule. :)
+//
+// Currently, Module* is 1:n w/ DebuggerModule.
+// We add a notion of PrimaryModule so that:
+// Module* is 1:1 w/ DebuggerModule::GetPrimaryModule();
+// This should help transition towards exposing shared modules.
+// If the Runtime module is shared, then this gives a common DM.
+// If the runtime module is not shared, then this is an identity function.
+// </TODO>
+//-----------------------------------------------------------------------------
+inline DebuggerModule * DebuggerModule::GetPrimaryModule()
+{
+ _ASSERTE(m_pPrimaryModule != NULL);
+ return m_pPrimaryModule;
+}
+
+//-----------------------------------------------------------------------------
+// This is called by DebuggerModuleTable to set our primary module.
+//-----------------------------------------------------------------------------
+inline void DebuggerModule::SetPrimaryModule(DebuggerModule * pPrimary)
+{
+ _ASSERTE(pPrimary != NULL);
+ // Our primary module must by definition refer to the same runtime module as us
+ _ASSERTE(pPrimary->GetRuntimeModule() == this->GetRuntimeModule());
+
+ LOG((LF_CORDB, LL_EVERYTHING, "DM::SetPrimaryModule - this=%p, pPrimary=%p\n", this, pPrimary));
+ m_pPrimaryModule = pPrimary;
+}
+
+inline DebuggerEval * FuncEvalFrame::GetDebuggerEval()
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return m_pDebuggerEval;
+}
+
+inline unsigned FuncEvalFrame::GetFrameAttribs(void)
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+
+ if (GetDebuggerEval()->m_evalDuringException)
+ {
+ return FRAME_ATTR_NONE;
+ }
+ else
+ {
+ return FRAME_ATTR_RESUMABLE; // Treat the next frame as the top frame.
+ }
+}
+
+inline TADDR FuncEvalFrame::GetReturnAddressPtr()
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+
+ if (GetDebuggerEval()->m_evalDuringException)
+ {
+ return NULL;
+ }
+ else
+ {
+ return PTR_HOST_MEMBER_TADDR(FuncEvalFrame, this, m_ReturnAddress);
+ }
+}
+
+//
+// This updates the register display for a FuncEvalFrame.
+//
+inline void FuncEvalFrame::UpdateRegDisplay(const PREGDISPLAY pRD)
+{
+ SUPPORTS_DAC;
+ DebuggerEval * pDE = GetDebuggerEval();
+
+ // No context to update if we're doing a func eval from within exception processing.
+ if (pDE->m_evalDuringException)
+ {
+ return;
+ }
+
+#if !defined(_WIN64)
+ // Reset pContext; it's only valid for active (top-most) frame.
+ pRD->pContext = NULL;
+#endif // !_WIN64
+
+
+#ifdef _TARGET_X86_
+ // Update all registers in the reg display from the CONTEXT we stored when the thread was hijacked for this func
+ // eval. We have to update all registers, not just the callee saved registers, because we can hijack a thread at any
+ // point for a func eval, not just at a call site.
+ pRD->pEdi = &(pDE->m_context.Edi);
+ pRD->pEsi = &(pDE->m_context.Esi);
+ pRD->pEbx = &(pDE->m_context.Ebx);
+ pRD->pEdx = &(pDE->m_context.Edx);
+ pRD->pEcx = &(pDE->m_context.Ecx);
+ pRD->pEax = &(pDE->m_context.Eax);
+ pRD->pEbp = &(pDE->m_context.Ebp);
+ pRD->Esp = (DWORD)GetSP(&pDE->m_context);
+ pRD->PCTAddr = GetReturnAddressPtr();
+ pRD->ControlPC = *PTR_PCODE(pRD->PCTAddr);
+
+#elif defined(_TARGET_AMD64_)
+ pRD->IsCallerContextValid = FALSE;
+ pRD->IsCallerSPValid = FALSE; // Don't add usage of this flag. This is only temporary.
+
+ memcpy(pRD->pCurrentContext, &(pDE->m_context), sizeof(CONTEXT));
+
+ pRD->pCurrentContextPointers->Rax = &(pDE->m_context.Rax);
+ pRD->pCurrentContextPointers->Rcx = &(pDE->m_context.Rcx);
+ pRD->pCurrentContextPointers->Rdx = &(pDE->m_context.Rdx);
+ pRD->pCurrentContextPointers->R8 = &(pDE->m_context.R8);
+ pRD->pCurrentContextPointers->R9 = &(pDE->m_context.R9);
+ pRD->pCurrentContextPointers->R10 = &(pDE->m_context.R10);
+ pRD->pCurrentContextPointers->R11 = &(pDE->m_context.R11);
+
+ pRD->pCurrentContextPointers->Rbx = &(pDE->m_context.Rbx);
+ pRD->pCurrentContextPointers->Rsi = &(pDE->m_context.Rsi);
+ pRD->pCurrentContextPointers->Rdi = &(pDE->m_context.Rdi);
+ pRD->pCurrentContextPointers->Rbp = &(pDE->m_context.Rbp);
+ pRD->pCurrentContextPointers->R12 = &(pDE->m_context.R12);
+ pRD->pCurrentContextPointers->R13 = &(pDE->m_context.R13);
+ pRD->pCurrentContextPointers->R14 = &(pDE->m_context.R14);
+ pRD->pCurrentContextPointers->R15 = &(pDE->m_context.R15);
+
+ // SyncRegDisplayToCurrentContext() sets the pRD->SP and pRD->ControlPC on AMD64.
+ SyncRegDisplayToCurrentContext(pRD);
+
+#elif defined(_TARGET_ARM_)
+ pRD->IsCallerContextValid = FALSE;
+ pRD->IsCallerSPValid = FALSE; // Don't add usage of this flag. This is only temporary.
+
+ memcpy(pRD->pCurrentContext, &(pDE->m_context), sizeof(T_CONTEXT));
+
+ pRD->pCurrentContextPointers->R4 = &(pDE->m_context.R4);
+ pRD->pCurrentContextPointers->R5 = &(pDE->m_context.R5);
+ pRD->pCurrentContextPointers->R6 = &(pDE->m_context.R6);
+ pRD->pCurrentContextPointers->R7 = &(pDE->m_context.R7);
+ pRD->pCurrentContextPointers->R8 = &(pDE->m_context.R8);
+ pRD->pCurrentContextPointers->R9 = &(pDE->m_context.R9);
+ pRD->pCurrentContextPointers->R10 = &(pDE->m_context.R10);
+ pRD->pCurrentContextPointers->R11 = &(pDE->m_context.R11);
+ pRD->pCurrentContextPointers->Lr = &(pDE->m_context.Lr);
+
+ pRD->volatileCurrContextPointers.R0 = &(pDE->m_context.R0);
+ pRD->volatileCurrContextPointers.R1 = &(pDE->m_context.R1);
+ pRD->volatileCurrContextPointers.R2 = &(pDE->m_context.R2);
+ pRD->volatileCurrContextPointers.R3 = &(pDE->m_context.R3);
+ pRD->volatileCurrContextPointers.R12 = &(pDE->m_context.R12);
+
+ SyncRegDisplayToCurrentContext(pRD);
+#else
+ PORTABILITY_ASSERT("FuncEvalFrame::UpdateRegDisplay is not implemented on this platform.");
+#endif
+}
+
+#endif // DEBUGGER_INL_
diff --git a/src/debug/ee/debuggermodule.cpp b/src/debug/ee/debuggermodule.cpp
new file mode 100644
index 0000000000..25504263b1
--- /dev/null
+++ b/src/debug/ee/debuggermodule.cpp
@@ -0,0 +1,444 @@
+// 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.
+//*****************************************************************************
+// File: DebuggerModule.cpp
+//
+
+//
+// Stuff for tracking DebuggerModules.
+//
+//*****************************************************************************
+
+#include "stdafx.h"
+#include "../inc/common.h"
+#include "perflog.h"
+#include "eeconfig.h" // This is here even for retail & free builds...
+#include "vars.hpp"
+#include <limits.h>
+#include "ilformatter.h"
+#include "debuginfostore.h"
+
+
+/* ------------------------------------------------------------------------ *
+ * Debugger Module routines
+ * ------------------------------------------------------------------------ */
+
+// <TODO> (8/12/2002)
+// We need to stop lying to the debugger about not sharing Modules.
+// Primary Modules allow a transition to that. Once we stop lying,
+// then all modules will be their own Primary.
+// </TODO>
+// Select the primary module.
+// Primary Modules are selected DebuggerModules that map 1:1 w/ Module*.
+// If the runtime module is not shared, then we're our own Primary Module.
+// If the Runtime module is shared, the primary module is some specific instance.
+// Note that a domain-neutral module can be loaded into multiple domains without
+// being loaded into the default domain, and so there is no "primary module" as far
+// as the CLR is concerned - we just pick any one and call it primary.
+void DebuggerModule::PickPrimaryModule()
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ Debugger::DebuggerDataLockHolder ch(g_pDebugger);
+
+ LOG((LF_CORDB, LL_INFO100000, "DM::PickPrimaryModule, this=0x%p\n", this));
+
+ // We're our own primary module, unless something else proves otherwise.
+ // Note that we should be able to skip all of this if this module is not domain neutral
+ m_pPrimaryModule = this;
+
+ // This should be thread safe because our creation for the DebuggerModules
+ // are serialized.
+
+ // Lookup our Runtime Module. If it's already in there,
+ // then
+ DebuggerModuleTable * pTable = g_pDebugger->GetModuleTable();
+
+ // If the table doesn't exist yet, then we must be a primary module.
+ if (pTable == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO100000, "DM::PickPrimaryModule, this=0x%p, table not created yet\n", this));
+ return;
+ }
+
+ // Look through existing module list to find a common primary DebuggerModule
+ // for the given EE Module. We don't know what order we'll traverse in.
+
+ HASHFIND f;
+ for (DebuggerModule * m = pTable->GetFirstModule(&f);
+ m != NULL;
+ m = pTable->GetNextModule(&f))
+ {
+
+ if (m->GetRuntimeModule() == this->GetRuntimeModule())
+ {
+ // Make sure we're picking another primary module.
+ if (m->GetPrimaryModule() == m)
+ {
+ // If we find another one, it must be domain neutral
+ _ASSERTE( m_pRuntimeModule->GetAssembly()->IsDomainNeutral() );
+
+ m_pPrimaryModule = m;
+ LOG((LF_CORDB, LL_INFO100000, "DM::PickPrimaryModule, this=0x%p, primary=0x%p\n", this, m));
+ return;
+ }
+ }
+ } // end for
+
+ // If we got here, then this instance is a Primary Module.
+ LOG((LF_CORDB, LL_INFO100000, "DM::PickPrimaryModule, this=%p is first, primary.\n", this));
+}
+
+void DebuggerModule::SetCanChangeJitFlags(bool fCanChangeJitFlags)
+{
+ m_fCanChangeJitFlags = fCanChangeJitFlags;
+}
+
+#ifndef DACCESS_COMPILE
+
+
+DebuggerModuleTable::DebuggerModuleTable() : CHashTableAndData<CNewZeroData>(101)
+{
+ WRAPPER_NO_CONTRACT;
+
+ SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE;
+ NewInit(101, sizeof(DebuggerModuleEntry), 101);
+}
+
+DebuggerModuleTable::~DebuggerModuleTable()
+{
+ WRAPPER_NO_CONTRACT;
+
+ _ASSERTE(ThreadHoldsLock());
+ Clear();
+}
+
+
+#ifdef _DEBUG
+bool DebuggerModuleTable::ThreadHoldsLock()
+{
+ // In shutdown (g_fProcessDetach), the shutdown thread implicitly holds all locks.
+ return g_fProcessDetach || g_pDebugger->HasDebuggerDataLock();
+}
+#endif
+
+//
+// RemoveModules removes any module loaded into the given appdomain from the hash. This is used when we send an
+// ExitAppdomain event to ensure that there are no leftover modules in the hash. This can happen when we have shared
+// modules that aren't properly accounted for in the CLR. We miss sending UnloadModule events for those modules, so
+// we clean them up with this method.
+//
+void DebuggerModuleTable::RemoveModules(AppDomain *pAppDomain)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_INFO1000, "DMT::RM removing all modules from AD 0x%08x\n", pAppDomain));
+
+ _ASSERTE(ThreadHoldsLock());
+
+ HASHFIND hf;
+ DebuggerModuleEntry *pDME = (DebuggerModuleEntry *) FindFirstEntry(&hf);
+
+ while (pDME != NULL)
+ {
+ DebuggerModule *pDM = pDME->module;
+
+ if (pDM->GetAppDomain() == pAppDomain)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "DMT::RM removing DebuggerModule 0x%08x\n", pDM));
+
+ // Defer to the normal logic in RemoveModule for the actual removal. This accuratley simulates what
+ // happens when we process an UnloadModule event.
+ RemoveModule(pDM->GetRuntimeModule(), pAppDomain);
+
+ // Start back at the first entry since we just modified the hash.
+ pDME = (DebuggerModuleEntry *) FindFirstEntry(&hf);
+ }
+ else
+ {
+ pDME = (DebuggerModuleEntry *) FindNextEntry(&hf);
+ }
+ }
+
+ LOG((LF_CORDB, LL_INFO1000, "DMT::RM done removing all modules from AD 0x%08x\n", pAppDomain));
+}
+
+void DebuggerModuleTable::Clear()
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(ThreadHoldsLock());
+
+ HASHFIND hf;
+ DebuggerModuleEntry *pDME;
+
+ pDME = (DebuggerModuleEntry *) FindFirstEntry(&hf);
+
+ while (pDME)
+ {
+ DebuggerModule *pDM = pDME->module;
+ Module *pEEM = pDM->GetRuntimeModule();
+
+ TRACE_FREE(pDME->module);
+ DeleteInteropSafe(pDM);
+ Delete(HASH(pEEM), (HASHENTRY *) pDME);
+
+ pDME = (DebuggerModuleEntry *) FindFirstEntry(&hf);
+ }
+
+ CHashTableAndData<CNewZeroData>::Clear();
+}
+
+void DebuggerModuleTable::AddModule(DebuggerModule *pModule)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(ThreadHoldsLock());
+
+ _ASSERTE(pModule != NULL);
+
+ LOG((LF_CORDB, LL_EVERYTHING, "DMT::AM: DebuggerMod:0x%x Module:0x%x AD:0x%x\n",
+ pModule, pModule->GetRuntimeModule(), pModule->GetAppDomain()));
+
+ DebuggerModuleEntry * pEntry = (DebuggerModuleEntry *) Add(HASH(pModule->GetRuntimeModule()));
+ if (pEntry == NULL)
+ {
+ ThrowOutOfMemory();
+ }
+
+ pEntry->module = pModule;
+
+ // Don't need to update the primary module since it was set when we created the module.
+ _ASSERTE(pModule->GetPrimaryModule() != NULL);
+}
+
+//-----------------------------------------------------------------------------
+// Remove a DebuggerModule from the module table.
+// This occurs in response to AppDomain unload.
+// Note that this doesn't necessarily mean the EE Module is being unloaded (it may be shared)
+//-----------------------------------------------------------------------------
+void DebuggerModuleTable::RemoveModule(Module* module, AppDomain *pAppDomain)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(module != NULL);
+ _ASSERTE(ThreadHoldsLock());
+
+ DebuggerModule * pDeletedModule = NULL;
+
+ LOG((LF_CORDB, LL_EVERYTHING, "DMT::RM: mod:0x%x AD:0x%x neutral:0x%x\n",
+ module, pAppDomain, module->GetAssembly()->IsDomainNeutral() ));
+
+ // If this is a domain neutral module, then scan the complete list of DebuggerModules looking
+ // for the one with a matching appdomain id.
+ // Note: we have to make sure to lookup the module with the app domain parameter if the module lives in a shared
+ // assembly or the system assembly. <BUGNUM>Bugs 65943 & 81728.</BUGNUM>
+ _ASSERTE( SystemDomain::SystemAssembly()->IsDomainNeutral() );
+ if (module->GetAssembly()->IsDomainNeutral())
+ {
+ // This module is being unloaded from a specific AppDomain, but may still exist in other AppDomains
+
+ HASHFIND findmodule;
+ DebuggerModuleEntry *moduleentry;
+
+ for (moduleentry = (DebuggerModuleEntry*) FindFirstEntry(&findmodule);
+ moduleentry != NULL;
+ moduleentry = (DebuggerModuleEntry*) FindNextEntry(&findmodule))
+ {
+ DebuggerModule *pModule = moduleentry->module;
+
+ if ((pModule->GetRuntimeModule() == module) &&
+ (pModule->GetAppDomain() == pAppDomain))
+ {
+ LOG((LF_CORDB, LL_EVERYTHING, "DMT::RM: found 0x%x (DM:0x%x)\n",
+ moduleentry, moduleentry->module));
+
+ pDeletedModule = pModule;
+
+ // Remove from table
+ Delete(HASH(module), (HASHENTRY *)moduleentry);
+
+ break;
+ }
+ }
+ // we should always find the module!!
+ _ASSERTE (moduleentry != NULL);
+ }
+ else
+ {
+ // This module is not shared among multiple AppDomains
+
+ DebuggerModuleEntry *entry
+ = (DebuggerModuleEntry *) Find(HASH(module), KEY(module));
+
+ _ASSERTE(entry != NULL); // it had better be in there!
+
+ if (entry != NULL) // if its not, we fail gracefully in a free build
+ {
+ LOG((LF_CORDB, LL_EVERYTHING, "DMT::RM: found 0x%x (DM:0x%x)\n",
+ entry, entry->module));
+
+ pDeletedModule = entry->module;
+
+ // Remove from table
+ Delete(HASH(module), (HASHENTRY *)entry);
+
+ // There should not be any other entry in the table for the same module
+ _ASSERTE( Find(HASH(module), KEY(module)) == NULL );
+ }
+ }
+
+ _ASSERTE(pDeletedModule != NULL);
+
+ // Update the primary module pointers. If any other module had this as a
+ // primary module, then we have to update that pointer (since we can't
+ // have our primary module be deleted!)
+ {
+ HASHFIND findmodule;
+ DebuggerModuleEntry *moduleentry;
+
+ DebuggerModule * pNewPrimary = NULL;
+
+ for (moduleentry = (DebuggerModuleEntry*) FindFirstEntry(&findmodule);
+ moduleentry != NULL;
+ moduleentry = (DebuggerModuleEntry*) FindNextEntry(&findmodule))
+ {
+ DebuggerModule *pOther = moduleentry->module;
+ _ASSERTE(pOther != NULL);
+ _ASSERTE(pOther != pDeletedModule);
+
+ // If pOther's primary was just deleted, then update it.
+ if (pOther->GetPrimaryModule() == pDeletedModule)
+ {
+ if (pNewPrimary == NULL)
+ {
+ pNewPrimary = pOther;
+ LOG((LF_CORDB, LL_INFO1000, "DMT::RM changed primary module from 0x%p to 0x%p\n", pDeletedModule, pNewPrimary));
+ }
+ pOther->SetPrimaryModule(pNewPrimary);
+ }
+ } // end for
+ }
+
+ DeleteInteropSafe(pDeletedModule);
+}
+
+
+#endif // DACCESS_COMPILE
+
+DebuggerModule *DebuggerModuleTable::GetModule(Module* module)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(module != NULL);
+ _ASSERTE(ThreadHoldsLock());
+
+ DebuggerModuleEntry *entry
+ = (DebuggerModuleEntry *) Find(HASH(module), KEY(module));
+ if (entry == NULL)
+ return NULL;
+ else
+ return entry->module;
+}
+
+// We should never look for a NULL Module *
+DebuggerModule *DebuggerModuleTable::GetModule(Module* module, AppDomain* pAppDomain)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(module != NULL);
+ _ASSERTE(ThreadHoldsLock());
+
+
+ HASHFIND findmodule;
+ DebuggerModuleEntry *moduleentry;
+
+ for (moduleentry = (DebuggerModuleEntry*) FindFirstEntry(&findmodule);
+ moduleentry != NULL;
+ moduleentry = (DebuggerModuleEntry*) FindNextEntry(&findmodule))
+ {
+ DebuggerModule *pModule = moduleentry->module;
+
+ if ((pModule->GetRuntimeModule() == module) &&
+ (pModule->GetAppDomain() == pAppDomain))
+ return pModule;
+ }
+
+ // didn't find any match! So return a matching module for any app domain
+ return NULL;
+}
+
+DebuggerModule *DebuggerModuleTable::GetFirstModule(HASHFIND *info)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(ThreadHoldsLock());
+
+ DebuggerModuleEntry *entry = (DebuggerModuleEntry *) FindFirstEntry(info);
+ if (entry == NULL)
+ return NULL;
+ else
+ return entry->module;
+}
+
+DebuggerModule *DebuggerModuleTable::GetNextModule(HASHFIND *info)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(ThreadHoldsLock());
+
+ DebuggerModuleEntry *entry = (DebuggerModuleEntry *) FindNextEntry(info);
+ if (entry == NULL)
+ return NULL;
+ else
+ return entry->module;
+}
+
+
diff --git a/src/debug/ee/frameinfo.cpp b/src/debug/ee/frameinfo.cpp
new file mode 100644
index 0000000000..35e5bb9a09
--- /dev/null
+++ b/src/debug/ee/frameinfo.cpp
@@ -0,0 +1,2211 @@
+// 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.
+//*****************************************************************************
+// File: frameinfo.cpp
+//
+
+//
+// Code to find control info about a stack frame.
+//
+//*****************************************************************************
+
+#include "stdafx.h"
+
+// Include so we can get information out of ComMethodFrame
+#ifdef FEATURE_COMINTEROP
+#include "COMToClrCall.h"
+#endif
+
+// Get a frame pointer from a RegDisplay.
+// This is mostly used for chains and stub frames (i.e. internal frames), where we don't need an exact
+// frame pointer. This is why it is okay to use the current SP instead of the caller SP on IA64.
+// We should really rename this and possibly roll it into GetFramePointer() when we move the stackwalker
+// to OOP.
+FramePointer GetSP(REGDISPLAY * pRDSrc)
+{
+ FramePointer fp = FramePointer::MakeFramePointer(
+ (LPVOID)GetRegdisplaySP(pRDSrc));
+
+ return fp;
+}
+
+// Get a frame pointer from a RegDisplay.
+FramePointer GetFramePointer(REGDISPLAY * pRDSrc)
+{
+ return FramePointer::MakeFramePointer(GetRegdisplaySP(pRDSrc));
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Convert a FramePointer to a StackFrame and return it.
+//
+// Arguments:
+// fp - the FramePointer to be converted
+//
+// Return Value:
+// a StackFrame equivalent to the given FramePointer
+//
+// Notes:
+// We really should consolidate the two abstractions for "stack frame identifiers"
+// (StackFrame and FramePointer) when we move the debugger stackwalker to OOP.
+//
+
+FORCEINLINE StackFrame ConvertFPToStackFrame(FramePointer fp)
+{
+ return StackFrame((UINT_PTR)fp.GetSPValue());
+}
+
+/* ------------------------------------------------------------------------- *
+ * DebuggerFrameInfo routines
+ * ------------------------------------------------------------------------- */
+
+//struct DebuggerFrameData: Contains info used by the DebuggerWalkStackProc
+// to do a stack walk. The info and pData fields are handed to the pCallback
+// routine at each frame,
+struct DebuggerFrameData
+{
+ // Initialize this struct. Only done at the start of a stackwalk.
+ void Init(
+ Thread * _pThread,
+ FramePointer _targetFP,
+ BOOL fIgnoreNonmethodFrames, // generally true for stackwalking and false for stepping
+ DebuggerStackCallback _pCallback,
+ void *_pData
+ )
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ this->pCallback = _pCallback;
+ this->pData = _pData;
+
+ this->cRealCounter = 0;
+
+ this->thread = _pThread;
+ this->targetFP = _targetFP;
+ this->targetFound = (_targetFP == LEAF_MOST_FRAME);
+
+ this->ignoreNonmethodFrames = fIgnoreNonmethodFrames;
+
+ // For now, we can tie these to flags together.
+ // In everett, we disable SIS (For backwards compat).
+ this->fProvideInternalFrames = (fIgnoreNonmethodFrames != 0);
+
+ this->fNeedToSendEnterManagedChain = false;
+ this->fTrackingUMChain = false;
+ this->fHitExitFrame = false;
+
+ this->info.eStubFrameType = STUBFRAME_NONE;
+ this->info.quickUnwind = false;
+
+ this->info.frame = NULL;
+ this->needParentInfo = false;
+
+#ifdef WIN64EXCEPTIONS
+ this->fpParent = LEAF_MOST_FRAME;
+ this->info.fIsLeaf = true;
+ this->info.fIsFunclet = false;
+ this->info.fIsFilter = false;
+#endif // WIN64EXCEPTIONS
+
+ // Look strange? Go to definition of this field. I dare you.
+ this->info.fIgnoreThisFrameIfSuppressingUMChainFromComPlusMethodFrameGeneric = false;
+
+#if defined(_DEBUG)
+ this->previousFP = LEAF_MOST_FRAME;
+#endif // _DEBUG
+ }
+
+ // True if we need the next CrawlFrame to fill out part of this FrameInfo's data.
+ bool needParentInfo;
+
+ // The FrameInfo that we'll dispatch to the pCallback. This matches against
+ // the CrawlFrame for that frame that the callback belongs too.
+ FrameInfo info;
+
+ // Regdisplay that the EE stackwalker is updating.
+ REGDISPLAY regDisplay;
+
+
+#ifdef WIN64EXCEPTIONS
+ // This is used to skip funclets in a stackwalk. It marks the frame pointer to which we should skip.
+ FramePointer fpParent;
+#endif // WIN64EXCEPTIONS
+#if defined(_DEBUG)
+ // For debugging, track the previous FramePointer so we can assert that we're
+ // making progress through the stack.
+ FramePointer previousFP;
+#endif // _DEBUG
+
+ // whether we have hit an exit frame or not (i.e. a M2U frame)
+ bool fHitExitFrame;
+
+private:
+ // The scope of this field is each section of managed method frames on the stack.
+ bool fNeedToSendEnterManagedChain;
+
+ // Flag set when we first stack-walk to decide if we want to ignore certain frames.
+ // Stepping doesn't ignore these frames; end user stacktraces do.
+ BOOL ignoreNonmethodFrames;
+
+ // Do we want callbacks for internal frames?
+ // Steppers generally don't. User stack-walk does.
+ bool fProvideInternalFrames;
+
+ // Info for tracking unmanaged chains.
+ // We track the starting (leaf) context for an unmanaged chain, as well as the
+ // ending (root) framepointer.
+ bool fTrackingUMChain;
+ REGDISPLAY rdUMChainStart;
+ FramePointer fpUMChainEnd;
+
+ // Thread that the stackwalk is for.
+ Thread *thread;
+
+
+ // Target FP indicates at what point in the stackwalk we'll start dispatching callbacks.
+ // Naturally, if this is LEAF_MOST_FRAME, then all callbacks will be dispatched
+ FramePointer targetFP;
+ bool targetFound;
+
+ // Count # of callbacks we could have dispatched (assuming targetFP==LEAF_MOST_FRAME).
+ // Useful for detecting leaf.
+ int cRealCounter;
+
+ // Callback & user-data supplied to that callback.
+ DebuggerStackCallback pCallback;
+ void *pData;
+
+ private:
+
+ // Raw invoke. This just does some consistency asserts,
+ // and invokes the callback if we're in the requested target range.
+ StackWalkAction RawInvokeCallback(FrameInfo * pInfo)
+ {
+#ifdef _DEBUG
+ _ASSERTE(pInfo != NULL);
+ MethodDesc * md = pInfo->md;
+ // Invoke the callback to the user. Log what we're invoking.
+ LOG((LF_CORDB, LL_INFO10000, "DSWCallback: MD=%s,0x%p, Chain=%x, Stub=%x, Frame=0x%p, Internal=%d\n",
+ ((md == NULL) ? "None" : md->m_pszDebugMethodName), md,
+ pInfo->chainReason,
+ pInfo->eStubFrameType,
+ pInfo->frame, pInfo->internal));
+
+ // Make sure we're providing a valid FrameInfo for the callback.
+ pInfo->AssertValid();
+#endif
+ // Update counter. This provides a convenient check for leaf FrameInfo.
+ this->cRealCounter++;
+
+
+ // Only invoke if we're past the target.
+ if (!this->targetFound && IsEqualOrCloserToLeaf(this->targetFP, this->info.fp))
+ {
+ this->targetFound = true;
+ }
+
+ if (this->targetFound)
+ {
+ return (pCallback)(pInfo, pData);
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO10000, "Not invoking yet.\n"));
+ }
+
+ return SWA_CONTINUE;
+ }
+
+public:
+ // Invoke a callback. This may do extra logic to preserve the interface between
+ // the LS stackwalker and the LS:
+ // - don't invoke if we're not at the target yet
+ // - send EnterManagedChains if we need it.
+ StackWalkAction InvokeCallback(FrameInfo * pInfo)
+ {
+ // Track if we've sent any managed code yet.
+ // If we haven't, then don't send the enter-managed chain. This catches cases
+ // when we have leaf-most unmanaged chain.
+ if ((pInfo->frame == NULL) && (pInfo->md != NULL))
+ {
+ this->fNeedToSendEnterManagedChain = true;
+ }
+
+
+ // Do tracking to decide if we need to send a Enter-Managed chain.
+ if (pInfo->HasChainMarker())
+ {
+ if (pInfo->managed)
+ {
+ // If we're dispatching a managed-chain, then we don't need to send another one.
+ fNeedToSendEnterManagedChain = false;
+ }
+ else
+ {
+ // If we're dispatching an UM chain, then send the Managed one.
+ // Note that the only unmanaged chains are ThreadStart chains and UM chains.
+ if (fNeedToSendEnterManagedChain)
+ {
+ fNeedToSendEnterManagedChain = false;
+
+ FrameInfo f;
+
+ // Assume entry chain's FP is one pointer-width after the upcoming UM chain.
+ FramePointer fpRoot = FramePointer::MakeFramePointer(
+ (BYTE*) GetRegdisplaySP(&pInfo->registers) - sizeof(DWORD*));
+
+ f.InitForEnterManagedChain(fpRoot);
+ if (RawInvokeCallback(&f) == SWA_ABORT)
+ {
+ return SWA_ABORT;
+ }
+ }
+ }
+ }
+
+ return RawInvokeCallback(pInfo);
+ }
+
+ // Note that we should start tracking an Unmanaged Chain.
+ void BeginTrackingUMChain(FramePointer fpRoot, REGDISPLAY * pRDSrc)
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ _ASSERTE(!this->fTrackingUMChain);
+
+ CopyREGDISPLAY(&this->rdUMChainStart, pRDSrc);
+
+ this->fTrackingUMChain = true;
+ this->fpUMChainEnd = fpRoot;
+ this->fHitExitFrame = false;
+
+ LOG((LF_CORDB, LL_EVERYTHING, "UM Chain starting at Frame=0x%p\n", this->fpUMChainEnd.GetSPValue()));
+
+ // This UM chain may get cancelled later, so don't even worry about toggling the fNeedToSendEnterManagedChain bit here.
+ // Invoke() will track whether to send an Enter-Managed chain or not.
+ }
+
+ // For various heuristics, we may not want to send an UM chain.
+ void CancelUMChain()
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ _ASSERTE(this->fTrackingUMChain);
+ this->fTrackingUMChain = false;
+ }
+
+ // True iff we're currently tracking an unmanaged chain.
+ bool IsTrackingUMChain()
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ return this->fTrackingUMChain;
+ }
+
+
+
+ // Get/Set Regdisplay that starts an Unmanaged chain.
+ REGDISPLAY * GetUMChainStartRD()
+ {
+ LIMITED_METHOD_CONTRACT;
+ _ASSERTE(fTrackingUMChain);
+ return &rdUMChainStart;
+ }
+
+ // Get/Set FramePointer that ends an unmanaged chain.
+ void SetUMChainEnd(FramePointer fp)
+ {
+ LIMITED_METHOD_CONTRACT;
+ _ASSERTE(fTrackingUMChain);
+ fpUMChainEnd = fp;
+ }
+
+ FramePointer GetUMChainEnd()
+ {
+ LIMITED_METHOD_CONTRACT;
+ _ASSERTE(fTrackingUMChain);
+ return fpUMChainEnd;
+ }
+
+ // Get thread we're currently tracing.
+ Thread * GetThread()
+ {
+ LIMITED_METHOD_CONTRACT;
+ return thread;
+ }
+
+ // Returns true if we're on the leaf-callback (ie, we haven't dispatched a callback yet.
+ bool IsLeafCallback()
+ {
+ LIMITED_METHOD_CONTRACT;
+ return cRealCounter == 0;
+ }
+
+ bool ShouldProvideInternalFrames()
+ {
+ LIMITED_METHOD_CONTRACT;
+ return fProvideInternalFrames;
+ }
+ bool ShouldIgnoreNonmethodFrames()
+ {
+ LIMITED_METHOD_CONTRACT;
+ return ignoreNonmethodFrames != 0;
+ }
+};
+
+
+//---------------------------------------------------------------------------------------
+//
+// On IA64, the offset given by the OS during stackwalking is actually the offset at the call instruction.
+// This is different from x86 and X64, where the offset is immediately after the call instruction. In order
+// to have a uniform behaviour, we need to do adjust the relative offset on IA64. This function is a nop on
+// other platforms.
+//
+// Arguments:
+// pCF - the CrawlFrame for the current method frame
+// pInfo - This is the FrameInfo for the current method frame. We need to use the fIsLeaf field,
+// since no adjustment is necessary for leaf frames.
+//
+// Return Value:
+// returns the adjusted relative offset
+//
+
+inline ULONG AdjustRelOffset(CrawlFrame *pCF,
+ FrameInfo *pInfo)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ PRECONDITION(pCF != NULL);
+ }
+ CONTRACTL_END;
+
+#if defined(_TARGET_ARM_)
+ return pCF->GetRelOffset() & ~THUMB_CODE;
+#else
+ return pCF->GetRelOffset();
+#endif
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Even when there is an exit frame in the explicit frame chain, it does not necessarily mean that we have
+// actually called out to unmanaged code yet or that we actually have a managed call site. Given an exit
+// frame, this function determines if we have a managed call site and have already called out to unmanaged
+// code. If we have, then we return the caller SP as the potential frame pointer. Otherwise we return
+// LEAF_MOST_FRAME.
+//
+// Arguments:
+// pFrame - the exit frame to be checked
+// pData - the state of the current frame maintained by the debugger stackwalker
+// pPotentialFP - This is an out parameter. It returns the caller SP of the last managed caller if
+// there is a managed call site and we have already called out to unmanaged code.
+// Otherwise, LEAF_MOST_FRAME is returned.
+//
+// Return Value:
+// true - we have a managed call site and we have called out to unmanaged code
+// false - otherwise
+//
+
+bool HasExitRuntime(Frame *pFrame, DebuggerFrameData *pData, FramePointer *pPotentialFP)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER; // Callers demand this function be GC_NOTRIGGER.
+ MODE_ANY;
+ PRECONDITION(pFrame->GetFrameType() == Frame::TYPE_EXIT);
+ }
+ CONTRACTL_END;
+
+#ifdef _TARGET_X86_
+ TADDR returnIP, returnSP;
+
+ EX_TRY
+ {
+ // This is a real issue. This may be called while holding GC-forbid locks, and so
+ // this function can't trigger a GC. However, the only impl we have calls GC-trigger functions.
+ CONTRACT_VIOLATION(GCViolation);
+ pFrame->GetUnmanagedCallSite(NULL, &returnIP, &returnSP);
+ }
+ EX_CATCH
+ {
+ // We never expect an actual exception here (maybe in oom).
+ // If we get an exception, then simulate the default behavior for GetUnmanagedCallSite.
+ returnIP = NULL;
+ returnSP = NULL; // this will cause us to return true.
+ }
+ EX_END_CATCH(SwallowAllExceptions);
+
+ LOG((LF_CORDB, LL_INFO100000,
+ "DWSP: TYPE_EXIT: returnIP=0x%08x, returnSP=0x%08x, frame=0x%08x, threadFrame=0x%08x, regSP=0x%08x\n",
+ returnIP, returnSP, pFrame, pData->GetThread()->GetFrame(), GetRegdisplaySP(&pData->regDisplay)));
+
+ if (pPotentialFP != NULL)
+ {
+ *pPotentialFP = FramePointer::MakeFramePointer((void*)returnSP);
+ }
+
+ return ((pFrame != pData->GetThread()->GetFrame()) ||
+ (returnSP == NULL) ||
+ ((TADDR)GetRegdisplaySP(&pData->regDisplay) <= returnSP));
+
+#else // _TARGET_X86_
+ // DebuggerExitFrame always return a NULL returnSP on x86.
+ if (pFrame->GetVTablePtr() == DebuggerExitFrame::GetMethodFrameVPtr())
+ {
+ if (pPotentialFP != NULL)
+ {
+ *pPotentialFP = LEAF_MOST_FRAME;
+ }
+ return true;
+ }
+ else if (pFrame->GetVTablePtr() == InlinedCallFrame::GetMethodFrameVPtr())
+ {
+ InlinedCallFrame *pInlinedFrame = static_cast<InlinedCallFrame *>(pFrame);
+ LPVOID sp = (LPVOID)pInlinedFrame->GetCallSiteSP();
+
+ // The sp returned below is the sp of the caller, which is either an IL stub in the normal case
+ // or a normal managed method in the inlined pinvoke case.
+ // This sp may be the same as the frame's address, so we need to use the largest
+ // possible bsp value to make sure that this frame pointer is closer to the root than
+ // the frame pointer made from the frame address itself.
+ if (pPotentialFP != NULL)
+ {
+ *pPotentialFP = FramePointer::MakeFramePointer( (LPVOID)sp );
+ }
+
+ return ((pFrame != pData->GetThread()->GetFrame()) ||
+ InlinedCallFrame::FrameHasActiveCall(pInlinedFrame));
+
+ }
+ else
+ {
+ // It'll be nice if there's a way to assert that the current frame is indeed of a
+ // derived class of TransitionFrame.
+ TransitionFrame *pTransFrame = static_cast<TransitionFrame*>(pFrame);
+ LPVOID sp = (LPVOID)pTransFrame->GetSP();
+
+ // The sp returned below is the sp of the caller, which is either an IL stub in the normal case
+ // or a normal managed method in the inlined pinvoke case.
+ // This sp may be the same as the frame's address, so we need to use the largest
+ // possible bsp value to make sure that this frame pointer is closer to the root than
+ // the frame pointer made from the frame address itself.
+ if (pPotentialFP != NULL)
+ {
+ *pPotentialFP = FramePointer::MakeFramePointer( (LPVOID)sp );
+ }
+
+ return true;
+ }
+#endif // _TARGET_X86_
+}
+
+#ifdef _DEBUG
+
+//-----------------------------------------------------------------------------
+// Debug helpers to get name of Frame.
+//-----------------------------------------------------------------------------
+LPCUTF8 FrameInfo::DbgGetClassName()
+{
+ return (md == NULL) ? ("None") : (md->m_pszDebugClassName);
+}
+LPCUTF8 FrameInfo::DbgGetMethodName()
+{
+ return (md == NULL) ? ("None") : (md->m_pszDebugMethodName);
+}
+
+
+//-----------------------------------------------------------------------------
+// Debug helper to asserts invariants about a FrameInfo before we dispatch it.
+//-----------------------------------------------------------------------------
+void FrameInfo::AssertValid()
+{
+ LIMITED_METHOD_CONTRACT;
+
+ bool fMethod = this->HasMethodFrame();
+ bool fStub = this->HasStubFrame();
+ bool fChain = this->HasChainMarker();
+
+ // Can't be both Stub & Chain
+ _ASSERTE(!fStub || !fChain);
+
+ // Must be at least a Method, Stub or Chain or Internal
+ _ASSERTE(fMethod || fStub || fChain || this->internal);
+
+ // Check Managed status is consistent
+ if (fMethod)
+ {
+ _ASSERTE(this->managed); // We only report managed methods
+ }
+ if (fChain)
+ {
+ if (!managed)
+ {
+ // Only certain chains can be unmanaged
+ _ASSERTE((this->chainReason == CHAIN_THREAD_START) ||
+ (this->chainReason == CHAIN_ENTER_UNMANAGED));
+ }
+ else
+ {
+ // UM chains can never be managed.
+ _ASSERTE((this->chainReason != CHAIN_ENTER_UNMANAGED));
+ }
+
+ }
+
+ // FramePointer should be valid
+ _ASSERTE(this->fp != LEAF_MOST_FRAME);
+ _ASSERTE((this->fp != ROOT_MOST_FRAME) || (chainReason== CHAIN_THREAD_START) || (chainReason == CHAIN_ENTER_UNMANAGED));
+
+ // If we have a Method, then we need an AppDomain.
+ // (RS will need it to do lookup)
+ if (fMethod)
+ {
+ _ASSERTE(currentAppDomain != NULL);
+ _ASSERTE(managed);
+ // Stubs may have a method w/o any code (eg, PInvoke wrapper).
+ // @todo - Frame::TYPE_TP_METHOD_FRAME breaks this assert. Are there other cases too?
+ //_ASSERTE(fStub || (pIJM != NULL));
+ }
+
+ if (fStub)
+ {
+ // All stubs (except LightWeightFunctions) match up w/a Frame.
+ _ASSERTE(this->frame || (eStubFrameType == STUBFRAME_LIGHTWEIGHT_FUNCTION));
+ }
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// Get the DJI associated w/ this frame. This is a convenience function.
+// This is recommended over using MethodDescs because DJI's are version-aware.
+//-----------------------------------------------------------------------------
+DebuggerJitInfo * FrameInfo::GetJitInfoFromFrame()
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ // Not all FrameInfo objects correspond to actual code.
+ if (HasChainMarker() || HasStubFrame() || (frame != NULL))
+ {
+ return NULL;
+ }
+
+ DebuggerJitInfo *ji = NULL;
+
+ // @todo - we shouldn't need both a MD and an IP here.
+ EX_TRY
+ {
+ _ASSERTE(this->md != NULL);
+ ji = g_pDebugger->GetJitInfo(this->md, (const BYTE*)GetControlPC(&(this->registers)));
+ _ASSERTE(ji != NULL);
+ _ASSERTE(ji->m_fd == this->md);
+ }
+ EX_CATCH
+ {
+ ji = NULL;
+ }
+ EX_END_CATCH(SwallowAllExceptions);
+
+ return ji;
+}
+
+//-----------------------------------------------------------------------------
+// Get the DMI associated w/ this frame. This is a convenience function.
+// DMIs are 1:1 with the (token, module) pair.
+//-----------------------------------------------------------------------------
+DebuggerMethodInfo * FrameInfo::GetMethodInfoFromFrameOrThrow()
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ MethodDesc * pDesc = this->md;
+ mdMethodDef token = pDesc-> GetMemberDef();
+ Module * pRuntimeModule = pDesc->GetModule();
+
+ DebuggerMethodInfo *dmi = g_pDebugger->GetOrCreateMethodInfo(pRuntimeModule, token);
+ return dmi;
+}
+
+
+//-----------------------------------------------------------------------------
+// Init a FrameInfo for a UM chain.
+// We need a stackrange to give to an unmanaged debugger.
+// pRDSrc->Esp will provide the start (leaf) marker.
+// fpRoot will provide the end (root) portion.
+//-----------------------------------------------------------------------------
+void FrameInfo::InitForUMChain(FramePointer fpRoot, REGDISPLAY * pRDSrc)
+{
+ _ASSERTE(pRDSrc != NULL);
+
+ // Mark that we're an UM Chain (and nothing else).
+ this->frame = NULL;
+ this->md = NULL;
+
+ // Fp will be the end (root) of the stack range.
+ // pRDSrc->Sp will be the start (leaf) of the stack range.
+ CopyREGDISPLAY(&(this->registers), pRDSrc);
+ this->fp = fpRoot;
+
+ this->quickUnwind = false;
+ this->internal = false;
+ this->managed = false;
+
+ // These parts of the FrameInfo can be ignored for a UM chain.
+ this->relOffset = 0;
+ this->pIJM = NULL;
+ this->MethodToken = METHODTOKEN(NULL, 0);
+ this->currentAppDomain = NULL;
+ this->exactGenericArgsToken = NULL;
+
+ InitForScratchFrameInfo();
+
+ this->chainReason = CHAIN_ENTER_UNMANAGED;
+ this->eStubFrameType = STUBFRAME_NONE;
+
+#ifdef _DEBUG
+ FramePointer fpLeaf = GetSP(pRDSrc);
+ _ASSERTE(IsCloserToLeaf(fpLeaf, fpRoot));
+#endif
+
+#ifdef _DEBUG
+ // After we just init it, it had better be valid.
+ this->AssertValid();
+#endif
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// This is just a small helper to initialize the fields which are specific to 64-bit. Note that you should
+// only call this function on a scratch FrameInfo. Never call it on the FrameInfo used by the debugger
+// stackwalker to store information on the current frame.
+//
+
+void FrameInfo::InitForScratchFrameInfo()
+{
+#ifdef WIN64EXCEPTIONS
+ // The following flags cannot be trashed when we are calling this function on the curret FrameInfo
+ // (the one we keep track of across multiple stackwalker callbacks). Thus, make sure you do not call
+ // this function from InitForDynamicMethod(). In all other cases, we can call this method after we
+ // call InitFromStubHelper() because we are working on a local scratch variable.
+ this->fIsLeaf = false;
+ this->fIsFunclet = false;
+ this->fIsFilter = false;
+#endif // WIN64EXCEPTIONS
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// Init a FrameInfo for a stub. Stub frames map to internal frames on the RS. Stubs which we care about
+// usually contain an explicit frame which translates to an internal frame on the RS. Dynamic method is
+// the sole exception.
+//
+// Arguments:
+// pCF - the CrawlFrame containing the state of the current frame
+// pMDHint - some stubs have associated MethodDesc but others don't,
+// which is why this argument can be NULL
+// type - the type of the stub/internal frame
+//
+
+void FrameInfo::InitFromStubHelper(
+ CrawlFrame * pCF,
+ MethodDesc * pMDHint, // NULL ok
+ CorDebugInternalFrameType type
+)
+{
+ _ASSERTE(pCF != NULL);
+
+ Frame * pFrame = pCF->GetFrame();
+
+ LOG((LF_CORDB, LL_EVERYTHING, "InitFromStubHelper. Frame=0x%p, type=%d\n", pFrame, type));
+
+ // All Stubs have a Frame except for LightWeight methods
+ _ASSERTE((type == STUBFRAME_LIGHTWEIGHT_FUNCTION) || (pFrame != NULL));
+ REGDISPLAY *pRDSrc = pCF->GetRegisterSet();
+
+ this->frame = pFrame;
+
+ // Stub frames may be associated w/ a Method (as a hint). However this method
+ // will never have a JitManager b/c it will never have IL (if it had IL, we'd be a
+ // regulare frame, not a stub frame)
+ this->md = pMDHint;
+
+ CopyREGDISPLAY(&this->registers, pRDSrc);
+
+ // FramePointer must match up w/ an EE Frame b/c that's how we match
+ // we Exception callbacks.
+ if (pFrame != NULL)
+ {
+ this->fp = FramePointer::MakeFramePointer(
+ (LPVOID) pFrame);
+ }
+ else
+ {
+ this->fp = GetSP(pRDSrc);
+ }
+
+ this->quickUnwind = false;
+ this->internal = false;
+ this->managed = true;
+ this->relOffset = 0;
+ this->ambientSP = NULL;
+
+
+ // Method associated w/a stub will never have a JitManager.
+ this->pIJM = NULL;
+ this->MethodToken = METHODTOKEN(NULL, 0);
+ this->currentAppDomain = pCF->GetAppDomain();
+ this->exactGenericArgsToken = NULL;
+
+ // Stub frames are mutually exclusive with chain markers.
+ this->chainReason = CHAIN_NONE;
+ this->eStubFrameType = type;
+
+#ifdef _DEBUG
+ // After we just init it, it had better be valid.
+ this->AssertValid();
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Initialize a FrameInfo to be used for an "InternalFrame"
+// Frame should be a derived class of FramedMethodFrame.
+// FrameInfo's MethodDesc will be for managed wrapper for native call.
+//-----------------------------------------------------------------------------
+void FrameInfo::InitForM2UInternalFrame(CrawlFrame * pCF)
+{
+ // For a M2U call, there's a managed method wrapping the unmanaged call. Use that.
+ Frame * pFrame = pCF->GetFrame();
+ _ASSERTE(pFrame->GetTransitionType() == Frame::TT_M2U);
+ FramedMethodFrame * pM2U = static_cast<FramedMethodFrame*> (pFrame);
+ MethodDesc * pMDWrapper = pM2U->GetFunction();
+
+ // Soem M2U transitions may not have a function associated w/ them,
+ // so pMDWrapper may be NULL. PInvokeCalliFrame is an example.
+
+ InitFromStubHelper(pCF, pMDWrapper, STUBFRAME_M2U);
+ InitForScratchFrameInfo();
+}
+
+//-----------------------------------------------------------------------------
+// Initialize for the U2M case...
+//-----------------------------------------------------------------------------
+void FrameInfo::InitForU2MInternalFrame(CrawlFrame * pCF)
+{
+ PREFIX_ASSUME(pCF != NULL);
+ MethodDesc * pMDHint = NULL;
+
+#ifdef FEATURE_COMINTEROP
+ Frame * pFrame = pCF->GetFrame();
+ PREFIX_ASSUME(pFrame != NULL);
+
+
+ // For regular U2M PInvoke cases, we don't care about MD b/c it's just going to
+ // be the next frame.
+ // If we're a COM2CLR call, perhaps we can get the MD for the interface.
+ if (pFrame->GetVTablePtr() == ComMethodFrame::GetMethodFrameVPtr())
+ {
+ ComMethodFrame* pCOMFrame = static_cast<ComMethodFrame*> (pFrame);
+ ComCallMethodDesc* pCMD = reinterpret_cast<ComCallMethodDesc *> (pCOMFrame->ComMethodFrame::GetDatum());
+ pMDHint = pCMD->GetInterfaceMethodDesc();
+
+ // Some COM-interop cases don't have an intermediate interface method desc, so
+ // pMDHint may be null.
+ }
+#endif
+
+ InitFromStubHelper(pCF, pMDHint, STUBFRAME_U2M);
+ InitForScratchFrameInfo();
+}
+
+//-----------------------------------------------------------------------------
+// Init for an AD transition
+//-----------------------------------------------------------------------------
+void FrameInfo::InitForADTransition(CrawlFrame * pCF)
+{
+ Frame * pFrame;
+ pFrame = pCF->GetFrame();
+ _ASSERTE(pFrame->GetTransitionType() == Frame::TT_AppDomain);
+ MethodDesc * pMDWrapper = NULL;
+
+ InitFromStubHelper(pCF, pMDWrapper, STUBFRAME_APPDOMAIN_TRANSITION);
+ InitForScratchFrameInfo();
+}
+
+
+//-----------------------------------------------------------------------------
+// Init frame for a dynamic method.
+//-----------------------------------------------------------------------------
+void FrameInfo::InitForDynamicMethod(CrawlFrame * pCF)
+{
+ // These are just stack markers that there's a dynamic method on the callstack.
+ InitFromStubHelper(pCF, NULL, STUBFRAME_LIGHTWEIGHT_FUNCTION);
+ // Do not call InitForScratchFrameInfo() here! Please refer to the comment in that function.
+}
+
+//-----------------------------------------------------------------------------
+// Init an internal frame to mark a func-eval.
+//-----------------------------------------------------------------------------
+void FrameInfo::InitForFuncEval(CrawlFrame * pCF)
+{
+ // We don't store a MethodDesc hint referring to the method we're going to invoke because
+ // uses of stub frames will assume the MD is relative to the AppDomain the frame is in.
+ // For cross-AD funcevals, we're invoking a method in a domain other than the one this frame
+ // is in.
+ MethodDesc * pMDHint = NULL;
+
+ // Add a stub frame here to mark that there is a FuncEvalFrame on the stack.
+ InitFromStubHelper(pCF, pMDHint, STUBFRAME_FUNC_EVAL);
+ InitForScratchFrameInfo();
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Initialize a FrameInfo for sending the CHAIN_THREAD_START reason.
+// The common case is that the chain is NOT managed, since the lowest (closest to the root) managed method
+// is usually called from unmanaged code. In fact, in Whidbey, we should never have a managed chain.
+//
+// Arguments:
+// pRDSrc - a REGDISPLAY for the beginning (the leafmost frame) of the chain
+//
+void FrameInfo::InitForThreadStart(Thread * pThread, REGDISPLAY * pRDSrc)
+{
+ this->frame = (Frame *) FRAME_TOP;
+ this->md = NULL;
+ CopyREGDISPLAY(&(this->registers), pRDSrc);
+ this->fp = FramePointer::MakeFramePointer(pThread->GetCachedStackBase());
+ this->quickUnwind = false;
+ this->internal = false;
+ this->managed = false;
+ this->relOffset = 0;
+ this->pIJM = NULL;
+ this->MethodToken = METHODTOKEN(NULL, 0);
+
+ this->currentAppDomain = NULL;
+ this->exactGenericArgsToken = NULL;
+
+ InitForScratchFrameInfo();
+
+ this->chainReason = CHAIN_THREAD_START;
+ this->eStubFrameType = STUBFRAME_NONE;
+
+#ifdef _DEBUG
+ // After we just init it, it had better be valid.
+ this->AssertValid();
+#endif
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Initialize a FrameInfo for sending a CHAIN_ENTER_MANAGED.
+// A Enter-Managed chain is always sent immediately before an UM chain, meaning that the Enter-Managed chain
+// is closer to the leaf than the UM chain.
+//
+// Arguments:
+// fpRoot - This is the frame pointer for the Enter-Managed chain. It is currently arbitrarily set
+// to be one stack slot higher (closer to the leaf) than the frame pointer of the beginning
+// of the upcoming UM chain.
+//
+
+void FrameInfo::InitForEnterManagedChain(FramePointer fpRoot)
+{
+ // Nobody should use a EnterManagedChain's Frame*, but there's no
+ // good value to enforce that.
+ this->frame = (Frame *) FRAME_TOP;
+ this->md = NULL;
+ memset((void *)&this->registers, 0, sizeof(this->registers));
+ this->fp = fpRoot;
+
+ this->quickUnwind = true;
+ this->internal = false;
+ this->managed = true;
+ this->relOffset = 0;
+ this->pIJM = NULL;
+ this->MethodToken = METHODTOKEN(NULL, 0);
+
+ this->currentAppDomain = NULL;
+ this->exactGenericArgsToken = NULL;
+
+ InitForScratchFrameInfo();
+
+ this->chainReason = CHAIN_ENTER_MANAGED;
+ this->eStubFrameType = STUBFRAME_NONE;
+}
+
+//-----------------------------------------------------------------------------
+// Do tracking for UM chains.
+// This may invoke the UMChain callback and M2U callback.
+//-----------------------------------------------------------------------------
+StackWalkAction TrackUMChain(CrawlFrame *pCF, DebuggerFrameData *d)
+{
+ Frame *frame = g_pEEInterface->GetFrame(pCF);
+
+ // If we encounter an ExitFrame out in the wild, then we'll convert it to an UM chain.
+ if (!d->IsTrackingUMChain())
+ {
+ if ((frame != NULL) && (frame != FRAME_TOP) && (frame->GetFrameType() == Frame::TYPE_EXIT))
+ {
+ LOG((LF_CORDB, LL_EVERYTHING, "DWSP. ExitFrame while not tracking\n"));
+ REGDISPLAY* pRDSrc = pCF->GetRegisterSet();
+
+ d->BeginTrackingUMChain(GetSP(pRDSrc), pRDSrc);
+
+ // fall through and we'll send the UM chain.
+ }
+ else
+ {
+ return SWA_CONTINUE;
+ }
+ }
+
+ _ASSERTE(d->IsTrackingUMChain());
+
+
+ // If we're tracking an UM chain, then we need to:
+ // - possibly refine the start & end values as we get new information in the stacktrace.
+ // - possibly cancel the UM chain for various heuristics.
+ // - possibly dispatch if we've hit managed code again.
+
+ bool fDispatchUMChain = false;
+ // UM Chain stops when managed code starts again.
+ if (frame != NULL)
+ {
+ // If it's just a EE Frame, then update this as a possible end of stack range for the UM chain.
+ // (The end of a stack range is closer to the root.)
+ d->SetUMChainEnd(FramePointer::MakeFramePointer((LPVOID)(frame)));
+
+
+ Frame::ETransitionType t = frame->GetTransitionType();
+ int ft = frame->GetFrameType();
+
+
+ // Sometimes we may not want to show an UM chain b/c we know it's just
+ // code inside of mscorwks. (Eg: Funcevals & AD transitions both fall into this category).
+ // These are perfectly valid UM chains and we could give them if we wanted to.
+ if ((t == Frame::TT_AppDomain) || (ft == Frame::TYPE_FUNC_EVAL))
+ {
+ d->CancelUMChain();
+ return SWA_CONTINUE;
+ }
+
+ // If we hit an M2U frame, then go ahead and dispatch the UM chain now.
+ // This will likely also be an exit frame.
+ if (t == Frame::TT_M2U)
+ {
+ fDispatchUMChain = true;
+ }
+
+ // If we get an Exit frame, we can use that to "prune" the UM chain to a more friendly state.
+ // This heuristic is optional, it just eliminates lots of internal mscorwks frames from the callstack.
+ // Note that this heuristic is only useful if we get a callback on the entry frame
+ // (e.g. UMThkCallFrame) between the callback on the native marker and the callback on the exit frame.
+ // Otherwise the REGDISPLAY will be the same.
+ if (ft == Frame::TYPE_EXIT)
+ {
+ // If we have a valid reg-display (non-null IP) then update it.
+ // We may have an invalid reg-display if we have an exit frame on an inactive thread.
+ REGDISPLAY * pNewRD = pCF->GetRegisterSet();
+ if (GetControlPC(pNewRD) != NULL)
+ {
+ LOG((LF_CORDB, LL_EVERYTHING, "DWSP. updating RD while tracking UM chain\n"));
+ CopyREGDISPLAY(d->GetUMChainStartRD(), pNewRD);
+ }
+
+ FramePointer fpLeaf = GetSP(d->GetUMChainStartRD());
+ _ASSERTE(IsCloserToLeaf(fpLeaf, d->GetUMChainEnd()));
+
+
+ _ASSERTE(!d->fHitExitFrame); // should only have 1 exit frame per UM chain code.
+ d->fHitExitFrame = true;
+
+ FramePointer potentialFP;
+
+ FramePointer fpNewChainEnd = d->GetUMChainEnd();
+
+ // Check to see if we are inside the unmanaged call. We want to make sure we only report an exit frame after
+ // we've really exited. There is a short period between where we setup the frame and when we actually exit
+ // the runtime. This check is intended to ensure we're actually outside now.
+ if (HasExitRuntime(frame, d, &potentialFP))
+ {
+ LOG((LF_CORDB, LL_EVERYTHING, "HasExitRuntime. potentialFP=0x%p\n", potentialFP.GetSPValue()));
+
+ // If we have no call site, manufacture a FP using the current frame.
+ // If we do have a call site, then the FP is actually going to be the caller SP,
+ // where the caller is the last managed method before calling out to unmanaged code.
+ if (potentialFP == LEAF_MOST_FRAME)
+ {
+ fpNewChainEnd = FramePointer::MakeFramePointer((LPVOID)((BYTE*)frame - sizeof(LPVOID)));
+ }
+ else
+ {
+ fpNewChainEnd = potentialFP;
+ }
+
+ }
+ // For IL stubs, we may actually push an uninitialized InlinedCallFrame frame onto the frame chain
+ // in jitted managed code, and then later on initialize it in a native runtime helper. In this case, if
+ // HasExitRuntime() is false (meaning the frame is uninitialized), then we are actually still in managed
+ // code and have not made the call to native code yet, so we should report an unmanaged chain.
+ else
+ {
+ d->CancelUMChain();
+ return SWA_CONTINUE;
+ }
+
+ fDispatchUMChain = true;
+
+ // If we got a valid chain end, then prune the UM chain accordingly.
+ // Note that some EE Frames will give invalid info back so we have to check.
+ // PInvokeCalliFrame is one example (when doing MC++ function pointers)
+ if (IsCloserToRoot(fpNewChainEnd, fpLeaf))
+ {
+ d->SetUMChainEnd(fpNewChainEnd);
+ }
+ else
+ {
+ _ASSERTE(IsCloserToLeaf(fpLeaf, d->GetUMChainEnd()));
+ }
+ } // end ExitFrame
+
+ // Only CLR internal code / stubs can push Frames onto the Frame chain.
+ // So if we hit a raw interceptor frame before we hit any managed frame, then this whole
+ // UM chain must still be in CLR internal code.
+ // Either way, this UM chain has ended (and some new chain based off the frame has started)
+ // so we need to either Cancel the chain or dispatch it.
+ if (frame->GetInterception() != Frame::INTERCEPTION_NONE)
+ {
+ // Interceptors may contain calls out to unmanaged code (such as unmanaged dllmain when
+ // loading a new dll), so we need to dispatch these.
+ // These extra UM chains don't show in Everett, and so everett debuggers on whidbey
+ // may see new chains.
+ // We need to ensure that whidbey debuggers are updated first.
+ fDispatchUMChain = true;
+ }
+ }
+ else
+ {
+ // If it's a real method (not just an EE Frame), then the UM chain is over.
+ fDispatchUMChain = true;
+ }
+
+
+ if (fDispatchUMChain)
+ {
+ // Check if we should cancel the UM chain.
+
+ // We need to discriminate between the following 2 cases:
+ // 1) Managed -(a)-> mscorwks -(b)-> Managed (leaf)
+ // 2) Native -(a)-> mscorwks -(b)-> Managed (leaf)
+ //
+ // --INCORRECT RATIONALE SEE "CORRECTION" BELOW--
+ // Case 1 could happen if a managed call injects a stub (such as w/ delegates).
+ // In both cases, the (mscorwks-(b)->managed) transition causes a IsNativeMarker callback
+ // which initiates a UM chain. In case 1, we want to cancel the UM chain, but
+ // in case 2 we want to dispatch it.
+ // The difference is case #2 will have some EE Frame at (b) and case #1 won't.
+ // That EE Frame should have caused us to dispatch the call for the managed method, and
+ // thus by the time we get around to dispatching the UM Chain, we shouldn't have a managed
+ // method waiting to be dispatched in the DebuggerFrameData.
+ // --END INCORRECT RATIONALE--
+ //
+ // This is kind of messed up. First of all, the assertions on case 2 is not true on 64-bit.
+ // We won't have an explicit frame at (b). Secondly, case 1 is not always true either.
+ // Consider the case where we are calling a cctor at prestub time. This is what the stack may
+ // look like: managed -> PrestubMethodFrame -> GCFrame -> managed (cctor) (leaf). In this case,
+ // we will actually send the UM chain because we will have dispatched the call for the managed
+ // method (the cctor) when we get a callback for the GCFrame.
+ //
+ // --INCORRECT SEE "CORRECTION" BELOW--
+ // Keep in mind that this is just a heuristic to reduce the number of UM chains we are sending
+ // over to the RS.
+ // --END INCORRECT --
+ //
+ // CORRECTION: These UM chains also feed into the results of at least ControllerStackInfo and probably other
+ // places. Issue 650903 is a concrete example of how not filtering a UM chain causes correctness
+ // issues in the LS. This code may still have bugs in it based on those incorrect assumptions.
+ // A narrow fix for 650903 is the only thing that was changed at the time of adding this comment.
+ if (d->needParentInfo && d->info.HasMethodFrame())
+ {
+ LOG((LF_CORDB, LL_EVERYTHING, "Cancelling UM Chain b/c it's internal\n"));
+ d->CancelUMChain();
+ return SWA_CONTINUE;
+ }
+
+ // If we're NOT ignoring non-method frames, and we didn't get an explicit ExitFrame somewhere
+ // in this chain, then don't send the non-leaf UM chain.
+ // The practical cause here is that w/o an exit frame, we don't know where the UM chain
+ // is starting (could be from anywhere in mscorwks). And we can't patch any random spot in
+ // mscorwks.
+ // Sending leaf-UM chains is OK b/c we can't step-out to them (they're the leaf, duh).
+ // (ignoreNonmethodFrames is generally false for stepping and true for regular
+ // end-user stacktraces.)
+ //
+ // This check is probably unnecessary. The client of the debugger stackwalker should make
+ // the decision themselves as to what to do with the UM chain callbacks.
+ //
+ // -- INCORRECT SEE SEE "CORRECTION" BELOW --
+ // Currently, both
+ // ControllerStackInfo and InterceptorStackInfo ignore UM chains completely anyway.
+ // (For an example, refer to the cctor example in the previous comment.)
+ // -- END INCORRECT --
+ //
+ // CORRECTION: See issue 650903 for a concrete example of ControllerStackInfo getting a different
+ // result based on a UM chain that wasn't filtered. This code may still have issues in
+ // it based on those incorrect assumptions. A narrow fix for 650903 is the only thing
+ // that was changed at the time of adding this comment.
+ if (!d->fHitExitFrame && !d->ShouldIgnoreNonmethodFrames() && !d->IsLeafCallback())
+ {
+ LOG((LF_CORDB, LL_EVERYTHING, "Cancelling UM Chain b/c it's stepper not requested\n"));
+ d->CancelUMChain();
+ return SWA_CONTINUE;
+ }
+
+
+ // Ok, we haven't cancelled it yet, so go ahead and send the UM chain.
+ FrameInfo f;
+ FramePointer fpRoot = d->GetUMChainEnd();
+ FramePointer fpLeaf = GetSP(d->GetUMChainStartRD());
+
+ // If we didn't actually get any range, then don't bother sending it.
+ if (fpRoot == fpLeaf)
+ {
+ d->CancelUMChain();
+ return SWA_CONTINUE;
+ }
+
+ f.InitForUMChain(fpRoot, d->GetUMChainStartRD());
+
+#ifdef FEATURE_COMINTEROP
+ if ((frame != NULL) &&
+ (frame->GetVTablePtr() == ComPlusMethodFrame::GetMethodFrameVPtr()))
+ {
+ // This condition is part of the fix for 650903. (See
+ // code:ControllerStackInfo::WalkStack and code:DebuggerStepper::TrapStepOut
+ // for the other parts.) Here, we know that the frame we're looking it may be
+ // a ComPlusMethodFrameGeneric (this info is not otherwise plubmed down into
+ // the walker; even though the walker does get to see "f.frame", that may not
+ // be "frame"). Given this, if the walker chooses to ignore these frames
+ // (while doing a Step Out during managed-only debugging), then it can ignore
+ // this frame.
+ f.fIgnoreThisFrameIfSuppressingUMChainFromComPlusMethodFrameGeneric = true;
+ }
+#endif // FEATURE_COMINTEROP
+
+ if (d->InvokeCallback(&f) == SWA_ABORT)
+ {
+ // don't need to cancel if they abort.
+ return SWA_ABORT;
+ }
+ d->CancelUMChain(); // now that we've sent it, we're done.
+
+
+ // Check for a M2U internal frame.
+ if (d->ShouldProvideInternalFrames() && (frame != NULL) && (frame != FRAME_TOP))
+ {
+ // We want to dispatch a M2U transition right after we dispatch the UM chain.
+ Frame::ETransitionType t = frame->GetTransitionType();
+ if (t == Frame::TT_M2U)
+ {
+ // Frame for a M2U transition.
+ FrameInfo fM2U;
+ fM2U.InitForM2UInternalFrame(pCF);
+ if (d->InvokeCallback(&fM2U) == SWA_ABORT)
+ {
+ return SWA_ABORT;
+ }
+ }
+ }
+
+
+ }
+
+ return SWA_CONTINUE;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// A frame pointer is a unique identifier for a particular stack location. This function returns the
+// frame pointer for the current frame, whether it is a method frame or an explicit frame.
+//
+// Arguments:
+// pData - the state of the current frame maintained by the debugger stackwalker
+// pCF - the CrawlFrame for the current callback by the real stackwalker (i.e. StackWalkFramesEx());
+// this is NULL for the case where we fake an extra callbakc to top off a debugger stackwalk
+//
+// Return Value:
+// the frame pointer for the current frame
+//
+
+FramePointer GetFramePointerForDebugger(DebuggerFrameData* pData, CrawlFrame* pCF)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ FramePointer fpResult;
+
+#if defined(WIN64EXCEPTIONS)
+ if (pData->info.frame == NULL)
+ {
+ // This is a managed method frame.
+ fpResult = FramePointer::MakeFramePointer((LPVOID)GetRegdisplayStackMark(&pData->info.registers));
+ }
+ else
+ {
+ // This is an actual frame.
+ fpResult = FramePointer::MakeFramePointer((LPVOID)(pData->info.frame));
+ }
+
+#else // !WIN64EXCEPTIONS
+ if ((pCF == NULL || !pCF->IsFrameless()) && pData->info.frame != NULL)
+ {
+ //
+ // If we're in an explicit frame now, and the previous frame was
+ // also an explicit frame, pPC will not have been updated. So
+ // use the address of the frame itself as fp.
+ //
+ fpResult = FramePointer::MakeFramePointer((LPVOID)(pData->info.frame));
+
+ LOG((LF_CORDB, LL_INFO100000, "GFPFD: Two explicit frames in a row; using frame address 0x%p\n",
+ pData->info.frame));
+ }
+ else
+ {
+ //
+ // Otherwise use pPC as the frame pointer, as this will be
+ // pointing to the return address on the stack.
+ //
+ fpResult = FramePointer::MakeFramePointer((LPVOID)GetRegdisplayStackMark(&(pData->regDisplay)));
+ }
+
+#endif // !WIN64EXCEPTIONS
+
+ LOG((LF_CORDB, LL_INFO100000, "GFPFD: Frame pointer is 0x%p\n", fpResult.GetSPValue()));
+
+ return fpResult;
+}
+
+
+#ifdef WIN64EXCEPTIONS
+//---------------------------------------------------------------------------------------
+//
+// This function is called to determine if we should start skipping funclets. If we should, then we return the
+// frame pointer for the parent method frame. Otherwise we return LEAF_MOST_FRAME. If we are already skipping
+// frames, then we return the current frame pointer for the parent method frame.
+//
+// The return value of this function corresponds to the return value of ExceptionTracker::FindParentStackFrame().
+// Refer to that function for more information.
+//
+// Arguments:
+// fpCurrentParentMarker - This is the current frame pointer of the parent method frame. It can be
+// LEAF_MOST_FRAME if we are not currently skipping funclets.
+// pCF - the CrawlFrame for the current callback from the real stackwalker
+// fIsNonFilterFuncletFrame - whether the current frame is a non-filter funclet frame
+//
+// Return Value:
+// LEAF_MOST_FRAME - skipping not required
+// ROOT_MOST_FRAME - skip one frame and try again
+// anything else - skip all frames up to but not including the returned frame pointer
+//
+
+inline FramePointer CheckForParentFP(FramePointer fpCurrentParentMarker, CrawlFrame* pCF, bool fIsNonFilterFuncletFrame)
+{
+ WRAPPER_NO_CONTRACT;
+
+ if (fpCurrentParentMarker == LEAF_MOST_FRAME)
+ {
+ // When we encounter a funclet, we simply stop processing frames until we hit the parent
+ // of the funclet. Funclets and their parents have the same MethodDesc pointers, and they
+ // should really be treated as one frame. However, we report both of them and let the callers
+ // decide what they want to do with them. For example, DebuggerThread::TraceAndSendStack()
+ // should never report both frames, but ControllerStackInfo::GetStackInfo() may need both to
+ // determine where to put a patch. We use the fpParent as a flag to indicate if we are
+ // searching for a parent of a funclet.
+ //
+ // Note that filter funclets are an exception. We don't skip them.
+ if (fIsNonFilterFuncletFrame)
+ {
+ // We really should be using the same structure, but FramePointer is used everywhere in the debugger......
+ StackFrame sfParent = g_pEEInterface->FindParentStackFrame(pCF);
+ return FramePointer::MakeFramePointer((LPVOID)sfParent.SP);
+ }
+ else
+ {
+ return LEAF_MOST_FRAME;
+ }
+ }
+ else
+ {
+ // Just return the current marker if we are already skipping frames.
+ return fpCurrentParentMarker;
+ }
+}
+#endif // WIN64EXCEPTIONS
+
+
+//-----------------------------------------------------------------------------
+// StackWalkAction DebuggerWalkStackProc(): This is the callback called
+// by the EE stackwalker.
+// Note that since we don't know what the frame pointer for frame
+// X is until we've looked at the caller of frame X, we actually end up
+// stashing the info and pData pointers in the DebuggerFrameDat struct, and
+// then invoking pCallback when we've moved up one level, into the caller's
+// frame. We use the needParentInfo field to indicate that the previous frame
+// needed this (parental) info, and so when it's true we should invoke
+// pCallback.
+// What happens is this: if the previous frame set needParentInfo, then we
+// do pCallback (and set needParentInfo to false).
+// Then we look at the current frame - if it's frameless (ie,
+// managed), then we set needParentInfo to callback in the next frame.
+// Otherwise we must be at a chain boundary, and so we set the chain reason
+// appropriately. We then figure out what type of frame it is, setting
+// flags depending on the type. If the user should see this frame, then
+// we'll set needParentInfo to record it's existence. Lastly, if we're in
+// a funky frame, we'll explicitly update the register set, since the
+// CrawlFrame doesn't do it automatically.
+//-----------------------------------------------------------------------------
+StackWalkAction DebuggerWalkStackProc(CrawlFrame *pCF, void *data)
+{
+ DebuggerFrameData *d = (DebuggerFrameData *)data;
+
+ if (pCF->IsNativeMarker())
+ {
+#ifdef WIN64EXCEPTIONS
+ // The tricky part here is that we want to skip all frames between a funclet method frame
+ // and the parent method frame UNLESS the funclet is a filter. Moreover, we should never
+ // let a native marker execute the rest of this method, so we just short-circuit it here.
+ if ((d->fpParent != LEAF_MOST_FRAME) || d->info.IsNonFilterFuncletFrame())
+ {
+ return SWA_CONTINUE;
+ }
+#endif // WIN64EXCEPTIONS
+
+ // This REGDISPLAY is for the native method immediately following the managed method for which
+ // we have received the previous callback, i.e. the native caller of the last managed method
+ // we have encountered.
+ REGDISPLAY* pRDSrc = pCF->GetRegisterSet();
+ d->BeginTrackingUMChain(GetSP(pRDSrc), pRDSrc);
+
+ return SWA_CONTINUE;
+ }
+
+ // Note that a CrawlFrame may have both a methoddesc & an EE Frame.
+ Frame *frame = g_pEEInterface->GetFrame(pCF);
+ MethodDesc *md = pCF->GetFunction();
+
+ LOG((LF_CORDB, LL_EVERYTHING, "Calling DebuggerWalkStackProc. Frame=0x%p, md=0x%p(%s), native_marker=%d\n",
+ frame, md, (md == NULL || md == (MethodDesc*)POISONC) ? "null" : md->m_pszDebugMethodName, pCF->IsNativeMarker() ));
+
+ // The fp for a frame must be obtained from the _next_ frame. Fill it in now for the previous frame, if appropriate.
+ if (d->needParentInfo)
+ {
+ LOG((LF_CORDB, LL_INFO100000, "DWSP: NeedParentInfo.\n"));
+
+ d->info.fp = GetFramePointerForDebugger(d, pCF);
+
+#if defined(_DEBUG) && !defined(_TARGET_ARM_) && !defined(_TARGET_ARM64_)
+ // Make sure the stackwalk is making progress.
+ // On ARM this is invalid as the stack pointer does necessarily have to move when unwinding a frame.
+ _ASSERTE(IsCloserToLeaf(d->previousFP, d->info.fp));
+
+ d->previousFP = d->info.fp;
+#endif // _DEBUG && !_TARGET_ARM_
+
+ d->needParentInfo = false;
+
+ {
+ // Don't invoke Stubs if we're not asking for internal frames.
+ bool fDoInvoke = true;
+ if (!d->ShouldProvideInternalFrames())
+ {
+ if (d->info.HasStubFrame())
+ {
+ fDoInvoke = false;
+ }
+ }
+
+ LOG((LF_CORDB, LL_INFO1000000, "DWSP: handling our target\n"));
+
+ if (fDoInvoke)
+ {
+ if (d->InvokeCallback(&d->info) == SWA_ABORT)
+ {
+ return SWA_ABORT;
+ }
+ }
+
+ // @todo - eventually we should be initing our frame-infos properly
+ // and thus should be able to remove this.
+ d->info.eStubFrameType = STUBFRAME_NONE;
+ }
+ } // if (d->needParentInfo)
+
+
+#ifdef WIN64EXCEPTIONS
+ // The tricky part here is that we want to skip all frames between a funclet method frame
+ // and the parent method frame UNLESS the funclet is a filter. We only have to check for fpParent
+ // here (instead of checking d->info.fIsFunclet and d->info.fIsFilter as well, as in the beginning of
+ // this method) is because at this point, fpParent is already set by the code above.
+ if (d->fpParent == LEAF_MOST_FRAME)
+#endif // WIN64EXCEPTIONS
+ {
+ // Track the UM chain after we flush any managed goo from the last iteration.
+ if (TrackUMChain(pCF, d) == SWA_ABORT)
+ {
+ return SWA_ABORT;
+ }
+ }
+
+
+ // Track if we want to send a callback for this Frame / Method
+ bool use=false;
+
+ //
+ // Examine the frame.
+ //
+
+ // We assume that the stack walker is just updating the
+ // register display we passed in - assert it to be sure
+ _ASSERTE(pCF->GetRegisterSet() == &d->regDisplay);
+
+#ifdef WIN64EXCEPTIONS
+ Frame* pPrevFrame = d->info.frame;
+
+ // Here we need to determine if we are in a non-leaf frame, in which case we want to adjust the relative offset.
+ // Also, we need to check if this frame has faulted (throws a native exception), since if it has, then it should be
+ // considered the leaf frame (and thus we don't need to update the relative offset).
+ if (pCF->IsActiveFrame() || pCF->HasFaulted())
+ {
+ d->info.fIsLeaf = true;
+ }
+ else if ( (pPrevFrame != NULL) &&
+ (pPrevFrame->GetFrameType() == Frame::TYPE_EXIT) &&
+ !HasExitRuntime(pPrevFrame, d, NULL) )
+ {
+ // This is for the inlined NDirectMethodFrameGeneric case. We have not exit the runtime yet, so the current
+ // frame should still be regarded as the leaf frame.
+ d->info.fIsLeaf = true;
+ }
+ else
+ {
+ d->info.fIsLeaf = false;
+ }
+
+ d->info.fIsFunclet = pCF->IsFunclet();
+ d->info.fIsFilter = false;
+ if (d->info.fIsFunclet)
+ {
+ d->info.fIsFilter = pCF->IsFilterFunclet();
+ }
+
+ if (pCF->IsFrameless())
+ {
+ // Check if we are skipping.
+ if (d->fpParent != LEAF_MOST_FRAME)
+ {
+ // If fpParent is ROOT_MOST_FRAME, then we just need to skip one frame. Otherwise, we should stop
+ // skipping if the current frame pointer matches fpParent. In either case, clear fpParent, and
+ // then check again.
+ if ((d->fpParent == ROOT_MOST_FRAME) ||
+ ExceptionTracker::IsUnwoundToTargetParentFrame(pCF, ConvertFPToStackFrame(d->fpParent)))
+ {
+ LOG((LF_CORDB, LL_INFO100000, "DWSP: Stopping to skip funclet at 0x%p.\n", d->fpParent.GetSPValue()));
+
+ d->fpParent = LEAF_MOST_FRAME;
+ d->fpParent = CheckForParentFP(d->fpParent, pCF, d->info.IsNonFilterFuncletFrame());
+ }
+ }
+ }
+
+#endif // WIN64EXCEPTIONS
+
+ d->info.frame = frame;
+ d->info.ambientSP = NULL;
+
+ // Record the appdomain that the thread was in when it
+ // was running code for this frame.
+ d->info.currentAppDomain = pCF->GetAppDomain();
+
+ // Grab all the info from CrawlFrame that we need to
+ // check for "Am I in an exeption code blob?" now.
+
+#ifdef WIN64EXCEPTIONS
+ // We are still searching for the parent of the last funclet we encounter.
+ if (d->fpParent != LEAF_MOST_FRAME)
+ {
+ // We do nothing here.
+ LOG((LF_CORDB, LL_INFO100000, "DWSP: Skipping to parent method frame at 0x%p.\n", d->fpParent.GetSPValue()));
+ }
+ else
+#endif // WIN64EXCEPTIONS
+ // We should ignore IL stubs with no frames in our stackwalking.
+ // The only exception is dynamic methods. We want to report them when SIS is turned on.
+ if ((md != NULL) && md->IsILStub() && pCF->IsFrameless())
+ {
+#ifdef FEATURE_STUBS_AS_IL
+ if(md->AsDynamicMethodDesc()->IsMulticastStub())
+ {
+ use = true;
+ d->info.managed = true;
+ d->info.internal = false;
+ }
+#endif
+ // We do nothing here.
+ LOG((LF_CORDB, LL_INFO100000, "DWSP: Skip frameless IL stub.\n"));
+ }
+ else
+ // For frames w/o method data, send them as an internal stub frame.
+ if ((md != NULL) && md->IsDynamicMethod())
+ {
+ // Only Send the frame if "InternalFrames" are requested.
+ // Else completely ignore it.
+ if (d->ShouldProvideInternalFrames())
+ {
+ d->info.InitForDynamicMethod(pCF);
+
+ // We'll loop around to get the FramePointer. Only modification to FrameInfo
+ // after this is filling in framepointer and resetting MD.
+ use = true;
+ }
+ }
+ else if (pCF->IsFrameless())
+ {
+ // Regular managed-method.
+ LOG((LF_CORDB, LL_INFO100000, "DWSP: Is frameless.\n"));
+ use = true;
+ d->info.managed = true;
+ d->info.internal = false;
+ d->info.chainReason = CHAIN_NONE;
+ d->needParentInfo = true; // Possibly need chain reason
+ d->info.relOffset = AdjustRelOffset(pCF, &(d->info));
+ d->info.pIJM = pCF->GetJitManager();
+ d->info.MethodToken = pCF->GetMethodToken();
+
+#ifdef _TARGET_X86_
+ // This is collecting the ambientSP a lot more than we actually need it. Only time we need it is
+ // inspecting local vars that are based off the ambient esp.
+ d->info.ambientSP = pCF->GetAmbientSPFromCrawlFrame();
+#endif
+ }
+ else
+ {
+ d->info.pIJM = NULL;
+ d->info.MethodToken = METHODTOKEN(NULL, 0);
+
+ //
+ // Retrieve any interception info
+ //
+
+ // Each interception type in the switch statement below is associated with a chain reason.
+ // The other chain reasons are:
+ // CHAIN_INTERCEPTION - not used
+ // CHAIN_PROCESS_START - not used
+ // CHAIN_THREAD_START - thread start
+ // CHAIN_ENTER_MANAGED - managed chain
+ // CHAIN_ENTER_UNMANAGED - unmanaged chain
+ // CHAIN_DEBUGGER_EVAL - not used
+ // CHAIN_CONTEXT_SWITCH - not used
+ // CHAIN_FUNC_EVAL - funceval
+
+ switch (frame->GetInterception())
+ {
+ case Frame::INTERCEPTION_CLASS_INIT:
+ //
+ // Fall through
+ //
+
+ // V2 assumes that the only thing the prestub intercepts is the class constructor
+ case Frame::INTERCEPTION_PRESTUB:
+ d->info.chainReason = CHAIN_CLASS_INIT;
+ break;
+
+ case Frame::INTERCEPTION_EXCEPTION:
+ d->info.chainReason = CHAIN_EXCEPTION_FILTER;
+ break;
+
+ case Frame::INTERCEPTION_CONTEXT:
+ d->info.chainReason = CHAIN_CONTEXT_POLICY;
+ break;
+
+ case Frame::INTERCEPTION_SECURITY:
+ d->info.chainReason = CHAIN_SECURITY;
+ break;
+
+ default:
+ d->info.chainReason = CHAIN_NONE;
+ }
+
+ //
+ // Look at the frame type to figure out how to treat it.
+ //
+
+ LOG((LF_CORDB, LL_INFO100000, "DWSP: Chain reason is 0x%X.\n", d->info.chainReason));
+
+ switch (frame->GetFrameType())
+ {
+ case Frame::TYPE_ENTRY: // We now ignore entry + exit frames.
+ case Frame::TYPE_EXIT:
+ case Frame::TYPE_HELPER_METHOD_FRAME:
+ case Frame::TYPE_INTERNAL:
+
+ /* If we have a specific interception type, use it. However, if this
+ is the top-most frame (with a specific type), we can ignore it
+ and it wont appear in the stack-trace */
+#define INTERNAL_FRAME_ACTION(d, use) \
+ (d)->info.managed = true; \
+ (d)->info.internal = false; \
+ use = true
+
+ LOG((LF_CORDB, LL_INFO100000, "DWSP: Frame type is TYPE_INTERNAL.\n"));
+ if (d->info.chainReason == CHAIN_NONE || pCF->IsActiveFrame())
+ {
+ use = false;
+ }
+ else
+ {
+ INTERNAL_FRAME_ACTION(d, use);
+ }
+ break;
+
+ case Frame::TYPE_INTERCEPTION:
+ case Frame::TYPE_SECURITY: // Security is a sub-type of interception
+ LOG((LF_CORDB, LL_INFO100000, "DWSP: Frame type is TYPE_INTERCEPTION/TYPE_SECURITY.\n"));
+ d->info.managed = true;
+ d->info.internal = true;
+ use = true;
+ break;
+
+ case Frame::TYPE_CALL:
+ LOG((LF_CORDB, LL_INFO100000, "DWSP: Frame type is TYPE_CALL.\n"));
+ // In V4, StubDispatchFrame is only used on 64-bit (and PPC?) but not on x86. x86 uses a
+ // different code path which sets up a HelperMethodFrame instead. In V4.5, x86 and ARM
+ // both use the 64-bit code path and they set up a StubDispatchFrame as well. This causes
+ // a problem in the debugger stackwalker (see Dev11 Issue 13229) since the two frame types
+ // are treated differently. More specifically, a StubDispatchFrame causes the debugger
+ // stackwalk to make an invalid callback, i.e. a callback which is not for a managed method,
+ // an explicit frame, or a chain.
+ //
+ // Ideally we would just change the StubDispatchFrame to behave like a HMF, but it's
+ // too big of a change for an in-place release. For now I'm just making surgical fixes in
+ // the debugger stackwalker. This may introduce behavioural changes in on X64, but the
+ // chance of that is really small. StubDispatchFrame is only used in the virtual stub
+ // disptch code path. It stays on the stack in a small time window and it's not likely to
+ // be on the stack while some managed methods closer to the leaf are on the stack. There is
+ // only one scenario I know of, and that's the repro for Dev11 13229, but that's for x86 only.
+ // The jitted code on X64 behaves differently.
+ //
+ // Note that there is a corresponding change in DacDbiInterfaceImpl::GetInternalFrameType().
+ if (frame->GetVTablePtr() == StubDispatchFrame::GetMethodFrameVPtr())
+ {
+ use = false;
+ }
+ else
+ {
+ d->info.managed = true;
+ d->info.internal = false;
+ use = true;
+ }
+ break;
+
+ case Frame::TYPE_FUNC_EVAL:
+ LOG((LF_CORDB, LL_INFO100000, "DWSP: Frame type is TYPE_FUNC_EVAL.\n"));
+ d->info.managed = true;
+ d->info.internal = true;
+ // This is actually a nop. We reset the chain reason in InitForFuncEval() below.
+ // So is a FuncEvalFrame a chain or an internal frame?
+ d->info.chainReason = CHAIN_FUNC_EVAL;
+
+ {
+ // We only show a FuncEvalFrame if the funceval is not trying to abort the thread.
+ FuncEvalFrame *pFuncEvalFrame = static_cast<FuncEvalFrame *>(frame);
+ use = pFuncEvalFrame->ShowFrame() ? true : false;
+ }
+
+ // Send Internal frame. This is "inside" (leafmost) the chain, so we send it first
+ // since sending starts from the leaf.
+ if (use && d->ShouldProvideInternalFrames())
+ {
+ FrameInfo f;
+ f.InitForFuncEval(pCF);
+ if (d->InvokeCallback(&f) == SWA_ABORT)
+ {
+ return SWA_ABORT;
+ }
+ }
+
+ break;
+
+ // Put frames we want to ignore here:
+ case Frame::TYPE_MULTICAST:
+ LOG((LF_CORDB, LL_INFO100000, "DWSP: Frame type is TYPE_MULTICAST.\n"));
+ if (d->ShouldIgnoreNonmethodFrames())
+ {
+ // Multicast frames exist only to gc protect the arguments
+ // between invocations of a delegate. They don't have code that
+ // we can (currently) show the user (we could change this with
+ // work, but why bother? It's an internal stub, and even if the
+ // user could see it, they can't modify it).
+ LOG((LF_CORDB, LL_INFO100000, "DWSP: Skipping frame 0x%x b/c it's "
+ "a multicast frame!\n", frame));
+ use = false;
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO100000, "DWSP: NOT Skipping frame 0x%x even thought it's "
+ "a multicast frame!\n", frame));
+ INTERNAL_FRAME_ACTION(d, use);
+ }
+ break;
+
+#ifdef FEATURE_REMOTING
+ case Frame::TYPE_TP_METHOD_FRAME:
+ LOG((LF_CORDB, LL_INFO100000, "DWSP: Frame type is TYPE_TP_METHOD_FRAME.\n"));
+ if (d->ShouldIgnoreNonmethodFrames())
+ {
+ // Transparant Proxies push a frame onto the stack that they
+ // use to figure out where they're really going; this frame
+ // doesn't actually contain any code, although it does have
+ // enough info into fooling our routines into thinking it does:
+ // Just ignore these.
+ LOG((LF_CORDB, LL_INFO100000, "DWSP: Skipping frame 0x%x b/c it's "
+ "a transparant proxy frame!\n", frame));
+ use = false;
+ }
+ else
+ {
+ // Otherwise do the same thing as for internal frames
+ LOG((LF_CORDB, LL_INFO100000, "DWSP: NOT Skipping frame 0x%x even though it's "
+ "a transparant proxy frame!\n", frame));
+ INTERNAL_FRAME_ACTION(d, use);
+ }
+ break;
+#endif
+ default:
+ _ASSERTE(!"Invalid frame type!");
+ break;
+ }
+ }
+
+
+ // Check for ICorDebugInternalFrame stuff.
+ // These callbacks are dispatched out of band.
+ if (d->ShouldProvideInternalFrames() && (frame != NULL) && (frame != FRAME_TOP))
+ {
+ Frame::ETransitionType t = frame->GetTransitionType();
+ FrameInfo f;
+ bool fUse = false;
+
+ if (t == Frame::TT_U2M)
+ {
+ // We can invoke the Internal U2M frame now.
+ f.InitForU2MInternalFrame(pCF);
+ fUse = true;
+ }
+ else if (t == Frame::TT_AppDomain)
+ {
+ // Internal frame for an Appdomain transition.
+ // We used to ignore frames for ADs which we hadn't sent a Create event for yet. In V3 we send AppDomain
+ // create events immediately (before any assemblies are loaded), so this should no longer be an issue.
+ f.InitForADTransition(pCF);
+ fUse = true;
+ }
+
+ // Frame's setup. Now invoke the callback.
+ if (fUse)
+ {
+ if (d->InvokeCallback(&f) == SWA_ABORT)
+ {
+ return SWA_ABORT;
+ }
+ }
+ } // should we give frames?
+
+
+
+ if (use)
+ {
+ //
+ // If we are returning a complete stack walk from the helper thread, then we
+ // need to gather information to instantiate generics. However, a stepper doing
+ // a stackwalk does not need this information, so skip in that case.
+ //
+ if (d->ShouldIgnoreNonmethodFrames())
+ {
+ // Finding sizes of value types on the argument stack while
+ // looking for the arg runs the class loader in non-load mode.
+ ENABLE_FORBID_GC_LOADER_USE_IN_THIS_SCOPE();
+ d->info.exactGenericArgsToken = pCF->GetExactGenericArgsToken();
+ }
+ else
+ {
+ d->info.exactGenericArgsToken = NULL;
+ }
+
+ d->info.md = md;
+ CopyREGDISPLAY(&(d->info.registers), &(d->regDisplay));
+
+#if defined(_TARGET_AMD64_)
+ LOG((LF_CORDB, LL_INFO100000, "DWSP: Saving REGDISPLAY with sp = 0x%p, pc = 0x%p.\n",
+ GetRegdisplaySP(&(d->info.registers)),
+ GetControlPC(&(d->info.registers))));
+#endif // _TARGET_AMD64_
+
+ d->needParentInfo = true;
+ LOG((LF_CORDB, LL_INFO100000, "DWSP: Setting needParentInfo\n"));
+ }
+
+#if defined(WIN64EXCEPTIONS)
+ d->fpParent = CheckForParentFP(d->fpParent, pCF, d->info.IsNonFilterFuncletFrame());
+#endif // WIN64EXCEPTIONS
+
+ //
+ // The stackwalker doesn't update the register set for the
+ // case where a non-frameless frame is returning to another
+ // non-frameless frame. Cover this case.
+ //
+ // !!! This assumes that updating the register set multiple times
+ // for a given frame times is not a bad thing...
+ //
+ if (!pCF->IsFrameless())
+ {
+ LOG((LF_CORDB, LL_INFO100000, "DWSP: updating regdisplay.\n"));
+ pCF->GetFrame()->UpdateRegDisplay(&d->regDisplay);
+ }
+
+ return SWA_CONTINUE;
+}
+
+#if defined(_TARGET_X86_) && defined(FEATURE_INTEROP_DEBUGGING)
+// Helper to get the Wait-Sleep-Join bit from the thread
+bool IsInWaitSleepJoin(Thread * pThread)
+{
+ // Partial User state is sufficient because that has the bit we're checking against.
+ CorDebugUserState cts = g_pEEInterface->GetPartialUserState(pThread);
+ return ((cts & USER_WAIT_SLEEP_JOIN) != 0);
+}
+
+//-----------------------------------------------------------------------------
+// Decide if we should send an UM leaf chain.
+// This goes through a bunch of heuristics.
+// The driving guidelines here are:
+// - we try not to send an UM chain if it's just internal mscorwks stuff
+// and we know it can't have native user code.
+// (ex, anything beyond a filter context, various hijacks, etc).
+// - If it may have native user code, we send it anyway.
+//-----------------------------------------------------------------------------
+bool ShouldSendUMLeafChain(Thread * pThread)
+{
+ // If we're in shutodown, don't bother trying to sniff for an UM leaf chain.
+ // @todo - we'd like to never even be trying to stack trace on shutdown, this
+ // comes up when we do helper thread duty on shutdown.
+ if (g_fProcessDetach)
+ {
+ return false;
+ }
+
+ if (pThread->IsUnstarted() || pThread->IsDead())
+ {
+ return false;
+ }
+
+ // If a thread is suspended for sync purposes, it was suspended from managed
+ // code and the only native code is a mscorwks hijack.
+ // There are a few caveats here:
+ // - This means a thread will lose it's UM chain. But what if a user inactive thread
+ // enters the CLR from native code and hits a GC toggle? We'll lose that entire
+ // UM chain.
+ // - at a managed-only stop, preemptive threads are still live. Thus a thread
+ // may not have this state set, run a little, try to enter the GC, and then get
+ // this state set. Thus we'll lose the UM chain right out from under our noses.
+ Thread::ThreadState ts = pThread->GetSnapshotState();
+ if ((ts & Thread::TS_SyncSuspended) != 0)
+ {
+ // If we've been stopped inside the runtime (eg, at a gc-toggle) but
+ // not actually at a stopping context, then the thread must have some
+ // leafframes in mscorwks.
+ // We can detect this case by checking if GetManagedStoppedCtx(pThread) == NULL.
+ // This is very significant for notifcations (like LogMessage) that are
+ // dispatches from within mscorwks w/o a filter context.
+ // We don't send a UM chain for these cases because that would
+ // cause managed debug events to be dispatched w/ UM chains on the callstack.
+ // And that just seems wrong ...
+
+ return false;
+ }
+
+#ifdef FEATURE_HIJACK
+ if ((ts & Thread::TS_Hijacked) != 0)
+ {
+ return false;
+ }
+#endif
+
+ // This is pretty subjective. If we have a thread stopped in a managed sleep,
+ // managed wait, or managed join, then don't bother showing the native end of the
+ // stack. This check can be removed w/o impacting correctness.
+ // @todo - may be a problem if Sleep/Wait/Join go through a hosting interface
+ // which lands us in native user code.
+ // Partial User state is sufficient because that has the bit we're checking against.
+ if (IsInWaitSleepJoin(pThread))
+ {
+ return false;
+ }
+
+ // If we're tracing ourselves, we must be in managed code.
+ // Native user code can't initiate a managed stackwalk.
+ if (pThread == GetThread())
+ {
+ return false;
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Prepare a Leaf UM chain. This assumes we should send an UM leaf chain.
+// Returns true if we actually prep for an UM leaf,
+// false if we don't.
+//-----------------------------------------------------------------------------
+bool PrepareLeafUMChain(DebuggerFrameData * pData, CONTEXT * pCtxTemp)
+{
+ // Get the current user context (depends on if we're the active thread or not).
+ Thread * thread = pData->GetThread();
+ REGDISPLAY * pRDSrc = NULL;
+ REGDISPLAY rdTemp;
+
+
+#ifdef _DEBUG
+ // Anybody stopped at an native debug event (and hijacked) should have a filter ctx.
+ if (thread->GetInteropDebuggingHijacked() && (thread->GetFrame() != NULL) && (thread->GetFrame() != FRAME_TOP))
+ {
+ _ASSERTE(g_pEEInterface->GetThreadFilterContext(thread) != NULL);
+ }
+#endif
+
+ // If we're hijacked, then we assume we're in native code. This covers the active thread case.
+ if (g_pEEInterface->GetThreadFilterContext(thread) != NULL)
+ {
+ LOG((LF_CORDB, LL_EVERYTHING, "DWS - sending special case UM Chain.\n"));
+
+ // This will get it from the filter ctx.
+ pRDSrc = &(pData->regDisplay);
+ }
+ else
+ {
+ // For inactive thread, we may not be hijacked. So just get the current ctx.
+ // This will use a filter ctx if we have one.
+ // We may suspend a thread in native code w/o hijacking it, so it's still at it's live context.
+ // This can happen when we get a debug event on 1 thread; and then switch to look at another thread.
+ // This is very common when debugging apps w/ cross-thread causality (including COM STA objects)
+ pRDSrc = &rdTemp;
+
+ bool fOk;
+
+
+ // We need to get thread's context (InitRegDisplay will do that under the covers).
+ // If this is our thread, we're in bad shape. Fortunately that should never happen.
+ _ASSERTE(thread != GetThread());
+
+ Thread::SuspendThreadResult str = thread->SuspendThread();
+ if (str != Thread::STR_Success)
+ {
+ return false;
+ }
+
+ // @todo - this context is less important because the RS will overwrite it with the live context.
+ // We don't need to even bother getting it. We can just intialize the regdisplay w/ a sentinal.
+ fOk = g_pEEInterface->InitRegDisplay(thread, pRDSrc, pCtxTemp, false);
+ thread->ResumeThread();
+
+ if (!fOk)
+ {
+ return false;
+ }
+ }
+
+ // By now we have a Regdisplay from somewhere (filter ctx, current ctx, etc).
+ _ASSERTE(pRDSrc != NULL);
+
+ // If we're stopped in mscorwks (b/c of a handler for a managed BP), then the filter ctx will
+ // still be set out in jitted code.
+ // If our regdisplay is out in UM code , then send a UM chain.
+ BYTE* ip = (BYTE*) GetControlPC(pRDSrc);
+ if (g_pEEInterface->IsManagedNativeCode(ip))
+ {
+ return false;
+ }
+
+ LOG((LF_CORDB, LL_EVERYTHING, "DWS - sending leaf UM Chain.\n"));
+
+ // Get the ending fp. We may not have any managed goo on the stack (eg, native thread called
+ // into a managed method and then returned from it).
+ FramePointer fpRoot;
+ Frame * pFrame = thread->GetFrame();
+ if ((pFrame != NULL) && (pFrame != FRAME_TOP))
+ {
+ fpRoot = FramePointer::MakeFramePointer((void*) pFrame);
+ }
+ else
+ {
+ fpRoot= ROOT_MOST_FRAME;
+ }
+
+
+ // Start tracking an UM chain. We won't actually send the UM chain until
+ // we hit managed code. Since this is the leaf, we don't need to send an
+ // Enter-Managed chain either.
+ pData->BeginTrackingUMChain(fpRoot, pRDSrc);
+
+ return true;
+}
+#endif // defined(_TARGET_X86_) && defined(FEATURE_INTEROP_DEBUGGING)
+
+//-----------------------------------------------------------------------------
+// Entry function for the debugger's stackwalking layer.
+// This will invoke pCallback(FrameInfo * pInfo, pData) for each 'frame'
+//-----------------------------------------------------------------------------
+StackWalkAction DebuggerWalkStack(Thread *thread,
+ FramePointer targetFP,
+ CONTEXT *context,
+ BOOL contextValid,
+ DebuggerStackCallback pCallback,
+ void *pData,
+ BOOL fIgnoreNonmethodFrames)
+{
+ _ASSERTE(context != NULL);
+
+ DebuggerFrameData data;
+
+ StackWalkAction result = SWA_CONTINUE;
+ bool fRegInit = false;
+
+ LOG((LF_CORDB, LL_EVERYTHING, "DebuggerWalkStack called\n"));
+
+ if(contextValid || g_pEEInterface->GetThreadFilterContext(thread) != NULL)
+ {
+ fRegInit = g_pEEInterface->InitRegDisplay(thread, &data.regDisplay, context, contextValid != 0);
+ _ASSERTE(fRegInit);
+ }
+
+ if (!fRegInit)
+ {
+#if defined(CONTEXT_EXTENDED_REGISTERS)
+
+ // Note: the size of a CONTEXT record contains the extended registers, but the context pointer we're given
+ // here may not have room for them. Therefore, we only set the non-extended part of the context to 0.
+ memset((void *)context, 0, offsetof(CONTEXT, ExtendedRegisters));
+#else
+ memset((void *)context, 0, sizeof(CONTEXT));
+#endif
+ memset((void *)&data, 0, sizeof(data));
+
+#if defined(_TARGET_X86_)
+ // @todo - this seems pointless. context->Eip will be 0; and when we copy it over to the DebuggerRD,
+ // the context will be completely null.
+ data.regDisplay.ControlPC = context->Eip;
+ data.regDisplay.PCTAddr = (TADDR)&(context->Eip);
+
+#else
+ //
+ // @TODO: this should be the code for all platforms now that it uses FillRegDisplay,
+ // which encapsulates the platform variances. This could all be avoided if we used
+ // StackWalkFrames instead of StackWalkFramesEx.
+ //
+ ::SetIP(context, 0);
+ ::SetSP(context, 0);
+ FillRegDisplay(&data.regDisplay, context);
+
+ ::SetSP(data.regDisplay.pCallerContext, 0);
+#endif
+ }
+
+ data.Init(thread, targetFP, fIgnoreNonmethodFrames, pCallback, pData);
+
+
+#if defined(_TARGET_X86_) && defined(FEATURE_INTEROP_DEBUGGING)
+ CONTEXT ctxTemp; // Temp context for Leaf UM chain. Need it here so that it stays alive for whole stackwalk.
+
+ // Important case for Interop Debugging -
+ // We may be stopped in Native Code (perhaps at a BP) w/ no Transition frame on the stack!
+ // We still need to send an UM Chain for this case.
+ if (ShouldSendUMLeafChain(thread))
+ {
+ // It's possible this may fail (eg, GetContext fails on win9x), so we're not guaranteed
+ // to be sending an UM chain even though we want to.
+ PrepareLeafUMChain(&data, &ctxTemp);
+
+ }
+#endif // defined(_TARGET_X86_) && defined(FEATURE_INTEROP_DEBUGGING)
+
+ if ((result != SWA_FAILED) && !thread->IsUnstarted() && !thread->IsDead())
+ {
+ int flags = 0;
+
+ result = g_pEEInterface->StackWalkFramesEx(thread, &data.regDisplay,
+ DebuggerWalkStackProc,
+ &data, flags | HANDLESKIPPEDFRAMES | NOTIFY_ON_U2M_TRANSITIONS | ALLOW_ASYNC_STACK_WALK);
+ }
+ else
+ {
+ result = SWA_DONE;
+ }
+
+ if (result == SWA_DONE || result == SWA_FAILED) // SWA_FAILED if no frames
+ {
+ // Since Debugger StackWalk callbacks are delayed 1 frame from EE stackwalk callbacks, we
+ // have to touch up the 1 leftover here.
+ //
+ // This is safe only because we use the REGDISPLAY of the native marker callback for any subsequent
+ // explicit frames which do not update the REGDISPLAY. It's kind of fragile. If we can change
+ // the x86 real stackwalker to unwind one frame ahead of time, we can get rid of this code.
+ if (data.needParentInfo)
+ {
+ data.info.fp = GetFramePointerForDebugger(&data, NULL);
+
+ if (data.InvokeCallback(&data.info) == SWA_ABORT)
+ {
+ return SWA_ABORT;
+ }
+ }
+
+ //
+ // Top off the stack trace as necessary w/ a thread-start chain.
+ //
+ REGDISPLAY * pRegDisplay = &(data.regDisplay);
+ if (data.IsTrackingUMChain())
+ {
+ // This is the common case b/c managed code gets called from native code.
+ pRegDisplay = data.GetUMChainStartRD();
+ }
+
+
+ // All Thread starts in unmanaged code (at something like kernel32!BaseThreadStart),
+ // so all ThreadStart chains must be unmanaged.
+ // InvokeCallback will fabricate the EnterManaged chain if we haven't already sent one.
+ data.info.InitForThreadStart(thread, pRegDisplay);
+ result = data.InvokeCallback(&data.info);
+
+ }
+ return result;
+}
diff --git a/src/debug/ee/frameinfo.h b/src/debug/ee/frameinfo.h
new file mode 100644
index 0000000000..c4079029b8
--- /dev/null
+++ b/src/debug/ee/frameinfo.h
@@ -0,0 +1,209 @@
+// 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.
+//*****************************************************************************
+// File: frameinfo.h
+//
+
+//
+// Debugger stack walker
+//
+//*****************************************************************************
+
+#ifndef FRAMEINFO_H_
+#define FRAMEINFO_H_
+
+/* ========================================================================= */
+
+/* ------------------------------------------------------------------------- *
+ * Classes
+ * ------------------------------------------------------------------------- */
+
+class DebuggerJitInfo;
+
+// struct FrameInfo: Contains the information that will be handed to
+// DebuggerStackCallback functions (along with their own, individual
+// pData pointers).
+//
+// Frame *frame: The current explicit frame. NULL implies that
+// the method frame is frameless, meaning either unmanaged or managed. This
+// is set to be FRAME_TOP (0xFFffFFff) if the frame is the topmost, EE
+// placed frame.
+//
+// MethodDesc *md: MetdhodDesc for the method that's
+// executing in this method frame. Will be NULL if there is no MethodDesc
+// If we're in generic code this may be a representative (i.e. canonical)
+// MD, and extra information is available in the exactGenericArgsToken.
+// For explicit frames, this may point to the method the explicit frame refers to
+// (i.e. the method being jitted, or the interface method being called through
+// COM interop), however it must always point to a method within the same
+// domain of the explicit frame. Therefore, it is not used to point to the target of
+// FuncEval frames since the target may be in a different domain.
+//
+// void *fp: frame pointer. Actually filled in from
+// caller (parent) frame, so the DebuggerStackWalkProc must delay
+// the user callback for one frame. This is not technically necessary on WIN64, but
+// we follow the x86 model to keep things simpler. We should really consider changing
+// the real stackwalker on x86 to unwind one frame ahead of time like the 64-bit one.
+struct FrameInfo
+{
+public:
+ Frame *frame;
+ MethodDesc *md;
+
+ // the register set of the frame being reported
+ REGDISPLAY registers;
+ FramePointer fp;
+
+ // This field is propagated to the right side to become CordbRegisterSet::m_quicklyUnwind.
+ // If it is true, then the registers reported in the REGDISPLAY are invalid. It is only set to
+ // true in InitForEnterManagedChain(). In that case, we are passing a NULL REGDISPLAY anyway.
+ // This is such a misnomer.
+ bool quickUnwind;
+
+ // Set to true if we are dealing with an internal explicit frame. Currently this is only true
+ // for prestub frames, security frames, funceval frames, and certain debugger-specific frames
+ // (e.g. DebuggerClassInitMarkFrame, DebuggerSecurityCodeMarkFrame).
+ // This affects HasMethodFrame() below.
+ bool internal;
+
+ // whether the state contained in the FrameInfo represents a managed or unmanaged method frame/stub/chain;
+ // corresponds to ICorDebugChain::IsManaged()
+ bool managed;
+
+ // Native offset from beginning of the method.
+ ULONG relOffset;
+
+ // The ambient stackpointer. This can be use to compute esp-relative local variables,
+ // which can be common in frameless methods.
+ TADDR ambientSP;
+
+ // These two fields are only set for managed method frames.
+ IJitManager *pIJM;
+ METHODTOKEN MethodToken;
+
+ // This represents the current domain of the frame itself, and which
+ // the method specified by 'md' is executing in.
+ AppDomain *currentAppDomain;
+
+ // only set for stackwalking, not stepping
+ void *exactGenericArgsToken;
+
+#if defined(WIN64EXCEPTIONS)
+ // This field is only used on IA64 to determine which registers are available and
+ // whether we need to adjust the IP.
+ bool fIsLeaf;
+
+ // These two fields are used for funclets.
+ bool fIsFunclet;
+ bool fIsFilter;
+
+ bool IsFuncletFrame() { return fIsFunclet; }
+ bool IsFilterFrame() { return fIsFilter; }
+ bool IsNonFilterFuncletFrame() { return (fIsFunclet && !fIsFilter); }
+#endif // WIN64EXCEPTIONS
+
+
+ // A ridiculous flag that is targetting a very narrow fix at issue 650903 (4.5.1/Blue).
+ // This is set when the currently walked frame is a ComPlusMethodFrameGeneric. If the
+ // dude doing the walking is trying to ignore such frames (see
+ // code:ControllerStackInfo::m_suppressUMChainFromComPlusMethodFrameGeneric), AND
+ // this is set, then the walker just continues on to the next frame, without
+ // erroneously identifying this frame as the target frame. Only used during "Step
+ // Out" to a managed frame (i.e., managed-only debugging).
+ bool fIgnoreThisFrameIfSuppressingUMChainFromComPlusMethodFrameGeneric;
+
+ // In addition to a Method, a FrameInfo may also represent either a Chain or a Stub (but not both).
+ // chainReason corresponds to ICorDebugChain::GetReason().
+ CorDebugChainReason chainReason;
+ CorDebugInternalFrameType eStubFrameType;
+
+ // Helpers for initializing a FrameInfo for a chain or a stub frame.
+ void InitForM2UInternalFrame(CrawlFrame * pCF);
+ void InitForU2MInternalFrame(CrawlFrame * pCF);
+ void InitForADTransition(CrawlFrame * pCF);
+ void InitForDynamicMethod(CrawlFrame * pCF);
+ void InitForFuncEval(CrawlFrame * pCF);
+ void InitForThreadStart(Thread *thread, REGDISPLAY * pRDSrc);
+ void InitForUMChain(FramePointer fpRoot, REGDISPLAY * pRDSrc);
+ void InitForEnterManagedChain(FramePointer fpRoot);
+
+ // Does this FrameInfo represent a method frame? (aka a frameless frame)
+ // This may be combined w/ both StubFrames and ChainMarkers.
+ bool HasMethodFrame() { return md != NULL && !internal; }
+
+ // Is this frame for a stub?
+ // This is mutually exclusive w/ Chain Markers.
+ // StubFrames may also have a method frame as a "hint". Ex, a stub frame for a
+ // M2U transition may have the Method for the Managed Wrapper for the unmanaged call.
+ // Stub frames map to internal frames on the RS. They use the same enum
+ // (CorDebugInternalFrameType) to represent the type of the frame.
+ bool HasStubFrame() { return eStubFrameType != STUBFRAME_NONE; }
+
+ // Does this FrameInfo mark the start of a new chain? (A Frame info may both
+ // start a chain and represent a method)
+ bool HasChainMarker() { return chainReason != CHAIN_NONE; }
+
+ // Helper functions for retrieving the DJI and the DMI
+ DebuggerJitInfo * GetJitInfoFromFrame();
+ DebuggerMethodInfo * GetMethodInfoFromFrameOrThrow();
+
+ // Debug helper which nops in retail; and asserts invariants in debug.
+#ifdef _DEBUG
+ void AssertValid();
+
+ // Debug helpers to get name of frame. Useful in asserts + log statements.
+ LPCUTF8 DbgGetClassName();
+ LPCUTF8 DbgGetMethodName();
+#endif
+
+protected:
+ // These are common internal helpers shared by the other Init*() helpers above.
+ void InitForScratchFrameInfo();
+ void InitFromStubHelper(CrawlFrame * pCF, MethodDesc * pMDHint, CorDebugInternalFrameType type);
+
+};
+
+//StackWalkAction (*DebuggerStackCallback): This callback will
+// be invoked by DebuggerWalkStackProc at each method frame and explicit frame, passing the FrameInfo
+// and callback-defined pData to the method. The callback then returns a
+// SWA - if SWA_ABORT is returned then the walk stops immediately. If
+// SWA_CONTINUE is called, then the frame is walked & the next higher frame
+// will be used. If the current explicit frame is at the root of the stack, then
+// in the next iteration, DSC will be invoked with FrameInfo::frame == FRAME_TOP
+typedef StackWalkAction (*DebuggerStackCallback)(FrameInfo *frame, void *pData);
+
+//StackWalkAction DebuggerWalkStack(): Sets up everything for a
+// stack walk for the debugger, starts the stack walk (via
+// g_pEEInterface->StackWalkFramesEx), then massages the output. Note that it
+// takes a DebuggerStackCallback as an argument, but at each method frame and explicit frame
+// DebuggerWalkStackProc gets called, which in turn calls the
+// DebuggerStackCallback.
+// Thread * thread: the thread on which to do a stackwalk
+// void *targetFP: If you're looking for a specific frame, then
+// this should be set to the fp for that frame, and the callback won't
+// be called until that frame is reached. Otherwise, set it to LEAF_MOST_FRAME &
+// the callback will be called on every frame.
+// CONTEXT *context: Never NULL, b/c the callbacks require the
+// CONTEXT as a place to store some information. Either it points to an
+// uninitialized CONTEXT (contextValid should be false), or
+// a pointer to a valid CONTEXT for the thread. If it's NULL, InitRegDisplay
+// will fill it in for us, so we shouldn't go out of our way to set this up.
+// bool contextValid: TRUE if context points to a valid CONTEXT, FALSE
+// otherwise.
+// DebuggerStackCallback pCallback: User supplied callback to
+// be invoked at every frame that's at targetFP or higher.
+// void *pData: User supplied data that we shuffle around,
+// and then hand to pCallback.
+// BOOL fIgnoreNonmethodFrames: Generally true for end user stackwalking (e.g. displaying a stack trace) and
+// false for stepping (e.g. stepping out).
+
+StackWalkAction DebuggerWalkStack(Thread *thread,
+ FramePointer targetFP,
+ T_CONTEXT *pContext,
+ BOOL contextValid,
+ DebuggerStackCallback pCallback,
+ void *pData,
+ BOOL fIgnoreNonmethodFrames);
+
+#endif // FRAMEINFO_H_
diff --git a/src/debug/ee/funceval.cpp b/src/debug/ee/funceval.cpp
new file mode 100644
index 0000000000..eb8950deab
--- /dev/null
+++ b/src/debug/ee/funceval.cpp
@@ -0,0 +1,3984 @@
+// 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.
+// ****************************************************************************
+// File: funceval.cpp
+//
+
+//
+// funceval.cpp - Debugger func-eval routines.
+//
+// ****************************************************************************
+// Putting code & #includes, #defines, etc, before the stdafx.h will
+// cause the code,etc, to be silently ignored
+
+
+#include "stdafx.h"
+#include "debugdebugger.h"
+#include "ipcmanagerinterface.h"
+#include "../inc/common.h"
+#include "perflog.h"
+#include "eeconfig.h" // This is here even for retail & free builds...
+#include "../../dlls/mscorrc/resource.h"
+
+#ifdef FEATURE_REMOTING
+#include "remoting.h"
+#endif
+
+#include "context.h"
+#include "vars.hpp"
+#include "threads.h"
+#include "appdomain.inl"
+#include <limits.h>
+#include "ilformatter.h"
+
+#ifndef DACCESS_COMPILE
+
+//
+// This is the main file for processing func-evals. Nestle in
+// with a cup o' tea and read on.
+//
+// The most common case is handled in GCProtectArgsAndDoNormalFuncEval(), which follows
+// all the comments below. The two other corner cases are handled in
+// FuncEvalHijackWorker(), and are extremely straight-forward.
+//
+// There are several steps to successfully processing a func-eval. At a
+// very high level, the first step is to gather all the information necessary
+// to make the call (specifically, gather arg info and method info); the second
+// step is to actually make the call to managed code; finally, the third step
+// is to take all results and unpackage them.
+//
+// The first step (gathering arg and method info) has several critical passes that
+// must be made.
+// a) Protect all passed in args from a GC.
+// b) Transition into the appropriate AppDomain if necessary
+// c) Pre-allocate object for 'new' calls and, if necessary, box the 'this' argument. (May cause a GC)
+// d) Gather method info (May cause GC)
+// e) Gather info from runtime about args. (May cause a GC)
+// f) Box args that need to be, GC-protecting the newly boxed items. (May cause a GC)
+// g) Pre-allocate object for return values. (May cause a GC)
+// h) Copy to pBufferForArgsArray all the args. This array is used to hold values that
+// may need writable memory for ByRef args.
+// i) Create and load pArgumentArray to be passed as the stack for the managed call.
+// NOTE: From the time we load the first argument into the stack we cannot cause a GC
+// as the argument array cannot be GC-protected.
+//
+// The second step (Making the managed call), is relatively easy, and is a single call.
+//
+// The third step (unpacking all results), has a couple of passes as well.
+// a) Copy back all resulting values.
+// b) Free all temporary work memory.
+//
+//
+// The most difficult part of doing a func-eval is the first step, since once you
+// have everything set up, unpacking and calling are reverse, gc-safe, operations. Thus,
+// elaboration is needed on the first step.
+//
+// a) Protect all passed in args from a GC. This must be done in a gc-forbid region,
+// and the code path to this function must not trigger a gc either. In this function five
+// parallel arrays are used: pObjectRefArray, pMaybeInteriorPtrArray, pByRefMaybeInteriorPtrArray,
+// pBufferForArgsArray, and pArguments.
+// pObjectRefArray is used to gc-protect all arguments and results that are objects.
+// pMaybeInteriorPtrArray is used to gc-protect all arguments that might be pointers
+// to an interior of a managed object.
+// pByRefMaybeInteriorPtrArray is similar to pMaybeInteriorPtrArray, except that it protects the
+// address of the arguments instead of the arguments themselves. This is needed because we may have
+// by ref arguments whose address is an interior pointer into the GC heap.
+// pBufferForArgsArray is used strictly as a buffer for copying primitives
+// that need to be passed as ByRef, or may be enregistered. This array also holds
+// handles.
+// These first two arrays are mutually exclusive, that is, if there is an entry
+// in one array at index i, there should be no entry in either of the other arrays at
+// the same index.
+// pArguments is used as the complete array of arguments to pass to the managed function.
+//
+// Unfortunately the necessary information to complete pass (a) perfectly may cause a gc, so
+// instead, pass (a) is over-aggressive and protects the following: All object refs into
+// pObjectRefArray, and puts all values that could be raw pointers into pMaybeInteriorPtrArray.
+//
+// b) Discovers the method to be called, and if it is a 'new' allocate an object for the result.
+//
+// c) Gather information about the method that will be called.
+//
+// d) Here we gather information from the method signature which tells which args are
+// ByRef and various other flags. We will use this information in later passes.
+//
+// e) Using the information in pass (c), for each argument: box arguments, placing newly
+// boxed items into pObjectRefArray immediately after creating them.
+//
+// f) Pre-allocate any object for a returned value.
+//
+// g) Using the information is pass (c), all arguments are copied into a scratch buffer before
+// invoking the managed function.
+//
+// h) pArguments is loaded from the pre-allocated return object, the individual elements
+// of the other 3 arrays, and from any non-ByRef literals. This is the complete stack
+// to be passed to the managed function. For performance increase, it can remove any
+// overly aggressive items that were placed in pMaybeInteriorPtrArray.
+//
+
+//
+// IsElementTypeSpecial()
+//
+// This is a simple function used to check if a CorElementType needs special handling for func eval.
+//
+// parameters: type - the CorElementType which we need to check
+//
+// return value: true if the specified type needs special handling
+//
+inline static bool IsElementTypeSpecial(CorElementType type)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ return ((type == ELEMENT_TYPE_CLASS) ||
+ (type == ELEMENT_TYPE_OBJECT) ||
+ (type == ELEMENT_TYPE_ARRAY) ||
+ (type == ELEMENT_TYPE_SZARRAY) ||
+ (type == ELEMENT_TYPE_STRING));
+}
+
+//
+// GetAndSetLiteralValue()
+//
+// This helper function extracts the value out of the source pointer while taking into account alignment and size.
+// Then it stores the value into the destination pointer, again taking into account alignment and size.
+//
+// parameters: pDst - destination pointer
+// dstType - the CorElementType of the destination value
+// pSrc - source pointer
+// srcType - the CorElementType of the source value
+//
+// return value: none
+//
+inline static void GetAndSetLiteralValue(LPVOID pDst, CorElementType dstType, LPVOID pSrc, CorElementType srcType)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ UINT64 srcValue;
+
+ // Retrieve the value using the source CorElementType.
+ switch (g_pEEInterface->GetSizeForCorElementType(srcType))
+ {
+ case 1:
+ srcValue = (UINT64)*((BYTE*)pSrc);
+ break;
+ case 2:
+ srcValue = (UINT64)*((USHORT*)pSrc);
+ break;
+ case 4:
+ srcValue = (UINT64)*((UINT32*)pSrc);
+ break;
+ case 8:
+ srcValue = (UINT64)*((UINT64*)pSrc);
+ break;
+
+ default:
+ UNREACHABLE();
+ }
+
+ // Cast to the appropriate type using the destination CorElementType.
+ switch (dstType)
+ {
+ case ELEMENT_TYPE_BOOLEAN:
+ *(BYTE*)pDst = (BYTE)!!srcValue;
+ break;
+ case ELEMENT_TYPE_I1:
+ *(INT8*)pDst = (INT8)srcValue;
+ break;
+ case ELEMENT_TYPE_U1:
+ *(UINT8*)pDst = (UINT8)srcValue;
+ break;
+ case ELEMENT_TYPE_I2:
+ *(INT16*)pDst = (INT16)srcValue;
+ break;
+ case ELEMENT_TYPE_U2:
+ case ELEMENT_TYPE_CHAR:
+ *(UINT16*)pDst = (UINT16)srcValue;
+ break;
+#if !defined(_WIN64)
+ case ELEMENT_TYPE_I:
+#endif
+ case ELEMENT_TYPE_I4:
+ *(int*)pDst = (int)srcValue;
+ break;
+#if !defined(_WIN64)
+ case ELEMENT_TYPE_U:
+#endif
+ case ELEMENT_TYPE_U4:
+ case ELEMENT_TYPE_R4:
+ *(unsigned*)pDst = (unsigned)srcValue;
+ break;
+#if defined(_WIN64)
+ case ELEMENT_TYPE_I:
+#endif
+ case ELEMENT_TYPE_I8:
+ case ELEMENT_TYPE_R8:
+ *(INT64*)pDst = (INT64)srcValue;
+ break;
+
+#if defined(_WIN64)
+ case ELEMENT_TYPE_U:
+#endif
+ case ELEMENT_TYPE_U8:
+ *(UINT64*)pDst = (UINT64)srcValue;
+ break;
+ case ELEMENT_TYPE_FNPTR:
+ case ELEMENT_TYPE_PTR:
+ *(void **)pDst = (void *)(SIZE_T)srcValue;
+ break;
+
+ default:
+ UNREACHABLE();
+ }
+
+}
+
+
+//
+// Throw on not supported func evals
+//
+static void ValidateFuncEvalReturnType(DebuggerIPCE_FuncEvalType evalType, MethodTable * pMT)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ }
+ CONTRACTL_END;
+
+ if (pMT == g_pStringClass)
+ {
+ if (evalType == DB_IPCE_FET_NEW_OBJECT || evalType == DB_IPCE_FET_NEW_OBJECT_NC)
+ {
+ // Cannot call New object on String constructor.
+ COMPlusThrow(kArgumentException,W("Argument_CannotCreateString"));
+ }
+ }
+ else if (g_pEEInterface->IsTypedReference(pMT))
+ {
+ // Cannot create typed references through funceval.
+ if (evalType == DB_IPCE_FET_NEW_OBJECT || evalType == DB_IPCE_FET_NEW_OBJECT_NC || evalType == DB_IPCE_FET_NORMAL)
+ {
+ COMPlusThrow(kArgumentException, W("Argument_CannotCreateTypedReference"));
+ }
+ }
+}
+
+//
+// Given a register, return the value.
+//
+static SIZE_T GetRegisterValue(DebuggerEval *pDE, CorDebugRegister reg, void *regAddr, SIZE_T regValue)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ SIZE_T ret = 0;
+
+ // Check whether the register address is the marker value for a register in a non-leaf frame.
+ // This is related to the funceval breaking change.
+ //
+ if (regAddr == CORDB_ADDRESS_TO_PTR(kNonLeafFrameRegAddr))
+ {
+ ret = regValue;
+ }
+ else
+ {
+ switch (reg)
+ {
+ case REGISTER_STACK_POINTER:
+ ret = (SIZE_T)GetSP(&pDE->m_context);
+ break;
+
+ case REGISTER_FRAME_POINTER:
+ ret = (SIZE_T)GetFP(&pDE->m_context);
+ break;
+
+#if defined(_TARGET_X86_)
+ case REGISTER_X86_EAX:
+ ret = pDE->m_context.Eax;
+ break;
+
+ case REGISTER_X86_ECX:
+ ret = pDE->m_context.Ecx;
+ break;
+
+ case REGISTER_X86_EDX:
+ ret = pDE->m_context.Edx;
+ break;
+
+ case REGISTER_X86_EBX:
+ ret = pDE->m_context.Ebx;
+ break;
+
+ case REGISTER_X86_ESI:
+ ret = pDE->m_context.Esi;
+ break;
+
+ case REGISTER_X86_EDI:
+ ret = pDE->m_context.Edi;
+ break;
+
+#elif defined(_TARGET_AMD64_)
+ case REGISTER_AMD64_RAX:
+ ret = pDE->m_context.Rax;
+ break;
+
+ case REGISTER_AMD64_RCX:
+ ret = pDE->m_context.Rcx;
+ break;
+
+ case REGISTER_AMD64_RDX:
+ ret = pDE->m_context.Rdx;
+ break;
+
+ case REGISTER_AMD64_RBX:
+ ret = pDE->m_context.Rbx;
+ break;
+
+ case REGISTER_AMD64_RSI:
+ ret = pDE->m_context.Rsi;
+ break;
+
+ case REGISTER_AMD64_RDI:
+ ret = pDE->m_context.Rdi;
+ break;
+
+ case REGISTER_AMD64_R8:
+ ret = pDE->m_context.R8;
+ break;
+
+ case REGISTER_AMD64_R9:
+ ret = pDE->m_context.R9;
+ break;
+
+ case REGISTER_AMD64_R10:
+ ret = pDE->m_context.R10;
+ break;
+
+ case REGISTER_AMD64_R11:
+ ret = pDE->m_context.R11;
+ break;
+
+ case REGISTER_AMD64_R12:
+ ret = pDE->m_context.R12;
+ break;
+
+ case REGISTER_AMD64_R13:
+ ret = pDE->m_context.R13;
+ break;
+
+ case REGISTER_AMD64_R14:
+ ret = pDE->m_context.R14;
+ break;
+
+ case REGISTER_AMD64_R15:
+ ret = pDE->m_context.R15;
+ break;
+
+ // fall through
+ case REGISTER_AMD64_XMM0:
+ case REGISTER_AMD64_XMM1:
+ case REGISTER_AMD64_XMM2:
+ case REGISTER_AMD64_XMM3:
+ case REGISTER_AMD64_XMM4:
+ case REGISTER_AMD64_XMM5:
+ case REGISTER_AMD64_XMM6:
+ case REGISTER_AMD64_XMM7:
+ case REGISTER_AMD64_XMM8:
+ case REGISTER_AMD64_XMM9:
+ case REGISTER_AMD64_XMM10:
+ case REGISTER_AMD64_XMM11:
+ case REGISTER_AMD64_XMM12:
+ case REGISTER_AMD64_XMM13:
+ case REGISTER_AMD64_XMM14:
+ case REGISTER_AMD64_XMM15:
+ ret = FPSpillToR8(&(pDE->m_context.Xmm0) + (reg - REGISTER_AMD64_XMM0));
+ break;
+
+#endif // !_TARGET_X86_ && !_TARGET_AMD64_
+ default:
+ _ASSERT(!"Invalid register number!");
+
+ }
+ }
+
+ return ret;
+}
+
+//
+// Given a register, set its value.
+//
+static void SetRegisterValue(DebuggerEval *pDE, CorDebugRegister reg, void *regAddr, SIZE_T newValue)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ // Check whether the register address is the marker value for a register in a non-leaf frame.
+ // If so, then we can't update the register. Throw an exception to communicate this error.
+ if (regAddr == CORDB_ADDRESS_TO_PTR(kNonLeafFrameRegAddr))
+ {
+ COMPlusThrowHR(CORDBG_E_FUNC_EVAL_CANNOT_UPDATE_REGISTER_IN_NONLEAF_FRAME);
+ return;
+ }
+ else
+ {
+ switch (reg)
+ {
+ case REGISTER_STACK_POINTER:
+ SetSP(&pDE->m_context, newValue);
+ break;
+
+ case REGISTER_FRAME_POINTER:
+ SetFP(&pDE->m_context, newValue);
+ break;
+
+#ifdef _TARGET_X86_
+ case REGISTER_X86_EAX:
+ pDE->m_context.Eax = newValue;
+ break;
+
+ case REGISTER_X86_ECX:
+ pDE->m_context.Ecx = newValue;
+ break;
+
+ case REGISTER_X86_EDX:
+ pDE->m_context.Edx = newValue;
+ break;
+
+ case REGISTER_X86_EBX:
+ pDE->m_context.Ebx = newValue;
+ break;
+
+ case REGISTER_X86_ESI:
+ pDE->m_context.Esi = newValue;
+ break;
+
+ case REGISTER_X86_EDI:
+ pDE->m_context.Edi = newValue;
+ break;
+
+#elif defined(_TARGET_AMD64_)
+ case REGISTER_AMD64_RAX:
+ pDE->m_context.Rax = newValue;
+ break;
+
+ case REGISTER_AMD64_RCX:
+ pDE->m_context.Rcx = newValue;
+ break;
+
+ case REGISTER_AMD64_RDX:
+ pDE->m_context.Rdx = newValue;
+ break;
+
+ case REGISTER_AMD64_RBX:
+ pDE->m_context.Rbx = newValue;
+ break;
+
+ case REGISTER_AMD64_RSI:
+ pDE->m_context.Rsi = newValue;
+ break;
+
+ case REGISTER_AMD64_RDI:
+ pDE->m_context.Rdi = newValue;
+ break;
+
+ case REGISTER_AMD64_R8:
+ pDE->m_context.R8= newValue;
+ break;
+
+ case REGISTER_AMD64_R9:
+ pDE->m_context.R9= newValue;
+ break;
+
+ case REGISTER_AMD64_R10:
+ pDE->m_context.R10= newValue;
+ break;
+
+ case REGISTER_AMD64_R11:
+ pDE->m_context.R11 = newValue;
+ break;
+
+ case REGISTER_AMD64_R12:
+ pDE->m_context.R12 = newValue;
+ break;
+
+ case REGISTER_AMD64_R13:
+ pDE->m_context.R13 = newValue;
+ break;
+
+ case REGISTER_AMD64_R14:
+ pDE->m_context.R14 = newValue;
+ break;
+
+ case REGISTER_AMD64_R15:
+ pDE->m_context.R15 = newValue;
+ break;
+
+ // fall through
+ case REGISTER_AMD64_XMM0:
+ case REGISTER_AMD64_XMM1:
+ case REGISTER_AMD64_XMM2:
+ case REGISTER_AMD64_XMM3:
+ case REGISTER_AMD64_XMM4:
+ case REGISTER_AMD64_XMM5:
+ case REGISTER_AMD64_XMM6:
+ case REGISTER_AMD64_XMM7:
+ case REGISTER_AMD64_XMM8:
+ case REGISTER_AMD64_XMM9:
+ case REGISTER_AMD64_XMM10:
+ case REGISTER_AMD64_XMM11:
+ case REGISTER_AMD64_XMM12:
+ case REGISTER_AMD64_XMM13:
+ case REGISTER_AMD64_XMM14:
+ case REGISTER_AMD64_XMM15:
+ R8ToFPSpill(&(pDE->m_context.Xmm0) + (reg - REGISTER_AMD64_XMM0), newValue);
+ break;
+
+#endif // !_TARGET_X86_ && !_TARGET_AMD64_
+ default:
+ _ASSERT(!"Invalid register number!");
+
+ }
+ }
+}
+
+
+/*
+ * GetRegsiterValueAndReturnAddress
+ *
+ * This routine takes out a value from a register, or set of registers, into one of
+ * the given buffers (depending on size), and returns a pointer to the filled in
+ * buffer, or NULL on error.
+ *
+ * Parameters:
+ * pDE - pointer to the DebuggerEval object being processed.
+ * pFEAD - Information about this particular argument.
+ * pInt64Buf - pointer to a buffer of type INT64
+ * pSizeTBuf - pointer to a buffer of native size type.
+ *
+ * Returns:
+ * pointer to the filled in buffer, else NULL on error.
+ *
+ */
+static PVOID GetRegisterValueAndReturnAddress(DebuggerEval *pDE,
+ DebuggerIPCE_FuncEvalArgData *pFEAD,
+ INT64 *pInt64Buf,
+ SIZE_T *pSizeTBuf
+ )
+{
+ LIMITED_METHOD_CONTRACT;
+
+ PVOID pAddr;
+
+#if !defined(_WIN64)
+ pAddr = pInt64Buf;
+ DWORD *pLow = (DWORD*)(pInt64Buf);
+ DWORD *pHigh = pLow + 1;
+#endif // _WIN64
+
+ switch (pFEAD->argHome.kind)
+ {
+#if !defined(_WIN64)
+ case RAK_REGREG:
+ *pLow = GetRegisterValue(pDE, pFEAD->argHome.u.reg2, pFEAD->argHome.u.reg2Addr, pFEAD->argHome.u.reg2Value);
+ *pHigh = GetRegisterValue(pDE, pFEAD->argHome.reg1, pFEAD->argHome.reg1Addr, pFEAD->argHome.reg1Value);
+ break;
+
+ case RAK_MEMREG:
+ *pLow = GetRegisterValue(pDE, pFEAD->argHome.reg1, pFEAD->argHome.reg1Addr, pFEAD->argHome.reg1Value);
+ *pHigh = *((DWORD*)CORDB_ADDRESS_TO_PTR(pFEAD->argHome.addr));
+ break;
+
+ case RAK_REGMEM:
+ *pLow = *((DWORD*)CORDB_ADDRESS_TO_PTR(pFEAD->argHome.addr));
+ *pHigh = GetRegisterValue(pDE, pFEAD->argHome.reg1, pFEAD->argHome.reg1Addr, pFEAD->argHome.reg1Value);
+ break;
+#endif // _WIN64
+
+ case RAK_REG:
+ // Simply grab the value out of the proper register.
+ *pSizeTBuf = GetRegisterValue(pDE, pFEAD->argHome.reg1, pFEAD->argHome.reg1Addr, pFEAD->argHome.reg1Value);
+ pAddr = pSizeTBuf;
+ break;
+
+ default:
+ pAddr = NULL;
+ break;
+ }
+
+ return pAddr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Clean up any temporary value class variables we have allocated for the funceval.
+//
+// Arguments:
+// pStackStructArray - array whose elements track the location and type of the temporary variables
+//
+
+void CleanUpTemporaryVariables(ValueClassInfo ** ppProtectedValueClasses)
+{
+ while (*ppProtectedValueClasses != NULL)
+ {
+ ValueClassInfo * pValueClassInfo = *ppProtectedValueClasses;
+ *ppProtectedValueClasses = pValueClassInfo->pNext;
+
+ DeleteInteropSafe(reinterpret_cast<BYTE *>(pValueClassInfo));
+ }
+}
+
+
+#ifdef _DEBUG
+
+//
+// Create a parallel array that tracks that we have initialized information in
+// each array.
+//
+#define MAX_DATA_LOCATIONS_TRACKED 100
+
+typedef DWORD DataLocation;
+
+#define DL_NonExistent 0x00
+#define DL_ObjectRefArray 0x01
+#define DL_MaybeInteriorPtrArray 0x02
+#define DL_BufferForArgsArray 0x04
+#define DL_All 0xFF
+
+#endif // _DEBUG
+
+
+/*
+ * GetFuncEvalArgValue
+ *
+ * This routine is used to fill the pArgument array with the appropriate value. This function
+ * uses the three parallel array entries given, and places the correct value, or reference to
+ * the value in pArgument.
+ *
+ * Parameters:
+ * pDE - pointer to the DebuggerEval object being processed.
+ * pFEAD - Information about this particular argument.
+ * isByRef - Is the argument being passed ByRef.
+ * fNeedBoxOrUnbox - Did the argument need boxing or unboxing.
+ * argTH - The type handle for the argument.
+ * byrefArgSigType - The signature type of a parameter that isByRef == true.
+ * pArgument - Location to place the reference or value.
+ * pMaybeInteriorPtrArg - A pointer that contains a value that may be pointers to
+ * the interior of a managed object.
+ * pObjectRefArg - A pointer that contains an object ref. It was built previously.
+ * pBufferArg - A pointer for holding stuff that did not need to be protected.
+ *
+ * Returns:
+ * None.
+ *
+ */
+static void GetFuncEvalArgValue(DebuggerEval *pDE,
+ DebuggerIPCE_FuncEvalArgData *pFEAD,
+ bool isByRef,
+ bool fNeedBoxOrUnbox,
+ TypeHandle argTH,
+ CorElementType byrefArgSigType,
+ TypeHandle byrefArgTH,
+ ARG_SLOT *pArgument,
+ void *pMaybeInteriorPtrArg,
+ OBJECTREF *pObjectRefArg,
+ INT64 *pBufferArg,
+ ValueClassInfo ** ppProtectedValueClasses,
+ CorElementType argSigType
+ DEBUG_ARG(DataLocation dataLocation)
+ )
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE((dataLocation != DL_NonExistent) ||
+ (pFEAD->argElementType == ELEMENT_TYPE_VALUETYPE));
+
+ switch (pFEAD->argElementType)
+ {
+ case ELEMENT_TYPE_I8:
+ case ELEMENT_TYPE_U8:
+ case ELEMENT_TYPE_R8:
+ {
+ INT64 *pSource;
+
+#if defined(_WIN64)
+ _ASSERTE(dataLocation & DL_MaybeInteriorPtrArray);
+
+ pSource = (INT64 *)pMaybeInteriorPtrArg;
+#else // !_WIN64
+ _ASSERTE(dataLocation & DL_BufferForArgsArray);
+
+ pSource = pBufferArg;
+#endif // !_WIN64
+
+ if (!isByRef)
+ {
+ *((INT64*)pArgument) = *pSource;
+ }
+ else
+ {
+ *pArgument = PtrToArgSlot(pSource);
+ }
+ }
+ break;
+
+ case ELEMENT_TYPE_VALUETYPE:
+ {
+ SIZE_T v = 0;
+ LPVOID pAddr = NULL;
+ INT64 bigVal = 0;
+
+ if (pFEAD->argAddr != NULL)
+ {
+ pAddr = *((void **)pMaybeInteriorPtrArg);
+ }
+ else
+ {
+ pAddr = GetRegisterValueAndReturnAddress(pDE, pFEAD, &bigVal, &v);
+
+ if (pAddr == NULL)
+ {
+ COMPlusThrow(kArgumentNullException, W("ArgumentNull_Generic"));
+ }
+ }
+
+
+ _ASSERTE(pAddr);
+
+ if (!fNeedBoxOrUnbox && !isByRef)
+ {
+ _ASSERTE(argTH.GetMethodTable());
+
+ unsigned size = argTH.GetMethodTable()->GetNumInstanceFieldBytes();
+ if (size <= sizeof(ARG_SLOT)
+#if defined(_TARGET_AMD64_)
+ // On AMD64 we pass value types of size which are not powers of 2 by ref.
+ && ((size & (size-1)) == 0)
+#endif // _TARGET_AMD64_
+ )
+ {
+ memcpyNoGCRefs(ArgSlotEndianessFixup(pArgument, sizeof(LPVOID)), pAddr, size);
+ }
+ else
+ {
+ _ASSERTE(pFEAD->argAddr != NULL);
+#if defined(ENREGISTERED_PARAMTYPE_MAXSIZE)
+ if (ArgIterator::IsArgPassedByRef(argTH))
+ {
+ // On X64, by-value value class arguments which are bigger than 8 bytes are passed by reference
+ // according to the native calling convention. The same goes for value class arguments whose size
+ // is smaller than 8 bytes but not a power of 2. To avoid side effets, we need to allocate a
+ // temporary variable and pass that by reference instead. On ARM64, by-value value class
+ // arguments which are bigger than 16 bytes are passed by reference.
+ _ASSERTE(ppProtectedValueClasses != NULL);
+
+ BYTE * pTemp = new (interopsafe) BYTE[ALIGN_UP(sizeof(ValueClassInfo), 8) + size];
+
+ ValueClassInfo * pValueClassInfo = (ValueClassInfo *)pTemp;
+ LPVOID pData = pTemp + ALIGN_UP(sizeof(ValueClassInfo), 8);
+
+ memcpyNoGCRefs(pData, pAddr, size);
+ *pArgument = PtrToArgSlot(pData);
+
+ pValueClassInfo->pData = pData;
+ pValueClassInfo->pMT = argTH.GetMethodTable();
+
+ pValueClassInfo->pNext = *ppProtectedValueClasses;
+ *ppProtectedValueClasses = pValueClassInfo;
+ }
+ else
+#endif // ENREGISTERED_PARAMTYPE_MAXSIZE
+ *pArgument = PtrToArgSlot(pAddr);
+
+ }
+ }
+ else
+ {
+ if (fNeedBoxOrUnbox)
+ {
+ *pArgument = ObjToArgSlot(*pObjectRefArg);
+ }
+ else
+ {
+ if (pFEAD->argAddr)
+ {
+ *pArgument = PtrToArgSlot(pAddr);
+ }
+ else
+ {
+ // The argument is the address of where we're holding the primitive in the PrimitiveArg array. We
+ // stick the real value from the register into the PrimitiveArg array. It should be in a single
+ // register since it is pointer-sized.
+ _ASSERTE( pFEAD->argHome.kind == RAK_REG );
+ *pArgument = PtrToArgSlot(pBufferArg);
+ *pBufferArg = (INT64)v;
+ }
+ }
+ }
+ }
+ break;
+
+ default:
+ // literal values smaller than 8 bytes and "special types" (e.g. object, string, etc.)
+
+ {
+ INT64 *pSource;
+
+ INDEBUG(DataLocation expectedLocation);
+
+#ifdef _TARGET_X86_
+ if ((pFEAD->argElementType == ELEMENT_TYPE_I4) ||
+ (pFEAD->argElementType == ELEMENT_TYPE_U4) ||
+ (pFEAD->argElementType == ELEMENT_TYPE_R4))
+ {
+ INDEBUG(expectedLocation = DL_MaybeInteriorPtrArray);
+
+ pSource = (INT64 *)pMaybeInteriorPtrArg;
+ }
+ else
+#endif
+ if (IsElementTypeSpecial(pFEAD->argElementType))
+ {
+ INDEBUG(expectedLocation = DL_ObjectRefArray);
+
+ pSource = (INT64 *)pObjectRefArg;
+ }
+ else
+ {
+ INDEBUG(expectedLocation = DL_BufferForArgsArray);
+
+ pSource = pBufferArg;
+ }
+
+ if (pFEAD->argAddr != NULL)
+ {
+ if (!isByRef)
+ {
+ if (pFEAD->argIsHandleValue)
+ {
+ _ASSERTE(dataLocation & DL_BufferForArgsArray);
+
+ OBJECTHANDLE oh = *((OBJECTHANDLE*)(pBufferArg)); // Always comes from buffer
+ *pArgument = PtrToArgSlot(g_pEEInterface->GetObjectFromHandle(oh));
+ }
+ else
+ {
+ _ASSERTE(dataLocation & expectedLocation);
+
+ if (pSource != NULL)
+ {
+ *pArgument = *pSource; // may come from either array.
+ }
+ else
+ {
+ *pArgument = NULL;
+ }
+ }
+ }
+ else
+ {
+ if (pFEAD->argIsHandleValue)
+ {
+ _ASSERTE(dataLocation & DL_BufferForArgsArray);
+
+ *pArgument = *pBufferArg; // Buffer contains the object handle, in this case, so
+ // just copy that across.
+ }
+ else
+ {
+ _ASSERTE(dataLocation & expectedLocation);
+
+ *pArgument = PtrToArgSlot(pSource); // Load the argument with the address of our buffer.
+ }
+ }
+ }
+ else if (pFEAD->argIsLiteral)
+ {
+ _ASSERTE(dataLocation & expectedLocation);
+
+ if (!isByRef)
+ {
+ if (pSource != NULL)
+ {
+ *pArgument = *pSource; // may come from either array.
+ }
+ else
+ {
+ *pArgument = NULL;
+ }
+ }
+ else
+ {
+ *pArgument = PtrToArgSlot(pSource); // Load the argument with the address of our buffer.
+ }
+ }
+ else
+ {
+ if (!isByRef)
+ {
+ if (pSource != NULL)
+ {
+ *pArgument = *pSource; // may come from either array.
+ }
+ else
+ {
+ *pArgument = NULL;
+ }
+ }
+ else
+ {
+ *pArgument = PtrToArgSlot(pSource); // Load the argument with the address of our buffer.
+ }
+ }
+
+ // If we need to unbox, then unbox the arg now.
+ if (fNeedBoxOrUnbox)
+ {
+ if (!isByRef)
+ {
+ // function expects valuetype, argument received is class or object
+
+ // Take the ObjectRef off the stack.
+ ARG_SLOT oi1 = *pArgument;
+ OBJECTREF o1 = ArgSlotToObj(oi1);
+
+ // For Nullable types, we need a 'true' nullable to pass to the function, and we do this
+ // by passing a boxed nullable that we unbox. We allocated this space earlier however we
+ // did not know the data location until just now. Fill it in with the data and use that
+ // to pass to the function.
+
+ if (Nullable::IsNullableType(argTH))
+ {
+ _ASSERTE(*pObjectRefArg != 0);
+ _ASSERTE((*pObjectRefArg)->GetMethodTable() == argTH.GetMethodTable());
+ if (o1 != *pObjectRefArg)
+ {
+ Nullable::UnBoxNoCheck((*pObjectRefArg)->GetData(), o1, (*pObjectRefArg)->GetMethodTable());
+ o1 = *pObjectRefArg;
+ }
+ }
+
+ if (o1 == NULL)
+ {
+ COMPlusThrow(kArgumentException, W("ArgumentNull_Obj"));
+ }
+
+
+ if (!o1->GetMethodTable()->IsValueType())
+ {
+ COMPlusThrow(kArgumentException, W("Argument_BadObjRef"));
+ }
+
+
+ // Unbox the little fella to get a pointer to the raw data.
+ void *pData = o1->GetData();
+
+ // Get its size to make sure it fits in an ARG_SLOT
+ unsigned size = o1->GetMethodTable()->GetNumInstanceFieldBytes();
+
+ if (size <= sizeof(ARG_SLOT))
+ {
+ // Its not ByRef, so we need to copy the value class onto the ARG_SLOT.
+ CopyValueClassUnchecked(ArgSlotEndianessFixup(pArgument, sizeof(LPVOID)), pData, o1->GetMethodTable());
+ }
+ else
+ {
+ // Store pointer to the space in the ARG_SLOT
+ *pArgument = PtrToArgSlot(pData);
+ }
+ }
+ else
+ {
+ // Function expects byref valuetype, argument received is byref class.
+
+ // Grab the ObjectRef off the stack via the pointer on the stack. Note: the stack has a pointer to the
+ // ObjectRef since the arg was specified as byref.
+ OBJECTREF* op1 = (OBJECTREF*)ArgSlotToPtr(*pArgument);
+ if (op1 == NULL)
+ {
+ COMPlusThrow(kArgumentException, W("ArgumentNull_Obj"));
+ }
+ OBJECTREF o1 = *op1;
+
+ // For Nullable types, we need a 'true' nullable to pass to the function, and we do this
+ // by passing a boxed nullable that we unbox. We allocated this space earlier however we
+ // did not know the data location until just now. Fill it in with the data and use that
+ // to pass to the function.
+
+ if (Nullable::IsNullableType(byrefArgTH))
+ {
+ _ASSERTE(*pObjectRefArg != 0 && (*pObjectRefArg)->GetMethodTable() == byrefArgTH.GetMethodTable());
+ if (o1 != *pObjectRefArg)
+ {
+ Nullable::UnBoxNoCheck((*pObjectRefArg)->GetData(), o1, (*pObjectRefArg)->GetMethodTable());
+ o1 = *pObjectRefArg;
+ }
+ }
+
+ if (o1 == NULL)
+ {
+ COMPlusThrow(kArgumentException, W("ArgumentNull_Obj"));
+ }
+
+ _ASSERTE(o1->GetMethodTable()->IsValueType());
+
+ // Unbox the little fella to get a pointer to the raw data.
+ void *pData = o1->GetData();
+
+ // If it is ByRef, then we just replace the ObjectRef with a pointer to the data.
+ *pArgument = PtrToArgSlot(pData);
+ }
+ }
+
+ // Validate any objectrefs that are supposed to be on the stack.
+ // <TODO>@TODO: Move this to before the boxing/unboxing above</TODO>
+ if (!fNeedBoxOrUnbox)
+ {
+ Object *objPtr;
+ if (!isByRef)
+ {
+ if (IsElementTypeSpecial(argSigType))
+ {
+ // validate the integrity of the object
+ objPtr = (Object*)ArgSlotToPtr(*pArgument);
+ if (FAILED(ValidateObject(objPtr)))
+ {
+ COMPlusThrow(kArgumentException, W("Argument_BadObjRef"));
+ }
+ }
+ }
+ else
+ {
+ _ASSERTE(argSigType == ELEMENT_TYPE_BYREF);
+ if (IsElementTypeSpecial(byrefArgSigType))
+ {
+ objPtr = *(Object**)(ArgSlotToPtr(*pArgument));
+ if (FAILED(ValidateObject(objPtr)))
+ {
+ COMPlusThrow(kArgumentException, W("Argument_BadObjRef"));
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+static CorDebugRegister GetArgAddrFromReg( DebuggerIPCE_FuncEvalArgData *pFEAD)
+{
+ CorDebugRegister retval = REGISTER_INSTRUCTION_POINTER; // good as default as any
+#if defined(_WIN64)
+ retval = (pFEAD->argHome.kind == RAK_REG ?
+ pFEAD->argHome.reg1 :
+ (CorDebugRegister)((int)REGISTER_IA64_F0 + pFEAD->argHome.floatIndex));
+#else // !_WIN64
+ retval = pFEAD->argHome.reg1;
+#endif // !_WIN64
+ return retval;
+}
+
+//
+// Given info about a byref argument, retrieve the current value from the pBufferForArgsArray,
+// the pMaybeInteriorPtrArray, the pByRefMaybeInteriorPtrArray, or the pObjectRefArray. Then
+// place it back into the proper register or address.
+//
+// Note that we should never use the argAddr of the DebuggerIPCE_FuncEvalArgData in this function
+// since the address may be an interior GC pointer and may have been moved by the GC. Instead,
+// use the pByRefMaybeInteriorPtrArray.
+//
+static void SetFuncEvalByRefArgValue(DebuggerEval *pDE,
+ DebuggerIPCE_FuncEvalArgData *pFEAD,
+ CorElementType byrefArgSigType,
+ INT64 bufferByRefArg,
+ void *maybeInteriorPtrArg,
+ void *byRefMaybeInteriorPtrArg,
+ OBJECTREF objectRefByRefArg)
+{
+ WRAPPER_NO_CONTRACT;
+
+ switch (pFEAD->argElementType)
+ {
+ case ELEMENT_TYPE_I8:
+ case ELEMENT_TYPE_U8:
+ case ELEMENT_TYPE_R8:
+ // 64bit values
+ {
+ INT64 source;
+
+#if defined(_WIN64)
+ source = (INT64)maybeInteriorPtrArg;
+#else // !_WIN64
+ source = bufferByRefArg;
+#endif // !_WIN64
+
+ if (pFEAD->argIsLiteral)
+ {
+ // If this was a literal arg, then copy the updated primitive back into the literal.
+ memcpy(pFEAD->argLiteralData, &source, sizeof(pFEAD->argLiteralData));
+ }
+ else if (pFEAD->argAddr != NULL)
+ {
+ *((INT64 *)byRefMaybeInteriorPtrArg) = source;
+ return;
+ }
+ else
+ {
+#if !defined(_WIN64)
+ // RAK_REG is the only 4 byte type, all others are 8 byte types.
+ _ASSERTE(pFEAD->argHome.kind != RAK_REG);
+
+ SIZE_T *pLow = (SIZE_T*)(&source);
+ SIZE_T *pHigh = pLow + 1;
+
+ switch (pFEAD->argHome.kind)
+ {
+ case RAK_REGREG:
+ SetRegisterValue(pDE, pFEAD->argHome.u.reg2, pFEAD->argHome.u.reg2Addr, *pLow);
+ SetRegisterValue(pDE, pFEAD->argHome.reg1, pFEAD->argHome.reg1Addr, *pHigh);
+ break;
+
+ case RAK_MEMREG:
+ SetRegisterValue(pDE, pFEAD->argHome.reg1, pFEAD->argHome.reg1Addr, *pLow);
+ *((SIZE_T*)CORDB_ADDRESS_TO_PTR(pFEAD->argHome.addr)) = *pHigh;
+ break;
+
+ case RAK_REGMEM:
+ *((SIZE_T*)CORDB_ADDRESS_TO_PTR(pFEAD->argHome.addr)) = *pLow;
+ SetRegisterValue(pDE, pFEAD->argHome.reg1, pFEAD->argHome.reg1Addr, *pHigh);
+ break;
+
+ default:
+ break;
+ }
+#else // _WIN64
+ // The only types we use are RAK_REG and RAK_FLOAT, and both of them can be 4 or 8 bytes.
+ _ASSERTE((pFEAD->argHome.kind == RAK_REG) || (pFEAD->argHome.kind == RAK_FLOAT));
+
+ SetRegisterValue(pDE, pFEAD->argHome.reg1, pFEAD->argHome.reg1Addr, source);
+#endif // _WIN64
+ }
+ }
+ break;
+
+ default:
+ // literal values smaller than 8 bytes and "special types" (e.g. object, array, string, etc.)
+ {
+ SIZE_T source;
+
+#ifdef _TARGET_X86_
+ if ((pFEAD->argElementType == ELEMENT_TYPE_I4) ||
+ (pFEAD->argElementType == ELEMENT_TYPE_U4) ||
+ (pFEAD->argElementType == ELEMENT_TYPE_R4))
+ {
+ source = (SIZE_T)maybeInteriorPtrArg;
+ }
+ else
+ {
+#endif
+ source = (SIZE_T)bufferByRefArg;
+#ifdef _TARGET_X86_
+ }
+#endif
+
+ if (pFEAD->argIsLiteral)
+ {
+ // If this was a literal arg, then copy the updated primitive back into the literal.
+ // The literall buffer is a fixed size (8 bytes), but our source may be 4 or 8 bytes
+ // depending on the platform. To prevent reading past the end of the source, we
+ // zero the destination buffer and copy only as many bytes as available.
+ memset( pFEAD->argLiteralData, 0, sizeof(pFEAD->argLiteralData) );
+ if (IsElementTypeSpecial(pFEAD->argElementType))
+ {
+ _ASSERTE( sizeof(pFEAD->argLiteralData) >= sizeof(objectRefByRefArg) );
+ memcpy(pFEAD->argLiteralData, &objectRefByRefArg, sizeof(objectRefByRefArg));
+ }
+ else
+ {
+ _ASSERTE( sizeof(pFEAD->argLiteralData) >= sizeof(source) );
+ memcpy(pFEAD->argLiteralData, &source, sizeof(source));
+ }
+ }
+ else if (pFEAD->argAddr == NULL)
+ {
+ // If the 32bit value is enregistered, copy it back to the proper regs.
+
+ // RAK_REG is the only valid 4 byte type on WIN32. On WIN64, both RAK_REG and RAK_FLOAT can be
+ // 4 bytes or 8 bytes.
+ _ASSERTE((pFEAD->argHome.kind == RAK_REG)
+ WIN64_ONLY(|| (pFEAD->argHome.kind == RAK_FLOAT)));
+
+ CorDebugRegister regNum = GetArgAddrFromReg(pFEAD);
+
+ // Shove the result back into the proper register.
+ if (IsElementTypeSpecial(pFEAD->argElementType))
+ {
+ SetRegisterValue(pDE, regNum, pFEAD->argHome.reg1Addr, (SIZE_T)ObjToArgSlot(objectRefByRefArg));
+ }
+ else
+ {
+ SetRegisterValue(pDE, regNum, pFEAD->argHome.reg1Addr, (SIZE_T)source);
+ }
+ }
+ else
+ {
+ // If the result was an object by ref, then copy back the new location of the object (in GC case).
+ if (pFEAD->argIsHandleValue)
+ {
+ // do nothing. The Handle was passed in the pArgument array directly
+ }
+ else if (IsElementTypeSpecial(pFEAD->argElementType))
+ {
+ *((SIZE_T*)byRefMaybeInteriorPtrArg) = (SIZE_T)ObjToArgSlot(objectRefByRefArg);
+ }
+ else if (pFEAD->argElementType == ELEMENT_TYPE_VALUETYPE)
+ {
+ // Do nothing, we passed in the pointer to the valuetype in the pArgument array directly.
+ }
+ else
+ {
+ GetAndSetLiteralValue(byRefMaybeInteriorPtrArg, pFEAD->argElementType, &source, ELEMENT_TYPE_PTR);
+ }
+ }
+ } // end default
+ } // end switch
+}
+
+
+/*
+ * GCProtectAllPassedArgs
+ *
+ * This routine is the first step in doing a func-eval. For a complete overview, see
+ * the comments at the top of this file.
+ *
+ * This routine over-aggressively protects all arguments that may be references to
+ * managed objects. This function cannot crawl the function signature, since doing
+ * so may trigger a GC, and thus, we must assume everything is ByRef.
+ *
+ * Parameters:
+ * pDE - pointer to the DebuggerEval object being processed.
+ * pObjectRefArray - An array that contains any object refs. It was built previously.
+ * pMaybeInteriorPtrArray - An array that contains values that may be pointers to
+ * the interior of a managed object.
+ * pBufferForArgsArray - An array for holding stuff that does not need to be protected.
+ * Any handle for the 'this' pointer is put in here for pulling it out later.
+ *
+ * Returns:
+ * None.
+ *
+ */
+static void GCProtectAllPassedArgs(DebuggerEval *pDE,
+ OBJECTREF *pObjectRefArray,
+ void **pMaybeInteriorPtrArray,
+ void **pByRefMaybeInteriorPtrArray,
+ INT64 *pBufferForArgsArray
+ DEBUG_ARG(DataLocation pDataLocationArray[])
+ )
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_COOPERATIVE;
+ }
+ CONTRACTL_END;
+
+
+ DebuggerIPCE_FuncEvalArgData *argData = pDE->GetArgData();
+
+ unsigned currArgIndex = 0;
+
+ //
+ // Gather all the information for the parameters.
+ //
+ for ( ; currArgIndex < pDE->m_argCount; currArgIndex++)
+ {
+ DebuggerIPCE_FuncEvalArgData *pFEAD = &argData[currArgIndex];
+
+ // In case any of the arguments is a by ref argument and points into the GC heap,
+ // we need to GC protect their addresses as well.
+ if (pFEAD->argAddr != NULL)
+ {
+ pByRefMaybeInteriorPtrArray[currArgIndex] = pFEAD->argAddr;
+ }
+
+ switch (pFEAD->argElementType)
+ {
+ case ELEMENT_TYPE_I8:
+ case ELEMENT_TYPE_U8:
+ case ELEMENT_TYPE_R8:
+ // 64bit values
+
+#if defined(_WIN64)
+ //
+ // Only need to worry about protecting if a pointer is a 64 bit quantity.
+ //
+ _ASSERTE(sizeof(void *) == sizeof(INT64));
+
+ if (pFEAD->argAddr != NULL)
+ {
+ pMaybeInteriorPtrArray[currArgIndex] = *((void **)(pFEAD->argAddr));
+#ifdef _DEBUG
+ if (currArgIndex < MAX_DATA_LOCATIONS_TRACKED)
+ {
+ pDataLocationArray[currArgIndex] |= DL_MaybeInteriorPtrArray;
+ }
+#endif
+ }
+ else if (pFEAD->argIsLiteral)
+ {
+ _ASSERTE(sizeof(pFEAD->argLiteralData) >= sizeof(void *));
+
+ //
+ // If this is a byref literal arg, then it maybe an interior ptr.
+ //
+ void *v = NULL;
+ memcpy(&v, pFEAD->argLiteralData, sizeof(v));
+ pMaybeInteriorPtrArray[currArgIndex] = v;
+#ifdef _DEBUG
+ if (currArgIndex < MAX_DATA_LOCATIONS_TRACKED)
+ {
+ pDataLocationArray[currArgIndex] |= DL_MaybeInteriorPtrArray;
+ }
+#endif
+ }
+ else
+ {
+ _ASSERTE((pFEAD->argHome.kind == RAK_REG) || (pFEAD->argHome.kind == RAK_FLOAT));
+
+
+ CorDebugRegister regNum = GetArgAddrFromReg(pFEAD);
+ SIZE_T v = GetRegisterValue(pDE, regNum, pFEAD->argHome.reg1Addr, pFEAD->argHome.reg1Value);
+ pMaybeInteriorPtrArray[currArgIndex] = (void *)(v);
+
+#ifdef _DEBUG
+ if (currArgIndex < MAX_DATA_LOCATIONS_TRACKED)
+ {
+ pDataLocationArray[currArgIndex] |= DL_MaybeInteriorPtrArray;
+ }
+#endif
+ }
+#endif // _WIN64
+ break;
+
+ case ELEMENT_TYPE_VALUETYPE:
+ //
+ // If the value type address could be an interior pointer.
+ //
+ if (pFEAD->argAddr != NULL)
+ {
+ pMaybeInteriorPtrArray[currArgIndex] = ((void **)(pFEAD->argAddr));
+ }
+
+ INDEBUG(pDataLocationArray[currArgIndex] |= DL_MaybeInteriorPtrArray);
+ break;
+
+ case ELEMENT_TYPE_CLASS:
+ case ELEMENT_TYPE_OBJECT:
+ case ELEMENT_TYPE_STRING:
+ case ELEMENT_TYPE_ARRAY:
+ case ELEMENT_TYPE_SZARRAY:
+
+ if (pFEAD->argAddr != NULL)
+ {
+ if (pFEAD->argIsHandleValue)
+ {
+ OBJECTHANDLE oh = (OBJECTHANDLE)(pFEAD->argAddr);
+ pBufferForArgsArray[currArgIndex] = (INT64)(size_t)oh;
+
+ INDEBUG(pDataLocationArray[currArgIndex] |= DL_BufferForArgsArray);
+ }
+ else
+ {
+ pObjectRefArray[currArgIndex] = *((OBJECTREF *)(pFEAD->argAddr));
+
+ INDEBUG(pDataLocationArray[currArgIndex] |= DL_ObjectRefArray);
+ }
+ }
+ else if (pFEAD->argIsLiteral)
+ {
+ _ASSERTE(sizeof(pFEAD->argLiteralData) >= sizeof(OBJECTREF));
+ OBJECTREF v = NULL;
+ memcpy(&v, pFEAD->argLiteralData, sizeof(v));
+ pObjectRefArray[currArgIndex] = v;
+#ifdef _DEBUG
+ if (currArgIndex < MAX_DATA_LOCATIONS_TRACKED)
+ {
+ pDataLocationArray[currArgIndex] |= DL_ObjectRefArray;
+ }
+#endif
+ }
+ else
+ {
+ // RAK_REG is the only valid pointer-sized type.
+ _ASSERTE(pFEAD->argHome.kind == RAK_REG);
+
+ // Simply grab the value out of the proper register.
+ SIZE_T v = GetRegisterValue(pDE, pFEAD->argHome.reg1, pFEAD->argHome.reg1Addr, pFEAD->argHome.reg1Value);
+
+ // The argument is the address.
+ pObjectRefArray[currArgIndex] = (OBJECTREF)v;
+#ifdef _DEBUG
+ if (currArgIndex < MAX_DATA_LOCATIONS_TRACKED)
+ {
+ pDataLocationArray[currArgIndex] |= DL_ObjectRefArray;
+ }
+#endif
+ }
+ break;
+
+ case ELEMENT_TYPE_I4:
+ case ELEMENT_TYPE_U4:
+ case ELEMENT_TYPE_R4:
+ // 32bit values
+
+#ifdef _TARGET_X86_
+ _ASSERTE(sizeof(void *) == sizeof(INT32));
+
+ if (pFEAD->argAddr != NULL)
+ {
+ if (pFEAD->argIsHandleValue)
+ {
+ //
+ // Ignorable - no need to protect
+ //
+ }
+ else
+ {
+ pMaybeInteriorPtrArray[currArgIndex] = *((void **)(pFEAD->argAddr));
+#ifdef _DEBUG
+ if (currArgIndex < MAX_DATA_LOCATIONS_TRACKED)
+ {
+ pDataLocationArray[currArgIndex] |= DL_MaybeInteriorPtrArray;
+ }
+#endif
+ }
+ }
+ else if (pFEAD->argIsLiteral)
+ {
+ _ASSERTE(sizeof(pFEAD->argLiteralData) >= sizeof(INT32));
+
+ //
+ // If this is a byref literal arg, then it maybe an interior ptr.
+ //
+ void *v = NULL;
+ memcpy(&v, pFEAD->argLiteralData, sizeof(v));
+ pMaybeInteriorPtrArray[currArgIndex] = v;
+#ifdef _DEBUG
+ if (currArgIndex < MAX_DATA_LOCATIONS_TRACKED)
+ {
+ pDataLocationArray[currArgIndex] |= DL_MaybeInteriorPtrArray;
+ }
+#endif
+ }
+ else
+ {
+ // RAK_REG is the only valid 4 byte type on WIN32.
+ _ASSERTE(pFEAD->argHome.kind == RAK_REG);
+
+ // Simply grab the value out of the proper register.
+ SIZE_T v = GetRegisterValue(pDE, pFEAD->argHome.reg1, pFEAD->argHome.reg1Addr, pFEAD->argHome.reg1Value);
+
+ // The argument is the address.
+ pMaybeInteriorPtrArray[currArgIndex] = (void *)v;
+#ifdef _DEBUG
+ if (currArgIndex < MAX_DATA_LOCATIONS_TRACKED)
+ {
+ pDataLocationArray[currArgIndex] |= DL_MaybeInteriorPtrArray;
+ }
+#endif
+ }
+#endif // _TARGET_X86_
+
+ default:
+ //
+ // Ignorable - no need to protect
+ //
+ break;
+ }
+ }
+}
+
+/*
+ * ResolveFuncEvalGenericArgInfo
+ *
+ * This function pulls out any generic args and makes sure the method is loaded for it.
+ *
+ * Parameters:
+ * pDE - pointer to the DebuggerEval object being processed.
+ *
+ * Returns:
+ * None.
+ *
+ */
+void ResolveFuncEvalGenericArgInfo(DebuggerEval *pDE)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ }
+ CONTRACTL_END;
+
+ DebuggerIPCE_TypeArgData *firstdata = pDE->GetTypeArgData();
+ unsigned int nGenericArgs = pDE->m_genericArgsCount;
+ SIZE_T cbAllocSize;
+ if ((!ClrSafeInt<SIZE_T>::multiply(nGenericArgs, sizeof(TypeHandle *), cbAllocSize)) ||
+ (cbAllocSize != (size_t)(cbAllocSize)))
+ {
+ ThrowHR(COR_E_OVERFLOW);
+ }
+ TypeHandle * pGenericArgs = (nGenericArgs == 0) ? NULL : (TypeHandle *) _alloca(cbAllocSize);
+
+ //
+ // Snag the type arguments from the input and get the
+ // method desc that corresponds to the instantiated desc.
+ //
+ Debugger::TypeDataWalk walk(firstdata, pDE->m_genericArgsNodeCount);
+ walk.ReadTypeHandles(nGenericArgs, pGenericArgs);
+
+ // <TODO>better error message</TODO>
+ if (!walk.Finished())
+ {
+ COMPlusThrow(kArgumentException, W("Argument_InvalidGenericArg"));
+ }
+
+ // Find the proper MethodDesc that we need to call.
+ // Since we're already in the target domain, it can't be unloaded so it's safe to
+ // use domain specific structures like the Module*.
+ _ASSERTE( GetAppDomain() == pDE->m_debuggerModule->GetAppDomain() );
+ pDE->m_md = g_pEEInterface->LoadMethodDef(pDE->m_debuggerModule->GetRuntimeModule(),
+ pDE->m_methodToken,
+ nGenericArgs,
+ pGenericArgs,
+ &(pDE->m_ownerTypeHandle));
+
+
+ // We better have a MethodDesc at this point.
+ _ASSERTE(pDE->m_md != NULL);
+
+ IMDInternalImport *pInternalImport = pDE->m_md->GetMDImport();
+ DWORD dwAttr;
+ if (FAILED(pInternalImport->GetMethodDefProps(pDE->m_methodToken, &dwAttr)))
+ {
+ COMPlusThrow(kArgumentException, W("Argument_InvalidGenericArg"));
+ }
+
+ if (dwAttr & mdRequireSecObject)
+ {
+ // command window cannot evaluate a function with mdRequireSecObject is turned on because
+ // this is expecting to put a security object into caller's frame which we don't have.
+ //
+ COMPlusThrow(kArgumentException,W("Argument_CantCallSecObjFunc"));
+ }
+
+ ValidateFuncEvalReturnType(pDE->m_evalType , pDE->m_md->GetMethodTable());
+
+ // If this is a new object operation, then we should have a .ctor.
+ if ((pDE->m_evalType == DB_IPCE_FET_NEW_OBJECT) && !pDE->m_md->IsCtor())
+ {
+ COMPlusThrow(kArgumentException, W("Argument_MissingDefaultConstructor"));
+ }
+
+ pDE->m_md->EnsureActive();
+
+ // Run the Class Init for this class, if necessary.
+ MethodTable * pOwningMT = pDE->m_ownerTypeHandle.GetMethodTable();
+ pOwningMT->EnsureInstanceActive();
+ pOwningMT->CheckRunClassInitThrowing();
+
+ if (pDE->m_evalType == DB_IPCE_FET_NEW_OBJECT)
+ {
+ // Work out the exact type of the allocated object
+ pDE->m_resultType = (nGenericArgs == 0)
+ ? TypeHandle(pDE->m_md->GetMethodTable())
+ : g_pEEInterface->LoadInstantiation(pDE->m_md->GetModule(), pDE->m_md->GetMethodTable()->GetCl(), nGenericArgs, pGenericArgs);
+ }
+}
+
+
+/*
+ * BoxFuncEvalThisParameter
+ *
+ * This function is a helper for DoNormalFuncEval. It boxes the 'this' parameter if necessary.
+ * For example, when a method Object.ToString is called on a value class like System.DateTime
+ *
+ * Parameters:
+ * pDE - pointer to the DebuggerEval object being processed.
+ * argData - Array of information about the arguments.
+ * pMaybeInteriorPtrArray - An array that contains values that may be pointers to
+ * the interior of a managed object.
+ * pObjectRef - A GC protected place to put a boxed value, if necessary.
+ *
+ * Returns:
+ * None
+ *
+ */
+void BoxFuncEvalThisParameter(DebuggerEval *pDE,
+ DebuggerIPCE_FuncEvalArgData *argData,
+ void **pMaybeInteriorPtrArray,
+ OBJECTREF *pObjectRefArg // out
+ DEBUG_ARG(DataLocation pDataLocationArray[])
+ )
+{
+ WRAPPER_NO_CONTRACT;
+
+ //
+ // See if we have a value type that is going to be passed as a 'this' pointer.
+ //
+ if ((pDE->m_evalType != DB_IPCE_FET_NEW_OBJECT) &&
+ !pDE->m_md->IsStatic() &&
+ (pDE->m_argCount > 0))
+ {
+ // Allocate the space for box nullables. Nullable parameters need a unboxed
+ // nullable value to point at, where our current representation does not have
+ // an unboxed value inside them. Thus we need another buffer to hold it (and
+ // gcprotects it. We used boxed values for this by converting them to 'true'
+ // nullable form, calling the function, and in the case of byrefs, converting
+ // them back afterward.
+
+ MethodTable* pMT = pDE->m_md->GetMethodTable();
+ if (Nullable::IsNullableType(pMT))
+ {
+ OBJECTREF obj = AllocateObject(pMT);
+ if (*pObjectRefArg != NULL)
+ {
+ BOOL typesMatch = Nullable::UnBox(obj->GetData(), *pObjectRefArg, pMT);
+ (void)typesMatch; //prevent "unused variable" error from GCC
+ _ASSERTE(typesMatch);
+ }
+ *pObjectRefArg = obj;
+ }
+
+ if (argData[0].argElementType == ELEMENT_TYPE_VALUETYPE)
+ {
+ //
+ // See if we need to box up the 'this' parameter.
+ //
+ if (!pDE->m_md->GetMethodTable()->IsValueType())
+ {
+ DebuggerIPCE_FuncEvalArgData *pFEAD = &argData[0];
+ SIZE_T v;
+ LPVOID pAddr = NULL;
+ INT64 bigVal;
+
+ {
+ GCX_FORBID(); //pAddr is unprotected from the time we initialize it
+
+ if (pFEAD->argAddr != NULL)
+ {
+ _ASSERTE(pDataLocationArray[0] & DL_MaybeInteriorPtrArray);
+ pAddr = pMaybeInteriorPtrArray[0];
+ INDEBUG(pDataLocationArray[0] &= ~DL_MaybeInteriorPtrArray);
+ }
+ else
+ {
+
+ pAddr = GetRegisterValueAndReturnAddress(pDE, pFEAD, &bigVal, &v);
+
+ if (pAddr == NULL)
+ {
+ COMPlusThrow(kArgumentNullException, W("ArgumentNull_Generic"));
+ }
+ }
+
+ _ASSERTE(pAddr != NULL);
+ } //GCX_FORBID
+
+ GCPROTECT_BEGININTERIOR(pAddr); //ReadTypeHandle may trigger a GC and move the object that has the value type at pAddr as a field
+
+ //
+ // Grab the class of this value type. If the type is a parameterized
+ // struct type then it may not have yet been loaded by the EE (generics
+ // code sharing may have meant we have never bothered to create the exact
+ // type yet).
+ //
+ // A buffer should have been allocated for the full struct type
+ _ASSERTE(argData[0].fullArgType != NULL);
+ Debugger::TypeDataWalk walk((DebuggerIPCE_TypeArgData *) argData[0].fullArgType, argData[0].fullArgTypeNodeCount);
+
+ TypeHandle typeHandle = walk.ReadTypeHandle();
+
+ if (typeHandle.IsNull())
+ {
+ COMPlusThrow(kArgumentException, W("Argument_BadObjRef"));
+ }
+ //
+ // Box up this value type
+ //
+ *pObjectRefArg = typeHandle.GetMethodTable()->Box(pAddr);
+ if (Nullable::IsNullableType(typeHandle.GetMethodTable()) && (*pObjectRefArg == NULL))
+ {
+ COMPlusThrow(kArgumentNullException, W("ArgumentNull_Obj"));
+ }
+ GCPROTECT_END();
+
+ INDEBUG(pDataLocationArray[0] |= DL_ObjectRefArray);
+ }
+ }
+ }
+}
+
+
+//
+// This is used to store (temporarily) information about the arguments that func-eval
+// will pass. It is used only for the args of the function, not the return buffer nor
+// the 'this' pointer, if there is any of either.
+//
+struct FuncEvalArgInfo
+{
+ CorElementType argSigType;
+ CorElementType byrefArgSigType;
+ TypeHandle byrefArgTypeHandle;
+ bool fNeedBoxOrUnbox;
+ TypeHandle sigTypeHandle;
+};
+
+
+
+/*
+ * GatherFuncEvalArgInfo
+ *
+ * This function is a helper for DoNormalFuncEval. It gathers together all the information
+ * necessary to process the arguments.
+ *
+ * Parameters:
+ * pDE - pointer to the DebuggerEval object being processed.
+ * mSig - The metadata signature of the fuction to call.
+ * argData - Array of information about the arguments.
+ * pFEArgInfo - An array of structs to hold the argument information.
+ *
+ * Returns:
+ * None.
+ *
+ */
+void GatherFuncEvalArgInfo(DebuggerEval *pDE,
+ MetaSig mSig,
+ DebuggerIPCE_FuncEvalArgData *argData,
+ FuncEvalArgInfo *pFEArgInfo // out
+ )
+{
+ WRAPPER_NO_CONTRACT;
+
+ unsigned currArgIndex = 0;
+
+ if ((pDE->m_evalType == DB_IPCE_FET_NORMAL) && !pDE->m_md->IsStatic())
+ {
+ //
+ // Skip over the 'this' arg, since this function is not supposed to mess with it.
+ //
+ currArgIndex++;
+ }
+
+ //
+ // Gather all the information for the parameters.
+ //
+ for ( ; currArgIndex < pDE->m_argCount; currArgIndex++)
+ {
+ DebuggerIPCE_FuncEvalArgData *pFEAD = &argData[currArgIndex];
+
+ //
+ // Move to the next arg in the signature.
+ //
+ CorElementType argSigType = mSig.NextArgNormalized();
+ _ASSERTE(argSigType != ELEMENT_TYPE_END);
+
+ //
+ // If this arg is a byref arg, then we'll need to know what type we're referencing for later...
+ //
+ TypeHandle byrefTypeHandle = TypeHandle();
+ CorElementType byrefArgSigType = ELEMENT_TYPE_END;
+ if (argSigType == ELEMENT_TYPE_BYREF)
+ {
+ byrefArgSigType = mSig.GetByRefType(&byrefTypeHandle);
+ }
+
+ //
+ // If the sig says class but we've got a value class parameter, then remember that we need to box it. If
+ // the sig says value class, but we've got a boxed value class, then remember that we need to unbox it.
+ //
+ bool fNeedBoxOrUnbox = ((argSigType == ELEMENT_TYPE_CLASS) && (pFEAD->argElementType == ELEMENT_TYPE_VALUETYPE)) ||
+ (((argSigType == ELEMENT_TYPE_VALUETYPE) && ((pFEAD->argElementType == ELEMENT_TYPE_CLASS) || (pFEAD->argElementType == ELEMENT_TYPE_OBJECT))) ||
+ // This is when method signature is expecting a BYREF ValueType, yet we recieve the boxed valuetype's handle.
+ (pFEAD->argElementType == ELEMENT_TYPE_CLASS && argSigType == ELEMENT_TYPE_BYREF && byrefArgSigType == ELEMENT_TYPE_VALUETYPE));
+
+ pFEArgInfo[currArgIndex].argSigType = argSigType;
+ pFEArgInfo[currArgIndex].byrefArgSigType = byrefArgSigType;
+ pFEArgInfo[currArgIndex].byrefArgTypeHandle = byrefTypeHandle;
+ pFEArgInfo[currArgIndex].fNeedBoxOrUnbox = fNeedBoxOrUnbox;
+ pFEArgInfo[currArgIndex].sigTypeHandle = mSig.GetLastTypeHandleThrowing();
+ }
+}
+
+
+/*
+ * BoxFuncEvalArguments
+ *
+ * This function is a helper for DoNormalFuncEval. It boxes all the arguments that
+ * need to be.
+ *
+ * Parameters:
+ * pDE - pointer to the DebuggerEval object being processed.
+ * argData - Array of information about the arguments.
+ * pFEArgInfo - An array of structs to hold the argument information.
+ * pMaybeInteriorPtrArray - An array that contains values that may be pointers to
+ * the interior of a managed object.
+ * pObjectRef - A GC protected place to put a boxed value, if necessary.
+ *
+ * Returns:
+ * None
+ *
+ */
+void BoxFuncEvalArguments(DebuggerEval *pDE,
+ DebuggerIPCE_FuncEvalArgData *argData,
+ FuncEvalArgInfo *pFEArgInfo,
+ void **pMaybeInteriorPtrArray,
+ OBJECTREF *pObjectRef // out
+ DEBUG_ARG(DataLocation pDataLocationArray[])
+ )
+{
+ WRAPPER_NO_CONTRACT;
+
+ unsigned currArgIndex = 0;
+
+
+ if ((pDE->m_evalType == DB_IPCE_FET_NORMAL) && !pDE->m_md->IsStatic())
+ {
+ //
+ // Skip over the 'this' arg, since this function is not supposed to mess with it.
+ //
+ currArgIndex++;
+ }
+
+ //
+ // Gather all the information for the parameters.
+ //
+ for ( ; currArgIndex < pDE->m_argCount; currArgIndex++)
+ {
+ DebuggerIPCE_FuncEvalArgData *pFEAD = &argData[currArgIndex];
+
+ // Allocate the space for box nullables. Nullable parameters need a unboxed
+ // nullable value to point at, where our current representation does not have
+ // an unboxed value inside them. Thus we need another buffer to hold it (and
+ // gcprotects it. We used boxed values for this by converting them to 'true'
+ // nullable form, calling the function, and in the case of byrefs, converting
+ // them back afterward.
+
+ TypeHandle th = pFEArgInfo[currArgIndex].sigTypeHandle;
+ if (pFEArgInfo[currArgIndex].argSigType == ELEMENT_TYPE_BYREF)
+ th = pFEArgInfo[currArgIndex].byrefArgTypeHandle;
+
+ if (!th.IsNull() && Nullable::IsNullableType(th))
+ {
+
+ OBJECTREF obj = AllocateObject(th.AsMethodTable());
+ if (pObjectRef[currArgIndex] != NULL)
+ {
+ BOOL typesMatch = Nullable::UnBox(obj->GetData(), pObjectRef[currArgIndex], th.AsMethodTable());
+ (void)typesMatch; //prevent "unused variable" error from GCC
+ _ASSERTE(typesMatch);
+ }
+ pObjectRef[currArgIndex] = obj;
+ }
+
+ //
+ // Check if we should box this value now
+ //
+ if ((pFEAD->argElementType == ELEMENT_TYPE_VALUETYPE) &&
+ (pFEArgInfo[currArgIndex].argSigType == ELEMENT_TYPE_BYREF) &&
+ pFEArgInfo[currArgIndex].fNeedBoxOrUnbox)
+ {
+ SIZE_T v;
+ INT64 bigVal;
+ LPVOID pAddr = NULL;
+
+ if (pFEAD->argAddr != NULL)
+ {
+ _ASSERTE(pDataLocationArray[currArgIndex] & DL_MaybeInteriorPtrArray);
+ pAddr = pMaybeInteriorPtrArray[currArgIndex];
+ INDEBUG(pDataLocationArray[currArgIndex] &= ~DL_MaybeInteriorPtrArray);
+ }
+ else
+ {
+
+ pAddr = GetRegisterValueAndReturnAddress(pDE, pFEAD, &bigVal, &v);
+
+ if (pAddr == NULL)
+ {
+ COMPlusThrow(kArgumentNullException, W("ArgumentNull_Generic"));
+ }
+ }
+
+ _ASSERTE(pAddr != NULL);
+
+ MethodTable * pMT = pFEArgInfo[currArgIndex].sigTypeHandle.GetMethodTable();
+
+ //
+ // Stuff the newly boxed item into our GC-protected array.
+ //
+ pObjectRef[currArgIndex] = pMT->Box(pAddr);
+
+#ifdef _DEBUG
+ if (currArgIndex < MAX_DATA_LOCATIONS_TRACKED)
+ {
+ pDataLocationArray[currArgIndex] |= DL_ObjectRefArray;
+ }
+#endif
+ }
+ }
+}
+
+
+/*
+ * GatherFuncEvalMethodInfo
+ *
+ * This function is a helper for DoNormalFuncEval. It gathers together all the information
+ * necessary to process the method
+ *
+ * Parameters:
+ * pDE - pointer to the DebuggerEval object being processed.
+ * mSig - The metadata signature of the fuction to call.
+ * argData - Array of information about the arguments.
+ * ppUnboxedMD - Returns a resolve method desc if the original is an unboxing stub.
+ * pObjectRefArray - GC protected array of objects passed to this func-eval call.
+ * used to resolve down to the method target for generics.
+ * pBufferForArgsArray - Array of values not needing gc-protection. May hold the
+ * handle for the method targer for generics.
+ * pfHasRetBuffArg - TRUE if the function has a return buffer.
+ * pRetValueType - The TypeHandle of the return value.
+ *
+ *
+ * Returns:
+ * None.
+ *
+ */
+void GatherFuncEvalMethodInfo(DebuggerEval *pDE,
+ MetaSig mSig,
+ DebuggerIPCE_FuncEvalArgData *argData,
+ MethodDesc **ppUnboxedMD,
+ OBJECTREF *pObjectRefArray,
+ INT64 *pBufferForArgsArray,
+ BOOL *pfHasRetBuffArg, // out
+ BOOL *pfHasNonStdByValReturn, // out
+ TypeHandle *pRetValueType // out, only if fHasRetBuffArg == true
+ DEBUG_ARG(DataLocation pDataLocationArray[])
+ )
+{
+ WRAPPER_NO_CONTRACT;
+
+ //
+ // If 'this' is a non-static function that points to an unboxing stub, we need to return the
+ // unboxed method desc to really call.
+ //
+ if ((pDE->m_evalType != DB_IPCE_FET_NEW_OBJECT) && !pDE->m_md->IsStatic() && pDE->m_md->IsUnboxingStub())
+ {
+ *ppUnboxedMD = pDE->m_md->GetMethodTable()->GetUnboxedEntryPointMD(pDE->m_md);
+ }
+
+ //
+ // Resolve down to the method on the class of the 'this' parameter.
+ //
+ if ((pDE->m_evalType != DB_IPCE_FET_NEW_OBJECT) && pDE->m_md->IsVtableMethod())
+ {
+ //
+ // Assuming that a constructor can't be an interface method...
+ //
+ _ASSERTE(pDE->m_evalType == DB_IPCE_FET_NORMAL);
+
+ //
+ // We need to go grab the 'this' argument to figure out what class we're headed for...
+ //
+ if (pDE->m_argCount == 0)
+ {
+ COMPlusThrow(kArgumentException, W("Argument_BadObjRef"));
+ }
+
+ //
+ // We should have a valid this pointer.
+ // <TODO>@todo: But the check should cover the register kind as well!</TODO>
+ //
+ if ((argData[0].argHome.kind == RAK_NONE) && (argData[0].argAddr == NULL))
+ {
+ COMPlusThrow(kArgumentNullException, W("ArgumentNull_Generic"));
+ }
+
+ //
+ // Assume we can only have this for real objects or boxed value types, not value classes...
+ //
+ _ASSERTE((argData[0].argElementType == ELEMENT_TYPE_OBJECT) ||
+ (argData[0].argElementType == ELEMENT_TYPE_STRING) ||
+ (argData[0].argElementType == ELEMENT_TYPE_CLASS) ||
+ (argData[0].argElementType == ELEMENT_TYPE_ARRAY) ||
+ (argData[0].argElementType == ELEMENT_TYPE_SZARRAY) ||
+ ((argData[0].argElementType == ELEMENT_TYPE_VALUETYPE) &&
+ (pObjectRefArray[0] != NULL)));
+
+ //
+ // Now get the object pointer to our first arg.
+ //
+ OBJECTREF objRef = NULL;
+ GCPROTECT_BEGIN(objRef);
+
+ if (argData[0].argElementType == ELEMENT_TYPE_VALUETYPE)
+ {
+ //
+ // In this case, we know where it is.
+ //
+ objRef = pObjectRefArray[0];
+ _ASSERTE(pDataLocationArray[0] & DL_ObjectRefArray);
+ }
+ else
+ {
+ TypeHandle dummyTH;
+ ARG_SLOT objSlot;
+
+ //
+ // Take out the first arg. We're gonna trick GetFuncEvalArgValue by passing in just our
+ // object ref as the stack.
+ //
+ // Note that we are passing ELEMENT_TYPE_END in the last parameter because we want to
+ // supress the the valid object ref check.
+ //
+ GetFuncEvalArgValue(pDE,
+ &(argData[0]),
+ false,
+ false,
+ dummyTH,
+ ELEMENT_TYPE_CLASS,
+ dummyTH,
+ &objSlot,
+ NULL,
+ pObjectRefArray,
+ pBufferForArgsArray,
+ NULL,
+ ELEMENT_TYPE_END
+ DEBUG_ARG(pDataLocationArray[0])
+ );
+
+ objRef = ArgSlotToObj(objSlot);
+ }
+
+ //
+ // Validate the object
+ //
+ if (FAILED(ValidateObject(OBJECTREFToObject(objRef))))
+ {
+ COMPlusThrow(kArgumentException, W("Argument_BadObjRef"));
+ }
+
+ //
+ // Null isn't valid in this case!
+ //
+ if (objRef == NULL)
+ {
+ COMPlusThrow(kArgumentNullException, W("ArgumentNull_Obj"));
+ }
+
+ //
+ // Make sure that the object supplied is of a type that can call the method supplied.
+ //
+ if (!g_pEEInterface->ObjIsInstanceOf(OBJECTREFToObject(objRef), pDE->m_ownerTypeHandle))
+ {
+ COMPlusThrow(kArgumentException, W("Argument_CORDBBadMethod"));
+ }
+
+ //
+ // Now, find the proper MethodDesc for this interface method based on the object we're invoking the
+ // method on.
+ //
+ pDE->m_targetCodeAddr = pDE->m_md->GetCallTarget(&objRef, pDE->m_ownerTypeHandle);
+
+ GCPROTECT_END();
+ }
+ else
+ {
+ pDE->m_targetCodeAddr = pDE->m_md->GetCallTarget(NULL, pDE->m_ownerTypeHandle);
+ }
+
+ //
+ // Get the resulting type now. Doing this may trigger a GC or throw.
+ //
+ if (pDE->m_evalType != DB_IPCE_FET_NEW_OBJECT)
+ {
+ pDE->m_resultType = mSig.GetRetTypeHandleThrowing();
+ }
+
+ //
+ // Check if there is an explicit return argument, or if the return type is really a VALUETYPE but our
+ // calling convention is passing it in registers. We just need to remember the pretValueClass so
+ // that we will box it properly on our way out.
+ //
+ {
+ ArgIterator argit(&mSig);
+ *pfHasRetBuffArg = argit.HasRetBuffArg();
+ *pfHasNonStdByValReturn = argit.HasNonStandardByvalReturn();
+ }
+
+ CorElementType retType = mSig.GetReturnType();
+ CorElementType retTypeNormalized = mSig.GetReturnTypeNormalized();
+
+
+ if (*pfHasRetBuffArg || *pfHasNonStdByValReturn
+ || ((retType == ELEMENT_TYPE_VALUETYPE) && (retType != retTypeNormalized)))
+ {
+ *pRetValueType = mSig.GetRetTypeHandleThrowing();
+ }
+ else
+ {
+ //
+ // Make sure the caller initialized this value
+ //
+ _ASSERTE((*pRetValueType).IsNull());
+ }
+}
+
+/*
+ * CopyArgsToBuffer
+ *
+ * This routine copies all the arguments to a local buffer, so that any one that needs to be
+ * passed can be. Note that this local buffer is NOT GC-protected, and so all the values
+ * in the buffer may not be relied on. You *must* use GetFuncEvalArgValue() to load up the
+ * Arguments for the call, because it has the logic to decide which of the parallel arrays to pull
+ * from.
+ *
+ * Parameters:
+ * pDE - pointer to the DebuggerEval object being processed.
+ * argData - Array of information about the arguments.
+ * pFEArgInfo - An array of structs to hold the argument information. Must have be previously filled in.
+ * pBufferArray - An array to store values.
+ *
+ * Returns:
+ * None.
+ *
+ */
+void CopyArgsToBuffer(DebuggerEval *pDE,
+ DebuggerIPCE_FuncEvalArgData *argData,
+ FuncEvalArgInfo *pFEArgInfo,
+ INT64 *pBufferArray
+ DEBUG_ARG(DataLocation pDataLocationArray[])
+ )
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ unsigned currArgIndex = 0;
+
+
+ if ((pDE->m_evalType == DB_IPCE_FET_NORMAL) && !pDE->m_md->IsStatic())
+ {
+ //
+ // Skip over the 'this' arg, since this function is not supposed to mess with it.
+ //
+ currArgIndex++;
+ }
+
+ //
+ // Spin thru each argument now
+ //
+ for ( ; currArgIndex < pDE->m_argCount; currArgIndex++)
+ {
+ DebuggerIPCE_FuncEvalArgData *pFEAD = &argData[currArgIndex];
+ BOOL isByRef = (pFEArgInfo[currArgIndex].argSigType == ELEMENT_TYPE_BYREF);
+ BOOL fNeedBoxOrUnbox;
+ fNeedBoxOrUnbox = pFEArgInfo[currArgIndex].fNeedBoxOrUnbox;
+
+
+ LOG((LF_CORDB, LL_EVERYTHING, "CATB: currArgIndex=%d\n",
+ currArgIndex));
+ LOG((LF_CORDB, LL_EVERYTHING,
+ "\t: argSigType=0x%x, byrefArgSigType=0x%0x, inType=0x%0x\n",
+ pFEArgInfo[currArgIndex].argSigType,
+ pFEArgInfo[currArgIndex].byrefArgSigType,
+ pFEAD->argElementType));
+
+ INT64 *pDest = &(pBufferArray[currArgIndex]);
+
+ switch (pFEAD->argElementType)
+ {
+ case ELEMENT_TYPE_I8:
+ case ELEMENT_TYPE_U8:
+ case ELEMENT_TYPE_R8:
+
+ if (pFEAD->argAddr != NULL)
+ {
+ *pDest = *(INT64*)(pFEAD->argAddr);
+#ifdef _DEBUG
+ if (currArgIndex < MAX_DATA_LOCATIONS_TRACKED)
+ {
+ pDataLocationArray[currArgIndex] |= DL_BufferForArgsArray;
+ }
+#endif
+ }
+ else if (pFEAD->argIsLiteral)
+ {
+ _ASSERTE(sizeof(pFEAD->argLiteralData) >= sizeof(void *));
+
+ // If this is a literal arg, then we just copy the data.
+ memcpy(pDest, pFEAD->argLiteralData, sizeof(INT64));
+#ifdef _DEBUG
+ if (currArgIndex < MAX_DATA_LOCATIONS_TRACKED)
+ {
+ pDataLocationArray[currArgIndex] |= DL_BufferForArgsArray;
+ }
+#endif
+ }
+ else
+ {
+
+#if !defined(_WIN64)
+ // RAK_REG is the only 4 byte type, all others are 8 byte types.
+ _ASSERTE(pFEAD->argHome.kind != RAK_REG);
+
+ INT64 bigVal = 0;
+ SIZE_T v;
+ INT64 *pAddr;
+
+ pAddr = (INT64*)GetRegisterValueAndReturnAddress(pDE, pFEAD, &bigVal, &v);
+
+ if (pAddr == NULL)
+ {
+ COMPlusThrow(kArgumentNullException, W("ArgumentNull_Generic"));
+ }
+
+ *pDest = *pAddr;
+
+#else // _WIN64
+ // Both RAK_REG and RAK_FLOAT can be either 4 bytes or 8 bytes.
+ _ASSERTE((pFEAD->argHome.kind == RAK_REG) || (pFEAD->argHome.kind == RAK_FLOAT));
+
+ CorDebugRegister regNum = GetArgAddrFromReg(pFEAD);
+ *pDest = GetRegisterValue(pDE, regNum, pFEAD->argHome.reg1Addr, pFEAD->argHome.reg1Value);
+#endif // _WIN64
+
+
+
+#ifdef _DEBUG
+ if (currArgIndex < MAX_DATA_LOCATIONS_TRACKED)
+ {
+ pDataLocationArray[currArgIndex] |= DL_BufferForArgsArray;
+ }
+#endif
+ }
+ break;
+
+ case ELEMENT_TYPE_VALUETYPE:
+
+ //
+ // For value types, we dont do anything here, instead delay until GetFuncEvalArgInfo
+ //
+ break;
+
+ case ELEMENT_TYPE_CLASS:
+ case ELEMENT_TYPE_OBJECT:
+ case ELEMENT_TYPE_STRING:
+ case ELEMENT_TYPE_ARRAY:
+ case ELEMENT_TYPE_SZARRAY:
+
+ if (pFEAD->argAddr != NULL)
+ {
+ if (!isByRef)
+ {
+ if (pFEAD->argIsHandleValue)
+ {
+ OBJECTHANDLE oh = (OBJECTHANDLE)(pFEAD->argAddr);
+ *pDest = (INT64)(size_t)oh;
+ }
+ else
+ {
+ *pDest = *((SIZE_T*)(pFEAD->argAddr));
+ }
+#ifdef _DEBUG
+ if (currArgIndex < MAX_DATA_LOCATIONS_TRACKED)
+ {
+ pDataLocationArray[currArgIndex] |= DL_BufferForArgsArray;
+ }
+#endif
+ }
+ else
+ {
+ if (pFEAD->argIsHandleValue)
+ {
+ *pDest = (INT64)(size_t)(pFEAD->argAddr);
+ }
+ else
+ {
+ *pDest = *(SIZE_T*)(pFEAD->argAddr);
+ }
+#ifdef _DEBUG
+ if (currArgIndex < MAX_DATA_LOCATIONS_TRACKED)
+ {
+ pDataLocationArray[currArgIndex] |= DL_BufferForArgsArray;
+ }
+#endif
+ }
+ }
+ else if (pFEAD->argIsLiteral)
+ {
+ _ASSERTE(sizeof(pFEAD->argLiteralData) >= sizeof(INT64));
+
+ // The called function may expect a larger/smaller value than the literal value.
+ // So we convert the value to the right type.
+
+ CONSISTENCY_CHECK_MSGF(((pFEArgInfo[currArgIndex].argSigType == ELEMENT_TYPE_CLASS) ||
+ (pFEArgInfo[currArgIndex].argSigType == ELEMENT_TYPE_SZARRAY) ||
+ (pFEArgInfo[currArgIndex].argSigType == ELEMENT_TYPE_ARRAY)) ||
+ (isByRef && ((pFEArgInfo[currArgIndex].byrefArgSigType == ELEMENT_TYPE_CLASS) ||
+ (pFEArgInfo[currArgIndex].byrefArgSigType == ELEMENT_TYPE_SZARRAY) ||
+ (pFEArgInfo[currArgIndex].byrefArgSigType == ELEMENT_TYPE_ARRAY))),
+ ("argSigType=0x%0x, byrefArgSigType=0x%0x, isByRef=%d",
+ pFEArgInfo[currArgIndex].argSigType,
+ pFEArgInfo[currArgIndex].byrefArgSigType,
+ isByRef));
+
+ LOG((LF_CORDB, LL_EVERYTHING,
+ "argSigType=0x%0x, byrefArgSigType=0x%0x, isByRef=%d\n",
+ pFEArgInfo[currArgIndex].argSigType, pFEArgInfo[currArgIndex].byrefArgSigType, isByRef));
+
+ *(SIZE_T*)pDest = *(SIZE_T*)pFEAD->argLiteralData;
+#ifdef _DEBUG
+ if (currArgIndex < MAX_DATA_LOCATIONS_TRACKED)
+ {
+ pDataLocationArray[currArgIndex] |= DL_BufferForArgsArray;
+ }
+#endif
+ }
+ else
+ {
+ // RAK_REG is the only valid 4 byte type on WIN32. On WIN64, RAK_REG and RAK_FLOAT
+ // can both be either 4 bytes or 8 bytes;
+ _ASSERTE((pFEAD->argHome.kind == RAK_REG)
+ WIN64_ONLY(|| (pFEAD->argHome.kind == RAK_FLOAT)));
+
+ CorDebugRegister regNum = GetArgAddrFromReg(pFEAD);
+
+ // Simply grab the value out of the proper register.
+ SIZE_T v = GetRegisterValue(pDE, regNum, pFEAD->argHome.reg1Addr, pFEAD->argHome.reg1Value);
+ *pDest = v;
+#ifdef _DEBUG
+ if (currArgIndex < MAX_DATA_LOCATIONS_TRACKED)
+ {
+ pDataLocationArray[currArgIndex] |= DL_BufferForArgsArray;
+ }
+#endif
+ }
+ break;
+
+ default:
+ // 4-byte, 2-byte, or 1-byte values
+
+ if (pFEAD->argAddr != NULL)
+ {
+ if (!isByRef)
+ {
+ if (pFEAD->argIsHandleValue)
+ {
+ OBJECTHANDLE oh = (OBJECTHANDLE)(pFEAD->argAddr);
+ *pDest = (INT64)(size_t)oh;
+ }
+ else
+ {
+ GetAndSetLiteralValue(pDest, pFEArgInfo[currArgIndex].argSigType,
+ pFEAD->argAddr, pFEAD->argElementType);
+ }
+#ifdef _DEBUG
+ if (currArgIndex < MAX_DATA_LOCATIONS_TRACKED)
+ {
+ pDataLocationArray[currArgIndex] |= DL_BufferForArgsArray;
+ }
+#endif
+ }
+ else
+ {
+ if (pFEAD->argIsHandleValue)
+ {
+ *pDest = (INT64)(size_t)(pFEAD->argAddr);
+ }
+ else
+ {
+ // We have to make sure we only grab the correct size of memory from the source. On IA64, we
+ // have to make sure we don't cause misaligned data exceptions as well. Then we put the value
+ // into the pBufferArray. The reason is that we may be passing in some values by ref to a
+ // function that's expecting something of a bigger size. Thus, if we don't do this, then we'll
+ // be bashing memory right next to the source value as the function being called acts upon some
+ // bigger value.
+ GetAndSetLiteralValue(pDest, pFEArgInfo[currArgIndex].byrefArgSigType,
+ pFEAD->argAddr, pFEAD->argElementType);
+ }
+#ifdef _DEBUG
+ if (currArgIndex < MAX_DATA_LOCATIONS_TRACKED)
+ {
+ pDataLocationArray[currArgIndex] |= DL_BufferForArgsArray;
+ }
+#endif
+ }
+ }
+ else if (pFEAD->argIsLiteral)
+ {
+ _ASSERTE(sizeof(pFEAD->argLiteralData) >= sizeof(INT32));
+
+ // The called function may expect a larger/smaller value than the literal value,
+ // so we convert the value to the right type.
+
+ CONSISTENCY_CHECK_MSGF(
+ ((pFEArgInfo[currArgIndex].argSigType>=ELEMENT_TYPE_BOOLEAN) && (pFEArgInfo[currArgIndex].argSigType<=ELEMENT_TYPE_R8)) ||
+ (pFEArgInfo[currArgIndex].argSigType == ELEMENT_TYPE_PTR) ||
+ (pFEArgInfo[currArgIndex].argSigType == ELEMENT_TYPE_I) ||
+ (pFEArgInfo[currArgIndex].argSigType == ELEMENT_TYPE_U) ||
+ (isByRef && ((pFEArgInfo[currArgIndex].byrefArgSigType>=ELEMENT_TYPE_BOOLEAN) && (pFEArgInfo[currArgIndex].byrefArgSigType<=ELEMENT_TYPE_R8))),
+ ("argSigType=0x%0x, byrefArgSigType=0x%0x, isByRef=%d", pFEArgInfo[currArgIndex].argSigType, pFEArgInfo[currArgIndex].byrefArgSigType, isByRef));
+
+ LOG((LF_CORDB, LL_EVERYTHING,
+ "argSigType=0x%0x, byrefArgSigType=0x%0x, isByRef=%d\n",
+ pFEArgInfo[currArgIndex].argSigType,
+ pFEArgInfo[currArgIndex].byrefArgSigType,
+ isByRef));
+
+ CorElementType relevantType = (isByRef ? pFEArgInfo[currArgIndex].byrefArgSigType : pFEArgInfo[currArgIndex].argSigType);
+
+ GetAndSetLiteralValue(pDest, relevantType, pFEAD->argLiteralData, pFEAD->argElementType);
+#ifdef _DEBUG
+ if (currArgIndex < MAX_DATA_LOCATIONS_TRACKED)
+ {
+ pDataLocationArray[currArgIndex] |= DL_BufferForArgsArray;
+ }
+#endif
+ }
+ else
+ {
+ // RAK_REG is the only valid 4 byte type on WIN32. On WIN64, RAK_REG and RAK_FLOAT
+ // can both be either 4 bytes or 8 bytes;
+ _ASSERTE((pFEAD->argHome.kind == RAK_REG)
+ WIN64_ONLY(|| (pFEAD->argHome.kind == RAK_FLOAT)));
+
+ CorDebugRegister regNum = GetArgAddrFromReg(pFEAD);
+
+ // Simply grab the value out of the proper register.
+ SIZE_T v = GetRegisterValue(pDE, regNum, pFEAD->argHome.reg1Addr, pFEAD->argHome.reg1Value);
+ *pDest = v;
+#ifdef _DEBUG
+ if (currArgIndex < MAX_DATA_LOCATIONS_TRACKED)
+ {
+ pDataLocationArray[currArgIndex] |= DL_BufferForArgsArray;
+ }
+#endif
+ }
+ }
+ }
+}
+
+
+/*
+ * PackArgumentArray
+ *
+ * This routine fills a given array with the correct values for passing to a managed function.
+ * It uses various component arrays that contain information to correctly create the argument array.
+ *
+ * Parameters:
+ * pDE - pointer to the DebuggerEval object being processed.
+ * argData - Array of information about the arguments.
+ * pUnboxedMD - MethodDesc of the function to call, after unboxing.
+ * RetValueType - Type Handle of the return value of the managed function we will call.
+ * pFEArgInfo - An array of structs to hold the argument information. Must have be previously filled in.
+ * pObjectRefArray - An array that contains any object refs. It was built previously.
+ * pMaybeInteriorPtrArray - An array that contains values that may be pointers to
+ * the interior of a managed object.
+ * pBufferForArgsArray - An array that contains values that need writable memory space
+ * for passing ByRef.
+ * newObj - Pre-allocated object for a 'new' call.
+ * pArguments - This array is packed from the above arrays.
+ * ppRetValue - Return value buffer if fRetValueArg is TRUE
+ *
+ * Returns:
+ * None.
+ *
+ */
+void PackArgumentArray(DebuggerEval *pDE,
+ DebuggerIPCE_FuncEvalArgData *argData,
+ FuncEvalArgInfo *pFEArgInfo,
+ MethodDesc *pUnboxedMD,
+ TypeHandle RetValueType,
+ OBJECTREF *pObjectRefArray,
+ void **pMaybeInteriorPtrArray,
+ INT64 *pBufferForArgsArray,
+ ValueClassInfo ** ppProtectedValueClasses,
+ OBJECTREF newObj,
+ BOOL fRetValueArg,
+ ARG_SLOT *pArguments,
+ PVOID * ppRetValue
+ DEBUG_ARG(DataLocation pDataLocationArray[])
+ )
+{
+ WRAPPER_NO_CONTRACT;
+
+ GCX_FORBID();
+
+ unsigned currArgIndex = 0;
+ unsigned currArgSlot = 0;
+
+
+ //
+ // THIS POINTER (if any)
+ // For non-static methods, or when returning a new object,
+ // the first arg in the array is 'this' or the new object.
+ //
+ if (pDE->m_evalType == DB_IPCE_FET_NEW_OBJECT)
+ {
+ //
+ // If this is a new object op, then we need to fill in the 0'th
+ // arg slot with the 'this' ptr.
+ //
+ pArguments[0] = ObjToArgSlot(newObj);
+
+ //
+ // If we are invoking a function on a value class, but we have a boxed value class for 'this',
+ // then go ahead and unbox it and leave a ref to the value class on the stack as 'this'.
+ //
+ if (pDE->m_md->GetMethodTable()->IsValueType())
+ {
+ _ASSERTE(newObj->GetMethodTable()->IsValueType());
+
+ // This is one of those places we use true boxed nullables
+ _ASSERTE(!Nullable::IsNullableType(pDE->m_md->GetMethodTable()) ||
+ newObj->GetMethodTable() == pDE->m_md->GetMethodTable());
+ void *pData = newObj->GetData();
+ pArguments[0] = PtrToArgSlot(pData);
+ }
+
+ //
+ // Bump up the arg slot
+ //
+ currArgSlot++;
+ }
+ else if (!pDE->m_md->IsStatic())
+ {
+ //
+ // Place 'this' first in the array for non-static methods.
+ //
+ TypeHandle dummyTH;
+ bool isByRef = false;
+ bool fNeedBoxOrUnbox = false;
+
+ // We had better have an object for a 'this' argument!
+ CorElementType et = argData[0].argElementType;
+
+ if (!(IsElementTypeSpecial(et) ||
+ et == ELEMENT_TYPE_VALUETYPE))
+ {
+ COMPlusThrow(kArgumentOutOfRangeException, W("ArgumentOutOfRange_Enum"));
+ }
+
+ LOG((LF_CORDB, LL_EVERYTHING, "this: currArgSlot=%d, currArgIndex=%d et=0x%x\n", currArgSlot, currArgIndex, et));
+
+ if (pDE->m_md->GetMethodTable()->IsValueType())
+ {
+ // For value classes, the 'this' parameter is always passed by reference.
+ // However do not unbox if we are calling an unboxing stub.
+ if (pDE->m_md == pUnboxedMD)
+ {
+ // pDE->m_md is expecting an unboxed this pointer. Then we will unbox it.
+ isByRef = true;
+
+ // Remember if we need to unbox this parameter, though.
+ if ((et == ELEMENT_TYPE_CLASS) || (et == ELEMENT_TYPE_OBJECT))
+ {
+ fNeedBoxOrUnbox = true;
+ }
+ }
+ }
+ else if (et == ELEMENT_TYPE_VALUETYPE)
+ {
+ // When the method that we invoking is defined on non value type and we receive the ValueType as input,
+ // we are calling methods on System.Object. In this case, we need to box the input ValueType.
+ fNeedBoxOrUnbox = true;
+ }
+
+ GetFuncEvalArgValue(pDE,
+ &argData[currArgIndex],
+ isByRef,
+ fNeedBoxOrUnbox,
+ dummyTH,
+ ELEMENT_TYPE_CLASS,
+ pDE->m_md->GetMethodTable(),
+ &(pArguments[currArgSlot]),
+ &(pMaybeInteriorPtrArray[currArgIndex]),
+ &(pObjectRefArray[currArgIndex]),
+ &(pBufferForArgsArray[currArgIndex]),
+ NULL,
+ ELEMENT_TYPE_OBJECT
+ DEBUG_ARG((currArgIndex < MAX_DATA_LOCATIONS_TRACKED) ? pDataLocationArray[currArgIndex]
+ : DL_All)
+ );
+
+ LOG((LF_CORDB, LL_EVERYTHING, "this = 0x%08x\n", ArgSlotToPtr(pArguments[currArgSlot])));
+
+ // We need to check 'this' for a null ref ourselves... NOTE: only do this if we put an object reference on
+ // the stack. If we put a byref for a value type, then we don't need to do this!
+ if (!isByRef)
+ {
+ // The this pointer is not a unboxed value type.
+
+ ARG_SLOT oi1 = pArguments[currArgSlot];
+ OBJECTREF o1 = ArgSlotToObj(oi1);
+
+ if (FAILED(ValidateObject(OBJECTREFToObject(o1))))
+ {
+ COMPlusThrow(kArgumentException, W("Argument_BadObjRef"));
+ }
+
+ if (OBJECTREFToObject(o1) == NULL)
+ {
+ COMPlusThrow(kNullReferenceException, W("NullReference_This"));
+ }
+
+ // For interface method, we have already done the check early on.
+ if (!pDE->m_md->IsInterface())
+ {
+ // We also need to make sure that the method that we are invoking is either defined on this object or the direct/indirect
+ // base objects.
+ Object *objPtr = OBJECTREFToObject(o1);
+ MethodTable *pMT = objPtr->GetMethodTable();
+ // <TODO> Do this check in the following cases as well... </TODO>
+ if (!pMT->IsArray()
+ && !pMT->IsTransparentProxy()
+ && !pDE->m_md->IsSharedByGenericInstantiations())
+ {
+ TypeHandle thFrom = TypeHandle(pMT);
+ TypeHandle thTarget = TypeHandle(pDE->m_md->GetMethodTable());
+ //<TODO> What about MaybeCast?</TODO>
+ if (thFrom.CanCastToNoGC(thTarget) == TypeHandle::CannotCast)
+ {
+ COMPlusThrow(kArgumentException, W("Argument_CORDBBadMethod"));
+ }
+ }
+ }
+ }
+
+ //
+ // Increment up both arrays.
+ //
+ currArgSlot++;
+ currArgIndex++;
+ }
+
+ // Special handling for functions that return value classes.
+ if (fRetValueArg)
+ {
+ LOG((LF_CORDB, LL_EVERYTHING, "retBuff: currArgSlot=%d, currArgIndex=%d\n", currArgSlot, currArgIndex));
+
+ //
+ // Allocate buffer for return value and GC protect it in case it contains object references
+ //
+ unsigned size = RetValueType.GetMethodTable()->GetNumInstanceFieldBytes();
+
+#ifdef FEATURE_HFA
+ // The buffer for HFAs has to be always ENREGISTERED_RETURNTYPE_MAXSIZE
+ size = max(size, ENREGISTERED_RETURNTYPE_MAXSIZE);
+#endif
+
+ BYTE * pTemp = new (interopsafe) BYTE[ALIGN_UP(sizeof(ValueClassInfo), 8) + size];
+
+ ValueClassInfo * pValueClassInfo = (ValueClassInfo *)pTemp;
+ LPVOID pData = pTemp + ALIGN_UP(sizeof(ValueClassInfo), 8);
+
+ memset(pData, 0, size);
+
+ pValueClassInfo->pData = pData;
+ pValueClassInfo->pMT = RetValueType.GetMethodTable();
+
+ pValueClassInfo->pNext = *ppProtectedValueClasses;
+ *ppProtectedValueClasses = pValueClassInfo;
+
+ pArguments[currArgSlot++] = PtrToArgSlot(pData);
+ *ppRetValue = pData;
+ }
+
+ // REAL ARGUMENTS (if any)
+ // Now do the remaining args
+ for ( ; currArgIndex < pDE->m_argCount; currArgSlot++, currArgIndex++)
+ {
+ DebuggerIPCE_FuncEvalArgData *pFEAD = &argData[currArgIndex];
+
+ LOG((LF_CORDB, LL_EVERYTHING, "currArgSlot=%d, currArgIndex=%d\n",
+ currArgSlot,
+ currArgIndex));
+ LOG((LF_CORDB, LL_EVERYTHING,
+ "\t: argSigType=0x%x, byrefArgSigType=0x%0x, inType=0x%0x\n",
+ pFEArgInfo[currArgIndex].argSigType,
+ pFEArgInfo[currArgIndex].byrefArgSigType,
+ pFEAD->argElementType));
+
+
+ GetFuncEvalArgValue(pDE,
+ pFEAD,
+ pFEArgInfo[currArgIndex].argSigType == ELEMENT_TYPE_BYREF,
+ pFEArgInfo[currArgIndex].fNeedBoxOrUnbox,
+ pFEArgInfo[currArgIndex].sigTypeHandle,
+ pFEArgInfo[currArgIndex].byrefArgSigType,
+ pFEArgInfo[currArgIndex].byrefArgTypeHandle,
+ &(pArguments[currArgSlot]),
+ &(pMaybeInteriorPtrArray[currArgIndex]),
+ &(pObjectRefArray[currArgIndex]),
+ &(pBufferForArgsArray[currArgIndex]),
+ ppProtectedValueClasses,
+ pFEArgInfo[currArgIndex].argSigType
+ DEBUG_ARG((currArgIndex < MAX_DATA_LOCATIONS_TRACKED) ? pDataLocationArray[currArgIndex]
+ : DL_All)
+ );
+ }
+}
+
+/*
+ * UnpackFuncEvalResult
+ *
+ * This routine takes the resulting object of a func-eval, and does any copying, boxing, unboxing, necessary.
+ *
+ * Parameters:
+ * pDE - pointer to the DebuggerEval object being processed.
+ * newObj - Pre-allocated object for NEW_OBJ func-evals.
+ * retObject - Pre-allocated object to be filled in with the info in pRetBuff.
+ * RetValueType - The return type of the function called.
+ * pRetBuff - The raw bytes returned by the func-eval call when there is a return buffer parameter.
+ *
+ *
+ * Returns:
+ * None.
+ *
+ */
+void UnpackFuncEvalResult(DebuggerEval *pDE,
+ OBJECTREF newObj,
+ OBJECTREF retObject,
+ TypeHandle RetValueType,
+ void *pRetBuff
+ )
+{
+ CONTRACTL
+ {
+ WRAPPER(THROWS);
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+
+ // Ah, but if this was a new object op, then the result is really
+ // the object we allocated above...
+ if (pDE->m_evalType == DB_IPCE_FET_NEW_OBJECT)
+ {
+ // We purposely do not morph nullables to be boxed Ts here because debugger EE's otherwise
+ // have no way of creating true nullables that they need for their own purposes.
+ pDE->m_result[0] = ObjToArgSlot(newObj);
+ pDE->m_retValueBoxing = Debugger::AllBoxed;
+ }
+ else if (!RetValueType.IsNull())
+ {
+ LOG((LF_CORDB, LL_EVERYTHING, "FuncEval call is saving a boxed VC return value.\n"));
+
+ //
+ // We pre-created it above
+ //
+ _ASSERTE(retObject != NULL);
+
+ // This is one of those places we use true boxed nullables
+ _ASSERTE(!Nullable::IsNullableType(RetValueType)||
+ retObject->GetMethodTable() == RetValueType.GetMethodTable());
+
+ if (pRetBuff != NULL)
+ {
+ // box the object
+ CopyValueClass(retObject->GetData(),
+ pRetBuff,
+ RetValueType.GetMethodTable(),
+ retObject->GetAppDomain());
+ }
+ else
+ {
+ // box the primitive returned, retObject is a true nullable for nullabes, It will be Normalized later
+ CopyValueClass(retObject->GetData(),
+ pDE->m_result,
+ RetValueType.GetMethodTable(),
+ retObject->GetAppDomain());
+ }
+
+ pDE->m_result[0] = ObjToArgSlot(retObject);
+ pDE->m_retValueBoxing = Debugger::AllBoxed;
+ }
+ else
+ {
+ //
+ // Other FuncEvals return primitives as unboxed.
+ //
+ pDE->m_retValueBoxing = Debugger::OnlyPrimitivesUnboxed;
+ }
+
+ LOG((LF_CORDB, LL_INFO10000, "FuncEval call has saved the return value.\n"));
+ // No exception, so it worked as far as we're concerned.
+ pDE->m_successful = true;
+
+ // If the result is an object, then place the object
+ // reference into a strong handle and place the handle into the
+ // pDE to protect the result from a collection.
+ CorElementType retClassET = pDE->m_resultType.GetSignatureCorElementType();
+
+ if ((pDE->m_retValueBoxing == Debugger::AllBoxed) ||
+ !RetValueType.IsNull() ||
+ IsElementTypeSpecial(retClassET))
+ {
+ LOG((LF_CORDB, LL_EVERYTHING, "Creating strong handle for boxed DoNormalFuncEval result.\n"));
+ OBJECTHANDLE oh = pDE->m_thread->GetDomain()->CreateStrongHandle(ArgSlotToObj(pDE->m_result[0]));
+ pDE->m_result[0] = (INT64)(LONG_PTR)oh;
+ pDE->m_vmObjectHandle = VMPTR_OBJECTHANDLE::MakePtr(oh);
+ }
+}
+
+/*
+ * UnpackFuncEvalArguments
+ *
+ * This routine takes the resulting object of a func-eval, and does any copying, boxing, unboxing, necessary.
+ *
+ * Parameters:
+ * pDE - pointer to the DebuggerEval object being processed.
+ * newObj - Pre-allocated object for NEW_OBJ func-evals.
+ * retObject - Pre-allocated object to be filled in with the info in pSource.
+ * RetValueType - The return type of the function called.
+ * pSource - The raw bytes returned by the func-eval call when there is a hidden parameter.
+ *
+ *
+ * Returns:
+ * None.
+ *
+ */
+void UnpackFuncEvalArguments(DebuggerEval *pDE,
+ DebuggerIPCE_FuncEvalArgData *argData,
+ MetaSig mSig,
+ BOOL staticMethod,
+ OBJECTREF *pObjectRefArray,
+ void **pMaybeInteriorPtrArray,
+ void **pByRefMaybeInteriorPtrArray,
+ INT64 *pBufferForArgsArray
+ )
+{
+ WRAPPER_NO_CONTRACT;
+
+ // Update any enregistered byrefs with their new values from the
+ // proper byref temporary array.
+ if (pDE->m_argCount > 0)
+ {
+ mSig.Reset();
+
+ unsigned currArgIndex = 0;
+
+ if ((pDE->m_evalType == DB_IPCE_FET_NORMAL) && !pDE->m_md->IsStatic())
+ {
+ //
+ // Skip over the 'this' arg, since this function is not supposed to mess with it.
+ //
+ currArgIndex++;
+ }
+
+ for (; currArgIndex < pDE->m_argCount; currArgIndex++)
+ {
+ CorElementType argSigType = mSig.NextArgNormalized();
+
+ LOG((LF_CORDB, LL_EVERYTHING, "currArgIndex=%d argSigType=0x%x\n", currArgIndex, argSigType));
+
+ _ASSERTE(argSigType != ELEMENT_TYPE_END);
+
+ if (argSigType == ELEMENT_TYPE_BYREF)
+ {
+ TypeHandle byrefClass = TypeHandle();
+ CorElementType byrefArgSigType = mSig.GetByRefType(&byrefClass);
+
+ // If these are the true boxed nullables we created in BoxFuncEvalArguments, convert them back
+ pObjectRefArray[currArgIndex] = Nullable::NormalizeBox(pObjectRefArray[currArgIndex]);
+
+ LOG((LF_CORDB, LL_EVERYTHING, "DoNormalFuncEval: Updating enregistered byref...\n"));
+ SetFuncEvalByRefArgValue(pDE,
+ &argData[currArgIndex],
+ byrefArgSigType,
+ pBufferForArgsArray[currArgIndex],
+ pMaybeInteriorPtrArray[currArgIndex],
+ pByRefMaybeInteriorPtrArray[currArgIndex],
+ pObjectRefArray[currArgIndex]
+ );
+ }
+ }
+ }
+}
+
+
+/*
+ * FuncEvalWrapper
+ *
+ * Helper function for func-eval. We have to split it out so that we can put a __try / __finally in to
+ * notify on a Catch-Handler found.
+ *
+ * Parameters:
+ * pDE - pointer to the DebuggerEval object being processed.
+ * pArguments - created stack to pass for the call.
+ * pCatcherStackAddr - stack address to report as the Catch Handler Found location.
+ *
+ * Returns:
+ * None.
+ *
+ */
+void FuncEvalWrapper(MethodDescCallSite* pMDCS, DebuggerEval *pDE, const ARG_SLOT *pArguments, BYTE *pCatcherStackAddr)
+{
+ struct Param : NotifyOfCHFFilterWrapperParam
+ {
+ MethodDescCallSite* pMDCS;
+ DebuggerEval *pDE;
+ const ARG_SLOT *pArguments;
+ };
+
+ Param param;
+ param.pFrame = pCatcherStackAddr; // Inherited from NotifyOfCHFFilterWrapperParam
+ param.pMDCS = pMDCS;
+ param.pDE = pDE;
+ param.pArguments = pArguments;
+
+ PAL_TRY(Param *, pParam, &param)
+ {
+ pParam->pMDCS->CallWithValueTypes_RetArgSlot(pParam->pArguments, pParam->pDE->m_result, sizeof(pParam->pDE->m_result));
+ }
+ PAL_EXCEPT_FILTER(NotifyOfCHFFilterWrapper)
+ {
+ // Should never reach here b/c handler should always continue search.
+ _ASSERTE(false);
+ }
+ PAL_ENDTRY
+}
+
+/*
+ * RecordFuncEvalException
+ *
+ * Helper function records the details of an exception that occurred during a FuncEval
+ * Note that this should be called from within the target domain of the FuncEval.
+ *
+ * Parameters:
+ * pDE - pointer to the DebuggerEval object being processed
+ * ppException - the Exception object that was thrown
+ *
+ * Returns:
+ * None.
+ */
+static void RecordFuncEvalException(DebuggerEval *pDE,
+ OBJECTREF ppException )
+{
+ CONTRACTL
+ {
+ THROWS; // CreateStrongHandle could throw OOM
+ GC_NOTRIGGER;
+ MODE_COOPERATIVE;
+ }
+ CONTRACTL_END;
+
+ // We got an exception. Make the exception into our result.
+ pDE->m_successful = false;
+ LOG((LF_CORDB, LL_EVERYTHING, "D::FEHW - Exception during funceval.\n"));
+
+ //
+ // Special handling for thread abort exceptions. We need to explicitly reset the
+ // abort request on the EE thread, then make sure to place this thread on a thunk
+ // that will re-raise the exception when we continue the process. Note: we still
+ // pass this thread abort exception up as the result of the eval.
+ //
+ if (IsExceptionOfType(kThreadAbortException, &ppException))
+ {
+ if (pDE->m_aborting != DebuggerEval::FE_ABORT_NONE)
+ {
+ //
+ // Reset the abort request.
+ //
+ pDE->m_thread->UserResetAbort(Thread::TAR_FuncEval);
+
+ //
+ // This is the abort we sent down.
+ //
+ memset(pDE->m_result, 0, sizeof(pDE->m_result));
+ pDE->m_resultType = TypeHandle();
+ pDE->m_aborted = true;
+ pDE->m_retValueBoxing = Debugger::NoValueTypeBoxing;
+
+ LOG((LF_CORDB, LL_EVERYTHING, "D::FEHW - funceval abort exception.\n"));
+ }
+ else
+ {
+ //
+ // This must have come from somewhere else, remember that we need to
+ // rethrow this.
+ //
+ pDE->m_rethrowAbortException = true;
+
+ //
+ // The result is the exception object.
+ //
+ pDE->m_result[0] = ObjToArgSlot(ppException);
+
+ pDE->m_resultType = ppException->GetTypeHandle();
+ OBJECTHANDLE oh = pDE->m_thread->GetDomain()->CreateStrongHandle(ArgSlotToObj(pDE->m_result[0]));
+ pDE->m_result[0] = (ARG_SLOT)PTR_TO_CORDB_ADDRESS(oh);
+ pDE->m_vmObjectHandle = VMPTR_OBJECTHANDLE::MakePtr(oh);
+ pDE->m_retValueBoxing = Debugger::NoValueTypeBoxing;
+
+ LOG((LF_CORDB, LL_EVERYTHING, "D::FEHW - Non-FE abort thread abort..\n"));
+ }
+ }
+ else
+ {
+ //
+ // The result is the exception object.
+ //
+ pDE->m_result[0] = ObjToArgSlot(ppException);
+
+ pDE->m_resultType = ppException->GetTypeHandle();
+ OBJECTHANDLE oh = pDE->m_thread->GetDomain()->CreateStrongHandle(ArgSlotToObj(pDE->m_result[0]));
+ pDE->m_result[0] = (ARG_SLOT)(LONG_PTR)oh;
+ pDE->m_vmObjectHandle = VMPTR_OBJECTHANDLE::MakePtr(oh);
+
+ pDE->m_retValueBoxing = Debugger::NoValueTypeBoxing;
+
+ LOG((LF_CORDB, LL_EVERYTHING, "D::FEHW - Exception for the user.\n"));
+ }
+}
+
+
+/*
+ * DoNormalFuncEval
+ *
+ * Does the main body of work (steps 1c onward) for the normal func-eval algorithm detailed at the
+ * top of this file. The args have already been GC protected and we've transitioned into the appropriate
+ * domain (steps 1a & 1b). This has to be a seperate function from GCProtectArgsAndDoNormalFuncEval
+ * because otherwise we can't reliably find the right GCFrames to pop when unwinding the stack due to
+ * an exception on 64-bit platforms (we have some GCFrames outside of the TRY, and some inside,
+ * and they won't necesarily be layed out sequentially on the stack if they are all in the same function).
+ *
+ * Parameters:
+ * pDE - pointer to the DebuggerEval object being processed.
+ * pCatcherStackAddr - stack address to report as the Catch Handler Found location.
+ * pObjectRefArray - An array to hold object ref args. This array is protected from GC's.
+ * pMaybeInteriorPtrArray - An array to hold values that may be pointers into a managed object.
+ * This array is protected from GCs.
+ * pByRefMaybeInteriorPtrArray - An array to hold values that may be pointers into a managed
+ * object. This array is protected from GCs. This array protects the address of the arguments
+ * while the pMaybeInteriorPtrArray protects the value of the arguments. We need to do this
+ * because of by ref arguments.
+ * pBufferForArgsArray - a buffer of temporary scratch space for things that do not need to be
+ * protected, or are protected for free (e.g. Handles).
+ * pDataLocationArray - an array of tracking data for debug sanity checks
+ *
+ * Returns:
+ * None.
+ */
+static void DoNormalFuncEval( DebuggerEval *pDE,
+ BYTE *pCatcherStackAddr,
+ OBJECTREF *pObjectRefArray,
+ void **pMaybeInteriorPtrArray,
+ void **pByRefMaybeInteriorPtrArray,
+ INT64 *pBufferForArgsArray,
+ ValueClassInfo ** ppProtectedValueClasses
+ DEBUG_ARG(DataLocation pDataLocationArray[])
+ )
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ }
+ CONTRACTL_END;
+
+ //
+ // Now that all the args are protected, we can go back and deal with generic args and resolving
+ // all their information.
+ //
+ ResolveFuncEvalGenericArgInfo(pDE);
+
+ //
+ // Grab the signature of the method we're working on and do some error checking.
+ // Note that if this instantiated generic code, then this will
+ // correctly give as an instantiated view of the signature that we can iterate without
+ // worrying about generic items in the signature.
+ //
+ MetaSig mSig(pDE->m_md);
+
+ BYTE callingconvention = mSig.GetCallingConvention();
+ if (!isCallConv(callingconvention, IMAGE_CEE_CS_CALLCONV_DEFAULT))
+ {
+ // We don't support calling vararg!
+ COMPlusThrow(kArgumentException, W("Argument_CORDBBadVarArgCallConv"));
+ }
+
+ //
+ // We'll need to know if this is a static method or not.
+ //
+ BOOL staticMethod = pDE->m_md->IsStatic();
+
+ _ASSERTE((pDE->m_evalType == DB_IPCE_FET_NORMAL) || !staticMethod);
+
+ //
+ // Do Step 1c - Pre-allocate space for new objects.
+ //
+ OBJECTREF newObj = NULL;
+ GCPROTECT_BEGIN(newObj);
+
+ SIZE_T allocArgCnt = 0;
+
+ if (pDE->m_evalType == DB_IPCE_FET_NEW_OBJECT)
+ {
+ ValidateFuncEvalReturnType(DB_IPCE_FET_NEW_OBJECT, pDE->m_resultType.GetMethodTable());
+ pDE->m_resultType.GetMethodTable()->EnsureInstanceActive();
+ newObj = AllocateObject(pDE->m_resultType.GetMethodTable());
+
+ //
+ // Note: we account for an extra argument in the count passed
+ // in. We use this to increase the space allocated for args,
+ // and we use it to control the number of args copied into
+ // those arrays below. Note: m_argCount already includes space
+ // for this.
+ //
+ allocArgCnt = pDE->m_argCount + 1;
+ }
+ else
+ {
+ allocArgCnt = pDE->m_argCount;
+ }
+
+ //
+ // Validate the argument count with mSig.
+ //
+ if (allocArgCnt != (mSig.NumFixedArgs() + (staticMethod ? 0 : 1)))
+ {
+ COMPlusThrow(kTargetParameterCountException, W("Arg_ParmCnt"));
+ }
+
+ //
+ // Do Step 1d - Gather information about the method that will be called.
+ //
+ // An array to hold information about the parameters to be passed. This is
+ // all the information we need to gather before entering the GCX_FORBID area.
+ //
+ DebuggerIPCE_FuncEvalArgData *argData = pDE->GetArgData();
+
+ MethodDesc *pUnboxedMD = pDE->m_md;
+ BOOL fHasRetBuffArg;
+ BOOL fHasNonStdByValReturn;
+ TypeHandle RetValueType;
+
+ BoxFuncEvalThisParameter(pDE,
+ argData,
+ pMaybeInteriorPtrArray,
+ pObjectRefArray
+ DEBUG_ARG(pDataLocationArray)
+ );
+
+ GatherFuncEvalMethodInfo(pDE,
+ mSig,
+ argData,
+ &pUnboxedMD,
+ pObjectRefArray,
+ pBufferForArgsArray,
+ &fHasRetBuffArg,
+ &fHasNonStdByValReturn,
+ &RetValueType
+ DEBUG_ARG(pDataLocationArray)
+ );
+
+ //
+ // Do Step 1e - Gather info from runtime about args (may trigger a GC).
+ //
+ SIZE_T cbAllocSize;
+ if (!(ClrSafeInt<SIZE_T>::multiply(pDE->m_argCount, sizeof(FuncEvalArgInfo), cbAllocSize)) ||
+ (cbAllocSize != (size_t)(cbAllocSize)))
+ {
+ ThrowHR(COR_E_OVERFLOW);
+ }
+ FuncEvalArgInfo * pFEArgInfo = (FuncEvalArgInfo *)_alloca(cbAllocSize);
+ memset(pFEArgInfo, 0, cbAllocSize);
+
+ GatherFuncEvalArgInfo(pDE, mSig, argData, pFEArgInfo);
+
+ //
+ // Do Step 1f - Box or unbox arguments one at a time, placing newly boxed items into
+ // pObjectRefArray immediately after creating them.
+ //
+ BoxFuncEvalArguments(pDE,
+ argData,
+ pFEArgInfo,
+ pMaybeInteriorPtrArray,
+ pObjectRefArray
+ DEBUG_ARG(pDataLocationArray)
+ );
+
+#ifdef _DEBUG
+ if (!RetValueType.IsNull())
+ {
+ _ASSERTE(RetValueType.IsValueType());
+ }
+#endif
+
+ //
+ // Do Step 1g - Pre-allocate any return value object.
+ //
+ OBJECTREF retObject = NULL;
+ GCPROTECT_BEGIN(retObject);
+
+ if ((pDE->m_evalType != DB_IPCE_FET_NEW_OBJECT) && !RetValueType.IsNull())
+ {
+ ValidateFuncEvalReturnType(pDE->m_evalType, RetValueType.GetMethodTable());
+ RetValueType.GetMethodTable()->EnsureInstanceActive();
+ retObject = AllocateObject(RetValueType.GetMethodTable());
+ }
+
+ //
+ // Do Step 1h - Copy into scratch buffer all enregistered arguments, and
+ // ByRef literals.
+ //
+ CopyArgsToBuffer(pDE,
+ argData,
+ pFEArgInfo,
+ pBufferForArgsArray
+ DEBUG_ARG(pDataLocationArray)
+ );
+
+ //
+ // We presume that the function has a return buffer. This assumption gets squeezed out
+ // when we pack the argument array.
+ //
+ allocArgCnt++;
+
+ LOG((LF_CORDB, LL_EVERYTHING,
+ "Func eval for %s::%s: allocArgCnt=%d\n",
+ pDE->m_md->m_pszDebugClassName,
+ pDE->m_md->m_pszDebugMethodName,
+ allocArgCnt));
+
+ MethodDescCallSite funcToEval(pDE->m_md, pDE->m_targetCodeAddr);
+
+ //
+ // Do Step 1i - Create and pack argument array for managed function call.
+ //
+ // Allocate space for argument stack
+ //
+ if ((!ClrSafeInt<SIZE_T>::multiply(allocArgCnt, sizeof(ARG_SLOT), cbAllocSize)) ||
+ (cbAllocSize != (size_t)(cbAllocSize)))
+ {
+ ThrowHR(COR_E_OVERFLOW);
+ }
+ ARG_SLOT * pArguments = (ARG_SLOT *)_alloca(cbAllocSize);
+ memset(pArguments, 0, cbAllocSize);
+
+ LPVOID pRetBuff = NULL;
+
+ PackArgumentArray(pDE,
+ argData,
+ pFEArgInfo,
+ pUnboxedMD,
+ RetValueType,
+ pObjectRefArray,
+ pMaybeInteriorPtrArray,
+ pBufferForArgsArray,
+ ppProtectedValueClasses,
+ newObj,
+#ifdef FEATURE_HFA
+ fHasRetBuffArg || fHasNonStdByValReturn,
+#else
+ fHasRetBuffArg,
+#endif
+ pArguments,
+ &pRetBuff
+ DEBUG_ARG(pDataLocationArray)
+ );
+
+ //
+ //
+ // Do Step 2 - Make the call!
+ //
+ //
+ FuncEvalWrapper(&funcToEval, pDE, pArguments, pCatcherStackAddr);
+ {
+
+ // We have now entered the zone where taking a GC is fatal until we get the
+ // return value all fixed up.
+ //
+ GCX_FORBID();
+
+
+ //
+ //
+ // Do Step 3 - Unpack results and update ByRef arguments.
+ //
+ //
+ //
+ LOG((LF_CORDB, LL_EVERYTHING, "FuncEval call has returned\n"));
+
+
+ // GC still can't happen until we get our return value out half way through the unpack function
+
+ UnpackFuncEvalResult(pDE,
+ newObj,
+ retObject,
+ RetValueType,
+ pRetBuff
+ );
+ }
+
+ UnpackFuncEvalArguments(pDE,
+ argData,
+ mSig,
+ staticMethod,
+ pObjectRefArray,
+ pMaybeInteriorPtrArray,
+ pByRefMaybeInteriorPtrArray,
+ pBufferForArgsArray
+ );
+
+ GCPROTECT_END(); // retObject
+ GCPROTECT_END(); // newObj
+}
+
+/*
+ * GCProtectArgsAndDoNormalFuncEval
+ *
+ * This routine is the primary entrypoint for normal func-evals. It implements the algorithm
+ * described at the top of this file, doing steps 1a and 1b itself, then calling DoNormalFuncEval
+ * to do the rest.
+ *
+ * Parameters:
+ * pDE - pointer to the DebuggerEval object being processed.
+ * pCatcherStackAddr - stack address to report as the Catch Handler Found location.
+ *
+ * Returns:
+ * None.
+ *
+ */
+static void GCProtectArgsAndDoNormalFuncEval(DebuggerEval *pDE,
+ BYTE *pCatcherStackAddr )
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ }
+ CONTRACTL_END;
+
+
+ INDEBUG(DataLocation pDataLocationArray[MAX_DATA_LOCATIONS_TRACKED]);
+
+ //
+ // An array to hold object ref args. This array is protected from GC's.
+ //
+ SIZE_T cbAllocSize;
+ if ((!ClrSafeInt<SIZE_T>::multiply(pDE->m_argCount, sizeof(OBJECTREF), cbAllocSize)) ||
+ (cbAllocSize != (size_t)(cbAllocSize)))
+ {
+ ThrowHR(COR_E_OVERFLOW);
+ }
+ OBJECTREF * pObjectRefArray = (OBJECTREF*)_alloca(cbAllocSize);
+ memset(pObjectRefArray, 0, cbAllocSize);
+ GCPROTECT_ARRAY_BEGIN(*pObjectRefArray, pDE->m_argCount);
+
+ //
+ // An array to hold values that may be pointers into a managed object. This array
+ // is protected from GCs.
+ //
+ if ((!ClrSafeInt<SIZE_T>::multiply(pDE->m_argCount, sizeof(void**), cbAllocSize)) ||
+ (cbAllocSize != (size_t)(cbAllocSize)))
+ {
+ ThrowHR(COR_E_OVERFLOW);
+ }
+ void ** pMaybeInteriorPtrArray = (void **)_alloca(cbAllocSize);
+ memset(pMaybeInteriorPtrArray, 0, cbAllocSize);
+ GCPROTECT_BEGININTERIOR_ARRAY(*pMaybeInteriorPtrArray, (UINT)(cbAllocSize/sizeof(OBJECTREF)));
+
+ //
+ // An array to hold values that may be pointers into a managed object. This array
+ // is protected from GCs. This array protects the address of the arguments while the
+ // pMaybeInteriorPtrArray protects the value of the arguments. We need to do this because
+ // of by ref arguments.
+ //
+ if ((!ClrSafeInt<SIZE_T>::multiply(pDE->m_argCount, sizeof(void**), cbAllocSize)) ||
+ (cbAllocSize != (size_t)(cbAllocSize)))
+ {
+ ThrowHR(COR_E_OVERFLOW);
+ }
+ void ** pByRefMaybeInteriorPtrArray = (void **)_alloca(cbAllocSize);
+ memset(pByRefMaybeInteriorPtrArray, 0, cbAllocSize);
+ GCPROTECT_BEGININTERIOR_ARRAY(*pByRefMaybeInteriorPtrArray, (UINT)(cbAllocSize/sizeof(OBJECTREF)));
+
+ //
+ // A buffer of temporary scratch space for things that do not need to be protected, or
+ // are protected for free (e.g. Handles).
+ //
+ if ((!ClrSafeInt<SIZE_T>::multiply(pDE->m_argCount, sizeof(INT64), cbAllocSize)) ||
+ (cbAllocSize != (size_t)(cbAllocSize)))
+ {
+ ThrowHR(COR_E_OVERFLOW);
+ }
+ INT64 *pBufferForArgsArray = (INT64*)_alloca(cbAllocSize);
+ memset(pBufferForArgsArray, 0, cbAllocSize);
+
+ FrameWithCookie<ProtectValueClassFrame> protectValueClassFrame;
+
+ //
+ // Initialize our tracking array
+ //
+ INDEBUG(memset(pDataLocationArray, 0, sizeof(DataLocation) * (MAX_DATA_LOCATIONS_TRACKED)));
+
+ {
+ GCX_FORBID();
+
+ //
+ // Do step 1a
+ //
+ GCProtectAllPassedArgs(pDE,
+ pObjectRefArray,
+ pMaybeInteriorPtrArray,
+ pByRefMaybeInteriorPtrArray,
+ pBufferForArgsArray
+ DEBUG_ARG(pDataLocationArray)
+ );
+
+ }
+
+ //
+ // Do step 1b: we can switch domains since everything is now protected.
+ // Note that before this point, it's unsafe to rely on pDE->m_module since it may be
+ // invalid due to an AD unload.
+ // All normal func evals should have an AppDomain specified.
+ //
+ _ASSERTE( pDE->m_appDomainId.m_dwId != 0 );
+ ENTER_DOMAIN_ID( pDE->m_appDomainId );
+
+ // Wrap everything in a EX_TRY so we catch any exceptions that could be thrown.
+ // Note that we don't let any thrown exceptions cross the AppDomain boundary because we don't
+ // want them to get marshalled.
+ EX_TRY
+ {
+ DoNormalFuncEval(
+ pDE,
+ pCatcherStackAddr,
+ pObjectRefArray,
+ pMaybeInteriorPtrArray,
+ pByRefMaybeInteriorPtrArray,
+ pBufferForArgsArray,
+ protectValueClassFrame.GetValueClassInfoList()
+ DEBUG_ARG(pDataLocationArray)
+ );
+ }
+ EX_CATCH
+ {
+ // We got an exception. Make the exception into our result.
+ OBJECTREF ppException = GET_THROWABLE();
+ GCX_FORBID();
+ RecordFuncEvalException( pDE, ppException);
+ }
+ // Note: we need to catch all exceptioins here because they all get reported as the result of
+ // the funceval. If a ThreadAbort occurred other than for a funcEval abort, we'll re-throw it manually.
+ EX_END_CATCH(SwallowAllExceptions);
+
+ // Restore context
+ END_DOMAIN_TRANSITION;
+
+ protectValueClassFrame.Pop();
+
+ CleanUpTemporaryVariables(protectValueClassFrame.GetValueClassInfoList());
+
+ GCPROTECT_END(); // pByRefMaybeInteriorPtrArray
+ GCPROTECT_END(); // pMaybeInteriorPtrArray
+ GCPROTECT_END(); // pObjectRefArray
+ LOG((LF_CORDB, LL_EVERYTHING, "DoNormalFuncEval: returning...\n"));
+}
+
+
+void FuncEvalHijackRealWorker(DebuggerEval *pDE, Thread* pThread, FuncEvalFrame* pFEFrame)
+{
+ BYTE * pCatcherStackAddr = (BYTE*) pFEFrame;
+
+ // Handle normal func evals in DoNormalFuncEval
+ if ((pDE->m_evalType == DB_IPCE_FET_NEW_OBJECT) || (pDE->m_evalType == DB_IPCE_FET_NORMAL))
+ {
+ GCProtectArgsAndDoNormalFuncEval(pDE, pCatcherStackAddr);
+ LOG((LF_CORDB, LL_EVERYTHING, "DoNormalFuncEval has returned.\n"));
+ return;
+ }
+
+ // The method may be in a different AD than the thread.
+ // The RS already verified that all of the arguments are in the same appdomain as the function
+ // (because we can't verify it here).
+ // Note that this is exception safe, so we are guarenteed to be in the correct AppDomain when
+ // we leave this method.
+ // Before this, we can't safely use the DebuggerModule* since the domain may have been unloaded.
+ ENTER_DOMAIN_ID( pDE->m_appDomainId );
+
+ OBJECTREF newObj = NULL;
+ GCPROTECT_BEGIN(newObj);
+
+ // Wrap everything in a EX_TRY so we catch any exceptions that could be thrown.
+ // Note that we don't let any thrown exceptions cross the AppDomain boundary because we don't
+ // want them to get marshalled.
+ EX_TRY
+ {
+ DebuggerIPCE_TypeArgData *firstdata = pDE->GetTypeArgData();
+ DWORD nGenericArgs = pDE->m_genericArgsCount;
+
+ SIZE_T cbAllocSize;
+ if ((!ClrSafeInt<SIZE_T>::multiply(nGenericArgs, sizeof(TypeHandle *), cbAllocSize)) ||
+ (cbAllocSize != (size_t)(cbAllocSize)))
+ {
+ ThrowHR(COR_E_OVERFLOW);
+ }
+ TypeHandle *pGenericArgs = (nGenericArgs == 0) ? NULL : (TypeHandle *) _alloca(cbAllocSize);
+ //
+ // Snag the type arguments from the input and get the
+ // method desc that corresponds to the instantiated desc.
+ //
+ Debugger::TypeDataWalk walk(firstdata, pDE->m_genericArgsNodeCount);
+ walk.ReadTypeHandles(nGenericArgs, pGenericArgs);
+
+ // <TODO>better error message</TODO>
+ if (!walk.Finished())
+ COMPlusThrow(kArgumentException, W("Argument_InvalidGenericArg"));
+
+ switch (pDE->m_evalType)
+ {
+ case DB_IPCE_FET_NEW_OBJECT_NC:
+ {
+
+ // Find the class.
+ TypeHandle thClass = g_pEEInterface->LoadClass(pDE->m_debuggerModule->GetRuntimeModule(),
+ pDE->m_classToken);
+
+ if (thClass.IsNull())
+ COMPlusThrow(kArgumentNullException, W("ArgumentNull_Type"));
+
+ // Apply any type arguments
+ TypeHandle th =
+ (nGenericArgs == 0)
+ ? thClass
+ : g_pEEInterface->LoadInstantiation(pDE->m_debuggerModule->GetRuntimeModule(),
+ pDE->m_classToken, nGenericArgs, pGenericArgs);
+
+ if (th.IsNull() || th.ContainsGenericVariables())
+ COMPlusThrow(kArgumentException, W("Argument_InvalidGenericArg"));
+
+ // Run the Class Init for this type, if necessary.
+ MethodTable * pOwningMT = th.GetMethodTable();
+ pOwningMT->EnsureInstanceActive();
+ pOwningMT->CheckRunClassInitThrowing();
+
+ // Create a new instance of the class
+
+ ValidateFuncEvalReturnType(DB_IPCE_FET_NEW_OBJECT_NC, th.GetMethodTable());
+
+ newObj = AllocateObject(th.GetMethodTable());
+
+ // No exception, so it worked.
+ pDE->m_successful = true;
+
+ // So is the result type.
+ pDE->m_resultType = th;
+
+ //
+ // Box up all returned objects
+ //
+ pDE->m_retValueBoxing = Debugger::AllBoxed;
+
+ // Make a strong handle for the result.
+ OBJECTHANDLE oh = pDE->m_thread->GetDomain()->CreateStrongHandle(newObj);
+ pDE->m_result[0] = (ARG_SLOT)(LONG_PTR)oh;
+ pDE->m_vmObjectHandle = VMPTR_OBJECTHANDLE::MakePtr(oh);
+
+ break;
+ }
+
+ case DB_IPCE_FET_NEW_STRING:
+ {
+ // Create the string. m_argData is not necessarily null terminated...
+ // The numeration parameter represents the string length, not the buffer size, but
+ // we have passed the buffer size across to copy our data properly, so must divide back out.
+ // NewString will return NULL if pass null, but want an empty string in that case, so
+ // just create an EmptyString explicitly.
+ if ((pDE->m_argData == NULL) || (pDE->m_stringSize == 0))
+ {
+ newObj = StringObject::GetEmptyString();
+ }
+ else
+ {
+ newObj = StringObject::NewString(pDE->GetNewStringArgData(), (int)(pDE->m_stringSize/sizeof(WCHAR)));
+ }
+
+ // No exception, so it worked.
+ pDE->m_successful = true;
+
+ // Result type is, of course, a string.
+ pDE->m_resultType = newObj->GetTypeHandle();
+
+ // Place the result in a strong handle to protect it from a collection.
+ OBJECTHANDLE oh = pDE->m_thread->GetDomain()->CreateStrongHandle(newObj);
+ pDE->m_result[0] = (ARG_SLOT)(LONG_PTR)oh;
+ pDE->m_vmObjectHandle = VMPTR_OBJECTHANDLE::MakePtr(oh);
+
+ break;
+ }
+
+ case DB_IPCE_FET_NEW_ARRAY:
+ {
+ // <TODO>@todo: We're only gonna handle SD arrays for right now.</TODO>
+ if (pDE->m_arrayRank > 1)
+ COMPlusThrow(kRankException, W("Rank_MultiDimNotSupported"));
+
+ // Grab the elementType from the arg/data area.
+ _ASSERTE(nGenericArgs == 1);
+ TypeHandle th = pGenericArgs[0];
+
+ CorElementType et = th.GetSignatureCorElementType();
+ // Gotta be a primitive, class, or System.Object.
+ if (((et < ELEMENT_TYPE_BOOLEAN) || (et > ELEMENT_TYPE_R8)) &&
+ !IsElementTypeSpecial(et))
+ {
+ COMPlusThrow(kArgumentOutOfRangeException, W("ArgumentOutOfRange_Enum"));
+ }
+
+ // Grab the dims from the arg/data area. These come after the type arguments.
+ SIZE_T *dims;
+ dims = (SIZE_T*) (firstdata + pDE->m_genericArgsNodeCount);
+
+ if (IsElementTypeSpecial(et))
+ {
+ newObj = AllocateObjectArray((DWORD)dims[0], th);
+ }
+ else
+ {
+ // Create a simple array. Note: we can only do this type of create here due to the checks above.
+ newObj = AllocatePrimitiveArray(et, (DWORD)dims[0]);
+ }
+
+ // No exception, so it worked.
+ pDE->m_successful = true;
+
+ // Result type is, of course, the type of the array.
+ pDE->m_resultType = newObj->GetTypeHandle();
+
+ // Place the result in a strong handle to protect it from a collection.
+ OBJECTHANDLE oh = pDE->m_thread->GetDomain()->CreateStrongHandle(newObj);
+ pDE->m_result[0] = (ARG_SLOT)(LONG_PTR)oh;
+ pDE->m_vmObjectHandle = VMPTR_OBJECTHANDLE::MakePtr(oh);
+
+ break;
+ }
+
+ default:
+ _ASSERTE(!"Invalid eval type!");
+ }
+ }
+ EX_CATCH
+ {
+ // We got an exception. Make the exception into our result.
+ OBJECTREF ppException = GET_THROWABLE();
+ GCX_FORBID();
+ RecordFuncEvalException( pDE, ppException);
+ }
+ // Note: we need to catch all exceptioins here because they all get reported as the result of
+ // the funceval. If a ThreadAbort occurred other than for a funcEval abort, we'll re-throw it manually.
+ EX_END_CATCH(SwallowAllExceptions);
+
+ GCPROTECT_END();
+
+ //
+ // Restore context
+ //
+ END_DOMAIN_TRANSITION;
+
+}
+
+//
+// FuncEvalHijackWorker is the function that managed threads start executing in order to perform a function
+// evaluation. Control is transfered here on the proper thread by hijacking that that's IP to this method in
+// Debugger::FuncEvalSetup. This function can also be called directly by a Runtime thread that is stopped sending a
+// first or second chance exception to the Right Side.
+//
+// The DebuggerEval object may get deleted by the helper thread doing a CleanupFuncEval while this thread is blocked
+// sending the eval complete.
+void * STDCALL FuncEvalHijackWorker(DebuggerEval *pDE)
+{
+ CONTRACTL
+ {
+ MODE_COOPERATIVE;
+ GC_TRIGGERS;
+ THROWS;
+ SO_NOT_MAINLINE;
+
+ PRECONDITION(CheckPointer(pDE));
+ }
+ CONTRACTL_END;
+
+
+
+ Thread *pThread = NULL;
+ CONTEXT *filterContext = NULL;
+
+ {
+ GCX_FORBID();
+
+ LOG((LF_CORDB, LL_INFO100000, "D:FEHW for pDE:%08x evalType:%d\n", pDE, pDE->m_evalType));
+
+ pThread = GetThread();
+
+#ifndef DACCESS_COMPILE
+#ifdef _DEBUG
+ //
+ // Flush all debug tracking information for this thread on object refs as it
+ // only approximates proper tracking and may have stale data, resulting in false
+ // positives. We dont want that as func-eval runs a lot, so flush them now.
+ //
+ g_pEEInterface->ObjectRefFlush(pThread);
+#endif
+#endif
+
+ if (!pDE->m_evalDuringException)
+ {
+ //
+ // From this point forward we use FORBID regions to guard against GCs.
+ // Refer to code:Debugger::FuncEvalSetup to see the increment was done.
+ //
+ g_pDebugger->DecThreadsAtUnsafePlaces();
+ }
+
+ // Preemptive GC is disabled at the start of this method.
+ _ASSERTE(g_pEEInterface->IsPreemptiveGCDisabled());
+
+ DebuggerController::DispatchFuncEvalEnter(pThread);
+
+
+ // If we've got a filter context still installed, then remove it while we do the work...
+ filterContext = g_pEEInterface->GetThreadFilterContext(pDE->m_thread);
+
+ if (filterContext)
+ {
+ _ASSERTE(pDE->m_evalDuringException);
+ g_pEEInterface->SetThreadFilterContext(pDE->m_thread, NULL);
+ }
+
+ }
+
+ //
+ // Special handling for a re-abort eval. We don't setup a EX_TRY or try to lookup a function to call. All we do
+ // is have this thread abort itself.
+ //
+ if (pDE->m_evalType == DB_IPCE_FET_RE_ABORT)
+ {
+ //
+ // Push our FuncEvalFrame. The return address is equal to the IP in the saved context in the DebuggerEval. The
+ // m_Datum becomes the ptr to the DebuggerEval. The frame address also serves as the address of the catch-handler-found.
+ //
+ FrameWithCookie<FuncEvalFrame> FEFrame(pDE, GetIP(&pDE->m_context), false);
+ FEFrame.Push();
+
+ pDE->m_thread->UserAbort(pDE->m_requester, EEPolicy::TA_Safe, INFINITE, Thread::UAC_Normal);
+ _ASSERTE(!"Should not return from UserAbort here!");
+ return NULL;
+ }
+
+ //
+ // We cannot scope the following in a GCX_FORBID(), but we would like to. But we need the frames on the
+ // stack here, so they must never go out of scope.
+ //
+
+ //
+ // Push our FuncEvalFrame. The return address is equal to the IP in the saved context in the DebuggerEval. The
+ // m_Datum becomes the ptr to the DebuggerEval. The frame address also serves as the address of the catch-handler-found.
+ //
+ FrameWithCookie<FuncEvalFrame> FEFrame(pDE, GetIP(&pDE->m_context), true);
+ FEFrame.Push();
+
+ // On ARM the single step flag is per-thread and not per context. We need to make sure that the SS flag is cleared
+ // for the funceval, and that the state is back to what it should be after the funceval completes.
+#ifdef _TARGET_ARM_
+ bool ssEnabled = pDE->m_thread->IsSingleStepEnabled();
+ if (ssEnabled)
+ pDE->m_thread->DisableSingleStep();
+#endif
+
+ FuncEvalHijackRealWorker(pDE, pThread, &FEFrame);
+
+#ifdef _TARGET_ARM_
+ if (ssEnabled)
+ pDE->m_thread->EnableSingleStep();
+#endif
+
+
+
+ LOG((LF_CORDB, LL_EVERYTHING, "FuncEval has finished its primary work.\n"));
+
+ //
+ // The func-eval is now completed, successfully or with failure, aborted or run-to-completion.
+ //
+ pDE->m_completed = true;
+
+ if (pDE->m_thread->IsAbortRequested())
+ {
+ //
+ // Check if an unmanaged thread tried to also abort this thread while we
+ // were doing the func-eval, then that kind we want to rethrow. The check
+ // versus m_aborted is for the case where the FE was aborted, we caught that,
+ // then cleared the FEAbort request, but there is still an outstanding abort
+ // - then it must be a user abort.
+ //
+ if ((pDE->m_aborting == DebuggerEval::FE_ABORT_NONE) || pDE->m_aborted)
+ {
+ pDE->m_rethrowAbortException = true;
+ }
+
+ //
+ // Reset the abort request if a func-eval abort was submitted, but the func-eval completed
+ // before the abort could take place, we want to make sure we do not throw an abort exception
+ // in this case.
+ //
+ if (pDE->m_aborting != DebuggerEval::FE_ABORT_NONE)
+ {
+ pDE->m_thread->UserResetAbort(Thread::TAR_FuncEval);
+ }
+
+ }
+
+ // Codepitching can hijack our frame's return address. That means that we'll need to update PC in our saved context
+ // so that when its restored, its like we've returned to the codepitching hijack. At this point, the old value of
+ // EIP is worthless anyway.
+ if (!pDE->m_evalDuringException)
+ {
+ SetIP(&pDE->m_context, (SIZE_T)FEFrame.GetReturnAddress());
+ }
+
+ //
+ // Disable all steppers and breakpoints created during the func-eval
+ //
+ DebuggerController::DispatchFuncEvalExit(pThread);
+
+ void *dest = NULL;
+
+ if (!pDE->m_evalDuringException)
+ {
+ // Signal to the helper thread that we're done with our func eval. Start by creating a DebuggerFuncEvalComplete
+ // object. Give it an address at which to create the patch, which is a chunk of memory specified by our
+ // DebuggerEval big enough to hold a breakpoint instruction.
+#ifdef _TARGET_ARM_
+ dest = (BYTE*)((DWORD)&(pDE->m_bpInfoSegment->m_breakpointInstruction) | THUMB_CODE);
+#else
+ dest = &(pDE->m_bpInfoSegment->m_breakpointInstruction);
+#endif
+
+ //
+ // The created object below sets up itself as a hijack and will destroy itself when the hijack and work
+ // is done.
+ //
+
+ DebuggerFuncEvalComplete *comp;
+ comp = new (interopsafe) DebuggerFuncEvalComplete(pThread, dest);
+ _ASSERTE(comp != NULL); // would have thrown
+
+ // Pop the FuncEvalFrame now that we're pretty much done. Make sure we
+ // don't pop the frame too early. Because GC can be triggered in our grabbing of
+ // Debugger lock. If we pop the FE frame without setting back thread filter context,
+ // the frames left uncrawlable.
+ //
+ FEFrame.Pop();
+ }
+ else
+ {
+ // We don't have to setup any special hijacks to return from here when we've been processing during an
+ // exception. We just go ahead and send the FuncEvalComplete event over now. Don't forget to enable/disable PGC
+ // around the call...
+ _ASSERTE(g_pEEInterface->IsPreemptiveGCDisabled());
+
+ if (filterContext != NULL)
+ {
+ g_pEEInterface->SetThreadFilterContext(pDE->m_thread, filterContext);
+ }
+
+ // Pop the FuncEvalFrame now that we're pretty much done.
+ FEFrame.Pop();
+
+
+ {
+ //
+ // This also grabs the debugger lock, so we can atomically check if a detach has
+ // happened.
+ //
+ SENDIPCEVENT_BEGIN(g_pDebugger, pDE->m_thread);
+
+ if ((pDE->m_thread->GetDomain() != NULL) && pDE->m_thread->GetDomain()->IsDebuggerAttached())
+ {
+
+ if (CORDebuggerAttached())
+ {
+ g_pDebugger->FuncEvalComplete(pDE->m_thread, pDE);
+
+ g_pDebugger->SyncAllThreads(SENDIPCEVENT_PtrDbgLockHolder);
+ }
+
+ }
+
+ SENDIPCEVENT_END;
+ }
+ }
+
+
+ // pDE may now point to deleted memory if the helper thread did a CleanupFuncEval while we
+ // were blocked waiting for a continue after the func-eval complete.
+
+ // We return the address that we want to resume executing at.
+ return dest;
+
+}
+
+
+#if defined(WIN64EXCEPTIONS) && !defined(FEATURE_PAL)
+
+EXTERN_C EXCEPTION_DISPOSITION
+FuncEvalHijackPersonalityRoutine(IN PEXCEPTION_RECORD pExceptionRecord
+ WIN64_ARG(IN ULONG64 MemoryStackFp)
+ NOT_WIN64_ARG(IN ULONG32 MemoryStackFp),
+ IN OUT PCONTEXT pContextRecord,
+ IN OUT PDISPATCHER_CONTEXT pDispatcherContext
+ )
+{
+ DebuggerEval* pDE = NULL;
+#if defined(_TARGET_AMD64_)
+ pDE = *(DebuggerEval**)(pDispatcherContext->EstablisherFrame);
+#elif defined(_TARGET_ARM_)
+ // on ARM the establisher frame is the SP of the caller of FuncEvalHijack, on other platforms it's FuncEvalHijack's SP.
+ // in FuncEvalHijack we allocate 8 bytes of stack space and then store R0 at the current SP, so if we subtract 8 from
+ // the establisher frame we can get the stack location where R0 was stored.
+ pDE = *(DebuggerEval**)(pDispatcherContext->EstablisherFrame - 8);
+
+#elif defined(_TARGET_ARM64_)
+ // on ARM64 the establisher frame is the SP of the caller of FuncEvalHijack.
+ // in FuncEvalHijack we allocate 32 bytes of stack space and then store R0 at the current SP + 16, so if we subtract 16 from
+ // the establisher frame we can get the stack location where R0 was stored.
+ pDE = *(DebuggerEval**)(pDispatcherContext->EstablisherFrame - 16);
+#else
+ _ASSERTE(!"NYI - FuncEvalHijackPersonalityRoutine()");
+#endif
+
+ FixupDispatcherContext(pDispatcherContext, &(pDE->m_context), pContextRecord);
+
+ // Returning ExceptionCollidedUnwind will cause the OS to take our new context record and
+ // dispatcher context and restart the exception dispatching on this call frame, which is
+ // exactly the behavior we want.
+ return ExceptionCollidedUnwind;
+}
+
+
+#endif // WIN64EXCEPTIONS && !FEATURE_PAL
+
+#endif // ifndef DACCESS_COMPILE
diff --git a/src/debug/ee/functioninfo.cpp b/src/debug/ee/functioninfo.cpp
new file mode 100644
index 0000000000..83c185cfc9
--- /dev/null
+++ b/src/debug/ee/functioninfo.cpp
@@ -0,0 +1,2472 @@
+// 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.
+//*****************************************************************************
+
+//
+// File: DebuggerModule.cpp
+//
+// Stuff for tracking DebuggerModules.
+//
+//*****************************************************************************
+
+#include "stdafx.h"
+#include "../inc/common.h"
+#include "perflog.h"
+#include "eeconfig.h" // This is here even for retail & free builds...
+#include "vars.hpp"
+#include <limits.h>
+#include "ilformatter.h"
+#include "debuginfostore.h"
+#include "../../vm/methoditer.h"
+
+#ifndef DACCESS_COMPILE
+
+bool DbgIsSpecialILOffset(DWORD offset)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ return (offset == (ULONG) ICorDebugInfo::PROLOG ||
+ offset == (ULONG) ICorDebugInfo::EPILOG ||
+ offset == (ULONG) ICorDebugInfo::NO_MAPPING);
+}
+
+// Helper to use w/ the debug stores.
+BYTE* InteropSafeNew(void * , size_t cBytes)
+{
+ BYTE * p = new (interopsafe, nothrow) BYTE[cBytes];
+ return p;
+}
+
+
+//
+// This is only fur internal debugging.
+//
+#ifdef LOGGING
+static void _dumpVarNativeInfo(ICorDebugInfo::NativeVarInfo* vni)
+{
+ WRAPPER_NO_CONTRACT;
+
+ LOG((LF_CORDB, LL_INFO1000000, "Var %02d: 0x%04x-0x%04x vlt=",
+ vni->varNumber,
+ vni->startOffset, vni->endOffset,
+ vni->loc.vlType));
+
+ switch (vni->loc.vlType)
+ {
+ case ICorDebugInfo::VLT_REG:
+ LOG((LF_CORDB, LL_INFO1000000, "REG reg=%d\n", vni->loc.vlReg.vlrReg));
+ break;
+
+ case ICorDebugInfo::VLT_REG_BYREF:
+ LOG((LF_CORDB, LL_INFO1000000, "REG_BYREF reg=%d\n", vni->loc.vlReg.vlrReg));
+ break;
+
+ case ICorDebugInfo::VLT_STK:
+ LOG((LF_CORDB, LL_INFO1000000, "STK reg=%d off=0x%04x (%d)\n",
+ vni->loc.vlStk.vlsBaseReg,
+ vni->loc.vlStk.vlsOffset,
+ vni->loc.vlStk.vlsOffset));
+ break;
+
+ case ICorDebugInfo::VLT_STK_BYREF:
+ LOG((LF_CORDB, LL_INFO1000000, "STK_BYREF reg=%d off=0x%04x (%d)\n",
+ vni->loc.vlStk.vlsBaseReg,
+ vni->loc.vlStk.vlsOffset,
+ vni->loc.vlStk.vlsOffset));
+ break;
+
+ case ICorDebugInfo::VLT_REG_REG:
+ LOG((LF_CORDB, LL_INFO1000000, "REG_REG reg1=%d reg2=%d\n",
+ vni->loc.vlRegReg.vlrrReg1,
+ vni->loc.vlRegReg.vlrrReg2));
+ break;
+
+ case ICorDebugInfo::VLT_REG_STK:
+ LOG((LF_CORDB, LL_INFO1000000, "REG_STK reg=%d basereg=%d off=0x%04x (%d)\n",
+ vni->loc.vlRegStk.vlrsReg,
+ vni->loc.vlRegStk.vlrsStk.vlrssBaseReg,
+ vni->loc.vlRegStk.vlrsStk.vlrssOffset,
+ vni->loc.vlRegStk.vlrsStk.vlrssOffset));
+ break;
+
+ case ICorDebugInfo::VLT_STK_REG:
+ LOG((LF_CORDB, LL_INFO1000000, "STK_REG basereg=%d off=0x%04x (%d) reg=%d\n",
+ vni->loc.vlStkReg.vlsrStk.vlsrsBaseReg,
+ vni->loc.vlStkReg.vlsrStk.vlsrsOffset,
+ vni->loc.vlStkReg.vlsrStk.vlsrsOffset,
+ vni->loc.vlStkReg.vlsrReg));
+ break;
+
+ case ICorDebugInfo::VLT_STK2:
+ LOG((LF_CORDB, LL_INFO1000000, "STK_STK reg=%d off=0x%04x (%d)\n",
+ vni->loc.vlStk2.vls2BaseReg,
+ vni->loc.vlStk2.vls2Offset,
+ vni->loc.vlStk2.vls2Offset));
+ break;
+
+ case ICorDebugInfo::VLT_FPSTK:
+ LOG((LF_CORDB, LL_INFO1000000, "FPSTK reg=%d\n",
+ vni->loc.vlFPstk.vlfReg));
+ break;
+
+ case ICorDebugInfo::VLT_FIXED_VA:
+ LOG((LF_CORDB, LL_INFO1000000, "FIXED_VA offset=%d (%d)\n",
+ vni->loc.vlFixedVarArg.vlfvOffset,
+ vni->loc.vlFixedVarArg.vlfvOffset));
+ break;
+
+
+ default:
+ LOG((LF_CORDB, LL_INFO1000000, "???\n"));
+ break;
+ }
+}
+#endif
+
+#if defined(WIN64EXCEPTIONS)
+void DebuggerJitInfo::InitFuncletAddress()
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ m_funcletCount = (int)g_pEEInterface->GetFuncletStartOffsets((const BYTE*)m_addrOfCode, NULL, 0);
+
+ if (m_funcletCount == 0)
+ {
+ _ASSERTE(m_rgFunclet == NULL);
+ return;
+ }
+
+ m_rgFunclet = (DWORD*)(new (interopsafe, nothrow) DWORD[m_funcletCount]);
+
+ // All bets are off for stepping this method.
+ if (m_rgFunclet == NULL)
+ {
+ m_funcletCount = 0;
+ return;
+ }
+
+ // This will get the offsets relative to the parent method start as if
+ // the funclet was in contiguous memory (i.e. not hot/cold split).
+ g_pEEInterface->GetFuncletStartOffsets((const BYTE*)m_addrOfCode, m_rgFunclet, m_funcletCount);
+}
+
+//
+// DebuggerJitInfo::GetFuncletOffsetByIndex()
+//
+// Given a funclet index, return its starting offset.
+//
+// parameters: index - index of the funclet
+//
+// return value: starting offset of the specified funclet, or -1 if the index is invalid
+//
+DWORD DebuggerJitInfo::GetFuncletOffsetByIndex(int index)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ if (index < 0 || index >= m_funcletCount)
+ {
+ return (-1);
+ }
+
+ return m_rgFunclet[index];
+}
+
+//
+// DebuggerJitInfo::GetFuncletIndex()
+//
+// Given an offset or an absolute address, return the index of the funclet containing it.
+//
+// parameters: offsetOrAddr - an offset or an absolute address in the method
+// mode - whether the first argument is an offset or an absolute address
+//
+// return value: the index of the funclet containing the specified offset or address,
+// or -1 if it's invalid
+//
+int DebuggerJitInfo::GetFuncletIndex(CORDB_ADDRESS offsetOrAddr, GetFuncletIndexMode mode)
+{
+ WRAPPER_NO_CONTRACT;
+
+ DWORD offset = 0;
+ if (mode == GFIM_BYOFFSET)
+ {
+ offset = (DWORD)offsetOrAddr;
+ }
+
+ // If the address doesn't fall in any of the funclets (or if the
+ // method doesn't have any funclet at all), then return PARENT_METHOD_INDEX.
+ // <TODO>
+ // What if there's an overflow?
+ // </TODO>
+ if (!m_codeRegionInfo.IsMethodAddress((const BYTE *)(mode == GFIM_BYOFFSET ? (size_t)m_codeRegionInfo.OffsetToAddress(offset) : offsetOrAddr)))
+ {
+ return PARENT_METHOD_INDEX;
+ }
+
+ if ( ( m_funcletCount == 0 ) ||
+ ( (mode == GFIM_BYOFFSET) && (offset < m_rgFunclet[0]) ) ||
+ ( (mode == GFIM_BYADDRESS) && (offsetOrAddr < (size_t)m_codeRegionInfo.OffsetToAddress(m_rgFunclet[0])) ) )
+ {
+ return PARENT_METHOD_INDEX;
+ }
+
+ for (int i = 0; i < m_funcletCount; i++)
+ {
+ if (i == (m_funcletCount - 1))
+ {
+ return i;
+ }
+ else if ( ( (mode == GFIM_BYOFFSET) && (offset < m_rgFunclet[i+1]) ) ||
+ ( (mode == GFIM_BYADDRESS) && (offsetOrAddr < (size_t)m_codeRegionInfo.OffsetToAddress(m_rgFunclet[i+1])) ) )
+ {
+ return i;
+ }
+ }
+
+ UNREACHABLE();
+}
+
+#endif // WIN64EXCEPTIONS
+
+// It is entirely possible that we have multiple sequence points for the
+// same IL offset (because of funclets, optimization, etc.). Just to be
+// uniform in all cases, let's return the sequence point with the smallest
+// native offset if fWantFirst is TRUE.
+#if defined(WIN64EXCEPTIONS)
+#define ADJUST_MAP_ENTRY(_map, _wantFirst) \
+ if ((_wantFirst)) \
+ for ( ; (_map) > m_sequenceMap && (((_map)-1)->ilOffset == (_map)->ilOffset); (_map)--); \
+ else \
+ for ( ; (_map) < m_sequenceMap + (m_sequenceMapCount-1) && (((_map)+1)->ilOffset == (_map)->ilOffset); (_map)++);
+#else
+#define ADJUST_MAP_ENTRY(_map, _wantFirst)
+#endif // _WIN64
+
+DebuggerJitInfo::DebuggerJitInfo(DebuggerMethodInfo *minfo, MethodDesc *fd) :
+ m_fd(fd),
+ m_pLoaderModule(fd->GetLoaderModule()),
+ m_jitComplete(false),
+#ifdef EnC_SUPPORTED
+ m_encBreakpointsApplied(false),
+#endif //EnC_SUPPORTED
+ m_methodInfo(minfo),
+ m_addrOfCode(NULL),
+ m_sizeOfCode(0), m_prevJitInfo(NULL), m_nextJitInfo(NULL),
+ m_lastIL(0),
+ m_sequenceMap(NULL),
+ m_sequenceMapCount(0),
+ m_callsiteMap(NULL),
+ m_callsiteMapCount(0),
+ m_sequenceMapSorted(false),
+ m_varNativeInfo(NULL), m_varNativeInfoCount(0),
+ m_fAttemptInit(false)
+#if defined(WIN64EXCEPTIONS)
+ ,m_rgFunclet(NULL)
+ , m_funcletCount(0)
+#endif // defined(WIN64EXCEPTIONS)
+{
+ WRAPPER_NO_CONTRACT;
+
+ // A DJI is just the debugger's cache of interesting information +
+ // various debugger-specific state for a method (like Enc).
+ // So only be createing DJIs when a debugger is actually attached.
+ // The profiler also piggy-backs on the DJIs.
+ // @Todo - the managed stackwalker in the BCL also builds on DJIs.
+ //_ASSERTE(CORDebuggerAttached() || CORProfilerPresent());
+
+ _ASSERTE(minfo);
+ m_encVersion = minfo->GetCurrentEnCVersion();
+ _ASSERTE(m_encVersion >= CorDB_DEFAULT_ENC_FUNCTION_VERSION);
+ LOG((LF_CORDB,LL_EVERYTHING, "DJI::DJI : created at 0x%x\n", this));
+
+ // Debugger doesn't track LightWeight codegen methods.
+ // We should never even be creating a DJI for one.
+ _ASSERTE(!m_fd->IsDynamicMethod());
+}
+
+DebuggerILToNativeMap *DebuggerJitInfo::MapILOffsetToMapEntry(SIZE_T offset, BOOL *exact, BOOL fWantFirst)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ CAN_TAKE_LOCK; // GetSequenceMapCount calls LazyInitBounds() which can eventually
+ // call ExecutionManager::IncrementReader
+ }
+ CONTRACTL_END;
+
+ // Ideally we should be able to assert this, since the binary search in this function
+ // assumes that the sequence points are sorted by IL offset (NO_MAPPING, PROLOG, and EPILOG
+ // are actually -1, -2, and -3, respectively). However, the sequence points in pdb's use
+ // -1 to mean "end of the method", which is different from our semantics of using 0.
+ // _ASSERTE(offset != NO_MAPPING && offset != PROLOG && offset != EPILOG);
+
+ //
+ // Binary search for matching map element.
+ //
+
+ DebuggerILToNativeMap *mMin = GetSequenceMap();
+ DebuggerILToNativeMap *mMax = mMin + GetSequenceMapCount();
+
+ _ASSERTE(m_sequenceMapSorted);
+ _ASSERTE( mMin < mMax ); //otherwise we have no code
+
+ if (exact)
+ {
+ *exact = FALSE;
+ }
+
+ if (mMin)
+ {
+ while (mMin + 1 < mMax)
+ {
+ _ASSERTE(mMin>=m_sequenceMap);
+ DebuggerILToNativeMap *mMid = mMin + ((mMax - mMin)>>1);
+ _ASSERTE(mMid>=m_sequenceMap);
+
+ if (offset == mMid->ilOffset)
+ {
+ if (exact)
+ {
+ *exact = TRUE;
+ }
+ ADJUST_MAP_ENTRY(mMid, fWantFirst);
+ return mMid;
+ }
+ else if (offset < mMid->ilOffset && mMid->ilOffset != (ULONG) ICorDebugInfo::PROLOG)
+ {
+ mMax = mMid;
+ }
+ else
+ {
+ mMin = mMid;
+ }
+ }
+
+ if (exact && offset == mMin->ilOffset)
+ {
+ *exact = TRUE;
+ }
+ ADJUST_MAP_ENTRY(mMin, fWantFirst);
+ }
+ return mMin;
+}
+
+void DebuggerJitInfo::InitILToNativeOffsetIterator(ILToNativeOffsetIterator &iterator, SIZE_T ilOffset)
+{
+ WRAPPER_NO_CONTRACT;
+
+ iterator.Init(this, ilOffset);
+}
+
+
+DebuggerJitInfo::NativeOffset DebuggerJitInfo::MapILOffsetToNative(DebuggerJitInfo::ILOffset ilOffset)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ NativeOffset resultOffset;
+
+ DebuggerILToNativeMap *map = MapILOffsetToMapEntry(ilOffset.m_ilOffset, &(resultOffset.m_fExact));
+
+#if defined(WIN64EXCEPTIONS)
+ // See if we want the map entry for the parent.
+ if (ilOffset.m_funcletIndex <= PARENT_METHOD_INDEX)
+ {
+#endif // _WIN64
+ PREFIX_ASSUME( map != NULL );
+ LOG((LF_CORDB, LL_INFO10000, "DJI::MILOTN: ilOff 0x%x to nat 0x%x exact:0x%x (Entry IL Off:0x%x)\n",
+ ilOffset.m_ilOffset, map->nativeStartOffset, resultOffset.m_fExact, map->ilOffset));
+
+ resultOffset.m_nativeOffset = map->nativeStartOffset;
+
+#if defined(WIN64EXCEPTIONS)
+ }
+ else
+ {
+ // funcletIndex is guaranteed to be >= 0 at this point.
+ if (ilOffset.m_funcletIndex > (m_funcletCount - 1))
+ {
+ resultOffset.m_fExact = FALSE;
+ resultOffset.m_nativeOffset = ((SIZE_T)-1);
+ }
+ else
+ {
+ // Initialize the funclet range.
+ // ASSUMES that funclets are contiguous which they currently are...
+ DWORD funcletStartOffset = GetFuncletOffsetByIndex(ilOffset.m_funcletIndex);
+ DWORD funcletEndOffset;
+ if (ilOffset.m_funcletIndex < (m_funcletCount - 1))
+ {
+ funcletEndOffset = GetFuncletOffsetByIndex(ilOffset.m_funcletIndex + 1);
+ }
+ else
+ {
+ funcletEndOffset = (DWORD)m_sizeOfCode;
+ }
+
+ SIZE_T ilTargetOffset = map->ilOffset;
+
+ DebuggerILToNativeMap *mapEnd = GetSequenceMap() + GetSequenceMapCount();
+
+ for (; map < mapEnd && map->ilOffset == ilTargetOffset; map++)
+ {
+ if ((map->nativeStartOffset >= funcletStartOffset) &&
+ (map->nativeStartOffset < funcletEndOffset))
+ {
+ // This is the normal case where the start offset falls in
+ // the range of the funclet.
+ resultOffset.m_nativeOffset = map->nativeStartOffset;
+ break;
+ }
+ }
+
+ if (map == mapEnd || map->ilOffset != ilTargetOffset)
+ {
+ resultOffset.m_fExact = FALSE;
+ resultOffset.m_nativeOffset = ((SIZE_T)-1);
+ }
+ }
+ }
+#endif // WIN64EXCEPTIONS
+
+ return resultOffset;
+}
+
+
+DebuggerJitInfo::ILToNativeOffsetIterator::ILToNativeOffsetIterator()
+{
+ LIMITED_METHOD_CONTRACT;
+
+ m_dji = NULL;
+ m_currentILOffset.m_ilOffset = INVALID_IL_OFFSET;
+#ifdef WIN64EXCEPTIONS
+ m_currentILOffset.m_funcletIndex = PARENT_METHOD_INDEX;
+#endif
+}
+
+void DebuggerJitInfo::ILToNativeOffsetIterator::Init(DebuggerJitInfo* dji, SIZE_T ilOffset)
+{
+ WRAPPER_NO_CONTRACT;
+
+ m_dji = dji;
+ m_currentILOffset.m_ilOffset = ilOffset;
+#ifdef WIN64EXCEPTIONS
+ m_currentILOffset.m_funcletIndex = PARENT_METHOD_INDEX;
+#endif
+
+ m_currentNativeOffset = m_dji->MapILOffsetToNative(m_currentILOffset);
+}
+
+bool DebuggerJitInfo::ILToNativeOffsetIterator::IsAtEnd()
+{
+ LIMITED_METHOD_CONTRACT;
+
+ return (m_currentILOffset.m_ilOffset == INVALID_IL_OFFSET);
+}
+
+SIZE_T DebuggerJitInfo::ILToNativeOffsetIterator::Current(BOOL* pfExact)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ if (pfExact != NULL)
+ {
+ *pfExact = m_currentNativeOffset.m_fExact;
+ }
+ return m_currentNativeOffset.m_nativeOffset;
+}
+
+SIZE_T DebuggerJitInfo::ILToNativeOffsetIterator::CurrentAssertOnlyOne(BOOL* pfExact)
+{
+ WRAPPER_NO_CONTRACT;
+
+ SIZE_T nativeOffset = Current(pfExact);
+
+ Next();
+ _ASSERTE(IsAtEnd());
+
+ return nativeOffset;
+}
+
+void DebuggerJitInfo::ILToNativeOffsetIterator::Next()
+{
+#if defined(WIN64EXCEPTIONS)
+ NativeOffset tmpNativeOffset;
+
+ for (m_currentILOffset.m_funcletIndex += 1;
+ m_currentILOffset.m_funcletIndex < m_dji->GetFuncletCount();
+ m_currentILOffset.m_funcletIndex++)
+ {
+ tmpNativeOffset = m_dji->MapILOffsetToNative(m_currentILOffset);
+ if (tmpNativeOffset.m_nativeOffset != ((SIZE_T)-1) &&
+ tmpNativeOffset.m_nativeOffset != m_currentNativeOffset.m_nativeOffset)
+ {
+ m_currentNativeOffset = tmpNativeOffset;
+ break;
+ }
+ }
+
+ if (m_currentILOffset.m_funcletIndex == m_dji->GetFuncletCount())
+ {
+ m_currentILOffset.m_ilOffset = INVALID_IL_OFFSET;
+ }
+#else // !WIN64EXCEPTIONS
+ m_currentILOffset.m_ilOffset = INVALID_IL_OFFSET;
+#endif // !WIN64EXCEPTIONS
+}
+
+
+
+// SIZE_T DebuggerJitInfo::MapSpecialToNative(): Maps something like
+// a prolog to a native offset.
+// CordDebugMappingResult mapping: Mapping type to be looking for.
+// SIZE_T which: Which one. <TODO>For now, set to zero. <@todo Later, we'll
+// change this to some value that we get back from MapNativeToILOffset
+// to indicate which of the (possibly multiple epilogs) that may
+// be present.</TODO>
+
+SIZE_T DebuggerJitInfo::MapSpecialToNative(CorDebugMappingResult mapping,
+ SIZE_T which,
+ BOOL *pfAccurate)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ PRECONDITION(NULL != pfAccurate);
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_INFO10000, "DJI::MSTN map:0x%x which:0x%x\n", mapping, which));
+
+ bool fFound;
+ SIZE_T cFound = 0;
+
+ DebuggerILToNativeMap *m = GetSequenceMap();
+ DebuggerILToNativeMap *mEnd = m + GetSequenceMapCount();
+ if (m)
+ {
+ while(m < mEnd)
+ {
+ _ASSERTE(m>=GetSequenceMap());
+
+ fFound = false;
+
+ if (DbgIsSpecialILOffset(m->ilOffset))
+ cFound++;
+
+ if (cFound == which)
+ {
+ _ASSERTE( (mapping == MAPPING_PROLOG &&
+ m->ilOffset == (ULONG) ICorDebugInfo::PROLOG) ||
+ (mapping == MAPPING_EPILOG &&
+ m->ilOffset == (ULONG) ICorDebugInfo::EPILOG) ||
+ ((mapping == MAPPING_NO_INFO || mapping == MAPPING_UNMAPPED_ADDRESS) &&
+ m->ilOffset == (ULONG) ICorDebugInfo::NO_MAPPING)
+ );
+
+ (*pfAccurate) = TRUE;
+ LOG((LF_CORDB, LL_INFO10000, "DJI::MSTN found mapping to nat:0x%x\n",
+ m->nativeStartOffset));
+ return m->nativeStartOffset;
+ }
+ m++;
+ }
+ }
+
+ LOG((LF_CORDB, LL_INFO10000, "DJI::MSTN No mapping found :(\n"));
+ (*pfAccurate) = FALSE;
+
+ return 0;
+}
+
+#if defined(WIN64EXCEPTIONS)
+//
+// DebuggerJitInfo::MapILOffsetToNativeForSetIP()
+//
+// This function maps an IL offset to a native offset, taking into account cloned finallys and nested EH clauses.
+//
+// parameters: offsetILTo - the destination IP, in IL offset
+// funcletIndexFrom - the funclet index of the source IP
+// pEHRT - tree structure for keeping track of EH clause information
+// pExact - pointer for returning whether the mapping is exact or not
+//
+// return value: destination IP, in native offset
+//
+SIZE_T DebuggerJitInfo::MapILOffsetToNativeForSetIP(SIZE_T offsetILTo, int funcletIndexFrom,
+ EHRangeTree* pEHRT, BOOL* pExact)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ DebuggerILToNativeMap* pMap = MapILOffsetToMapEntry(offsetILTo, pExact, TRUE);
+ DebuggerILToNativeMap* pMapEnd = GetSequenceMap() + GetSequenceMapCount();
+
+ _ASSERTE(pMap == m_sequenceMap ||
+ (pMap - 1)->ilOffset == (ULONG)ICorDebugInfo::NO_MAPPING ||
+ (pMap - 1)->ilOffset == (ULONG)ICorDebugInfo::PROLOG ||
+ (pMap - 1)->ilOffset == (ULONG)ICorDebugInfo::EPILOG ||
+ pMap->ilOffset > (pMap - 1)->ilOffset);
+
+ SIZE_T offsetNatTo = pMap->nativeStartOffset;
+
+ if (m_funcletCount == 0 ||
+ pEHRT == NULL ||
+ FAILED(pEHRT->m_hrInit))
+ {
+ return offsetNatTo;
+ }
+
+ // Multiple sequence points may have the same IL offset, which means that the code is duplicated in
+ // multiple funclets and/or in the parent method. If the destination offset maps to multiple sequence
+ // points (and hence to multiple funclets), we try to find the a sequence point which is in the same
+ // funclet as the source sequence point. If we can't find one, then the operation is going to fail
+ // anyway, so we just return the first sequence point we find.
+ for (DebuggerILToNativeMap* pMapCur = pMap + 1;
+ (pMapCur < pMapEnd) && (pMapCur->ilOffset == pMap->ilOffset);
+ pMapCur++)
+ {
+ int funcletIndexTo = GetFuncletIndex(pMapCur->nativeStartOffset, DebuggerJitInfo::GFIM_BYOFFSET);
+ if (funcletIndexFrom == funcletIndexTo)
+ {
+ return pMapCur->nativeStartOffset;
+ }
+ }
+
+ return offsetNatTo;
+}
+#endif // _WIN64
+
+// void DebuggerJitInfo::MapILRangeToMapEntryRange(): MIRTMER
+// calls MapILOffsetToNative for the startOffset (putting the
+// result into start), and the endOffset (putting the result into end).
+// SIZE_T startOffset: IL offset from beginning of function.
+// SIZE_T endOffset: IL offset from beginngin of function,
+// or zero to indicate that the end of the function should be used.
+// DebuggerILToNativeMap **start: Contains start & end
+// native offsets that correspond to startOffset. Set to NULL if
+// there is no mapping info.
+// DebuggerILToNativeMap **end: Contains start & end native
+// offsets that correspond to endOffset. Set to NULL if there
+// is no mapping info.
+void DebuggerJitInfo::MapILRangeToMapEntryRange(SIZE_T startOffset,
+ SIZE_T endOffset,
+ DebuggerILToNativeMap **start,
+ DebuggerILToNativeMap **end)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_INFO1000000,
+ "DJI::MIRTMER: IL 0x%04x-0x%04x\n",
+ startOffset, endOffset));
+
+ if (GetSequenceMapCount() == 0)
+ {
+ *start = NULL;
+ *end = NULL;
+ return;
+ }
+
+ *start = MapILOffsetToMapEntry(startOffset);
+
+ //
+ // end points to the last range that endOffset maps to, not past
+ // the last range.
+ // We want to return the last IL, and exclude the epilog
+ if (endOffset == 0)
+ {
+ *end = GetSequenceMap() + GetSequenceMapCount() - 1;
+ _ASSERTE(*end>=m_sequenceMap);
+
+ while ( ((*end)->ilOffset == (ULONG) ICorDebugInfo::EPILOG||
+ (*end)->ilOffset == (ULONG) ICorDebugInfo::NO_MAPPING)
+ && (*end) > m_sequenceMap)
+ {
+ (*end)--;
+ _ASSERTE(*end>=m_sequenceMap);
+
+ }
+ }
+ else
+ *end = MapILOffsetToMapEntry(endOffset - 1, NULL
+ WIN64_ARG(FALSE));
+
+ _ASSERTE(*end>=m_sequenceMap);
+
+
+ LOG((LF_CORDB, LL_INFO1000000,
+ "DJI::MIRTMER: IL 0x%04x-0x%04x --> 0x%04x 0x%08x-0x%08x\n"
+ " --> 0x%04x 0x%08x-0x%08x\n",
+ startOffset, endOffset,
+ (*start)->ilOffset,
+ (*start)->nativeStartOffset, (*start)->nativeEndOffset,
+ (*end)->ilOffset,
+ (*end)->nativeStartOffset, (*end)->nativeEndOffset));
+}
+
+// @dbgtodo Microsoft inspection: This function has been replicated in DacDbiStructures so
+// this version can be deleted when inspection is complete.
+
+// DWORD DebuggerJitInfo::MapNativeOffsetToIL(): Given a native
+// offset for the DebuggerJitInfo, compute
+// the IL offset from the beginning of the same method.
+// Returns: Offset of the IL instruction that contains
+// the native offset,
+// SIZE_T nativeOffset: [IN] Native Offset
+// CorDebugMappingResult *map: [OUT] explains the
+// quality of the matching & special cases
+// SIZE_T which: It's possible to have multiple EPILOGs, or
+// multiple unmapped regions within a method. This opaque value
+// specifies which special region we're talking about. This
+// param has no meaning if map & (MAPPING_EXACT|MAPPING_APPROXIMATE)
+// Basically, this gets handed back to MapSpecialToNative, later.
+DWORD DebuggerJitInfo::MapNativeOffsetToIL(SIZE_T nativeOffsetToMap,
+ CorDebugMappingResult *map,
+ DWORD *which,
+ BOOL skipPrologs)
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ NOTHROW;
+ GC_NOTRIGGER;
+ PRECONDITION(map != NULL);
+ PRECONDITION(which != NULL);
+ }
+ CONTRACTL_END;
+
+ DWORD nativeOffset = (DWORD)nativeOffsetToMap;
+
+ (*which) = 0;
+ DebuggerILToNativeMap *m = GetSequenceMap();
+ DebuggerILToNativeMap *mEnd = m + GetSequenceMapCount();
+
+ LOG((LF_CORDB,LL_INFO10000,"DJI::MNOTI: nativeOffset = 0x%x\n", nativeOffset));
+
+ if (m)
+ {
+ while (m < mEnd)
+ {
+ _ASSERTE(m>=m_sequenceMap);
+
+#ifdef LOGGING
+ if (m->ilOffset == (ULONG) ICorDebugInfo::PROLOG )
+ LOG((LF_CORDB,LL_INFO10000,"DJI::MNOTI: m->natStart:0x%x m->natEnd:0x%x il:PROLOG\n", m->nativeStartOffset, m->nativeEndOffset));
+ else if (m->ilOffset == (ULONG) ICorDebugInfo::EPILOG )
+ LOG((LF_CORDB,LL_INFO10000,"DJI::MNOTI: m->natStart:0x%x m->natEnd:0x%x il:EPILOG\n", m->nativeStartOffset, m->nativeEndOffset));
+ else if (m->ilOffset == (ULONG) ICorDebugInfo::NO_MAPPING)
+ LOG((LF_CORDB,LL_INFO10000,"DJI::MNOTI: m->natStart:0x%x m->natEnd:0x%x il:NO MAP\n", m->nativeStartOffset, m->nativeEndOffset));
+ else
+ LOG((LF_CORDB,LL_INFO10000,"DJI::MNOTI: m->natStart:0x%x m->natEnd:0x%x il:0x%x src:0x%x\n", m->nativeStartOffset, m->nativeEndOffset, m->ilOffset, m->source));
+#endif // LOGGING
+
+ if (m->ilOffset == (ULONG) ICorDebugInfo::PROLOG ||
+ m->ilOffset == (ULONG) ICorDebugInfo::EPILOG ||
+ m->ilOffset == (ULONG) ICorDebugInfo::NO_MAPPING)
+ {
+ (*which)++;
+ }
+
+ if (nativeOffset >= m->nativeStartOffset
+ && ((m->nativeEndOffset == 0 &&
+ m->ilOffset != (ULONG) ICorDebugInfo::PROLOG)
+ || nativeOffset < m->nativeEndOffset))
+ {
+ ULONG ilOff = m->ilOffset;
+
+ if( m->ilOffset == (ULONG) ICorDebugInfo::PROLOG )
+ {
+ if (skipPrologs && nativeOffset < m->nativeEndOffset)
+ {
+ // If the caller requested to skip prologs, we simply restart the walk
+ // with the offset set to the end of the prolog.
+ nativeOffset = m->nativeEndOffset;
+ continue;
+ }
+
+ ilOff = 0;
+ (*map) = MAPPING_PROLOG;
+ LOG((LF_CORDB,LL_INFO10000,"DJI::MNOTI: MAPPING_PROLOG\n"));
+
+ }
+ else if (m->ilOffset == (ULONG) ICorDebugInfo::NO_MAPPING)
+ {
+ ilOff = 0;
+ (*map) = MAPPING_UNMAPPED_ADDRESS ;
+ LOG((LF_CORDB,LL_INFO10000,"DJI::MNOTI:MAPPING_"
+ "UNMAPPED_ADDRESS\n"));
+ }
+ else if( m->ilOffset == (ULONG) ICorDebugInfo::EPILOG )
+ {
+ ilOff = m_lastIL;
+ (*map) = MAPPING_EPILOG;
+ LOG((LF_CORDB,LL_INFO10000,"DJI::MNOTI:MAPPING_EPILOG\n"));
+ }
+ else if (nativeOffset == m->nativeStartOffset)
+ {
+ (*map) = MAPPING_EXACT;
+ LOG((LF_CORDB,LL_INFO10000,"DJI::MNOTI:MAPPING_EXACT\n"));
+ }
+ else
+ {
+ (*map) = MAPPING_APPROXIMATE;
+ LOG((LF_CORDB,LL_INFO10000,"DJI::MNOTI:MAPPING_"
+ "APPROXIMATE\n"));
+ }
+
+ return ilOff;
+ }
+ m++;
+ }
+ }
+
+ (*map) = MAPPING_NO_INFO;
+ LOG((LF_CORDB,LL_INFO10000,"DJI::MNOTI:NO_INFO\n"));
+ return 0;
+}
+
+/******************************************************************************
+ *
+ ******************************************************************************/
+DebuggerJitInfo::~DebuggerJitInfo()
+{
+ TRACE_FREE(m_sequenceMap);
+ if (m_sequenceMap != NULL)
+ {
+ DeleteInteropSafe(((BYTE *)m_sequenceMap));
+ }
+
+ TRACE_FREE(m_varNativeInfo);
+ if (m_varNativeInfo != NULL)
+ {
+ DeleteInteropSafe(m_varNativeInfo);
+ }
+
+#if defined(WIN64EXCEPTIONS)
+ if (m_rgFunclet)
+ {
+ DeleteInteropSafe(m_rgFunclet);
+ m_rgFunclet = NULL;
+ }
+#endif // WIN64EXCEPTIONS
+
+
+#ifdef _DEBUG
+ // Trash pointers to garbage.
+ // Don't null out since there may be runtime checks against NULL.
+ // Set to a non-null random pointer value that will cause an immediate AV on deref.
+ m_fd = (MethodDesc*) 0x1;
+ m_methodInfo = (DebuggerMethodInfo*) 0x1;
+ m_prevJitInfo = (DebuggerJitInfo*) 0x01;
+ m_nextJitInfo = (DebuggerJitInfo*) 0x01;
+#endif
+
+
+ LOG((LF_CORDB,LL_EVERYTHING, "DJI::~DJI : deleted at 0x%p\n", this));
+}
+
+
+// Lazy initialize the Debugger-Jit-Info
+void DebuggerJitInfo::LazyInitBounds()
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ NOTHROW;
+ GC_NOTRIGGER;
+ PRECONDITION(ThisMaybeHelperThread());
+ PRECONDITION(!g_pDebugger->HasDebuggerDataLock());
+ } CONTRACTL_END;
+
+ //@todo: this method is not synchronized. Mei-chin's recent work should cover this one
+ // Only attempt lazy-init once
+ // new LOG message
+ LOG((LF_CORDB,LL_EVERYTHING, "DJI::LazyInitBounds: this=0x%x m_fAttemptInit %s\n", this, m_fAttemptInit == true? "true": "false"));
+ if (m_fAttemptInit)
+ {
+ return;
+ }
+ m_fAttemptInit = true;
+
+ EX_TRY
+ {
+ LOG((LF_CORDB,LL_EVERYTHING, "DJI::LazyInitBounds: this=0x%x Initing\n", this));
+ // Should have already been jitted
+ _ASSERTE(this->m_jitComplete);
+
+ MethodDesc * mdesc = this->m_fd;
+
+ DebugInfoRequest request;
+
+ _ASSERTE(this->m_addrOfCode != NULL); // must have address to disambguate the Enc cases.
+ // Caller already resolved generics when they craeted the DJI, so we don't need to repeat.
+ // Note the MethodDesc may not yet have the jitted info, so we'll also use the starting address we got in the jit complete callback.
+ request.InitFromStartingAddr(mdesc, (PCODE)this->m_addrOfCode);
+
+
+ // Bounds info.
+ ULONG32 cMap = 0;
+ ICorDebugInfo::OffsetMapping *pMap = NULL;
+ ULONG32 cVars = 0;
+ ICorDebugInfo::NativeVarInfo *pVars = NULL;
+
+ BOOL fSuccess = DebugInfoManager::GetBoundariesAndVars(
+ request,
+ InteropSafeNew, NULL, // allocator
+ &cMap, &pMap,
+ &cVars, &pVars);
+ LOG((LF_CORDB,LL_EVERYTHING, "DJI::LazyInitBounds: this=0x%x GetBoundariesAndVars success=0x%x\n", this, fSuccess));
+ if (fSuccess)
+ {
+ this->SetBoundaries(cMap, pMap);
+ this->SetVars(cVars, pVars);
+ }
+ }
+ EX_CATCH
+ {
+ LOG((LF_CORDB,LL_WARNING, "DJI::LazyInitBounds: this=0x%x Exception was thrown and caught\n", this));
+ // Just catch the exception. The DJI maps may or may-not be intialized,
+ // but they should still be in a consistent state, so we should be ok.
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+}
+
+/******************************************************************************
+ * SetVars() takes ownership of pVars
+ ******************************************************************************/
+void DebuggerJitInfo::SetVars(ULONG32 cVars, ICorDebugInfo::NativeVarInfo *pVars)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ if (m_varNativeInfo)
+ {
+ return;
+ }
+
+ m_varNativeInfo = pVars;
+ m_varNativeInfoCount = cVars;
+
+ LOG((LF_CORDB, LL_INFO1000000, "D::sV: var count is %d\n",
+ m_varNativeInfoCount));
+
+#ifdef LOGGING
+ for (unsigned int i = 0; i < m_varNativeInfoCount; i++)
+ {
+ ICorDebugInfo::NativeVarInfo* vni = &(m_varNativeInfo[i]);
+ _dumpVarNativeInfo(vni);
+ }
+#endif
+}
+
+CHECK DebuggerJitInfo::Check() const
+{
+ LIMITED_METHOD_CONTRACT;
+
+ CHECK_OK;
+}
+
+// Invariants for a DebuggerJitInfo
+// These should always be true at any well defined point.
+CHECK DebuggerJitInfo::Invariant() const
+{
+ LIMITED_METHOD_CONTRACT;
+ CHECK((m_sequenceMapCount == 0) == (m_sequenceMap == NULL));
+ CHECK(m_methodInfo != NULL);
+ CHECK(m_fd != NULL);
+
+ CHECK_OK;
+}
+
+
+#if !defined(DACCESS_COMPILE)
+/******************************************************************************
+ * SetBoundaries() takes ownership of pMap
+ ******************************************************************************/
+void DebuggerJitInfo::SetBoundaries(ULONG32 cMap, ICorDebugInfo::OffsetMapping *pMap)
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ THROWS;
+ GC_NOTRIGGER;
+ PRECONDITION(CheckPointer(this));
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB,LL_EVERYTHING, "DJI::SetBoundaries: this=0x%x cMap=0x%x pMap=0x%x\n", this, cMap, pMap));
+ _ASSERTE((cMap == 0) == (pMap == NULL));
+
+ if (cMap == 0)
+ return;
+
+ if (m_sequenceMap)
+ {
+ return;
+ }
+
+ ULONG ilLast = 0;
+#ifdef _DEBUG
+ // We assume that the map is sorted by native offset
+ if (cMap > 1)
+ {
+ for(ICorDebugInfo::OffsetMapping * pEntry = pMap;
+ pEntry < (pMap + cMap - 1);
+ pEntry++)
+ {
+ _ASSERTE(pEntry->nativeOffset <= (pEntry+1)->nativeOffset);
+ }
+ }
+#endif //_DEBUG
+
+ //
+ // <TODO>@todo perf: allocating these on the heap is slow. We could do
+ // better knowing that these live for the life of the run, just
+ // like the DebuggerJitInfo's.</TODO>
+ //
+ m_sequenceMap = (DebuggerILToNativeMap *)new (interopsafe) DebuggerILToNativeMap[cMap];
+ LOG((LF_CORDB,LL_EVERYTHING, "DJI::SetBoundaries: this=0x%x m_sequenceMap=0x%x\n", this, m_sequenceMap));
+ _ASSERTE(m_sequenceMap != NULL); // we'll throw on null
+
+ m_sequenceMapCount = cMap;
+
+ DebuggerILToNativeMap *m = m_sequenceMap;
+
+ // For the instrumented-IL case, we need to remove all duplicate entries.
+ // So we keep a record of the last old IL offset. If the current old IL
+ // offset is the same as the last old IL offset, we remove it.
+ // Pick a unique initial value (-10) so that the 1st doesn't accidentally match.
+ int ilPrevOld = -10;
+
+ InstrumentedILOffsetMapping mapping =
+ m_methodInfo->GetRuntimeModule()->GetInstrumentedILOffsetMapping(m_methodInfo->m_token);
+
+ //
+ // <TODO>@todo perf: we could do the vast majority of this
+ // post-processing work the first time the sequence point map is
+ // demanded. That would allow us to simply hold the raw array for
+ // 95% of the functions jitted while debugging, and 100% of them
+ // when just running/tracking.</TODO>
+ const DWORD call_inst = (DWORD)ICorDebugInfo::CALL_INSTRUCTION;
+ for(ULONG32 idxJitMap = 0; idxJitMap < cMap; idxJitMap++)
+ {
+ const ICorDebugInfo::OffsetMapping * const pMapEntry = &pMap[idxJitMap];
+ _ASSERTE(m >= m_sequenceMap);
+ _ASSERTE(m < m_sequenceMap + m_sequenceMapCount);
+
+ ilLast = max((int)ilLast, (int)pMapEntry->ilOffset);
+
+ // Simply copy everything over, since we translate to
+ // CorDebugMappingResults immediately prior to handing
+ // back to user...
+ m->nativeStartOffset = pMapEntry->nativeOffset;
+ m->ilOffset = pMapEntry->ilOffset;
+ m->source = pMapEntry->source;
+
+ // Keep in mind that if we have an instrumented code translation
+ // table, we may have asked for completely different IL offsets
+ // than the user thinks we did.....
+
+ // If we did instrument, then we can't have any sequence points that
+ // are "in-between" the old-->new map that the profiler gave us.
+ // Ex, if map is:
+ // (6 old -> 36 new)
+ // (8 old -> 50 new)
+ // And the jit gives us an entry for 44 new, that will map back to 6 old.
+ // Since the map can only have one entry for 6 old, we remove 44 new.
+ if (m_methodInfo->HasInstrumentedILMap())
+ {
+ int ilThisOld = m_methodInfo->TranslateToInstIL(&mapping,
+ pMapEntry->ilOffset,
+ bInstrumentedToOriginal);
+
+ if (ilThisOld == ilPrevOld)
+ {
+ // If this translated to the same old IL offset as the last entry,
+ // then this is "in between". Skip it.
+ m_sequenceMapCount--; // one less seq point in the DJI's map
+ continue;
+ }
+ m->ilOffset = ilThisOld;
+ ilPrevOld = ilThisOld;
+ }
+
+ if (m > m_sequenceMap && (m->source & call_inst) != call_inst)
+ {
+ DebuggerILToNativeMap *last = m-1;
+ if ((last->source & call_inst) == call_inst)
+ last = (last > m_sequenceMap) ? last - 1 : NULL;
+
+ if (last && (last->source & call_inst) != call_inst && m->ilOffset == last->ilOffset)
+ {
+ // JIT gave us an extra entry (probably zero), so mush
+ // it into the one we've already got.
+ // <TODO> Why does this happen?</TODO>
+ m_sequenceMapCount--;
+ continue;
+ }
+ }
+
+
+ // Move to next entry in the debugger's table
+ m++;
+ } // end for
+
+ DeleteInteropSafe(pMap);
+
+ _ASSERTE(m == m_sequenceMap + m_sequenceMapCount);
+
+ m_lastIL = ilLast;
+
+ // Set nativeEndOffset in debugger's il->native map
+ // Do this before we resort by IL.
+ unsigned int i;
+ for(i = 0; i < m_sequenceMapCount - 1; i++)
+ {
+ // We need to not use CALL_INSTRUCTION's IL start offset.
+ unsigned int j = i + 1;
+ while ((m_sequenceMap[j].source & call_inst) == call_inst && j < m_sequenceMapCount-1)
+ j++;
+
+ m_sequenceMap[i].nativeEndOffset = m_sequenceMap[j].nativeStartOffset;
+ }
+
+ m_sequenceMap[i].nativeEndOffset = 0;
+ m_sequenceMap[i].source = (ICorDebugInfo::SourceTypes)
+ ((DWORD) m_sequenceMap[i].source |
+ (DWORD)ICorDebugInfo::NATIVE_END_OFFSET_UNKNOWN);
+
+ // Now resort by IL.
+ MapSortIL isort(m_sequenceMap, m_sequenceMapCount);
+
+ isort.Sort();
+
+ m_sequenceMapSorted = true;
+
+ m_callsiteMapCount = m_sequenceMapCount;
+ while (m_sequenceMapCount > 0 && (m_sequenceMap[m_sequenceMapCount-1].source & call_inst) == call_inst)
+ m_sequenceMapCount--;
+
+ m_callsiteMap = m_sequenceMap + m_sequenceMapCount;
+ m_callsiteMapCount -= m_sequenceMapCount;
+
+ LOG((LF_CORDB, LL_INFO100000, "DJI::SetBoundaries: this=0x%x boundary count is %d (%d callsites)\n",
+ this, m_sequenceMapCount, m_callsiteMapCount));
+
+#ifdef LOGGING
+ for (unsigned int count = 0; count < m_sequenceMapCount + m_callsiteMapCount; count++)
+ {
+ if( m_sequenceMap[count].ilOffset ==
+ (ULONG) ICorDebugInfo::PROLOG )
+ LOG((LF_CORDB, LL_INFO1000000,
+ "D::sB: PROLOG --> 0x%08x -- 0x%08x",
+ m_sequenceMap[count].nativeStartOffset,
+ m_sequenceMap[count].nativeEndOffset));
+ else if ( m_sequenceMap[count].ilOffset ==
+ (ULONG) ICorDebugInfo::EPILOG )
+ LOG((LF_CORDB, LL_INFO1000000,
+ "D::sB: EPILOG --> 0x%08x -- 0x%08x",
+ m_sequenceMap[count].nativeStartOffset,
+ m_sequenceMap[count].nativeEndOffset));
+ else if ( m_sequenceMap[count].ilOffset ==
+ (ULONG) ICorDebugInfo::NO_MAPPING )
+ LOG((LF_CORDB, LL_INFO1000000,
+ "D::sB: NO MAP --> 0x%08x -- 0x%08x",
+ m_sequenceMap[count].nativeStartOffset,
+ m_sequenceMap[count].nativeEndOffset));
+ else
+ LOG((LF_CORDB, LL_INFO1000000,
+ "D::sB: 0x%04x (Real:0x%04x) --> 0x%08x -- 0x%08x",
+ m_sequenceMap[count].ilOffset,
+ m_methodInfo->TranslateToInstIL(&mapping,
+ m_sequenceMap[count].ilOffset,
+ bOriginalToInstrumented),
+ m_sequenceMap[count].nativeStartOffset,
+ m_sequenceMap[count].nativeEndOffset));
+
+ LOG((LF_CORDB, LL_INFO1000000, " Src:0x%x\n", m_sequenceMap[count].source));
+
+ }
+#endif //LOGGING
+}
+#endif // !DACCESS_COMPILE
+
+// Init a DJI after it's jitted.
+void DebuggerJitInfo::Init(TADDR newAddress)
+{
+ // Shouldn't initialize while holding the lock b/c intialzing may call functions that lock,
+ // and thus we'd have a locking violation.
+ _ASSERTE(!g_pDebugger->HasDebuggerDataLock());
+
+ this->m_addrOfCode = (ULONG_PTR)PTR_TO_CORDB_ADDRESS((BYTE*) newAddress);
+ this->m_jitComplete = true;
+
+ this->m_codeRegionInfo.InitializeFromStartAddress((PCODE)this->m_addrOfCode);
+ this->m_sizeOfCode = this->m_codeRegionInfo.getSizeOfTotalCode();
+
+ this->m_encVersion = this->m_methodInfo->GetCurrentEnCVersion();
+
+#if defined(WIN64EXCEPTIONS)
+ this->InitFuncletAddress();
+#endif // WIN64EXCEPTIONS
+
+ LOG((LF_CORDB,LL_INFO10000,"De::JITCo:Got DJI 0x%p(V %d),"
+ "Hot section from 0x%p to 0x%p "
+ "Cold section from 0x%p to 0x%p "
+ "varCount=%d seqCount=%d\n",
+ this, this->m_encVersion,
+ this->m_codeRegionInfo.getAddrOfHotCode(),
+ this->m_codeRegionInfo.getAddrOfHotCode() + this->m_codeRegionInfo.getSizeOfHotCode(),
+ this->m_codeRegionInfo.getAddrOfColdCode(),
+ this->m_codeRegionInfo.getAddrOfColdCode() + this->m_codeRegionInfo.getSizeOfColdCode(),
+ (ULONG)this->m_addrOfCode,
+ (ULONG)this->m_addrOfCode+(ULONG)this->m_sizeOfCode,
+ this->GetVarNativeInfoCount(),
+ this->GetSequenceMapCount()));
+
+#if defined(LOGGING)
+ for (unsigned int i = 0; i < this->GetSequenceMapCount(); i++)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "De::JITCo: seq map 0x%x - "
+ "IL offset 0x%x native start offset 0x%x native end offset 0x%x source 0x%x\n",
+ i, this->GetSequenceMap()[i].ilOffset,
+ this->GetSequenceMap()[i].nativeStartOffset,
+ this->GetSequenceMap()[i].nativeEndOffset,
+ this->GetSequenceMap()[i].source));
+ }
+#endif // LOGGING
+
+}
+
+/******************************************************************************
+ *
+ ******************************************************************************/
+ICorDebugInfo::SourceTypes DebuggerJitInfo::GetSrcTypeFromILOffset(SIZE_T ilOffset)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ BOOL exact = FALSE;
+ DebuggerILToNativeMap *pMap = MapILOffsetToMapEntry(ilOffset, &exact);
+
+ LOG((LF_CORDB, LL_INFO100000, "DJI::GSTFILO: for il 0x%x, got entry 0x%p,"
+ "(il 0x%x) nat 0x%x to 0x%x, SourceTypes 0x%x, exact:%x\n", ilOffset, pMap,
+ pMap->ilOffset, pMap->nativeStartOffset, pMap->nativeEndOffset, pMap->source,
+ exact));
+
+ if (!exact)
+ {
+ return ICorDebugInfo::SOURCE_TYPE_INVALID;
+ }
+
+ return pMap->source;
+}
+
+/******************************************************************************
+ *
+ ******************************************************************************/
+DebuggerMethodInfo::~DebuggerMethodInfo()
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ NOTHROW;
+ GC_NOTRIGGER;
+ DESTRUCTOR_CHECK;
+ }
+ CONTRACTL_END;
+
+ DeleteJitInfoList();
+
+ LOG((LF_CORDB,LL_EVERYTHING, "DMI::~DMI : deleted at 0x%p\n", this));
+}
+
+// Translate between old & new offsets (w/ respect to Instrumented IL).
+
+// Don't interpolate
+ULONG32 DebuggerMethodInfo::TranslateToInstIL(const InstrumentedILOffsetMapping * pMapping,
+ ULONG32 offOrig,
+ bool fOrigToInst)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ SIZE_T iMap;
+ SIZE_T cMap = pMapping->GetCount();
+ // some negative IL offsets have special meaning. Don't translate
+ // those (just return as is). See ICorDebugInfo::MappingTypes
+ if ((cMap == 0) || (offOrig < 0))
+ {
+ return offOrig;
+ }
+
+ ARRAY_PTR_COR_IL_MAP rgMap = pMapping->GetOffsets();
+
+ // This assumes:
+ // - map is sorted in increasing order by both old & new
+ // - round down.
+ if (fOrigToInst)
+ {
+ // Translate: old --> new
+
+ // Treat it as prolog if offOrig is not in remapping range
+ if ((offOrig < rgMap[0].oldOffset) || (offOrig == (ULONG32)ICorDebugInfo::PROLOG))
+ {
+ return (ULONG32)ICorDebugInfo::PROLOG;
+ }
+
+ if (offOrig == (ULONG32)ICorDebugInfo::EPILOG)
+ {
+ return (ULONG32)ICorDebugInfo::EPILOG;
+ }
+
+ if (offOrig == (ULONG32)ICorDebugInfo::NO_MAPPING)
+ {
+ return (ULONG32)ICorDebugInfo::NO_MAPPING;
+ }
+
+ for(iMap = 1; iMap < cMap; iMap++)
+ {
+ if (offOrig < rgMap[iMap].oldOffset)
+ return rgMap[iMap-1].newOffset;
+ }
+
+ return rgMap[iMap - 1].newOffset;
+ }
+ else
+ {
+ // Translate: new --> old
+
+ // Treat it as prolog if offOrig is not in remapping range
+ if ((offOrig < rgMap[0].newOffset) || (offOrig == (ULONG32)ICorDebugInfo::PROLOG))
+ {
+ return (ULONG32)ICorDebugInfo::PROLOG;
+ }
+
+ if (offOrig == (ULONG32)ICorDebugInfo::EPILOG)
+ {
+ return (ULONG32)ICorDebugInfo::EPILOG;
+ }
+
+ if (offOrig == (ULONG32)ICorDebugInfo::NO_MAPPING)
+ {
+ return (ULONG32)ICorDebugInfo::NO_MAPPING;
+ }
+
+ for(iMap = 1; iMap < cMap; iMap++)
+ {
+ if (offOrig < rgMap[iMap].newOffset)
+ return rgMap[iMap-1].oldOffset;
+ }
+
+ return rgMap[iMap - 1].oldOffset;
+ }
+}
+
+/******************************************************************************
+ * Constructor for DebuggerMethodInfo
+ ******************************************************************************/
+DebuggerMethodInfo::DebuggerMethodInfo(Module *module, mdMethodDef token) :
+ m_currentEnCVersion(CorDB_DEFAULT_ENC_FUNCTION_VERSION),
+ m_module(module),
+ m_token(token),
+ m_prevMethodInfo(NULL),
+ m_nextMethodInfo(NULL),
+ m_latestJitInfo(NULL),
+ m_fHasInstrumentedILMap(false)
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ WRAPPER(THROWS);
+ WRAPPER(GC_TRIGGERS);
+ CONSTRUCTOR_CHECK;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB,LL_EVERYTHING, "DMI::DMI : created at 0x%p\n", this));
+
+ _ASSERTE(g_pDebugger->HasDebuggerDataLock());
+
+ DebuggerModule * pModule = GetPrimaryModule();
+
+ m_fJMCStatus = false;
+
+ // If there's no module, then this isn't a JMC function.
+ // This can happen since DMIs are created for debuggable code, and
+ // Modules are only created if a debugger is actually attached.
+ if (pModule != NULL)
+ {
+ // Use the accessor so that we keep the module's count properly updated.
+ SetJMCStatus(pModule->GetRuntimeModule()->GetJMCStatus());
+ }
+ }
+
+
+/******************************************************************************
+ * Get the primary debugger module for this DMI. This is 1:1 w/ an EE Module.
+ ******************************************************************************/
+DebuggerModule* DebuggerMethodInfo::GetPrimaryModule()
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(g_pDebugger->HasDebuggerDataLock());
+
+ DebuggerModuleTable * pTable = g_pDebugger->GetModuleTable();
+
+ // If we're tracking debug info but no debugger's attached, then
+ // we won't have a table for the modules yet.
+ if (pTable == NULL)
+ return NULL;
+
+ DebuggerModule * pModule = pTable->GetModule(GetRuntimeModule());
+ if (pModule == NULL)
+ {
+ // We may be missing the module even if we have the table.
+ // 1.) If there's no debugger attached (so we're not getting ModuleLoad events).
+ // 2.) If we're asking for this while in DllMain of the module we're currently loading,
+ // we won't have gotten the ModuleLoad event yet.
+ return NULL;
+ }
+
+ // Only give back primary modules...
+ DebuggerModule * p2 = pModule->GetPrimaryModule();
+ _ASSERTE(p2 != NULL);
+
+ return p2;
+}
+
+/******************************************************************************
+ * Get the runtime module for this DMI
+ ******************************************************************************/
+Module * DebuggerMethodInfo::GetRuntimeModule()
+{
+ LIMITED_METHOD_CONTRACT;
+
+ return m_module;
+}
+
+#endif // !DACCESS_COMPILE
+
+
+//---------------------------------------------------------------------------------------
+//
+// Find the DebuggerJitInfo (DJI) for the given MethodDesc and native start address.
+// We need the native start address because generic methods may have multiple instances
+// of jitted code. This function does not create the DJI if it does not already exist.
+//
+// Arguments:
+// pMD - the MD to lookup; must be non-NULL
+// addrNativeStartAddr - the native start address of jitted code
+//
+// Return Value:
+// Returns the DJI corresponding to the specified MD and native start address.
+// NULL if the DJI is not found.
+//
+
+DebuggerJitInfo * DebuggerMethodInfo::FindJitInfo(MethodDesc * pMD,
+ TADDR addrNativeStartAddr)
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ SUPPORTS_DAC;
+ NOTHROW;
+ GC_NOTRIGGER;
+ PRECONDITION(pMD != NULL);
+ }
+ CONTRACTL_END;
+
+
+ DebuggerJitInfo * pCheck = m_latestJitInfo;
+ while (pCheck != NULL)
+ {
+ if ( (pCheck->m_fd == dac_cast<PTR_MethodDesc>(pMD)) &&
+ (pCheck->m_addrOfCode == addrNativeStartAddr) )
+ {
+ return pCheck;
+ }
+
+ pCheck = pCheck->m_prevJitInfo;
+ }
+
+ return NULL;
+}
+
+
+#if !defined(DACCESS_COMPILE)
+
+/*
+ * FindOrCreateInitAndAddJitInfo
+ *
+ * This routine allocates a new DJI, adding it to the DMI.
+ *
+ * Parameters:
+ * fd - the method desc to create a DJI for.
+ *
+ * Returns
+ * A pointer to the created DJI, or NULL.
+ *
+ */
+
+DebuggerJitInfo *DebuggerMethodInfo::FindOrCreateInitAndAddJitInfo(MethodDesc* fd)
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ THROWS;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(fd != NULL);
+
+ // This will grab the latest EnC version.
+ TADDR addr = (TADDR) g_pEEInterface->GetFunctionAddress(fd);
+
+ if (addr == NULL)
+ return NULL;
+
+ // Check the lsit to see if we've already populated an entry for this JitInfo.
+ // If we didn't have a JitInfo before, lazily create it now.
+ // We don't care if we were prejitted or not.
+ //
+ // We haven't got the lock yet so we'll repeat this lookup once
+ // we've taken the lock.
+ DebuggerJitInfo * pResult = FindJitInfo(fd, addr);
+ if (pResult != NULL)
+ {
+ // Found!
+ return pResult;
+ }
+
+
+ // CreateInitAndAddJitInfo takes a lock and checks the list again, which
+ // makes this thread-safe.
+ return CreateInitAndAddJitInfo(fd, addr);
+}
+
+// Create a DJI around a method-desc. The EE already has all the information we need for a DJI,
+// the DJI just serves as a cache of the information for the debugger.
+// Caller makes no guarantees about whether the DJI is already in the table. (Caller should avoid this if
+// it knows it's in the table, but b/c we can't expect caller to synchronize w/ the other threads).
+DebuggerJitInfo *DebuggerMethodInfo::CreateInitAndAddJitInfo(MethodDesc* fd, TADDR startAddr)
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ THROWS;
+ GC_NOTRIGGER;
+ PRECONDITION(!g_pDebugger->HasDebuggerDataLock());
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(fd != NULL);
+
+ // May or may-not be jitted, that's why we passed in the start addr & size explicitly.
+ _ASSERTE(startAddr != NULL);
+
+
+ // No support for light-weight codegen methods.
+ if (fd->IsDynamicMethod())
+ {
+ return NULL;
+ }
+
+
+ DebuggerJitInfo *dji = new (interopsafe) DebuggerJitInfo(this, fd);
+ _ASSERTE(dji != NULL); // throws on oom error
+
+ _ASSERTE(dji->m_methodInfo == this); // this should be set
+
+ TRACE_ALLOC(dji);
+
+ // Init may take locks that violate the debugger-data lock, so we can't init while we hold that lock.
+ // But we can't init after we add it to the table and release the lock b/c another thread may pick
+ // if up in the uninitialized state.
+ // So we initialize a private copy of the DJI before we take the debugger-data lock.
+ dji->Init(startAddr);
+
+ dji->m_nextJitInfo = NULL;
+
+ //
+ //<TODO>@TODO : _ASSERTE(EnC);</TODO>
+ //
+ {
+ Debugger::DebuggerDataLockHolder debuggerDataLockHolder(g_pDebugger);
+
+ // We need to ensure that another thread didn't go in and add this exact same DJI?
+ {
+ DebuggerJitInfo * pResult = FindJitInfo(dji->m_fd, (TADDR)dji->m_addrOfCode);
+ if (pResult != NULL)
+ {
+ // Found!
+ _ASSERTE(pResult->m_sizeOfCode == dji->m_sizeOfCode);
+ DeleteInteropSafe(dji);
+ return pResult;
+ }
+ }
+
+ // We know it's not in the table. Go add it!
+ DebuggerJitInfo *djiPrev = m_latestJitInfo;
+
+ LOG((LF_CORDB,LL_INFO10000,"DMI:CAAJI: current head of dji list:0x%08x\n", djiPrev));
+
+ if (djiPrev != NULL)
+ {
+ dji->m_prevJitInfo = djiPrev;
+ djiPrev->m_nextJitInfo = dji;
+
+ m_latestJitInfo = dji;
+
+ LOG((LF_CORDB,LL_INFO10000,"DMI:CAAJI: DJI version 0x%04x for %s\n",
+ GetCurrentEnCVersion(),
+ dji->m_fd->m_pszDebugMethodName));
+ }
+ else
+ {
+ m_latestJitInfo = dji;
+ }
+
+ } // DebuggerDataLockHolder out of scope - release implied
+
+ // We've now added a new DJI into the table and released the lock. Thus any other thread
+ // can come and use our DJI. Good thing we inited the DJI _before_ adding it to the table.
+
+ LOG((LF_CORDB,LL_INFO10000,"DMI:CAAJI: new head of dji list:0x%08x\n", m_latestJitInfo));
+
+ return dji;
+}
+
+/*
+ * DeleteJitInfo
+ *
+ * This routine remove a DJI from the DMI's list and deletes the memory.
+ *
+ * Parameters:
+ * dji - The DJI to delete.
+ *
+ * Returns
+ * None.
+ *
+ */
+
+void DebuggerMethodInfo::DeleteJitInfo(DebuggerJitInfo *dji)
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ Debugger::DebuggerDataLockHolder debuggerDataLockHolder(g_pDebugger);
+
+ LOG((LF_CORDB,LL_INFO10000,"DMI:DJI: dji:0x%08x\n", dji));
+
+ DebuggerJitInfo *djiPrev = dji->m_prevJitInfo;
+
+ if (djiPrev != NULL)
+ {
+ djiPrev->m_nextJitInfo = dji->m_nextJitInfo;
+ }
+
+ if (dji->m_nextJitInfo != NULL)
+ {
+ dji->m_nextJitInfo->m_prevJitInfo = djiPrev;
+ }
+ else
+ {
+ //
+ // This DJI is the head of the list
+ //
+ _ASSERTE(m_latestJitInfo == dji);
+
+ m_latestJitInfo = djiPrev;
+ }
+
+ TRACE_FREE(dji);
+
+ DeleteInteropSafe(dji);
+
+ // DebuggerDataLockHolder out of scope - release implied
+}
+
+/*
+ * DeleteJitInfoList
+ *
+ * This routine removes all the DJIs from the current DMI.
+ *
+ * Parameters:
+ * None.
+ *
+ * Returns
+ * None.
+ *
+ */
+
+void DebuggerMethodInfo::DeleteJitInfoList(void)
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ Debugger::DebuggerDataLockHolder debuggerDataLockHolder(g_pDebugger);
+
+ while(m_latestJitInfo != NULL)
+ {
+ DeleteJitInfo(m_latestJitInfo);
+ }
+
+ // DebuggerDataLockHolder out of scope - release implied
+}
+
+
+// Iterate through all existing DJIs. See header for expected usage.
+DebuggerMethodInfo::DJIIterator::DJIIterator()
+{
+ LIMITED_METHOD_CONTRACT;
+
+ m_pCurrent = NULL;
+ m_pLoaderModuleFilter = NULL;
+}
+
+bool DebuggerMethodInfo::DJIIterator::IsAtEnd()
+{
+ LIMITED_METHOD_CONTRACT;
+
+ return m_pCurrent == NULL;
+}
+
+DebuggerJitInfo * DebuggerMethodInfo::DJIIterator::Current()
+{
+ LIMITED_METHOD_CONTRACT;
+
+ return m_pCurrent;
+}
+
+void DebuggerMethodInfo::DJIIterator::Next(BOOL fFirst /*=FALSE*/)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ FORBID_FAULT;
+ MODE_ANY;
+ CANNOT_TAKE_LOCK;
+ }
+ CONTRACTL_END;
+
+ if (!fFirst)
+ {
+ PREFIX_ASSUME(m_pCurrent != NULL); // IsAtEnd() should have caught this.
+ m_pCurrent = m_pCurrent->m_prevJitInfo;
+ }
+
+ // Check if we're at the end of the list, in which case we're done.
+ for ( ; m_pCurrent != NULL; m_pCurrent = m_pCurrent->m_prevJitInfo)
+ {
+ Module * pLoaderModule = m_pCurrent->m_pLoaderModule;
+
+ // Obey the module filter if it's provided
+ if ((m_pLoaderModuleFilter != NULL) && (m_pLoaderModuleFilter != pLoaderModule))
+ continue;
+
+ // Skip modules that are unloaded, but still hanging around. Note that we can't use DebuggerModule for this check
+ // because of it is deleted pretty early during unloading, and we do not want to recreate it.
+ if (pLoaderModule->GetLoaderAllocator()->IsUnloaded())
+ continue;
+
+ break;
+ }
+}
+
+
+/******************************************************************************
+ * Return true iff this method is jitted
+ ******************************************************************************/
+bool DebuggerMethodInfo::HasJitInfos()
+{
+ LIMITED_METHOD_CONTRACT;
+ _ASSERTE(g_pDebugger->HasDebuggerDataLock());
+ return (m_latestJitInfo != NULL);
+}
+
+/******************************************************************************
+ * Return true iff this has been EnCed since last time function was jitted.
+ ******************************************************************************/
+bool DebuggerMethodInfo::HasMoreRecentEnCVersion()
+{
+ LIMITED_METHOD_CONTRACT;
+ return ((m_latestJitInfo != NULL) &&
+ (m_currentEnCVersion > m_latestJitInfo->m_encVersion));
+}
+
+/******************************************************************************
+ * Updated the instrumented-IL map
+ ******************************************************************************/
+void DebuggerMethodInfo::SetInstrumentedILMap(COR_IL_MAP * pMap, SIZE_T cEntries)
+{
+ InstrumentedILOffsetMapping mapping;
+ mapping.SetMappingInfo(cEntries, pMap);
+
+ GetRuntimeModule()->SetInstrumentedILOffsetMapping(m_token, mapping);
+
+ m_fHasInstrumentedILMap = true;
+}
+
+/******************************************************************************
+ * Get the JMC status for a given function.
+ ******************************************************************************/
+bool DebuggerMethodInfo::IsJMCFunction()
+{
+ LIMITED_METHOD_CONTRACT;
+ return m_fJMCStatus;
+}
+
+/******************************************************************************
+ * Set the JMC status to a given value
+ ******************************************************************************/
+void DebuggerMethodInfo::SetJMCStatus(bool fStatus)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(g_pDebugger->HasDebuggerDataLock());
+
+ // First check if this is a no-op.
+ // Do this first b/c there may be some cases where we don't have a DebuggerModule
+ // yet but are still calling SetJMCStatus(false), like if we detach before attach is complete.
+ bool fOldStatus = IsJMCFunction();
+
+ if (fOldStatus == fStatus)
+ {
+ // if no change, then there's nothing to do.
+ LOG((LF_CORDB,LL_EVERYTHING, "DMI::SetJMCStatus: %p, keeping old status, %d\n", this, fStatus));
+ return;
+ }
+
+ // For a perf-optimization, our Module needs to know if it has any user
+ // code. If it doesn't, it shouldn't dispatch through the JMC probes.
+ // So modules keep a count of # of JMC functions - if the count is 0, the
+ // module can set is JMC probe flag to 0 and skip the JMC probes.
+ Module * pRuntimeModule = this->GetRuntimeModule();
+
+ // Update the module's count.
+ if (!fStatus)
+ {
+ LOG((LF_CORDB,LL_EVERYTHING, "DMI::SetJMCStatus: %p, changing to non-user code\n", this));
+ _ASSERTE(pRuntimeModule->HasAnyJMCFunctions());
+ pRuntimeModule->DecJMCFuncCount();
+ }
+ else
+ {
+ LOG((LF_CORDB,LL_EVERYTHING, "DMI::SetJMCStatus: %p, changing to user code\n", this));
+ pRuntimeModule->IncJMCFuncCount();
+ _ASSERTE(pRuntimeModule->HasAnyJMCFunctions());
+ }
+
+ m_fJMCStatus = fStatus;
+
+ // We should update our module's JMC status...
+ g_pDebugger->UpdateModuleJMCFlag(pRuntimeModule, DebuggerController::GetTotalMethodEnter() != 0);
+
+}
+
+// Get an iterator that will go through ALL native code-blobs (DJI) in the specified
+// AppDomain, optionally filtered by loader module (if pLoaderModuleFilter != NULL).
+// This is EnC/ Generics / Prejit aware.
+void DebuggerMethodInfo::IterateAllDJIs(AppDomain * pAppDomain, Module * pLoaderModuleFilter, DebuggerMethodInfo::DJIIterator * pEnum)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ THROWS;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(pEnum != NULL);
+ _ASSERTE(pAppDomain != NULL);
+
+ // Esnure we have DJIs for everything.
+ CreateDJIsForNativeBlobs(pAppDomain, pLoaderModuleFilter);
+
+ pEnum->m_pCurrent = m_latestJitInfo;
+ pEnum->m_pLoaderModuleFilter = pLoaderModuleFilter;
+
+ // Advance to the first DJI that passes the filter
+ pEnum->Next(TRUE);
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Bring the DJI cache up to date.
+//
+// Arguments:
+// * pAppDomain - Create DJIs only for this AppDomain
+// * pLoaderModuleFilter - If non-NULL, create DJIs only for MethodDescs whose
+// loader module matches this one. (This can be different from m_module in the
+// case of generics defined in one module and instantiated in another). If
+// non-NULL, create DJIs for all modules in pAppDomain.
+//
+
+void DebuggerMethodInfo::CreateDJIsForNativeBlobs(AppDomain * pAppDomain, Module * pLoaderModuleFilter /* = NULL */)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ THROWS;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ // If we're not stopped and the module we're iterating over allows types to load,
+ // then it's possible new native blobs are being created underneath us.
+ _ASSERTE(g_pDebugger->IsStopped() || ((pLoaderModuleFilter != NULL) && !pLoaderModuleFilter->IsReadyForTypeLoad()));
+
+ // @todo - we really only need to do this if the stop-counter goes up (else we know nothing new is added).
+ // B/c of generics, it's possible that new instantiations of a method may have been jitted.
+ // So just loop through all known instantiations and ensure that we have all the DJIs.
+ // Note that this iterator won't show previous EnC versions, but we're already guaranteed to
+ // have DJIs for every verision of a method that was EnCed.
+ // This also handles the possibility of getting the same methoddesc back from the iterator.
+ // It also lets EnC + generics play nice together (including if an generic method was EnC-ed)
+ LoadedMethodDescIterator it(pAppDomain, m_module, m_token);
+ CollectibleAssemblyHolder<DomainAssembly *> pDomainAssembly;
+ while (it.Next(pDomainAssembly.This()))
+ {
+ MethodDesc * pDesc = it.Current();
+ if (!pDesc->HasNativeCode())
+ {
+ continue;
+ }
+
+ Module * pLoaderModule = pDesc->GetLoaderModule();
+
+ // Obey the module filter if it's provided
+ if ((pLoaderModuleFilter != NULL) && (pLoaderModuleFilter != pLoaderModule))
+ continue;
+
+ // Skip modules that are unloaded, but still hanging around. Note that we can't use DebuggerModule for this check
+ // because of it is deleted pretty early during unloading, and we do not want to recreate it.
+ if (pLoaderModule->GetLoaderAllocator()->IsUnloaded())
+ continue;
+
+ // We just ask for the DJI to ensure that it's lazily created.
+ // This should only fail in an oom scenario.
+ DebuggerJitInfo * djiTest = g_pDebugger->GetLatestJitInfoFromMethodDesc(pDesc);
+ if (djiTest == NULL)
+ {
+ // We're oom. Give up.
+ ThrowOutOfMemory();
+ return;
+ }
+ }
+}
+
+/*
+ * GetLatestJitInfo
+ *
+ * This routine returns the lastest DJI we have for a particular DMI.
+ * DJIs are lazily created.
+ * Parameters:
+ * None.
+ *
+ * Returns
+ * a possibly NULL pointer to a DJI.
+ *
+ */
+
+// For logging and other internal purposes, provide a non-initializing accessor.
+DebuggerJitInfo* DebuggerMethodInfo::GetLatestJitInfo_NoCreate()
+{
+ return m_latestJitInfo;
+}
+
+
+DebuggerMethodInfoTable::DebuggerMethodInfoTable() : CHashTableAndData<CNewZeroData>(101)
+{
+ CONTRACTL
+ {
+ WRAPPER(THROWS);
+ GC_NOTRIGGER;
+
+ CONSTRUCTOR_CHECK;
+ }
+ CONTRACTL_END;
+
+ SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE;
+ HRESULT hr = NewInit(101, sizeof(DebuggerMethodInfoEntry), 101);
+
+ if (FAILED(hr))
+ {
+ ThrowWin32(hr);
+ }
+}
+
+HRESULT DebuggerMethodInfoTable::AddMethodInfo(Module *pModule,
+ mdMethodDef token,
+ DebuggerMethodInfo *mi)
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_NOTRIGGER;
+
+ INSTANCE_CHECK;
+ PRECONDITION(CheckPointer(pModule));
+ PRECONDITION(CheckPointer(mi));
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_INFO1000, "DMIT::AMI Adding dmi:0x%x Mod:0x%x tok:"
+ "0x%x nVer:0x%x\n", mi, pModule, token, mi->GetCurrentEnCVersion()));
+
+ _ASSERTE(mi != NULL);
+
+ _ASSERTE(g_pDebugger->HasDebuggerDataLock());
+
+ HRESULT hr = OverwriteMethodInfo(pModule, token, mi, TRUE);
+ if (hr == S_OK)
+ return hr;
+
+ DebuggerMethodInfoKey dmik;
+ dmik.pModule = pModule;
+ dmik.token = token;
+
+ DebuggerMethodInfoEntry *dmie =
+ (DebuggerMethodInfoEntry *) Add(HASH(&dmik));
+
+ if (dmie != NULL)
+ {
+ dmie->key.pModule = pModule;
+ dmie->key.token = token;
+ dmie->mi = mi;
+
+ LOG((LF_CORDB, LL_INFO1000, "DMIT::AJI: mod:0x%x tok:0%x ",
+ pModule, token));
+ return S_OK;
+ }
+
+ ThrowOutOfMemory();
+ return S_OK;
+}
+
+HRESULT DebuggerMethodInfoTable::OverwriteMethodInfo(Module *pModule,
+ mdMethodDef token,
+ DebuggerMethodInfo *mi,
+ BOOL fOnlyIfNull)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+
+ PRECONDITION(CheckPointer(pModule));
+ PRECONDITION(CheckPointer(mi));
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_INFO1000, "DMIT::OJI: dmi:0x%x mod:0x%x tok:0x%x\n", mi,
+ pModule, token));
+
+ _ASSERTE(g_pDebugger->HasDebuggerDataLock());
+
+ DebuggerMethodInfoKey dmik;
+ dmik.pModule = pModule;
+ dmik.token = token;
+
+ DebuggerMethodInfoEntry *entry
+ = (DebuggerMethodInfoEntry *) Find(HASH(&dmik), KEY(&dmik));
+ if (entry != NULL)
+ {
+ if ( (fOnlyIfNull &&
+ entry->mi == NULL) ||
+ !fOnlyIfNull)
+ {
+ entry->mi = mi;
+
+ LOG((LF_CORDB, LL_INFO1000, "DMIT::OJI: mod:0x%x tok:0x%x remap"
+ "nVer:0x%x\n", pModule, token, entry->nVersionLastRemapped));
+ return S_OK;
+ }
+ }
+
+ return E_FAIL;
+}
+
+// pModule is being destroyed - remove any entries that belong to it. Why?
+// (a) Correctness: the module can be reloaded at the same address,
+// which will cause accidental matches with our hashtable (indexed by
+// {Module*,mdMethodDef}
+// (b) Perf: don't waste the memory!
+void DebuggerMethodInfoTable::ClearMethodsOfModule(Module *pModule)
+{
+ WRAPPER_NO_CONTRACT;
+
+ _ASSERTE(g_pDebugger->HasDebuggerDataLock());
+
+ LOG((LF_CORDB, LL_INFO1000000, "CMOM:mod:0x%x (%S)\n", pModule
+ ,pModule->GetDebugName()));
+
+ HASHFIND info;
+
+ DebuggerMethodInfoEntry *entry
+ = (DebuggerMethodInfoEntry *) FindFirstEntry(&info);
+ while(entry != NULL)
+ {
+ Module *pMod = entry->key.pModule ;
+ if (pMod == pModule)
+ {
+ // This method actually got mitted, at least
+ // once - remove all version info.
+ while(entry->mi != NULL)
+ {
+ DeleteEntryDMI(entry);
+ }
+
+ Delete(HASH(&(entry->key)), (HASHENTRY*)entry);
+ }
+ else
+ {
+ //
+ // Delete generic DJIs that have lifetime attached to this module
+ //
+ DebuggerMethodInfo * dmi = entry->mi;
+ while (dmi != NULL)
+ {
+ DebuggerJitInfo * dji = dmi->GetLatestJitInfo_NoCreate();
+ while (dji != NULL)
+ {
+ DebuggerJitInfo * djiPrev = dji->m_prevJitInfo;;
+
+ if (dji->m_pLoaderModule == pModule)
+ dmi->DeleteJitInfo(dji);
+
+ dji = djiPrev;
+ }
+
+ dmi = dmi->m_prevMethodInfo;
+ }
+ }
+
+ entry = (DebuggerMethodInfoEntry *) FindNextEntry(&info);
+ }
+}
+
+void DebuggerMethodInfoTable::DeleteEntryDMI(DebuggerMethodInfoEntry *entry)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ CAN_TAKE_LOCK; // DeleteInteropSafe() eventually calls DebuggerMethodInfo::DeleteJitInfoList
+ // which locks.
+ }
+ CONTRACTL_END;
+
+ DebuggerMethodInfo *dmiPrev = entry->mi->m_prevMethodInfo;
+ TRACE_FREE(entry->mi);
+ DeleteInteropSafe(entry->mi);
+ entry->mi = dmiPrev;
+ if ( dmiPrev != NULL )
+ dmiPrev->m_nextMethodInfo = NULL;
+}
+
+#endif // #ifndef DACCESS_COMPILE
+
+DebuggerJitInfo *DebuggerJitInfo::GetJitInfoByAddress(const BYTE *pbAddr )
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ DebuggerJitInfo *dji = this;
+
+#ifdef LOGGING
+ LOG((LF_CORDB,LL_INFO10000,"DJI:GJIBA finding DJI "
+ "corresponding to addr 0x%p, starting with 0x%p\n", pbAddr, dji));
+#endif //LOGGING
+
+ // If it's not NULL, but not in the range m_addrOfCode to end of function,
+ // then get the previous one.
+ while( dji != NULL &&
+ !CodeRegionInfo::GetCodeRegionInfo(dji).IsMethodAddress(pbAddr))
+ {
+ LOG((LF_CORDB,LL_INFO10000,"DJI:GJIBA: pbAddr 0x%p is not in code "
+ "0x%p (size:0x%p)\n", pbAddr, dji->m_addrOfCode,
+ dji->m_sizeOfCode));
+ dji = dji->m_prevJitInfo;
+ }
+
+#ifdef LOGGING
+ if (dji == NULL)
+ {
+ LOG((LF_CORDB,LL_INFO10000,"DJI:GJIBA couldn't find a DJI "
+ "corresponding to addr 0x%p\n", pbAddr));
+ }
+#endif //LOGGING
+ return dji;
+}
+
+PTR_DebuggerJitInfo DebuggerMethodInfo::GetLatestJitInfo(MethodDesc *mdesc)
+{
+ // dac checks ngen'ed image content first, so
+ // only check for existing JIT info.
+#ifndef DACCESS_COMPILE
+
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ THROWS;
+ CALLED_IN_DEBUGGERDATALOCK_HOLDER_SCOPE_MAY_GC_TRIGGERS_CONTRACT;
+ PRECONDITION(!g_pDebugger->HasDebuggerDataLock());
+ }
+ CONTRACTL_END;
+
+
+ if (m_latestJitInfo && m_latestJitInfo->m_fd == mdesc && !m_latestJitInfo->m_fd->HasClassOrMethodInstantiation())
+ return m_latestJitInfo;
+
+ // This ensures that there is an entry in the DJI list for this particular MethodDesc.
+ // in the case of generic code it may not be the first entry in the list.
+ FindOrCreateInitAndAddJitInfo(mdesc);
+
+#endif // #ifndef DACCESS_COMPILE
+
+ return m_latestJitInfo;
+}
+
+DebuggerMethodInfo *DebuggerMethodInfoTable::GetMethodInfo(Module *pModule, mdMethodDef token)
+{
+ WRAPPER_NO_CONTRACT;
+ SUPPORTS_DAC;
+
+ // CHECK_DMI_TABLE;
+
+ // @review. One of the BVTs causes this to be called before the table is initialized
+ // In particular, the changes to BREAKPOINT_ADD mean that this table is now consulted
+ // to determine if we have ever seen the method, rather than a call to LookupMethodDesc,
+ // which would have just returned NULL. In general it seems OK to consult this table
+ // when it is empty, so I've added this....
+ if (this == NULL)
+ return NULL;
+
+ DebuggerMethodInfoKey dmik;
+ dmik.pModule = dac_cast<PTR_Module>(pModule);
+ dmik.token = token;
+
+ DebuggerMethodInfoEntry *entry = dac_cast<PTR_DebuggerMethodInfoEntry>(Find(HASH(&dmik), KEY(&dmik)));
+
+ if (entry == NULL )
+ {
+ return NULL;
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO1000, "DMI::GMI: for methodDef 0x%x, got 0x%x prev:0x%x\n",
+ token, entry->mi, (entry->mi?entry->mi->m_prevMethodInfo:0)));
+ return entry->mi;
+ }
+}
+
+
+DebuggerMethodInfo *DebuggerMethodInfoTable::GetFirstMethodInfo(HASHFIND *info)
+{
+ CONTRACT(DebuggerMethodInfo*)
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+
+ PRECONDITION(CheckPointer(info));
+ POSTCONDITION(CheckPointer(RETVAL, NULL_OK));
+ }
+ CONTRACT_END;
+
+ _ASSERTE(g_pDebugger->HasDebuggerDataLock());
+
+ DebuggerMethodInfoEntry *entry = PTR_DebuggerMethodInfoEntry
+ (PTR_HOST_TO_TADDR(FindFirstEntry(info)));
+ if (entry == NULL)
+ RETURN NULL;
+ else
+ RETURN entry->mi;
+}
+
+DebuggerMethodInfo *DebuggerMethodInfoTable::GetNextMethodInfo(HASHFIND *info)
+{
+ CONTRACT(DebuggerMethodInfo*)
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+
+ PRECONDITION(CheckPointer(info));
+ POSTCONDITION(CheckPointer(RETVAL, NULL_OK));
+ }
+ CONTRACT_END;
+
+ _ASSERTE(g_pDebugger->HasDebuggerDataLock());
+
+ DebuggerMethodInfoEntry *entry = PTR_DebuggerMethodInfoEntry
+ (PTR_HOST_TO_TADDR(FindNextEntry(info)));
+
+ // We may have incremented the version number
+ // for methods that never got JITted, so we should
+ // pretend like they don't exist here.
+ while (entry != NULL &&
+ entry->mi == NULL)
+ {
+ entry = PTR_DebuggerMethodInfoEntry
+ (PTR_HOST_TO_TADDR(FindNextEntry(info)));
+ }
+
+ if (entry == NULL)
+ RETURN NULL;
+ else
+ RETURN entry->mi;
+}
+
+
+
+#ifdef DACCESS_COMPILE
+void
+DebuggerMethodInfoEntry::EnumMemoryRegions(CLRDataEnumMemoryFlags flags)
+{
+ SUPPORTS_DAC;
+
+ // This structure is in an array in the hash
+ // so the 'this' is implicitly enumerated by the
+ // array enum in CHashTable.
+
+ // For a MiniDumpNormal, what is needed for modules is already enumerated elsewhere.
+ // Don't waste time doing it here an extra time. Also, this will add many MB extra into the dump.
+ if ((key.pModule.IsValid()) &&
+ CLRDATA_ENUM_MEM_MINI != flags
+ && CLRDATA_ENUM_MEM_TRIAGE != flags)
+ {
+ key.pModule->EnumMemoryRegions(flags, true);
+ }
+
+ while (mi.IsValid())
+ {
+ mi->EnumMemoryRegions(flags);
+ mi = mi->m_prevMethodInfo;
+ }
+}
+
+void
+DebuggerMethodInfo::EnumMemoryRegions(CLRDataEnumMemoryFlags flags)
+{
+ DAC_ENUM_DTHIS();
+ SUPPORTS_DAC;
+
+ if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE)
+ {
+ // Modules are enumerated already for minidumps, save the empty calls.
+ if (m_module.IsValid())
+ {
+ m_module->EnumMemoryRegions(flags, true);
+ }
+
+ }
+
+ PTR_DebuggerJitInfo jitInfo = m_latestJitInfo;
+ while (jitInfo.IsValid())
+ {
+ jitInfo->EnumMemoryRegions(flags);
+ jitInfo = jitInfo->m_prevJitInfo;
+ }
+}
+
+void
+DebuggerJitInfo::EnumMemoryRegions(CLRDataEnumMemoryFlags flags)
+{
+ DAC_ENUM_DTHIS();
+ SUPPORTS_DAC;
+
+ if (m_methodInfo.IsValid())
+ {
+ m_methodInfo->EnumMemoryRegions(flags);
+ }
+
+ if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE)
+ {
+ if (m_fd.IsValid())
+ {
+ m_fd->EnumMemoryRegions(flags);
+ }
+
+ DacEnumMemoryRegion(PTR_TO_TADDR(GetSequenceMap()),
+ GetSequenceMapCount() * sizeof(DebuggerILToNativeMap));
+ DacEnumMemoryRegion(PTR_TO_TADDR(GetVarNativeInfo()),
+ GetVarNativeInfoCount() *
+ sizeof(ICorDebugInfo::NativeVarInfo));
+ }
+}
+
+
+void DebuggerMethodInfoTable::EnumMemoryRegions(CLRDataEnumMemoryFlags flags)
+{
+ WRAPPER_NO_CONTRACT;
+
+ DAC_ENUM_VTHIS();
+ CHashTableAndData<CNewZeroData>::EnumMemoryRegions(flags);
+
+ for (ULONG i = 0; i < m_iEntries; i++)
+ {
+ DebuggerMethodInfoEntry* entry =
+ PTR_DebuggerMethodInfoEntry(PTR_HOST_TO_TADDR(EntryPtr(i)));
+ entry->EnumMemoryRegions(flags);
+ }
+}
+#endif // #ifdef DACCESS_COMPILE
diff --git a/src/debug/ee/i386/.gitmirror b/src/debug/ee/i386/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/debug/ee/i386/.gitmirror
@@ -0,0 +1 @@
+Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror. \ No newline at end of file
diff --git a/src/debug/ee/i386/dbghelpers.asm b/src/debug/ee/i386/dbghelpers.asm
new file mode 100644
index 0000000000..b547c8bc18
--- /dev/null
+++ b/src/debug/ee/i386/dbghelpers.asm
@@ -0,0 +1,100 @@
+; 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.
+
+; ==++==
+;
+
+;
+; ==--==
+;
+; *** NOTE: If you make changes to this file, propagate the changes to
+; dbghelpers.s in this directory
+
+ .586
+ .model flat
+ .code
+
+ extern _FuncEvalHijackWorker@4:PROC
+
+; @dbgtodo- once we port Funceval, use the ExceptionHijack stub instead of this func-eval stub.
+;
+; This is the method that we hijack a thread running managed code. It calls
+; FuncEvalHijackWorker, which actually performs the func eval, then jumps to
+; the patch address so we can complete the cleanup.
+;
+; Note: the parameter is passed in eax - see Debugger::FuncEvalSetup for
+; details
+;
+_FuncEvalHijack@0 proc public
+ push eax ; the ptr to the DebuggerEval
+ call _FuncEvalHijackWorker@4
+ jmp eax ; return is the patch addresss to jmp to
+_FuncEvalHijack@0 endp
+
+
+
+;
+; Flares for interop debugging.
+; Flares are exceptions (breakpoints) at well known addresses which the RS
+; listens for when interop debugging.
+;
+
+; This exception is from managed code.
+_SignalHijackStartedFlare@0 proc public
+ int 3
+ ; make sure that the basic block is unique
+ test eax,1
+ ret
+_SignalHijackStartedFlare@0 endp
+
+; Start the handoff
+_ExceptionForRuntimeHandoffStartFlare@0 proc public
+ int 3
+ ; make sure that the basic block is unique
+ test eax,2
+ ret
+_ExceptionForRuntimeHandoffStartFlare@0 endp
+
+; Finish the handoff.
+_ExceptionForRuntimeHandoffCompleteFlare@0 proc public
+ int 3
+ ; make sure that the basic block is unique
+ test eax,3
+ ret
+_ExceptionForRuntimeHandoffCompleteFlare@0 endp
+
+; Return thread to pre-hijacked context.
+_SignalHijackCompleteFlare@0 proc public
+ int 3
+ ; make sure that the basic block is unique
+ test eax,4
+ ret
+_SignalHijackCompleteFlare@0 endp
+
+; This exception is from unmanaged code.
+_ExceptionNotForRuntimeFlare@0 proc public
+ int 3
+ ; make sure that the basic block is unique
+ test eax,5
+ ret
+_ExceptionNotForRuntimeFlare@0 endp
+
+; The Runtime is synchronized.
+_NotifyRightSideOfSyncCompleteFlare@0 proc public
+ int 3
+ ; make sure that the basic block is unique
+ test eax,6
+ ret
+_NotifyRightSideOfSyncCompleteFlare@0 endp
+
+
+
+; This goes at the end of the assembly file
+ end
+
+
+
+
+
+
diff --git a/src/debug/ee/i386/debuggerregdisplayhelper.cpp b/src/debug/ee/i386/debuggerregdisplayhelper.cpp
new file mode 100644
index 0000000000..576fdeb592
--- /dev/null
+++ b/src/debug/ee/i386/debuggerregdisplayhelper.cpp
@@ -0,0 +1,18 @@
+// 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.
+/* ------------------------------------------------------------------------- *
+ * DebuggerRegDisplayHelper.cpp -- implementation of the platform-dependent
+//
+
+ * methods for transferring information between
+ * REGDISPLAY and DebuggerREGDISPLAY
+ * ------------------------------------------------------------------------- */
+
+#include "stdafx.h"
+
+
+void CopyREGDISPLAY(REGDISPLAY* pDst, REGDISPLAY* pSrc)
+{
+ *pDst = *pSrc;
+}
diff --git a/src/debug/ee/i386/primitives.cpp b/src/debug/ee/i386/primitives.cpp
new file mode 100644
index 0000000000..e4d3c6cb76
--- /dev/null
+++ b/src/debug/ee/i386/primitives.cpp
@@ -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.
+
+//
+
+#include "stdafx.h"
+
+#include "../../shared/i386/primitives.cpp"
+
+
diff --git a/src/debug/ee/i386/x86walker.cpp b/src/debug/ee/i386/x86walker.cpp
new file mode 100644
index 0000000000..dd0346889f
--- /dev/null
+++ b/src/debug/ee/i386/x86walker.cpp
@@ -0,0 +1,500 @@
+// 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.
+//*****************************************************************************
+// File: x86walker.cpp
+//
+
+//
+// x86 instruction decoding/stepping logic
+//
+//*****************************************************************************
+
+#include "stdafx.h"
+
+#include "walker.h"
+
+#include "frames.h"
+#include "openum.h"
+
+
+#ifdef _TARGET_X86_
+
+//
+// The x86 walker is currently pretty minimal. It only recognizes call and return opcodes, plus a few jumps. The rest
+// is treated as unknown.
+//
+void NativeWalker::Decode()
+{
+ const BYTE *ip = m_ip;
+
+ m_type = WALK_UNKNOWN;
+ m_skipIP = NULL;
+ m_nextIP = NULL;
+
+ LOG((LF_CORDB, LL_INFO100000, "NW:Decode: m_ip 0x%x\n", m_ip));
+ //
+ // Skip instruction prefixes
+ //
+ do
+ {
+ switch (*ip)
+ {
+ // Segment overrides
+ case 0x26: // ES
+ case 0x2E: // CS
+ case 0x36: // SS
+ case 0x3E: // DS
+ case 0x64: // FS
+ case 0x65: // GS
+
+ // Size overrides
+ case 0x66: // Operand-Size
+ case 0x67: // Address-Size
+
+ // Lock
+ case 0xf0:
+
+ // String REP prefixes
+ case 0xf1:
+ case 0xf2: // REPNE/REPNZ
+ case 0xf3:
+ LOG((LF_CORDB, LL_INFO10000, "NW:Decode: prefix:%0.2x ", *ip));
+ ip++;
+ continue;
+
+ default:
+ break;
+ }
+ } while (0);
+
+ // Read the opcode
+ m_opcode = *ip++;
+
+ LOG((LF_CORDB, LL_INFO100000, "NW:Decode: ip 0x%x, m_opcode:%0.2x\n", ip, m_opcode));
+
+ if (m_opcode == 0xcc)
+ {
+ m_opcode = DebuggerController::GetPatchedOpcode(m_ip);
+ LOG((LF_CORDB, LL_INFO100000, "NW:Decode after patch look up: m_opcode:%0.2x\n", m_opcode));
+ }
+
+ // Analyze what we can of the opcode
+ switch (m_opcode)
+ {
+ case 0xff:
+ {
+
+ BYTE modrm = *ip++;
+ BYTE mod = (modrm & 0xC0) >> 6;
+ BYTE reg = (modrm & 0x38) >> 3;
+ BYTE rm = (modrm & 0x07);
+
+ BYTE *result = 0;
+ WORD displace = 0;
+
+ if ((reg != 2) && (reg != 3) && (reg != 4) && (reg != 5)) {
+ //
+ // This is not a CALL or JMP instruction, return, unknown.
+ //
+ return;
+ }
+
+
+ if (m_registers != NULL)
+ {
+ // Only try to decode registers if we actually have reg sets.
+ switch (mod) {
+ case 0:
+ case 1:
+ case 2:
+
+ if (rm == 4) {
+
+ //
+ // Get values from the SIB byte
+ //
+ BYTE ss = (*ip & 0xC0) >> 6;
+ BYTE index = (*ip & 0x38) >> 3;
+ BYTE base = (*ip & 0x7);
+
+ ip++;
+
+ //
+ // Get starting value
+ //
+ if ((mod == 0) && (base == 5)) {
+ result = 0;
+ } else {
+ result = (BYTE *)(size_t)GetRegisterValue(base);
+ }
+
+ //
+ // Add in the [index]
+ //
+ if (index != 0x4) {
+ result = result + (GetRegisterValue(index) << ss);
+ }
+
+ //
+ // Finally add in the offset
+ //
+ if (mod == 0) {
+
+ if (base == 5) {
+ result = result + *((unsigned int *)ip);
+ displace = 7;
+ } else {
+ displace = 3;
+ }
+
+ } else if (mod == 1) {
+
+ result = result + *((char *)ip);
+ displace = 4;
+
+ } else { // == 2
+
+ result = result + *((unsigned int *)ip);
+ displace = 7;
+
+ }
+
+ } else {
+
+ //
+ // Get the value we need from the register.
+ //
+
+ if ((mod == 0) && (rm == 5)) {
+ result = 0;
+ } else {
+ result = (BYTE *)GetRegisterValue(rm);
+ }
+
+ if (mod == 0) {
+
+ if (rm == 5) {
+ result = result + *((unsigned int *)ip);
+ displace = 6;
+ } else {
+ displace = 2;
+ }
+
+ } else if (mod == 1) {
+
+ result = result + *((char *)ip);
+ displace = 3;
+
+ } else { // == 2
+
+ result = result + *((unsigned int *)ip);
+ displace = 6;
+
+ }
+
+ }
+
+ //
+ // Now dereference thru the result to get the resulting IP.
+ //
+
+ // If result is bad, then this means we can't predict what the nextIP will be.
+ // That's ok - we just leave m_nextIp=NULL. We can still provide callers
+ // with the proper walk-type.
+ // In practice, this shouldn't happen unless the jit emits bad opcodes.
+ if (result != NULL)
+ {
+ result = (BYTE *)(*((unsigned int *)result));
+ }
+
+ break;
+
+ case 3:
+ default:
+
+ result = (BYTE *)GetRegisterValue(rm);
+ displace = 2;
+ break;
+
+ }
+ } // have registers
+
+ if ((reg == 2) || (reg == 3)) {
+ m_type = WALK_CALL;
+ } else if ((reg == 4) || (reg == 5)) {
+ m_type = WALK_BRANCH;
+ } else {
+ break;
+ }
+
+ if (m_registers != NULL)
+ {
+ m_nextIP = result;
+ m_skipIP = m_ip + displace;
+ }
+
+ break;
+ } // end of 0xFF case
+
+ case 0xe8:
+ {
+ m_type = WALK_CALL;
+
+ UINT32 disp = *((UINT32*)ip);
+ m_nextIP = ip + 4 + disp;
+ m_skipIP = ip + 4;
+
+ break;
+ }
+
+ case 0xe9:
+ {
+ m_type = WALK_BRANCH;
+
+ INT32 disp = *((INT32*)ip);
+ m_nextIP = ip + 4 + disp;
+ m_skipIP = ip + 4;
+
+ break;
+ }
+
+ case 0x9a:
+ m_type = WALK_CALL;
+
+ m_nextIP = (BYTE*) *((UINT32*)ip);
+ m_skipIP = ip + 4;
+
+ break;
+
+ case 0xc2:
+ case 0xc3:
+ case 0xca:
+ case 0xcb:
+ m_type = WALK_RETURN;
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+//
+// Given a regdisplay and a register number, return the value of the register.
+//
+
+DWORD NativeWalker::GetRegisterValue(int registerNumber)
+{
+ // If we're going to decode a register, then we'd better have a valid register set.
+ PREFIX_ASSUME(m_registers != NULL);
+
+ switch (registerNumber)
+ {
+ case 0:
+ return *m_registers->pEax;
+ break;
+ case 1:
+ return *m_registers->pEcx;
+ break;
+ case 2:
+ return *m_registers->pEdx;
+ break;
+ case 3:
+ return *m_registers->pEbx;
+ break;
+ case 4:
+ return m_registers->Esp;
+ break;
+ case 5:
+ return *m_registers->pEbp;
+ break;
+ case 6:
+ return *m_registers->pEsi;
+ break;
+ case 7:
+ return *m_registers->pEdi;
+ break;
+ default:
+ _ASSERTE(!"Invalid register number!");
+ }
+
+ return 0;
+}
+
+
+// static
+void NativeWalker::DecodeInstructionForPatchSkip(const BYTE *address, InstructionAttribute * pInstrAttrib)
+{
+ //
+ // Skip instruction prefixes
+ //
+
+ LOG((LF_CORDB, LL_INFO10000, "Patch decode: "));
+
+ if (pInstrAttrib == NULL)
+ return;
+
+ const BYTE * origAddr = address;
+
+ do
+ {
+ switch (*address)
+ {
+ // Segment overrides
+ case 0x26: // ES
+ case 0x2E: // CS
+ case 0x36: // SS
+ case 0x3E: // DS
+ case 0x64: // FS
+ case 0x65: // GS
+
+ // Size overrides
+ case 0x66: // Operand-Size
+ case 0x67: // Address-Size
+
+ // Lock
+ case 0xf0:
+
+ // String REP prefixes
+ case 0xf2: // REPNE/REPNZ
+ case 0xf3:
+ LOG((LF_CORDB, LL_INFO10000, "prefix:%0.2x ", *address));
+ address++;
+ continue;
+
+ default:
+ break;
+ }
+ } while (0);
+
+ // There can be at most 4 prefixes.
+ _ASSERTE(((address - origAddr) <= 4));
+
+ //
+ // Look at opcode to tell if it's a call or an
+ // absolute branch.
+ //
+
+ pInstrAttrib->Reset();
+
+ // Note that we only care about m_cbInstr, m_cbDisp, and m_dwOffsetToDisp for relative branches
+ // (either call or jump instructions).
+
+ switch (*address)
+ {
+ case 0xEA: // JMP far
+ case 0xC2: // RET
+ case 0xC3: // RET N
+ pInstrAttrib->m_fIsAbsBranch = true;
+ LOG((LF_CORDB, LL_INFO10000, "ABS:%0.2x\n", *address));
+ break;
+
+ case 0xE8: // CALL relative
+ pInstrAttrib->m_fIsCall = true;
+ pInstrAttrib->m_fIsRelBranch = true;
+ LOG((LF_CORDB, LL_INFO10000, "CALL REL:%0.2x\n", *address));
+
+ address += 1;
+ pInstrAttrib->m_cbDisp = 4;
+ break;
+
+ case 0xC8: // ENTER
+ pInstrAttrib->m_fIsCall = true;
+ pInstrAttrib->m_fIsAbsBranch = true;
+ LOG((LF_CORDB, LL_INFO10000, "CALL ABS:%0.2x\n", *address));
+ break;
+
+ case 0xFF: // CALL/JMP modr/m
+
+ //
+ // Read opcode modifier from modr/m
+ //
+
+ switch ((address[1]&0x38)>>3)
+ {
+ case 2:
+ case 3:
+ pInstrAttrib->m_fIsCall = true;
+ // fall through
+ case 4:
+ case 5:
+ pInstrAttrib->m_fIsAbsBranch = true;
+ }
+ LOG((LF_CORDB, LL_INFO10000, "CALL/JMP modr/m:%0.2x\n", *address));
+ break;
+
+ case 0x9A: // CALL ptr16:32
+ pInstrAttrib->m_fIsCall = true;
+ pInstrAttrib->m_fIsAbsBranch = true;
+ break;
+
+ case 0xEB: // JMP rel8
+ pInstrAttrib->m_fIsRelBranch = true;
+
+ address += 1;
+ pInstrAttrib->m_cbDisp = 1;
+ break;
+
+ case 0xE9: // JMP rel32
+ pInstrAttrib->m_fIsRelBranch = true;
+
+ address += 1;
+ pInstrAttrib->m_cbDisp = 4;
+ break;
+
+ case 0x0F: // Jcc (conditional jump)
+ // If the second opcode byte is betwen 0x80 and 0x8F, then it's a conditional jump.
+ // Conditional jumps are always relative.
+ if ((address[1] & 0xF0) == 0x80)
+ {
+ pInstrAttrib->m_fIsCond = true;
+ pInstrAttrib->m_fIsRelBranch = true;
+
+ address += 2; // 2-byte opcode
+ pInstrAttrib->m_cbDisp = 4;
+ }
+ break;
+
+ case 0x70:
+ case 0x71:
+ case 0x72:
+ case 0x73:
+ case 0x74:
+ case 0x75:
+ case 0x76:
+ case 0x77:
+ case 0x78:
+ case 0x79:
+ case 0x7A:
+ case 0x7B:
+ case 0x7C:
+ case 0x7D:
+ case 0x7E:
+ case 0x7F: // Jcc (conditional jump)
+ case 0xE3: // JCXZ/JECXZ (jump on CX/ECX zero)
+ pInstrAttrib->m_fIsCond = true;
+ pInstrAttrib->m_fIsRelBranch = true;
+
+ address += 1;
+ pInstrAttrib->m_cbDisp = 1;
+ break;
+
+ default:
+ LOG((LF_CORDB, LL_INFO10000, "NORMAL:%0.2x\n", *address));
+ }
+
+ // Get additional information for relative branches.
+ if (pInstrAttrib->m_fIsRelBranch)
+ {
+ _ASSERTE(pInstrAttrib->m_cbDisp != 0);
+ pInstrAttrib->m_dwOffsetToDisp = (address - origAddr);
+
+ // Relative jump and call instructions don't use the SIB byte, and there is no immediate value.
+ // So the instruction size is just the offset to the displacement plus the size of the displacement.
+ pInstrAttrib->m_cbInstr = pInstrAttrib->m_dwOffsetToDisp + pInstrAttrib->m_cbDisp;
+ }
+}
+
+
+#endif
diff --git a/src/debug/ee/rcthread.cpp b/src/debug/ee/rcthread.cpp
new file mode 100644
index 0000000000..d4e707dd06
--- /dev/null
+++ b/src/debug/ee/rcthread.cpp
@@ -0,0 +1,2142 @@
+// 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.
+//*****************************************************************************
+// File: RCThread.cpp
+//
+
+//
+// Runtime Controller Thread
+//
+//*****************************************************************************
+
+#include "stdafx.h"
+
+
+#include "securitywrapper.h"
+#include <aclapi.h>
+#include <hosting.h>
+
+#include "ipcmanagerinterface.h"
+#include "eemessagebox.h"
+#include "genericstackprobe.h"
+
+#ifndef SM_REMOTESESSION
+#define SM_REMOTESESSION 0x1000
+#endif
+
+#include <limits.h>
+
+#ifdef _DEBUG
+// Declare statics
+EEThreadId DebuggerRCThread::s_DbgHelperThreadId;
+#endif
+
+//
+// Constructor
+//
+DebuggerRCThread::DebuggerRCThread(Debugger * pDebugger)
+ : m_debugger(pDebugger),
+ m_pDCB(NULL),
+ m_thread(NULL),
+ m_run(true),
+ m_threadControlEvent(NULL),
+ m_helperThreadCanGoEvent(NULL),
+ m_fDetachRightSide(false)
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ WRAPPER(THROWS);
+ GC_NOTRIGGER;
+ CONSTRUCTOR_CHECK;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(pDebugger != NULL);
+
+ for( int i = 0; i < IPC_TARGET_COUNT;i++)
+ {
+ m_rgfInitRuntimeOffsets[i] = true;
+ }
+
+ // Initialize this here because we Destroy it in the DTOR.
+ // Note that this function can't fail.
+}
+
+
+//
+// Destructor. Cleans up all of the open handles the RC thread uses.
+// This expects that the RC thread has been stopped and has terminated
+// before being called.
+//
+DebuggerRCThread::~DebuggerRCThread()
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ NOTHROW;
+ GC_NOTRIGGER;
+ DESTRUCTOR_CHECK;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB,LL_INFO1000, "DebuggerRCThread::~DebuggerRCThread\n"));
+
+ // We explicitly leak the debugger object on shutdown. See Debugger::StopDebugger for details.
+ _ASSERTE(!"RCThread dtor should not be called.");
+}
+
+
+
+//---------------------------------------------------------------------------------------
+//
+// Close the IPC events associated with a debugger connection
+//
+// Notes:
+// The only IPC connection supported is OOP.
+//
+//---------------------------------------------------------------------------------------
+void DebuggerRCThread::CloseIPCHandles()
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ if( m_pDCB != NULL)
+ {
+ m_pDCB->m_rightSideProcessHandle.Close();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Helper to get the proper decorated name
+// Caller ensures that pBufSize is large enough. We'll assert just to check,
+// but no runtime failure.
+// pBuf - the output buffer to write the decorated name in
+// cBufSizeInChars - the size of the buffer in characters, including the null.
+// pPrefx - The undecorated name of the event.
+//-----------------------------------------------------------------------------
+void GetPidDecoratedName(__out_ecount(cBufSizeInChars) WCHAR * pBuf,
+ int cBufSizeInChars,
+ const WCHAR * pPrefix)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ DWORD pid = GetCurrentProcessId();
+
+ GetPidDecoratedName(pBuf, cBufSizeInChars, pPrefix, pid);
+}
+
+
+
+
+//-----------------------------------------------------------------------------
+// Simple wrapper to create win32 events.
+// This helps make DebuggerRCThread::Init pretty, beccause we
+// create lots of events there.
+// These will either:
+// 1) Create/Open and return an event
+// 2) or throw an exception.
+// @todo - should these be CLREvents? ClrCreateManualEvent / ClrCreateAutoEvent
+//-----------------------------------------------------------------------------
+HANDLE CreateWin32EventOrThrow(
+ LPSECURITY_ATTRIBUTES lpEventAttributes,
+ EEventResetType eType,
+ BOOL bInitialState
+)
+{
+ CONTRACT(HANDLE)
+ {
+ THROWS;
+ GC_NOTRIGGER;
+ PRECONDITION(CheckPointer(lpEventAttributes, NULL_OK));
+ POSTCONDITION(RETVAL != NULL);
+ }
+ CONTRACT_END;
+
+ HANDLE h = NULL;
+ h = WszCreateEvent(lpEventAttributes, (BOOL) eType, bInitialState, NULL);
+
+ if (h == NULL)
+ ThrowLastError();
+
+ RETURN h;
+}
+
+//-----------------------------------------------------------------------------
+// Open an event. Another helper for DebuggerRCThread::Init
+//-----------------------------------------------------------------------------
+HANDLE OpenWin32EventOrThrow(
+ DWORD dwDesiredAccess,
+ BOOL bInheritHandle,
+ LPCWSTR lpName
+)
+{
+ CONTRACT(HANDLE)
+ {
+ THROWS;
+ GC_NOTRIGGER;
+ POSTCONDITION(RETVAL != NULL);
+ }
+ CONTRACT_END;
+
+ HANDLE h = WszOpenEvent(
+ dwDesiredAccess,
+ bInheritHandle,
+ lpName
+ );
+ if (h == NULL)
+ ThrowLastError();
+
+ RETURN h;
+}
+
+//-----------------------------------------------------------------------------
+// Holder for IPC SecurityAttribute
+//-----------------------------------------------------------------------------
+IPCHostSecurityAttributeHolder::IPCHostSecurityAttributeHolder(DWORD pid)
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ THROWS;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ m_pSA = NULL;
+
+#ifdef FEATURE_IPCMAN
+ HRESULT hr = CCLRSecurityAttributeManager::GetHostSecurityAttributes(&m_pSA);
+ IfFailThrow(hr);
+
+ _ASSERTE(m_pSA != NULL);
+#endif // FEATURE_IPCMAN
+}
+
+SECURITY_ATTRIBUTES * IPCHostSecurityAttributeHolder::GetHostSA()
+{
+ LIMITED_METHOD_CONTRACT;
+ return m_pSA;
+}
+
+
+IPCHostSecurityAttributeHolder::~IPCHostSecurityAttributeHolder()
+{
+ LIMITED_METHOD_CONTRACT;
+
+#ifdef FEATURE_IPCMAN
+ CCLRSecurityAttributeManager::DestroyHostSecurityAttributes(m_pSA);
+#endif // FEATURE_IPCMAN
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Init
+//
+// Initialize the IPC block.
+//
+// Arguments:
+// hRsea - Handle to Right-Side Event Available event.
+// hRser - Handle to Right-Side Event Read event.
+// hLsea - Handle to Left-Side Event Available event.
+// hLser - Handle to Left-Side Event Read event.
+// hLsuwe - Handle to Left-Side unmanaged wait event.
+//
+// Notes:
+// The Init method works since there are no virtual functions - don't add any virtual functions without
+// changing this!
+// We assume ownership of the handles as soon as we're called; regardless of our success.
+// On failure, we throw.
+// Initialization of the debugger control block occurs partly on the left side and partly on
+// the right side. This initialization occurs in parallel, so it's unsafe to make assumptions about
+// the order in which the fields will be initialized.
+//
+//
+//---------------------------------------------------------------------------------------
+HRESULT DebuggerIPCControlBlock::Init(
+ HANDLE hRsea,
+ HANDLE hRser,
+ HANDLE hLsea,
+ HANDLE hLser,
+ HANDLE hLsuwe
+)
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ THROWS;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ // NOTE this works since there are no virtual functions - don't add any without changing this!
+ // Although we assume the IPC block is zero-initialized by the OS upon creation, we still need to clear
+ // the memory here to protect ourselves from DOS attack. One scenario is when a malicious debugger
+ // pre-creates a bogus IPC block. This means that our synchronization scheme won't work in DOS
+ // attack scenarios, but we will be messed up anyway.
+ // WARNING!!! m_DCBSize is used as a semaphore and is set to non-zero to signal that initialization of the
+ // WARNING!!! DCB is complete. if you remove the below memset be sure to initialize m_DCBSize to zero in the ctor!
+ memset( this, 0, sizeof( DebuggerIPCControlBlock) );
+
+ // Setup version checking info.
+ m_verMajor = VER_PRODUCTBUILD;
+ m_verMinor = VER_PRODUCTBUILD_QFE;
+
+#ifdef _DEBUG
+ m_checkedBuild = true;
+#else
+ m_checkedBuild = false;
+#endif
+ m_bHostingInFiber = false;
+
+ // Are we in fiber mode? In Whidbey, we do not support launch a fiber mode process
+ // nor do we support attach to a fiber mode process.
+ //
+ if (g_CORDebuggerControlFlags & DBCF_FIBERMODE)
+ {
+ m_bHostingInFiber = true;
+ }
+
+#if !defined(FEATURE_DBGIPC_TRANSPORT_VM)
+ // Copy RSEA and RSER into the control block.
+ if (!m_rightSideEventAvailable.SetLocal(hRsea))
+ {
+ ThrowLastError();
+ }
+
+ if (!m_rightSideEventRead.SetLocal(hRser))
+ {
+ ThrowLastError();
+ }
+
+ if (!m_leftSideUnmanagedWaitEvent.SetLocal(hLsuwe))
+ {
+ ThrowLastError();
+ }
+#endif // !FEATURE_DBGIPC_TRANSPORT_VM
+
+
+ // Mark the debugger special thread list as not dirty, empty and null.
+ m_specialThreadListDirty = false;
+ m_specialThreadListLength = 0;
+ m_specialThreadList = NULL;
+
+ m_shutdownBegun = false;
+
+ return S_OK;
+}
+
+#ifdef FEATURE_IPCMAN
+extern CCLRSecurityAttributeManager s_CLRSecurityAttributeManager;
+#endif // FEATURE_IPCMAN
+
+
+void DebuggerRCThread::WatchForStragglers(void)
+{
+ WRAPPER_NO_CONTRACT;
+
+ _ASSERTE(m_threadControlEvent != NULL);
+ LOG((LF_CORDB,LL_INFO100000, "DRCT::WFS:setting event to watch "
+ "for stragglers\n"));
+
+ SetEvent(m_threadControlEvent);
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Init sets up all the objects that the RC thread will need to run.
+//
+//
+// Return Value:
+// S_OK on success. May also throw.
+//
+// Assumptions:
+// Called during startup, even if we're not debugging.
+//
+//
+//---------------------------------------------------------------------------------------
+HRESULT DebuggerRCThread::Init(void)
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ THROWS;
+ GC_NOTRIGGER;
+ PRECONDITION(!ThisIsHelperThreadWorker()); // initialized by main thread
+ }
+ CONTRACTL_END;
+
+
+ LOG((LF_CORDB, LL_EVERYTHING, "DebuggerRCThreadInit called\n"));
+
+ DWORD dwStatus;
+ if (m_debugger == NULL)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ // Init should only be called once.
+ if (g_pRCThread != NULL)
+ {
+ ThrowHR(E_FAIL);
+ }
+
+ g_pRCThread = this;
+
+ m_favorData.Init(); // throws
+
+
+ // Create the thread control event.
+ m_threadControlEvent = CreateWin32EventOrThrow(NULL, kAutoResetEvent, FALSE);
+
+ // Create the helper thread can go event.
+ m_helperThreadCanGoEvent = CreateWin32EventOrThrow(NULL, kManualResetEvent, TRUE);
+
+ m_pDCB = new(nothrow) DebuggerIPCControlBlock;
+
+ // Don't fail out because the shared memory failed to create
+#if _DEBUG
+ if (m_pDCB == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO10000,
+ "DRCT::I: Failed to get Debug IPC block.\n"));
+ }
+#endif // _DEBUG
+
+ HRESULT hr;
+
+#if defined(FEATURE_DBGIPC_TRANSPORT_VM)
+
+ if (m_pDCB)
+ {
+ hr = m_pDCB->Init(NULL, NULL, NULL, NULL, NULL);
+ _ASSERTE(SUCCEEDED(hr)); // throws on error.
+ }
+#else //FEATURE_DBGIPC_TRANSPORT_VM
+
+ IPCHostSecurityAttributeHolder sa(GetCurrentProcessId());
+
+ // Create the events that the thread will need to receive events
+ // from the out of process piece on the right side.
+ // We will not fail out if CreateEvent fails for RSEA or RSER. Because
+ // the worst case is that debugger cannot attach to debuggee.
+ //
+ HandleHolder rightSideEventAvailable(WszCreateEvent(sa.GetHostSA(), (BOOL) kAutoResetEvent, FALSE, NULL));
+
+ // Security fix:
+ // We need to check the last error to see if the event was precreated or not
+ // If so, we need to release the handle right now.
+ //
+ dwStatus = GetLastError();
+ if (dwStatus == ERROR_ALREADY_EXISTS)
+ {
+ // clean up the handle now
+ rightSideEventAvailable.Clear();
+ }
+
+ HandleHolder rightSideEventRead(WszCreateEvent(sa.GetHostSA(), (BOOL) kAutoResetEvent, FALSE, NULL));
+
+ // Security fix:
+ // We need to check the last error to see if the event was precreated or not
+ // If so, we need to release the handle right now.
+ //
+ dwStatus = GetLastError();
+ if (dwStatus == ERROR_ALREADY_EXISTS)
+ {
+ // clean up the handle now
+ rightSideEventRead.Clear();
+ }
+
+
+ HandleHolder leftSideUnmanagedWaitEvent(CreateWin32EventOrThrow(NULL, kManualResetEvent, FALSE));
+
+ // Copy RSEA and RSER into the control block only if shared memory is created without error.
+ if (m_pDCB)
+ {
+ // Since Init() gets ownership of handles as soon as it's called, we can
+ // release our ownership now.
+ rightSideEventAvailable.SuppressRelease();
+ rightSideEventRead.SuppressRelease();
+ leftSideUnmanagedWaitEvent.SuppressRelease();
+
+ // NOTE: initialization of the debugger control block occurs partly on the left side and partly on
+ // the right side. This initialization occurs in parallel, so it's unsafe to make assumptions about
+ // the order in which the fields will be initialized.
+ hr = m_pDCB->Init(rightSideEventAvailable,
+ rightSideEventRead,
+ NULL,
+ NULL,
+ leftSideUnmanagedWaitEvent);
+
+ _ASSERTE(SUCCEEDED(hr)); // throws on error.
+ }
+#endif //FEATURE_DBGIPC_TRANSPORT_VM
+
+ if(m_pDCB)
+ {
+ // We have to ensure that most of the runtime offsets for the out-of-proc DCB are initialized right away. This is
+ // needed to support certian races during an interop attach. Since we can't know whether an interop attach will ever
+ // happen or not, we are forced to do this now. Note: this is really too early, as some data structures haven't been
+ // initialized yet!
+ hr = EnsureRuntimeOffsetsInit(IPC_TARGET_OUTOFPROC);
+ _ASSERTE(SUCCEEDED(hr)); // throw on error
+
+ // Note: we have to mark that we need the runtime offsets re-initialized for the out-of-proc DCB. This is because
+ // things like the patch table aren't initialized yet. Calling NeedRuntimeOffsetsReInit() ensures that this happens
+ // before we really need the patch table.
+ NeedRuntimeOffsetsReInit(IPC_TARGET_OUTOFPROC);
+
+ m_pDCB->m_helperThreadStartAddr = (void *) DebuggerRCThread::ThreadProcStatic;
+ m_pDCB->m_helperRemoteStartAddr = (void *) DebuggerRCThread::ThreadProcRemote;
+ m_pDCB->m_leftSideProtocolCurrent = CorDB_LeftSideProtocolCurrent;
+ m_pDCB->m_leftSideProtocolMinSupported = CorDB_LeftSideProtocolMinSupported;
+
+ LOG((LF_CORDB, LL_INFO10,
+ "DRCT::I: version info: %d.%d.%d current protocol=%d, min protocol=%d\n",
+ m_pDCB->m_verMajor,
+ m_pDCB->m_verMinor,
+ m_pDCB->m_checkedBuild,
+ m_pDCB->m_leftSideProtocolCurrent,
+ m_pDCB->m_leftSideProtocolMinSupported));
+
+ // Left-side always creates helper-thread.
+ // @dbgtodo inspection - by end of V3, LS will never create helper-thread :)
+ m_pDCB->m_rightSideShouldCreateHelperThread = false;
+
+ // m_DCBSize is used as a semaphore to indicate that the DCB is fully initialized.
+ // let's ensure that it's updated after all the other fields.
+ MemoryBarrier();
+ m_pDCB->m_DCBSize = sizeof(DebuggerIPCControlBlock);
+ }
+
+ return S_OK;
+}
+
+#ifndef FEATURE_PAL
+
+// This function is used to verify the security descriptor on an event
+// matches our expectation to prevent attack. This should be called when
+// we opened an event by name and assumed that the RS creates the event.
+// That means the event's dacl should match our default policy - current user
+// and admin. It can be narrower. By default, the DACL looks like the debugger
+// process user, debuggee user, and admin.
+//
+HRESULT DebuggerRCThread::VerifySecurityOnRSCreatedEvents(
+ HANDLE sse,
+ HANDLE lsea,
+ HANDLE lser)
+{
+
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ STRESS_LOG0(LF_CORDB,LL_INFO1000,"DRCT::VerifySecurityOnRSCreatedEvents\n");
+
+ if (lsea == NULL || lser == NULL)
+ {
+ // no valid handle, does not need to verify.
+ // The caller will close the handles
+ return E_FAIL;
+ }
+
+ HRESULT hr = S_OK;
+
+ SIZE_T i;
+ ACCESS_ALLOWED_ACE *pAllowAceSSE = NULL;
+ ACCESS_ALLOWED_ACE *pAllowAceLSEA = NULL;
+ ACCESS_ALLOWED_ACE *pAllowAceLSER = NULL;
+
+
+ EX_TRY
+ {
+ // Get security descriptors for the handles.
+ Win32SecurityDescriptor sdSSE;
+ sdSSE.InitFromHandle(sse);
+
+ Win32SecurityDescriptor sdLSEA;
+ sdLSEA.InitFromHandle(lsea);
+
+ Win32SecurityDescriptor sdLSER;
+ sdLSER.InitFromHandle(lser);
+
+
+
+
+ // Make sure all 3 have the same creator
+ // We've already verifed in CreateSetupSyncEvent that the SSE's owner is in the DACL.
+ if (!Sid::Equals(sdSSE.GetOwner(), sdLSEA.GetOwner()) ||
+ !Sid::Equals(sdSSE.GetOwner(), sdLSER.GetOwner()))
+ {
+ // Not equal! return now with failure code.
+ STRESS_LOG1(LF_CORDB,LL_INFO1000,"DRCT::VSORSCE failed on EqualSid - 0x%08x\n", hr);
+ ThrowHR(E_FAIL);
+ }
+
+ // DACL_SECURITY_INFORMATION
+ // Now verify the DACL. It should be only two of them at most. One of them is the
+ // target process SID.
+ Dacl daclSSE = sdSSE.GetDacl();
+ Dacl daclLSEA = sdLSEA.GetDacl();
+ Dacl daclLSER = sdLSER.GetDacl();
+
+
+ // Now all of these three ACL should be alike. There should be at most two of entries
+ // there. One if the debugger process's SID and one if debuggee sid.
+ if ((daclSSE.GetAceCount() != 1) && (daclSSE.GetAceCount() != 2))
+ {
+ ThrowHR(E_FAIL);
+ }
+
+
+ // All of the ace count should equal for all events.
+ if ((daclSSE.GetAceCount() != daclLSEA.GetAceCount()) ||
+ (daclSSE.GetAceCount() != daclLSER.GetAceCount()))
+ {
+ ThrowHR(E_FAIL);
+ }
+
+ // Now check the ACE inside.These should be all equal
+ for (i = 0; i < daclSSE.GetAceCount(); i++)
+ {
+ ACE_HEADER *pAce;
+
+ // Get the ace from the SSE
+ pAce = daclSSE.GetAce(i);
+ if (pAce->AceType != ACCESS_ALLOWED_ACE_TYPE)
+ {
+ ThrowHR(E_FAIL);
+ }
+ pAllowAceSSE = (ACCESS_ALLOWED_ACE*)pAce;
+
+ // Get the ace from LSEA
+ pAce = daclLSEA.GetAce(i);
+ if (pAce->AceType != ACCESS_ALLOWED_ACE_TYPE)
+ {
+ ThrowHR(E_FAIL);
+ }
+ pAllowAceLSEA = (ACCESS_ALLOWED_ACE*)pAce;
+
+ // This is the SID
+ // We can call EqualSid on this pAllowAce->SidStart
+ if (EqualSid((PSID)&(pAllowAceSSE->SidStart), (PSID)&(pAllowAceLSEA->SidStart)) == FALSE)
+ {
+ // ACE not equal. Fail out.
+ ThrowHR(E_FAIL);
+ }
+
+ // Get the ace from LSER
+ pAce = daclLSER.GetAce(i);
+ if (pAce->AceType != ACCESS_ALLOWED_ACE_TYPE)
+ {
+ ThrowHR(E_FAIL);
+ }
+ pAllowAceLSER = (ACCESS_ALLOWED_ACE*)pAce;
+
+ if (EqualSid((PSID)&(pAllowAceSSE->SidStart), (PSID)&(pAllowAceLSER->SidStart)) == FALSE)
+ {
+ // ACE not equal. Fail out.
+ ThrowHR(E_FAIL);
+ }
+ } // end for loop.
+
+
+ // The last ACE should be target process. That is it should be
+ // our process's sid!
+ //
+ if (pAllowAceLSER == NULL)
+ {
+ ThrowHR(E_FAIL);; // fail if we don't have the ACE.
+ }
+ {
+ SidBuffer sbCurrentProcess;
+ sbCurrentProcess.InitFromProcess(GetCurrentProcessId());
+ if (!Sid::Equals(sbCurrentProcess.GetSid(), (PSID)&(pAllowAceLSER->SidStart)))
+ {
+ ThrowHR(E_FAIL);
+ }
+ }
+ }
+ EX_CATCH
+ {
+ // If we threw an exception, then the verification failed.
+ hr = E_FAIL;
+ }
+ EX_END_CATCH(RethrowTerminalExceptions);
+
+ if (FAILED(hr))
+ {
+ STRESS_LOG1(LF_CORDB,LL_INFO1000,"DRCT::VSORSCE failed with - 0x%08x\n", hr);
+ }
+
+ return hr;
+}
+
+#endif // FEATURE_PAL
+
+//---------------------------------------------------------------------------------------
+//
+// Setup the Runtime Offsets struct.
+//
+// Arguments:
+// pDebuggerIPCControlBlock - Pointer to the debugger's portion of the IPC
+// block, which this routine will write into the offsets of various parts of
+// the runtime.
+//
+// Return Value:
+// S_OK on success.
+//
+//---------------------------------------------------------------------------------------
+HRESULT DebuggerRCThread::SetupRuntimeOffsets(DebuggerIPCControlBlock * pDebuggerIPCControlBlock)
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ NOTHROW;
+ GC_NOTRIGGER;
+
+ PRECONDITION(ThisMaybeHelperThread());
+ }
+ CONTRACTL_END;
+
+ // Allocate the struct if needed. We just fill in any existing one.
+ DebuggerIPCRuntimeOffsets * pDebuggerRuntimeOffsets = pDebuggerIPCControlBlock->m_pRuntimeOffsets;
+
+ if (pDebuggerRuntimeOffsets == NULL)
+ {
+ // Perhaps we should preallocate this. This is the only allocation
+ // that would force SendIPCEvent to throw an exception. It'd be very
+ // nice to have
+ CONTRACT_VIOLATION(ThrowsViolation);
+ pDebuggerRuntimeOffsets = new DebuggerIPCRuntimeOffsets();
+ _ASSERTE(pDebuggerRuntimeOffsets != NULL); // throws on oom
+ }
+
+ // Fill out the struct.
+#ifdef FEATURE_INTEROP_DEBUGGING
+ pDebuggerRuntimeOffsets->m_genericHijackFuncAddr = Debugger::GenericHijackFunc;
+ // Set flares - these only exist for interop debugging.
+ pDebuggerRuntimeOffsets->m_signalHijackStartedBPAddr = (void*) SignalHijackStartedFlare;
+ pDebuggerRuntimeOffsets->m_excepForRuntimeHandoffStartBPAddr = (void*) ExceptionForRuntimeHandoffStartFlare;
+ pDebuggerRuntimeOffsets->m_excepForRuntimeHandoffCompleteBPAddr = (void*) ExceptionForRuntimeHandoffCompleteFlare;
+ pDebuggerRuntimeOffsets->m_signalHijackCompleteBPAddr = (void*) SignalHijackCompleteFlare;
+ pDebuggerRuntimeOffsets->m_excepNotForRuntimeBPAddr = (void*) ExceptionNotForRuntimeFlare;
+ pDebuggerRuntimeOffsets->m_notifyRSOfSyncCompleteBPAddr = (void*) NotifyRightSideOfSyncCompleteFlare;
+
+#if !defined(FEATURE_CORESYSTEM)
+ // Grab the address of RaiseException in kernel32 because we have to play some games with exceptions
+ // that are generated there (just another reason why mixed mode debugging is shady). See bug 476768.
+ HMODULE hModule = WszGetModuleHandle(W("kernel32.dll"));
+ _ASSERTE(hModule != NULL);
+ PREFAST_ASSUME(hModule != NULL);
+ pDebuggerRuntimeOffsets->m_raiseExceptionAddr = GetProcAddress(hModule, "RaiseException");
+ _ASSERTE(pDebuggerRuntimeOffsets->m_raiseExceptionAddr != NULL);
+ hModule = NULL;
+#else
+ pDebuggerRuntimeOffsets->m_raiseExceptionAddr = NULL;
+#endif
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ pDebuggerRuntimeOffsets->m_pPatches = DebuggerController::GetPatchTable();
+ pDebuggerRuntimeOffsets->m_pPatchTableValid = (BOOL*)DebuggerController::GetPatchTableValidAddr();
+ pDebuggerRuntimeOffsets->m_offRgData = DebuggerPatchTable::GetOffsetOfEntries();
+ pDebuggerRuntimeOffsets->m_offCData = DebuggerPatchTable::GetOffsetOfCount();
+ pDebuggerRuntimeOffsets->m_cbPatch = sizeof(DebuggerControllerPatch);
+ pDebuggerRuntimeOffsets->m_offAddr = offsetof(DebuggerControllerPatch, address);
+ pDebuggerRuntimeOffsets->m_offOpcode = offsetof(DebuggerControllerPatch, opcode);
+ pDebuggerRuntimeOffsets->m_cbOpcode = sizeof(PRD_TYPE);
+ pDebuggerRuntimeOffsets->m_offTraceType = offsetof(DebuggerControllerPatch, trace.type);
+ pDebuggerRuntimeOffsets->m_traceTypeUnmanaged = TRACE_UNMANAGED;
+
+ // @dbgtodo inspection - this should all go away or be obtained from DacDbi Primitives.
+ g_pEEInterface->GetRuntimeOffsets(&pDebuggerRuntimeOffsets->m_TLSIndex,
+ &pDebuggerRuntimeOffsets->m_TLSIsSpecialIndex,
+ &pDebuggerRuntimeOffsets->m_TLSCantStopIndex,
+ &pDebuggerRuntimeOffsets->m_TLSIndexOfPredefs,
+ &pDebuggerRuntimeOffsets->m_EEThreadStateOffset,
+ &pDebuggerRuntimeOffsets->m_EEThreadStateNCOffset,
+ &pDebuggerRuntimeOffsets->m_EEThreadPGCDisabledOffset,
+ &pDebuggerRuntimeOffsets->m_EEThreadPGCDisabledValue,
+ &pDebuggerRuntimeOffsets->m_EEThreadDebuggerWordOffset,
+ &pDebuggerRuntimeOffsets->m_EEThreadFrameOffset,
+ &pDebuggerRuntimeOffsets->m_EEThreadMaxNeededSize,
+ &pDebuggerRuntimeOffsets->m_EEThreadSteppingStateMask,
+ &pDebuggerRuntimeOffsets->m_EEMaxFrameValue,
+ &pDebuggerRuntimeOffsets->m_EEThreadDebuggerFilterContextOffset,
+ &pDebuggerRuntimeOffsets->m_EEThreadCantStopOffset,
+ &pDebuggerRuntimeOffsets->m_EEFrameNextOffset,
+ &pDebuggerRuntimeOffsets->m_EEIsManagedExceptionStateMask);
+
+#ifndef FEATURE_IMPLICIT_TLS
+ _ASSERTE((pDebuggerRuntimeOffsets->m_TLSIndexOfPredefs != 0) || !"CExecutionEngine::TlsIndex is not initialized yet");
+#endif
+
+ // Remember the struct in the control block.
+ pDebuggerIPCControlBlock->m_pRuntimeOffsets = pDebuggerRuntimeOffsets;
+
+ return S_OK;
+}
+
+struct DebugFilterParam
+{
+ DebuggerIPCEvent *event;
+};
+
+// Filter called when we throw an exception while Handling events.
+static LONG _debugFilter(LPEXCEPTION_POINTERS ep, PVOID pv)
+{
+ LOG((LF_CORDB, LL_INFO10,
+ "Unhandled exception in Debugger::HandleIPCEvent\n"));
+
+ SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE;
+
+#if defined(_DEBUG) || !defined(FEATURE_CORESYSTEM)
+ DebuggerIPCEvent *event = ((DebugFilterParam *)pv)->event;
+
+ DWORD pid = GetCurrentProcessId();
+ DWORD tid = GetCurrentThreadId();
+
+ DebuggerIPCEventType type = (DebuggerIPCEventType) (event->type & DB_IPCE_TYPE_MASK);
+#endif // _DEBUG || !FEATURE_CORESYSTEM
+
+ // We should never AV here. In a debug build, throw up an assert w/ lots of useful (private) info.
+#ifdef _DEBUG
+ {
+ // We can't really use SStrings on the helper thread; though if we're at this point, we've already died.
+ // So go ahead and risk it and use them anyways.
+ SString sStack;
+ StackScratchBuffer buffer;
+ GetStackTraceAtContext(sStack, ep->ContextRecord);
+ const CHAR *string = NULL;
+
+ EX_TRY
+ {
+ string = sStack.GetANSI(buffer);
+ }
+ EX_CATCH
+ {
+ string = "*Could not retrieve stack*";
+ }
+ EX_END_CATCH(RethrowTerminalExceptions);
+
+ CONSISTENCY_CHECK_MSGF(false,
+ ("Unhandled exception on the helper thread.\nEvent=%s(0x%x)\nCode=0x%0x, Ip=0x%p, .cxr=%p, .exr=%p.\n pid=0x%x (%d), tid=0x%x (%d).\n-----\nStack of exception:\n%s\n----\n",
+ IPCENames::GetName(type), type,
+ ep->ExceptionRecord->ExceptionCode, GetIP(ep->ContextRecord), ep->ContextRecord, ep->ExceptionRecord,
+ pid, pid, tid, tid,
+ string));
+ }
+#endif
+
+// this message box doesn't work well on coresystem... we actually get in a recursive exception handling loop
+#ifndef FEATURE_CORESYSTEM
+ // We took an AV on the helper thread. This is a catastrophic situation so we can
+ // simply call the EE's catastrophic message box to display the error.
+ EEMessageBoxCatastrophic(
+ IDS_DEBUG_UNHANDLEDEXCEPTION_IPC, IDS_DEBUG_SERVICE_CAPTION,
+ type,
+ ep->ExceptionRecord->ExceptionCode,
+ GetIP(ep->ContextRecord),
+ pid, pid, tid, tid);
+#endif
+
+ // For debugging, we can change the behavior by manually setting eax.
+ // EXCEPTION_EXECUTE_HANDLER=1, EXCEPTION_CONTINUE_SEARCH=0, EXCEPTION_CONTINUE_EXECUTION=-1
+ return EXCEPTION_CONTINUE_SEARCH;
+}
+
+#ifdef _DEBUG
+// Tracking to ensure that we don't call New() for the normal (non interop-safe heap)
+// on the helper thread. We also can't do a normal allocation when we have hard
+// suspended any other thread (since it could hold the OS heap lock).
+
+// TODO: this probably belongs in the EE itself, not here in the debugger stuff.
+
+void AssertAllocationAllowed()
+{
+#ifdef USE_INTEROPSAFE_HEAP
+ // Don't forget to preserve error status!
+ DWORD err = GetLastError();
+
+ // We can mark certain
+ if (g_DbgSuppressAllocationAsserts == 0)
+ {
+
+ // if we have hard suspended any threads. We want to assert as it could cause deadlock
+ // since those suspended threads may hold the OS heap lock
+ if (g_fEEStarted) {
+ _ASSERTE (!EEAllocationDisallowed());
+ }
+
+ // Can't call IsDbgHelperSpecialThread() here b/c that changes program state.
+ // So we use our
+ if (DebuggerRCThread::s_DbgHelperThreadId.IsCurrentThread())
+ {
+ // In case assert allocates, bump up the 'OK' counter to avoid an infinite recursion.
+ SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE;
+
+ _ASSERTE(false || !"New called on Helper Thread");
+
+ }
+ }
+ SetLastError(err);
+#endif
+}
+#endif
+
+
+//---------------------------------------------------------------------------------------
+//
+// Primary function of the Runtime Controller thread. First, we let
+// the Debugger Interface know that we're up and running. Then, we run
+// the main loop.
+//
+//---------------------------------------------------------------------------------------
+void DebuggerRCThread::ThreadProc(void)
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ NOTHROW;
+ GC_TRIGGERS; // Debugger::SuspendComplete can trigger GC
+
+ // Although we're the helper thread, we haven't set it yet.
+ DISABLED(PRECONDITION(ThisIsHelperThreadWorker()));
+
+ INSTANCE_CHECK;
+ }
+ CONTRACTL_END;
+
+ STRESS_LOG_RESERVE_MEM (0);
+ // This message actually serves a purpose (which is why it is always run)
+ // The Stress log is run during hijacking, when other threads can be suspended
+ // at arbitrary locations (including when holding a lock that NT uses to serialize
+ // all memory allocations). By sending a message now, we insure that the stress
+ // log will not allocate memory at these critical times an avoid deadlock.
+ {
+ SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE;
+ STRESS_LOG0(LF_CORDB|LF_ALWAYS, LL_ALWAYS, "Debugger Thread spinning up\n");
+
+ // Call this to force creation of the TLS slots on helper-thread.
+ IsDbgHelperSpecialThread();
+ }
+
+#ifdef _DEBUG
+ // Track the helper thread.
+ s_DbgHelperThreadId.SetToCurrentThread();
+#endif
+ CantAllocHolder caHolder;
+
+
+#ifdef _DEBUG
+ // Cause wait in the helper thread startup. This lets us test against certain races.
+ // 1 = 6 sec. (shorter than Poll)
+ // 2 = 12 sec (longer than Poll).
+ // 3 = infinite - never comes up.
+ static int fDelayHelper = -1;
+
+ if (fDelayHelper == -1)
+ {
+ fDelayHelper = UnsafeGetConfigDWORD(CLRConfig::INTERNAL_DbgDelayHelper);
+ }
+
+ if (fDelayHelper)
+ {
+ DWORD dwSleep = 6000;
+
+ switch(fDelayHelper)
+ {
+ case 1: dwSleep = 6000; break;
+ case 2: dwSleep = 12000; break;
+ case 3: dwSleep = INFINITE; break;
+ }
+
+ ClrSleepEx(dwSleep, FALSE);
+ }
+#endif
+
+ LOG((LF_CORDB, LL_INFO1000, "DRCT::TP: helper thread spinning up...\n"));
+
+ // In case the shared memory is not initialized properly, it will be noop
+ if (m_pDCB == NULL)
+ {
+ return;
+ }
+
+ // Lock the debugger before spinning up.
+ Debugger::DebuggerLockHolder debugLockHolder(m_debugger);
+
+ if (m_pDCB->m_helperThreadId != 0)
+ {
+ // someone else has created a helper thread, we're outta here
+ // the most likely scenario here is that there was some kind of
+ // race between remotethread creation and localthread creation
+
+ LOG((LF_CORDB, LL_EVERYTHING, "Second debug helper thread creation detected, thread will safely suicide\n"));
+ // dbgLockHolder goes out of scope - implicit Release
+ return;
+ }
+
+ // this thread took the lock and there is no existing m_helperThreadID therefore
+ // this *IS* the helper thread and nobody else can be the helper thread
+
+ // the handle was created by the Start method
+ _ASSERTE(m_thread != NULL);
+
+#ifdef _DEBUG
+ // Make sure that we have the proper permissions.
+ {
+ DWORD dwWaitResult = WaitForSingleObject(m_thread, 0);
+ _ASSERTE(dwWaitResult == WAIT_TIMEOUT);
+ }
+#endif
+
+ // Mark that we're the true helper thread. Now that we've marked
+ // this, no other threads will ever become the temporary helper
+ // thread.
+ m_pDCB->m_helperThreadId = GetCurrentThreadId();
+
+ LOG((LF_CORDB, LL_INFO1000, "DRCT::TP: helper thread id is 0x%x helperThreadId\n",
+ m_pDCB->m_helperThreadId));
+
+ // If there is a temporary helper thread, then we need to wait for
+ // it to finish being the helper thread before we can become the
+ // helper thread.
+ if (m_pDCB->m_temporaryHelperThreadId != 0)
+ {
+ LOG((LF_CORDB, LL_INFO1000,
+ "DRCT::TP: temporary helper thread 0x%x is in the way, "
+ "waiting...\n",
+ m_pDCB->m_temporaryHelperThreadId));
+
+ debugLockHolder.Release();
+
+ // Wait for the temporary helper thread to finish up.
+ DWORD dwWaitResult = WaitForSingleObject(m_helperThreadCanGoEvent, INFINITE);
+ (void)dwWaitResult; //prevent "unused variable" error from GCC
+
+ LOG((LF_CORDB, LL_INFO1000, "DRCT::TP: done waiting for temp help to finish up.\n"));
+
+ _ASSERTE(dwWaitResult == WAIT_OBJECT_0);
+ _ASSERTE(m_pDCB->m_temporaryHelperThreadId==0);
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO1000, "DRCT::TP: no temp help in the way...\n"));
+
+ debugLockHolder.Release();
+ }
+
+ // Run the main loop as the true helper thread.
+ MainLoop();
+}
+
+void DebuggerRCThread::RightSideDetach(void)
+{
+ _ASSERTE( m_fDetachRightSide == false );
+ m_fDetachRightSide = true;
+#if !defined(FEATURE_DBGIPC_TRANSPORT_VM)
+ CloseIPCHandles();
+#endif // !FEATURE_DBGIPC_TRANSPORT_VM
+}
+
+//
+// These defines control how many times we spin while waiting for threads to sync and how often. Note its higher in
+// debug builds to allow extra time for threads to sync.
+//
+#define CorDB_SYNC_WAIT_TIMEOUT 20 // 20ms
+
+#ifdef _DEBUG
+#define CorDB_MAX_SYNC_SPIN_COUNT (10000 / CorDB_SYNC_WAIT_TIMEOUT) // (10 seconds)
+#else
+#define CorDB_MAX_SYNC_SPIN_COUNT (3000 / CorDB_SYNC_WAIT_TIMEOUT) // (3 seconds)
+#endif
+
+//
+// NDPWhidbey issue 10749 - Due to a compiler change for vc7.1,
+// Don't inline this function!
+// PAL_TRY allocates space on the stack and so can not be used within a loop,
+// else we'll slowly leak stack space w/ each interation and get an overflow.
+// So make this its own function to enforce that we free the stack space between
+// iterations.
+//
+bool HandleIPCEventWrapper(Debugger* pDebugger, DebuggerIPCEvent *e)
+{
+ struct Param : DebugFilterParam
+ {
+ Debugger* pDebugger;
+ bool wasContinue;
+ } param;
+ param.event = e;
+ param.pDebugger = pDebugger;
+ param.wasContinue = false;
+ PAL_TRY(Param *, pParam, &param)
+ {
+ pParam->wasContinue = pParam->pDebugger->HandleIPCEvent(pParam->event);
+ }
+ PAL_EXCEPT_FILTER(_debugFilter)
+ {
+ LOG((LF_CORDB, LL_INFO10, "Unhandled exception caught in Debugger::HandleIPCEvent\n"));
+ }
+ PAL_ENDTRY
+
+ return param.wasContinue;
+}
+
+bool DebuggerRCThread::HandleRSEA()
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ if (g_pEEInterface->GetThread() != NULL) { GC_TRIGGERS; } else { GC_NOTRIGGER; }
+ PRECONDITION(ThisIsHelperThreadWorker());
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB,LL_INFO10000, "RSEA from out of process (right side)\n"));
+ DebuggerIPCEvent * e;
+#if !defined(FEATURE_DBGIPC_TRANSPORT_VM)
+ // Make room for any Right Side event on the stack.
+ BYTE buffer[CorDBIPC_BUFFER_SIZE];
+ e = (DebuggerIPCEvent *) buffer;
+
+ // If the RSEA is signaled, then handle the event from the Right Side.
+ memcpy(e, GetIPCEventReceiveBuffer(), CorDBIPC_BUFFER_SIZE);
+#else
+ // Be sure to fetch the event into the official receive buffer since some event handlers assume it's there
+ // regardless of the the event buffer pointer passed to them.
+ e = GetIPCEventReceiveBuffer();
+ g_pDbgTransport->GetNextEvent(e, CorDBIPC_BUFFER_SIZE);
+#endif // !FEATURE_DBGIPC_TRANSPOPRT
+
+#if !defined(FEATURE_DBGIPC_TRANSPORT_VM)
+ // If no reply is required, then let the Right Side go since we've got a copy of the event now.
+ _ASSERTE(!e->asyncSend || !e->replyRequired);
+
+ if (!e->replyRequired && !e->asyncSend)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "DRCT::ML: no reply required, letting Right Side go.\n"));
+
+ BOOL succ = SetEvent(m_pDCB->m_rightSideEventRead);
+
+ if (!succ)
+ CORDBDebuggerSetUnrecoverableWin32Error(m_debugger, 0, true);
+ }
+#ifdef LOGGING
+ else if (e->asyncSend)
+ LOG((LF_CORDB, LL_INFO1000, "DRCT::ML: async send.\n"));
+ else
+ LOG((LF_CORDB, LL_INFO1000, "DRCT::ML: reply required, holding Right Side...\n"));
+#endif
+#endif // !FEATURE_DBGIPC_TRANSPORT_VM
+
+ // Pass the event to the debugger for handling. Returns true if the event was a Continue event and we can
+ // stop looking for stragglers. We wrap this whole thing in an exception handler to help us debug faults.
+ bool wasContinue = false;
+
+ wasContinue = HandleIPCEventWrapper(m_debugger, e);
+
+ return wasContinue;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Main loop of the Runtime Controller thread. It waits for IPC events
+// and dishes them out to the Debugger object for processing.
+//
+// Some of this logic is copied in Debugger::VrpcToVls
+//
+//---------------------------------------------------------------------------------------
+void DebuggerRCThread::MainLoop()
+{
+ // This function can only be called on native Debugger helper thread.
+ //
+
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ NOTHROW;
+
+ PRECONDITION(m_thread != NULL);
+ PRECONDITION(ThisIsHelperThreadWorker());
+ PRECONDITION(IsDbgHelperSpecialThread()); // Can only be called on native debugger helper thread
+ PRECONDITION((!ThreadStore::HoldingThreadStore()) || g_fProcessDetach);
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_INFO1000, "DRCT::ML:: running main loop\n"));
+
+ // Anbody doing helper duty is in a can't-stop range, period.
+ // Our helper thread is already in a can't-stop range, so this is particularly useful for
+ // threads doing helper duty.
+ CantStopHolder cantStopHolder;
+
+ HANDLE rghWaitSet[DRCT_COUNT_FINAL];
+
+#ifdef _DEBUG
+ DWORD dwSyncSpinCount = 0;
+#endif
+
+ // We start out just listening on RSEA and the thread control event...
+ unsigned int cWaitCount = DRCT_COUNT_INITIAL;
+ DWORD dwWaitTimeout = INFINITE;
+ rghWaitSet[DRCT_CONTROL_EVENT] = m_threadControlEvent;
+ rghWaitSet[DRCT_FAVORAVAIL] = GetFavorAvailableEvent();
+#if !defined(FEATURE_DBGIPC_TRANSPORT_VM)
+ rghWaitSet[DRCT_RSEA] = m_pDCB->m_rightSideEventAvailable;
+#else
+ rghWaitSet[DRCT_RSEA] = g_pDbgTransport->GetIPCEventReadyEvent();
+#endif // !FEATURE_DBGIPC_TRANSPORT_VM
+
+ CONTRACT_VIOLATION(ThrowsViolation);// HndCreateHandle throws, and this loop is not backstopped by any EH
+
+ // Lock holder. Don't take it yet. We take lock on this when we succeeded suspended runtime.
+ // We will release the lock later when continue happens and runtime resumes
+ Debugger::DebuggerLockHolder debugLockHolderSuspended(m_debugger, false);
+
+ while (m_run)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "DRCT::ML: waiting for event.\n"));
+
+#if !defined(FEATURE_DBGIPC_TRANSPORT_VM)
+ // If there is a debugger attached, wait on its handle, too...
+ if ((cWaitCount == DRCT_COUNT_INITIAL) &&
+ m_pDCB->m_rightSideProcessHandle.ImportToLocalProcess() != NULL)
+ {
+ _ASSERTE((cWaitCount + 1) == DRCT_COUNT_FINAL);
+ rghWaitSet[DRCT_DEBUGGER_EVENT] = m_pDCB->m_rightSideProcessHandle;
+ cWaitCount = DRCT_COUNT_FINAL;
+ }
+#endif // !FEATURE_DBGIPC_TRANSPORT_VM
+
+
+ if (m_fDetachRightSide)
+ {
+ m_fDetachRightSide = false;
+
+#if !defined(FEATURE_DBGIPC_TRANSPORT_VM)
+ _ASSERTE(cWaitCount == DRCT_COUNT_FINAL);
+ _ASSERTE((cWaitCount - 1) == DRCT_COUNT_INITIAL);
+
+ rghWaitSet[DRCT_DEBUGGER_EVENT] = NULL;
+ cWaitCount = DRCT_COUNT_INITIAL;
+#endif // !FEATURE_DBGIPC_TRANSPORT_VM
+ }
+
+ // Wait for an event from the Right Side.
+ DWORD dwWaitResult = WaitForMultipleObjectsEx(cWaitCount, rghWaitSet, FALSE, dwWaitTimeout, FALSE);
+
+ if (!m_run)
+ {
+ continue;
+ }
+
+
+ if (dwWaitResult == WAIT_OBJECT_0 + DRCT_DEBUGGER_EVENT)
+ {
+ // If the handle of the right side process is signaled, then we've lost our controlling debugger. We
+ // terminate this process immediatley in such a case.
+ LOG((LF_CORDB, LL_INFO1000, "DRCT::ML: terminating this process. Right Side has exited.\n"));
+ SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE;
+ EEPOLICY_HANDLE_FATAL_ERROR(0);
+ _ASSERTE(!"Should never reach this point.");
+ }
+ else if (dwWaitResult == WAIT_OBJECT_0 + DRCT_FAVORAVAIL)
+ {
+ // execute the callback set by DoFavor()
+ FAVORCALLBACK fpCallback = GetFavorFnPtr();
+ // We never expect the callback to be null unless some other component
+ // wrongly signals our event (see DD 463807).
+ // In case we messed up, we will not set the FavorReadEvent and will hang favor requesting thread.
+ if (fpCallback)
+ {
+ (*fpCallback)(GetFavorData());
+ SetEvent(GetFavorReadEvent());
+ }
+ }
+ else if (dwWaitResult == WAIT_OBJECT_0 + DRCT_RSEA)
+ {
+ bool fWasContinue = HandleRSEA();
+
+ if (fWasContinue)
+ {
+
+ // If they called continue, then we must have released the TSL.
+ _ASSERTE(!ThreadStore::HoldingThreadStore() || g_fProcessDetach);
+
+ // Let's release the lock here since runtime is resumed.
+ debugLockHolderSuspended.Release();
+
+ // This debugger thread shoud not be holding debugger locks anymore
+ _ASSERTE(!g_pDebugger->ThreadHoldsLock());
+#ifdef _DEBUG
+ // Always reset the syncSpinCount to 0 on a continue so that we have the maximum number of possible
+ // spins the next time we need to sync.
+ dwSyncSpinCount = 0;
+#endif
+
+ if (dwWaitTimeout != INFINITE)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "DRCT::ML:: don't check for stragglers due to continue.\n"));
+
+ dwWaitTimeout = INFINITE;
+ }
+
+ }
+ }
+ else if (dwWaitResult == WAIT_OBJECT_0 + DRCT_CONTROL_EVENT)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "DRCT::ML:: straggler event set.\n"));
+
+ Debugger::DebuggerLockHolder debugLockHolder(m_debugger);
+ // Make sure that we're still synchronizing...
+ if (m_debugger->IsSynchronizing())
+ {
+ LOG((LF_CORDB, LL_INFO1000, "DRCT::ML:: dropping the timeout.\n"));
+
+ dwWaitTimeout = CorDB_SYNC_WAIT_TIMEOUT;
+
+ //
+ // Skip waiting the first time and just give it a go. Note: Implicit
+ // release of the lock, because we are leaving its scope.
+ //
+ goto LWaitTimedOut;
+ }
+#ifdef LOGGING
+ else
+ LOG((LF_CORDB, LL_INFO1000, "DRCT::ML:: told to wait, but not syncing anymore.\n"));
+#endif
+ // dbgLockHolder goes out of scope - implicit Release
+ }
+ else if (dwWaitResult == WAIT_TIMEOUT)
+ {
+
+LWaitTimedOut:
+
+ LOG((LF_CORDB, LL_INFO1000, "DRCT::ML:: wait timed out.\n"));
+
+ // Debugger::DebuggerLockHolder debugLockHolder(m_debugger);
+ // Explicitly get the lock here since we try to check to see if
+ // have suspended. We will release the lock if we are not suspended yet.
+ //
+ debugLockHolderSuspended.Acquire();
+
+ // We should still be synchronizing, otherwise we would not have timed out.
+ _ASSERTE(m_debugger->IsSynchronizing());
+
+ LOG((LF_CORDB, LL_INFO1000, "DRCT::ML:: sweeping the thread list.\n"));
+
+#ifdef _DEBUG
+ // If we fail to suspend the CLR, don't bother waiting for a BVT to timeout,
+ // fire up an assert up now.
+ // Threads::m_DebugWillSyncCount+1 is the number of outstanding threads.
+ // We're trying to suspend any thread w/ TS_DebugWillSync set.
+ if (dwSyncSpinCount++ > CorDB_MAX_SYNC_SPIN_COUNT)
+ {
+ _ASSERTE_MSG(false, "Timeout trying to suspend CLR for debugging. Possibly a deadlock.\n"\
+ "You can ignore this assert to continue waiting\n");
+ dwSyncSpinCount = 0;
+ }
+#endif
+
+ // Don't call Sweep if we're doing helper thread duty.
+ // If we're doing helper thread duty, then we already Suspended the CLR, and we already hold the TSL.
+ bool fSuspended;
+ {
+ // SweepThreadsForDebug() may call new!!! ARGG!!!
+ SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE;
+ fSuspended = g_pEEInterface->SweepThreadsForDebug(false);
+ }
+
+ if (fSuspended)
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "DRCT::ML:: wait set empty after sweep.\n");
+
+ // There are no more threads to wait for, so go ahead and send the sync complete event.
+ m_debugger->SuspendComplete();
+ dwWaitTimeout = INFINITE;
+
+ // Note: we hold the thread store lock now and debugger lock...
+
+ // We also hold debugger lock the whole time that Runtime is stopped. We will release the debugger lock
+ // when we receive the Continue event that resumes the runtime.
+
+ _ASSERTE(ThreadStore::HoldingThreadStore() || g_fProcessDetach);
+ }
+ else
+ {
+ // If we're doing helper thread duty, then we expect to have been suspended already.
+ // And so the sweep should always succeed.
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "DRCT::ML:: threads still syncing after sweep.\n");
+ debugLockHolderSuspended.Release();
+ }
+ // debugLockHolderSuspended does not go out of scope. It has to be either released explicitly on the line above or
+ // we intend to hold the lock till we hit continue event.
+
+ }
+ }
+
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "DRCT::ML:: Exiting.\n");
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Main loop of the temporary Helper thread. It waits for IPC events
+// and dishes them out to the Debugger object for processing.
+//
+// Notes:
+// When we enter here, we are holding debugger lock and thread store lock.
+// The debugger lock was SuppressRelease in DoHelperThreadDuty. The continue event
+// that we are waiting for will trigger the corresponding release.
+//
+// IMPORTANT!!! READ ME!!!!
+// This MainLoop is similiar to MainLoop function above but simplified to deal with only
+// some scenario. So if you change here, you should look at MainLoop to see if same change is
+// required.
+//---------------------------------------------------------------------------------------
+void DebuggerRCThread::TemporaryHelperThreadMainLoop()
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+
+
+ // If we come in here, this managed thread is trying to do helper thread duty.
+ // It should be holding the debugger lock!!!
+ //
+ PRECONDITION(m_debugger->ThreadHoldsLock());
+ PRECONDITION((ThreadStore::HoldingThreadStore()) || g_fProcessDetach);
+ PRECONDITION(ThisIsTempHelperThread());
+ }
+ CONTRACTL_END;
+
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "DRCT::THTML:: Doing helper thread duty, running main loop.\n");
+ // Anbody doing helper duty is in a can't-stop range, period.
+ // Our helper thread is already in a can't-stop range, so this is particularly useful for
+ // threads doing helper duty.
+ CantStopHolder cantStopHolder;
+
+ HANDLE rghWaitSet[DRCT_COUNT_FINAL];
+
+#ifdef _DEBUG
+ DWORD dwSyncSpinCount = 0;
+#endif
+
+ // We start out just listening on RSEA and the thread control event...
+ unsigned int cWaitCount = DRCT_COUNT_INITIAL;
+ DWORD dwWaitTimeout = INFINITE;
+ rghWaitSet[DRCT_CONTROL_EVENT] = m_threadControlEvent;
+ rghWaitSet[DRCT_FAVORAVAIL] = GetFavorAvailableEvent();
+#if !defined(FEATURE_DBGIPC_TRANSPORT_VM)
+ rghWaitSet[DRCT_RSEA] = m_pDCB->m_rightSideEventAvailable;
+#else //FEATURE_DBGIPC_TRANSPORT_VM
+ rghWaitSet[DRCT_RSEA] = g_pDbgTransport->GetIPCEventReadyEvent();
+#endif // !FEATURE_DBGIPC_TRANSPORT_VM
+
+ CONTRACT_VIOLATION(ThrowsViolation);// HndCreateHandle throws, and this loop is not backstopped by any EH
+
+ while (m_run)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "DRCT::ML: waiting for event.\n"));
+
+ // Wait for an event from the Right Side.
+ DWORD dwWaitResult = WaitForMultipleObjectsEx(cWaitCount, rghWaitSet, FALSE, dwWaitTimeout, FALSE);
+
+ if (!m_run)
+ {
+ continue;
+ }
+
+
+ if (dwWaitResult == WAIT_OBJECT_0 + DRCT_DEBUGGER_EVENT)
+ {
+ // If the handle of the right side process is signaled, then we've lost our controlling debugger. We
+ // terminate this process immediatley in such a case.
+ LOG((LF_CORDB, LL_INFO1000, "DRCT::THTML: terminating this process. Right Side has exited.\n"));
+
+ TerminateProcess(GetCurrentProcess(), 0);
+ _ASSERTE(!"Should never reach this point.");
+ }
+ else if (dwWaitResult == WAIT_OBJECT_0 + DRCT_FAVORAVAIL)
+ {
+ // execute the callback set by DoFavor()
+ (*GetFavorFnPtr())(GetFavorData());
+
+ SetEvent(GetFavorReadEvent());
+ }
+ else if (dwWaitResult == WAIT_OBJECT_0 + DRCT_RSEA)
+ {
+ // @todo:
+ // We are only interested in dealing with Continue event here...
+ // Once we remove the HelperThread duty, this will just go away.
+ //
+ bool fWasContinue = HandleRSEA();
+
+ if (fWasContinue)
+ {
+ // If they called continue, then we must have released the TSL.
+ _ASSERTE(!ThreadStore::HoldingThreadStore() || g_fProcessDetach);
+
+#ifdef _DEBUG
+ // Always reset the syncSpinCount to 0 on a continue so that we have the maximum number of possible
+ // spins the next time we need to sync.
+ dwSyncSpinCount = 0;
+#endif
+
+ // HelperThread duty is finished. We have got a Continue message
+ goto LExit;
+ }
+ }
+ else if (dwWaitResult == WAIT_OBJECT_0 + DRCT_CONTROL_EVENT)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "DRCT::THTML:: straggler event set.\n"));
+
+ // Make sure that we're still synchronizing...
+ _ASSERTE(m_debugger->IsSynchronizing());
+ LOG((LF_CORDB, LL_INFO1000, "DRCT::THTML:: dropping the timeout.\n"));
+
+ dwWaitTimeout = CorDB_SYNC_WAIT_TIMEOUT;
+
+ //
+ // Skip waiting the first time and just give it a go. Note: Implicit
+ // release of the lock, because we are leaving its scope.
+ //
+ goto LWaitTimedOut;
+ }
+ else if (dwWaitResult == WAIT_TIMEOUT)
+ {
+
+LWaitTimedOut:
+
+ LOG((LF_CORDB, LL_INFO1000, "DRCT::THTML:: wait timed out.\n"));
+
+ // We should still be synchronizing, otherwise we would not have timed out.
+ _ASSERTE(m_debugger->IsSynchronizing());
+
+ LOG((LF_CORDB, LL_INFO1000, "DRCT::THTML:: sweeping the thread list.\n"));
+
+#ifdef _DEBUG
+ // If we fail to suspend the CLR, don't bother waiting for a BVT to timeout,
+ // fire up an assert up now.
+ // Threads::m_DebugWillSyncCount+1 is the number of outstanding threads.
+ // We're trying to suspend any thread w/ TS_DebugWillSync set.
+ if (dwSyncSpinCount++ > CorDB_MAX_SYNC_SPIN_COUNT)
+ {
+ _ASSERTE(false || !"Timeout trying to suspend CLR for debugging. Possibly a deadlock. "
+ "You can ignore this assert to continue waiting\n");
+ dwSyncSpinCount = 0;
+ }
+#endif
+
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "DRCT::THTML:: wait set empty after sweep.\n");
+
+ // We are holding Debugger lock (Look at the SuppressRelease on the DoHelperThreadDuty)
+ // The debugger lock will be released on the Continue event which we will then
+ // exit the loop.
+
+ // There are no more threads to wait for, so go ahead and send the sync complete event.
+ m_debugger->SuspendComplete();
+ dwWaitTimeout = INFINITE;
+
+ // Note: we hold the thread store lock now and debugger lock...
+ _ASSERTE(ThreadStore::HoldingThreadStore() || g_fProcessDetach);
+
+ }
+ }
+
+LExit:
+
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "DRCT::THTML:: Exiting.\n");
+}
+
+
+
+//
+// This is the thread's real thread proc. It simply calls to the
+// thread proc on the RCThread object.
+//
+/*static*/ DWORD WINAPI DebuggerRCThread::ThreadProcRemote(LPVOID)
+{
+ // We just wrap create a local thread and we're outta here
+ WRAPPER_NO_CONTRACT;
+
+ ClrFlsSetThreadType(ThreadType_DbgHelper);
+
+ LOG((LF_CORDB, LL_EVERYTHING, "ThreadProcRemote called\n"));
+#ifdef _DEBUG
+ dbgOnly_IdentifySpecialEEThread();
+#endif
+
+ // this method can be called both by a local createthread or a remote create thread
+ // so we must use the g_RCThread global to find the (unique!) this pointer
+ // we cannot count on the parameter.
+
+ DebuggerRCThread* t = (DebuggerRCThread*)g_pRCThread;
+
+ // This remote thread is created by the debugger process
+ // and so its ACLs will reflect permissions for the user running
+ // the debugger. If this process is running in the context of a
+ // different user then this (the now running) process will not be
+ // able to do operations on that (remote) thread.
+ //
+ // To avoid this problem, if we are the remote thread, then
+ // we simply launch a new, local, thread right here and let
+ // the remote thread die. This new thread is created the same
+ // way as always, and since it is created by this process
+ // this process will be able to synchronize with it and so forth
+
+ t->Start(); // this thread is remote, we must start a new thread
+
+ return 0;
+}
+
+//
+// This is the thread's real thread proc. It simply calls to the
+// thread proc on the RCThread object.
+//
+/*static*/ DWORD WINAPI DebuggerRCThread::ThreadProcStatic(LPVOID)
+{
+ // We just wrap the instance method DebuggerRCThread::ThreadProc
+ WRAPPER_NO_CONTRACT;
+
+ BEGIN_SO_INTOLERANT_CODE_NO_THROW_CHECK_THREAD_FORCE_SO();
+
+ ClrFlsSetThreadType(ThreadType_DbgHelper);
+
+ LOG((LF_CORDB, LL_EVERYTHING, "ThreadProcStatic called\n"));
+
+#ifdef _DEBUG
+ dbgOnly_IdentifySpecialEEThread();
+#endif
+
+ // We commit the thread's entire stack to ensure we're robust in low memory conditions. If we can't commit the
+ // stack, then we can't let the CLR continue to function.
+ BOOL fSuccess = Thread::CommitThreadStack(NULL);
+
+ if (!fSuccess)
+ {
+ STRESS_LOG0(LF_GC, LL_ALWAYS, "Thread::CommitThreadStack failed.\n");
+ _ASSERTE(!"Thread::CommitThreadStack failed.");
+ EEPOLICY_HANDLE_FATAL_ERROR(COR_E_STACKOVERFLOW);
+ }
+
+ DebuggerRCThread* t = (DebuggerRCThread*)g_pRCThread;
+
+ t->ThreadProc(); // this thread is local, go and become the helper
+
+ END_SO_INTOLERANT_CODE;
+
+ return 0;
+}
+
+RCThreadLazyInit * DebuggerRCThread::GetLazyData()
+{
+ return g_pDebugger->GetRCThreadLazyData();
+}
+
+
+//
+// Start actually creates and starts the RC thread. It waits for the thread
+// to come up and perform initial synchronization with the Debugger
+// Interface before returning.
+//
+HRESULT DebuggerRCThread::Start(void)
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr = S_OK;
+
+ LOG((LF_CORDB, LL_EVERYTHING, "DebuggerRCThread::Start called...\n"));
+
+ DWORD helperThreadId;
+
+ if (m_thread != NULL)
+ {
+ LOG((LF_CORDB, LL_EVERYTHING, "DebuggerRCThread::Start declined to start another helper thread...\n"));
+ return S_OK;
+ }
+
+ Debugger::DebuggerLockHolder debugLockHolder(m_debugger);
+
+ if (m_thread == NULL)
+ {
+ // Create suspended so that we can sniff the tid before the thread actually runs.
+ // This may not be before the native thread-create event, but should be before everything else.
+ // Note: strange as it may seem, the Right Side depends on us
+ // using CreateThread to create the helper thread here. If you
+ // ever change this to some other thread creation routine, you
+ // need to update the logic in process.cpp where we discover the
+ // helper thread on CREATE_THREAD_DEBUG_EVENTs...
+ m_thread = CreateThread(NULL, 0, DebuggerRCThread::ThreadProcStatic,
+ NULL, CREATE_SUSPENDED, &helperThreadId );
+
+ if (m_thread == NULL)
+ {
+ LOG((LF_CORDB, LL_EVERYTHING, "DebuggerRCThread failed, err=%d\n", GetLastError()));
+ hr = HRESULT_FROM_GetLastError();
+
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_EVERYTHING, "DebuggerRCThread start was successful, id=%d\n", helperThreadId));
+ }
+
+ // This gets published immediately.
+ DebuggerIPCControlBlock* dcb = GetDCB();
+ PREFIX_ASSUME(dcb != NULL);
+ dcb->m_realHelperThreadId = helperThreadId;
+
+#ifdef _DEBUG
+ // Record the OS Thread ID for debugging purposes.
+ m_DbgHelperThreadOSTid = helperThreadId ;
+#endif
+
+ if (m_thread != NULL)
+ {
+ ResumeThread(m_thread);
+ }
+
+ }
+
+ // unlock debugger lock is implied.
+
+ return hr;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Stop causes the RC thread to stop receiving events and exit.
+// It does not wait for it to exit before returning (hence "AsyncStop" instead of "Stop").
+//
+// Return Value:
+// Always S_OK at the moment.
+//
+//---------------------------------------------------------------------------------------
+HRESULT DebuggerRCThread::AsyncStop(void)
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ NOTHROW;
+ GC_NOTRIGGER;
+
+#ifdef _TARGET_X86_
+ PRECONDITION(!ThisIsHelperThreadWorker());
+#else
+ PRECONDITION(!ThisIsHelperThreadWorker());
+#endif
+ }
+ CONTRACTL_END;
+
+ HRESULT hr = S_OK;
+
+ m_run = FALSE;
+
+ // We need to get the helper thread out of its wait loop. So ping the thread-control event.
+ // (Don't ping RSEA since that event should be used only for IPC communication).
+ // Don't bother waiting for it to exit.
+ SetEvent(this->m_threadControlEvent);
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// This method checks that the runtime offset has been loaded, and if not, loads it.
+//
+//---------------------------------------------------------------------------------------
+HRESULT inline DebuggerRCThread::EnsureRuntimeOffsetsInit(IpcTarget ipcTarget)
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ NOTHROW;
+ GC_NOTRIGGER;
+
+ PRECONDITION(ThisMaybeHelperThread());
+ }
+ CONTRACTL_END;
+
+ HRESULT hr = S_OK;
+
+ if (m_rgfInitRuntimeOffsets[ipcTarget] == true)
+ {
+ hr = SetupRuntimeOffsets(m_pDCB);
+ _ASSERTE(SUCCEEDED(hr)); // throws on failure
+
+ // RuntimeOffsets structure is setup.
+ m_rgfInitRuntimeOffsets[ipcTarget] = false;
+ }
+
+ return hr;
+}
+
+//
+// Call this function to tell the rc thread that we need the runtime offsets re-initialized at the next avaliable time.
+//
+void DebuggerRCThread::NeedRuntimeOffsetsReInit(IpcTarget i)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ m_rgfInitRuntimeOffsets[i] = true;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Send an debug event to the Debugger. This may be either a notification
+// or a reply to a debugger query.
+//
+// Arguments:
+// iTarget - which connection. This must be IPC_TARGET_OUTOFPROC.
+//
+// Return Value:
+// S_OK on success
+//
+// Notes:
+// SendIPCEvent is used by the Debugger object to send IPC events to
+// the Debugger Interface. It waits for acknowledgement from the DI
+// before returning.
+//
+// This assumes that the event send buffer has been properly
+// filled in. All it does it wake up the DI and let it know that its
+// safe to copy the event out of this process.
+//
+// This function may block indefinitely if the controlling debugger
+// suddenly went away.
+//
+// @dbgtodo inspection - this is all a nop around SendRawEvent!
+//
+//---------------------------------------------------------------------------------------
+HRESULT DebuggerRCThread::SendIPCEvent()
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER; // duh, we're in preemptive..
+
+ if (ThisIsHelperThreadWorker())
+ {
+ // When we're stopped, the helper could actually be contracted as either mode-cooperative
+ // or mode-preemptive!
+ // If we're the helper thread, we're only sending events while we're stopped.
+ // Our callers will be mode-cooperative, so call this mode_cooperative to avoid a bunch
+ // of unncessary contract violations.
+ MODE_COOPERATIVE;
+ }
+ else
+ {
+ // Managed threads sending debug events should always be in preemptive mode.
+ MODE_PREEMPTIVE;
+ }
+
+
+ PRECONDITION(ThisMaybeHelperThread());
+ }
+ CONTRACTL_END;
+
+
+ // one right side
+ _ASSERTE(m_debugger->ThreadHoldsLock());
+
+ HRESULT hr = S_OK;
+
+ // All the initialization is already done in code:DebuggerRCThread.Init,
+ // so we can just go ahead and send the event.
+
+ DebuggerIPCEvent* pManagedEvent = GetIPCEventSendBuffer();
+
+ STRESS_LOG2(LF_CORDB, LL_INFO1000, "D::SendIPCEvent %s to outofproc appD 0x%x,\n",
+ IPCENames::GetName(pManagedEvent->type),
+ VmPtrToCookie(pManagedEvent->vmAppDomain));
+
+ // increase the debug counter
+ DbgLog((DebuggerIPCEventType)(pManagedEvent->type & DB_IPCE_TYPE_MASK));
+
+ g_pDebugger->SendRawEvent(pManagedEvent);
+
+ return hr;
+}
+
+//
+// Return true if the helper thread is up & running
+//
+bool DebuggerRCThread::IsRCThreadReady()
+{
+ LIMITED_METHOD_CONTRACT;
+
+ if (GetDCB() == NULL)
+ {
+ return false;
+ }
+
+ int idHelper = GetDCB()->m_helperThreadId;
+
+ // The simplest check. If the threadid isn't set, we're not ready.
+ if (idHelper == 0)
+ {
+ LOG((LF_CORDB, LL_EVERYTHING, "DRCT::IsReady - Helper not ready since DCB says id = 0.\n"));
+ return false;
+ }
+
+ // a more subtle check. It's possible the thread was up, but then
+ // an bad call to ExitProcess suddenly terminated the helper thread,
+ // leaving the threadid still non-0. So check the actual thread object
+ // and make sure it's still around.
+ int ret = WaitForSingleObject(m_thread, 0);
+ LOG((LF_CORDB, LL_EVERYTHING, "DRCT::IsReady - wait(0x%x)=%d, GetLastError() = %d\n", m_thread, ret, GetLastError()));
+
+ if (ret != WAIT_TIMEOUT)
+ {
+ return false;
+ }
+
+ return true;
+}
+
+
+HRESULT DebuggerRCThread::ReDaclEvents(PSECURITY_DESCRIPTOR pSecurityDescriptor)
+{
+ LIMITED_METHOD_CONTRACT;
+
+#ifndef FEATURE_PAL
+ if (m_pDCB != NULL)
+ {
+ if (m_pDCB->m_rightSideEventAvailable)
+ {
+ if (SetKernelObjectSecurity(m_pDCB->m_rightSideEventAvailable,
+ DACL_SECURITY_INFORMATION,
+ pSecurityDescriptor) == 0)
+ {
+ // failed!
+ return HRESULT_FROM_GetLastError();
+ }
+ }
+ if (m_pDCB->m_rightSideEventRead)
+ {
+ if (SetKernelObjectSecurity(m_pDCB->m_rightSideEventRead,
+ DACL_SECURITY_INFORMATION,
+ pSecurityDescriptor) == 0)
+ {
+ // failed!
+ return HRESULT_FROM_GetLastError();
+ }
+ }
+ }
+#endif // FEATURE_PAL
+
+ return S_OK;
+}
+
+
+//
+// A normal thread may hit a stack overflow and so we want to do
+// any stack-intensive work on the Helper thread so that we don't
+// use up the grace memory.
+// Note that DoFavor will block until the fp is executed
+//
+void DebuggerRCThread::DoFavor(FAVORCALLBACK fp, void * pData)
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ NOTHROW;
+ GC_TRIGGERS;
+
+ PRECONDITION(!ThisIsHelperThreadWorker());
+
+#ifdef PREFAST
+ // Prefast issue
+ // error C2664: 'CHECK CheckPointer(TypeHandle,IsNullOK)' : cannot convert parameter 1 from
+ // 'DebuggerRCThread::FAVORCALLBACK' to 'TypeHandle'
+#else
+ PRECONDITION(CheckPointer(fp));
+ PRECONDITION(CheckPointer(pData, NULL_OK));
+#endif
+ }
+ CONTRACTL_END;
+
+ // We are being called on managed thread only.
+ //
+
+ // We'll have problems if another thread comes in and
+ // deletes the RCThread object on us while we're in this call.
+ if (IsRCThreadReady())
+ {
+ // If the helper thread calls this, we deadlock.
+ // (Since we wait on an event that only the helper thread sets)
+ _ASSERTE(GetRCThreadId() != GetCurrentThreadId());
+
+ // Only lock if we're waiting on the helper thread.
+ // This should be the only place the FavorLock is used.
+ // Note this is never called on the helper thread.
+ CrstHolder ch(GetFavorLock());
+
+ SetFavorFnPtr(fp, pData);
+
+ // Our main message loop operating on the Helper thread will
+ // pickup that event, call the fp, and set the Read event
+ SetEvent(GetFavorAvailableEvent());
+
+ LOG((LF_CORDB, LL_INFO10000, "DRCT::DF - Waiting on FavorReadEvent for favor 0x%08x\n", fp));
+
+ // Wait for either the FavorEventRead to be set (which means that the favor
+ // was executed by the helper thread) or the helper thread's handle (which means
+ // that the helper thread exited without doing the favor, so we should do it)
+ //
+ // Note we are assuming that there's only 2 ways the helper thread can exit:
+ // 1) Someone calls ::ExitProcess, killing all threads. That will kill us too, so we're "ok".
+ // 2) Someone calls Stop(), causing the helper to exit gracefully. That's ok too. The helper
+ // didn't execute the Favor (else the FREvent would have been set first) and so we can.
+ //
+ // Beware of problems:
+ // 1) If the helper can block, we may deadlock.
+ // 2) If the helper can exit magically (or if we change the Wait to include a timeout) ,
+ // the helper thread may have not executed the favor, partially executed the favor,
+ // or totally executed the favor but not yet signaled the FavorReadEvent. We don't
+ // know what it did, so we don't know what we can do; so we're in an unstable state.
+
+ const HANDLE waitset [] = { GetFavorReadEvent(), m_thread };
+
+ // the favor worker thread will require a transition to cooperative mode in order to complete its work and we will
+ // wait for the favor to complete before terminating the process. if there is a GC in progress the favor thread
+ // will be blocked and if the thread requesting the favor is in cooperative mode we'll deadlock, so we switch to
+ // preemptive mode before waiting for the favor to complete (see Dev11 72349).
+ GCX_PREEMP();
+
+ DWORD ret = WaitForMultipleObjectsEx(
+ NumItems(waitset),
+ waitset,
+ FALSE,
+ INFINITE,
+ FALSE
+ );
+
+ DWORD wn = (ret - WAIT_OBJECT_0);
+ if (wn == 0) // m_FavorEventRead
+ {
+ // Favor was executed, nothing to do here.
+ LOG((LF_CORDB, LL_INFO10000, "DRCT::DF - favor 0x%08x finished, ret = %d\n", fp, ret));
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO10000, "DRCT::DF - lost helper thread during wait, "
+ "doing favor 0x%08x on current thread\n", fp));
+
+ // Since we have no timeout, we shouldn't be able to get an error on the wait,
+ // but just in case ...
+ _ASSERTE(ret != WAIT_FAILED);
+ _ASSERTE((wn == 1) && !"DoFavor - unexpected return from WFMO");
+
+ // Thread exited without doing favor, so execute it on our thread.
+ // If we're here because of a stack overflow, this may push us over the edge,
+ // but there's nothing else we can really do
+ (*fp)(pData);
+
+ ResetEvent(GetFavorAvailableEvent());
+ }
+
+ // m_fpFavor & m_pFavorData are meaningless now. We could set them
+ // to NULL, but we may as well leave them as is to leave a trail.
+
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO10000, "DRCT::DF - helper thread not ready, "
+ "doing favor 0x%08x on current thread\n", fp));
+ // If helper isn't ready yet, go ahead and execute the favor
+ // on the callee's space
+ (*fp)(pData);
+ }
+
+ // Drop a log message so that we know if we survived a stack overflow or not
+ LOG((LF_CORDB, LL_INFO10000, "DRCT::DF - Favor 0x%08x completed successfully\n", fp));
+}
+
+
+//
+// SendIPCReply simply indicates to the Right Side that a reply to a
+// two-way event is ready to be read and that the last event sent from
+// the Right Side has been fully processed.
+//
+// NOTE: this assumes that the event receive buffer has been properly
+// filled in. All it does it wake up the DI and let it know that its
+// safe to copy the event out of this process.
+//
+HRESULT DebuggerRCThread::SendIPCReply()
+{
+ HRESULT hr = S_OK;
+
+#ifdef LOGGING
+ DebuggerIPCEvent* event = GetIPCEventReceiveBuffer();
+
+ LOG((LF_CORDB, LL_INFO10000, "D::SIPCR: replying with %s.\n",
+ IPCENames::GetName(event->type)));
+#endif
+
+#if !defined(FEATURE_DBGIPC_TRANSPORT_VM)
+ BOOL succ = SetEvent(m_pDCB->m_rightSideEventRead);
+ if (!succ)
+ {
+ hr = CORDBDebuggerSetUnrecoverableWin32Error(m_debugger, 0, false);
+ }
+#else // !FEATURE_DBGIPC_TRANSPORT_VM
+ hr = g_pDbgTransport->SendEvent(GetIPCEventReceiveBuffer());
+ if (FAILED(hr))
+ {
+ m_debugger->UnrecoverableError(hr,
+ 0,
+ __FILE__,
+ __LINE__,
+ false);
+ }
+#endif // !FEATURE_DBGIPC_TRANSPORT_VM
+
+ return hr;
+}
+
+//
+// EarlyHelperThreadDeath handles the case where the helper
+// thread has been ripped out from underneath of us by
+// ExitProcess or TerminateProcess. These calls are bad, whacking
+// all threads except the caller in the process. This can happen, for
+// instance, when an app calls ExitProcess. All threads are wacked,
+// the main thread calls all DLL main's, and the EE starts shutting
+// down in its DLL main with the helper thread terminated.
+//
+void DebuggerRCThread::EarlyHelperThreadDeath(void)
+{
+ LOG((LF_CORDB, LL_INFO10000, "DRCT::EHTD\n"));
+
+ // If we ever spun up a thread...
+ if (m_thread != NULL && m_pDCB)
+ {
+ Debugger::DebuggerLockHolder debugLockHolder(m_debugger);
+
+ m_pDCB->m_helperThreadId = 0;
+
+ LOG((LF_CORDB, LL_INFO10000, "DRCT::EHTD helperThreadId\n"));
+ // dbgLockHolder goes out of scope - implicit Release
+ }
+}
+
diff --git a/src/debug/ee/shared.cpp b/src/debug/ee/shared.cpp
new file mode 100644
index 0000000000..584abf7da8
--- /dev/null
+++ b/src/debug/ee/shared.cpp
@@ -0,0 +1,15 @@
+// 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.
+
+//
+
+/*
+ *
+ * Common source file for all files in ..\shared for compiling into the left-side
+ *
+ */
+#include "stdafx.h"
+
+#include "../shared/utils.cpp"
+#include "../shared/dbgtransportsession.cpp"
diff --git a/src/debug/ee/stdafx.cpp b/src/debug/ee/stdafx.cpp
new file mode 100644
index 0000000000..f508973779
--- /dev/null
+++ b/src/debug/ee/stdafx.cpp
@@ -0,0 +1,12 @@
+// 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.
+//*****************************************************************************
+// File: stdafx.cpp
+//
+
+//
+// Host for precompiled headers.
+//
+//*****************************************************************************
+#include "stdafx.h" // Precompiled header key.
diff --git a/src/debug/ee/stdafx.h b/src/debug/ee/stdafx.h
new file mode 100644
index 0000000000..7ccfa8d984
--- /dev/null
+++ b/src/debug/ee/stdafx.h
@@ -0,0 +1,39 @@
+// 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.
+//*****************************************************************************
+// File: stdafx.h
+//
+
+//
+//*****************************************************************************
+#include <stdint.h>
+#include <wchar.h>
+#include <stdio.h>
+
+#include <windows.h>
+#if !defined(FEATURE_CORECLR)
+#undef GetCurrentTime // works around a macro def conflict of GetCurrentTime
+#include <windows.ui.xaml.h>
+#endif // !FEATURE_CORECLR
+
+#include <switches.h>
+#include <winwrap.h>
+
+#ifdef DACCESS_COMPILE
+#include <specstrings.h>
+#endif
+
+#include <util.hpp>
+
+#include <dbgtargetcontext.h>
+
+#include <cordbpriv.h>
+#include <dbgipcevents.h>
+#include "debugger.h"
+#include "walker.h"
+#include "controller.h"
+#include "frameinfo.h"
+#include <corerror.h>
+#include "../inc/common.h"
+
diff --git a/src/debug/ee/walker.h b/src/debug/ee/walker.h
new file mode 100644
index 0000000000..d7deb10ca4
--- /dev/null
+++ b/src/debug/ee/walker.h
@@ -0,0 +1,255 @@
+// 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.
+//*****************************************************************************
+// File: walker.h
+//
+
+//
+// Debugger code stream analysis routines
+//
+//*****************************************************************************
+
+#ifndef WALKER_H_
+#define WALKER_H_
+
+
+/* ========================================================================= */
+
+/* ------------------------------------------------------------------------- *
+ * Constants
+ * ------------------------------------------------------------------------- */
+
+enum WALK_TYPE
+{
+ WALK_NEXT,
+ WALK_BRANCH,
+ WALK_COND_BRANCH,
+ WALK_CALL,
+ WALK_RETURN,
+ WALK_BREAK,
+ WALK_THROW,
+ WALK_META,
+ WALK_UNKNOWN
+};
+
+// struct holding information for the instruction being skipped over
+struct InstructionAttribute
+{
+ bool m_fIsCall; // is this a call instruction?
+ bool m_fIsCond; // is this a conditional jump?
+ bool m_fIsAbsBranch; // is this an absolute branch (either a call or a jump)?
+ bool m_fIsRelBranch; // is this a relative branch (either a call or a jump)?
+ bool m_fIsWrite; // does the instruction write to an address?
+
+
+ DWORD m_cbInstr; // the size of the instruction
+ DWORD m_cbDisp; // the size of the displacement
+ DWORD m_dwOffsetToDisp; // the offset from the beginning of the instruction
+ // to the beginning of the displacement
+ BYTE m_cOperandSize; // the size of the operand
+
+ void Reset()
+ {
+ m_fIsCall = false;
+ m_fIsCond = false;
+ m_fIsAbsBranch = false;
+ m_fIsRelBranch = false;
+ m_fIsWrite = false;
+ m_cbInstr = 0;
+ m_cbDisp = 0;
+ m_dwOffsetToDisp = 0;
+ m_cOperandSize = 0;
+ }
+};
+
+/* ------------------------------------------------------------------------- *
+ * Classes
+ * ------------------------------------------------------------------------- */
+
+class Walker
+{
+protected:
+ Walker()
+ : m_type(WALK_UNKNOWN), m_registers(NULL), m_ip(0), m_skipIP(0), m_nextIP(0), m_isAbsoluteBranch(false)
+ {LIMITED_METHOD_CONTRACT; }
+
+public:
+
+ virtual void Init(const BYTE *ip, REGDISPLAY *pregisters)
+ {
+ PREFIX_ASSUME(pregisters != NULL);
+ _ASSERTE(GetControlPC(pregisters) == (PCODE)ip);
+
+ m_registers = pregisters;
+ SetIP(ip);
+ }
+
+ const BYTE *GetIP()
+ { return m_ip; }
+
+ WALK_TYPE GetOpcodeWalkType()
+ { return m_type; }
+
+ const BYTE *GetSkipIP()
+ { return m_skipIP; }
+
+ bool IsAbsoluteBranch()
+ { return m_isAbsoluteBranch; }
+
+ const BYTE *GetNextIP()
+ { return m_nextIP; }
+
+ // We don't currently keep the registers up to date
+ // <TODO> Check if it really works on IA64. </TODO>
+ virtual void Next() { m_registers = NULL; SetIP(m_nextIP); }
+ virtual void Skip() { m_registers = NULL; LOG((LF_CORDB, LL_INFO10000, "skipping over to %p \n", m_skipIP)); SetIP(m_skipIP); }
+
+ // Decode the instruction
+ virtual void Decode() = 0;
+
+private:
+ void SetIP(const BYTE *ip)
+ { m_ip = ip; Decode(); }
+
+protected:
+ WALK_TYPE m_type; // Type of instructions
+ REGDISPLAY *m_registers; // Registers
+ const BYTE *m_ip; // Current IP
+ const BYTE *m_skipIP; // IP if we skip the instruction
+ const BYTE *m_nextIP; // IP if the instruction is taken
+ bool m_isAbsoluteBranch; // Is it an obsolute branch or not
+};
+
+#ifdef _TARGET_X86_
+
+class NativeWalker : public Walker
+{
+public:
+ void Init(const BYTE *ip, REGDISPLAY *pregisters)
+ {
+ m_opcode = 0;
+ Walker::Init(ip, pregisters);
+ }
+
+ DWORD GetOpcode()
+ { return m_opcode; }
+/*
+ void SetRegDisplay(REGDISPLAY *registers)
+ { m_registers = registers; }
+*/
+ REGDISPLAY *GetRegDisplay()
+ { return m_registers; }
+
+ void Decode();
+ void DecodeModRM(BYTE mod, BYTE reg, BYTE rm, const BYTE *ip);
+ static void DecodeInstructionForPatchSkip(const BYTE *address, InstructionAttribute * pInstrAttrib);
+
+private:
+ DWORD GetRegisterValue(int registerNumber);
+
+ DWORD m_opcode; // Current instruction or opcode
+};
+
+#elif defined (_TARGET_ARM_)
+
+class NativeWalker : public Walker
+{
+public:
+ void Init(const BYTE *ip, REGDISPLAY *pregisters)
+ {
+ Walker::Init(ip, pregisters);
+ }
+
+ void Decode();
+
+private:
+ bool ConditionHolds(DWORD cond);
+ DWORD GetReg(DWORD reg);
+};
+
+#elif defined(_TARGET_AMD64_)
+
+class NativeWalker : public Walker
+{
+public:
+ void Init(const BYTE *ip, REGDISPLAY *pregisters)
+ {
+ m_opcode = 0;
+ Walker::Init(ip, pregisters);
+ }
+
+ DWORD GetOpcode()
+ { return m_opcode; }
+/*
+ void SetRegDisplay(REGDISPLAY *registers)
+ { m_registers = registers; }
+*/
+ REGDISPLAY *GetRegDisplay()
+ { return m_registers; }
+
+ void Decode();
+ void DecodeModRM(BYTE mod, BYTE reg, BYTE rm, const BYTE *ip);
+ static void DecodeInstructionForPatchSkip(const BYTE *address, InstructionAttribute * pInstrAttrib);
+
+private:
+ UINT64 GetRegisterValue(int registerNumber);
+
+ DWORD m_opcode; // Current instruction or opcode
+};
+#elif defined (_TARGET_ARM64_)
+#include "controller.h"
+class NativeWalker : public Walker
+{
+public:
+ void Init(const BYTE *ip, REGDISPLAY *pregisters)
+ {
+ Walker::Init(ip, pregisters);
+ }
+ void Decode();
+ static void NativeWalker::DecodeInstructionForPatchSkip(const BYTE *address, InstructionAttribute * pInstrAttrib)
+ {
+ pInstrAttrib->Reset();
+ }
+ static BOOL NativeWalker::DecodePCRelativeBranchInst(PT_CONTEXT context,const PRD_TYPE& opcode, PCODE& offset, WALK_TYPE& walk);
+ static BOOL NativeWalker::DecodeCallInst(const PRD_TYPE& opcode, int& RegNum, WALK_TYPE& walk);
+ static BYTE* SetupOrSimulateInstructionForPatchSkip(T_CONTEXT * context, SharedPatchBypassBuffer * m_pSharedPatchBypassBuffer, const BYTE *address, PRD_TYPE opcode);
+
+};
+#else
+PORTABILITY_WARNING("NativeWalker not implemented on this platform");
+class NativeWalker : public Walker
+{
+public:
+ void Init(const BYTE *ip, REGDISPLAY *pregisters)
+ {
+ m_opcode = 0;
+ Walker::Init(ip, pregisters);
+ }
+ DWORD GetOpcode()
+ { return m_opcode; }
+ void Next()
+ { Walker::Next(); }
+ void Skip()
+ { Walker::Skip(); }
+
+ void Decode()
+ {
+ PORTABILITY_ASSERT("NativeWalker not implemented on this platform");
+ m_type = WALK_UNKNOWN;
+ m_skipIP = m_ip++;
+ m_nextIP = m_ip++;
+ }
+
+ static void DecodeInstructionForPatchSkip(const BYTE *address, InstructionAttribute * pInstrAttrib)
+ {
+ PORTABILITY_ASSERT("NativeWalker not implemented on this platform");
+
+ }
+
+private:
+ DWORD m_opcode; // Current instruction or opcode
+};
+#endif
+
+#endif // WALKER_H_
diff --git a/src/debug/ee/wks/.gitmirror b/src/debug/ee/wks/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/debug/ee/wks/.gitmirror
@@ -0,0 +1 @@
+Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror. \ No newline at end of file
diff --git a/src/debug/ee/wks/CMakeLists.txt b/src/debug/ee/wks/CMakeLists.txt
new file mode 100644
index 0000000000..a096cbfca7
--- /dev/null
+++ b/src/debug/ee/wks/CMakeLists.txt
@@ -0,0 +1,68 @@
+if (WIN32)
+
+add_precompiled_header(stdafx.h ../stdafx.cpp CORDBEE_SOURCES_WKS)
+
+get_include_directories(ASM_INCLUDE_DIRECTORIES)
+get_compile_definitions(ASM_DEFINITIONS)
+set(ASM_OPTIONS /c /Zi /W3 /errorReport:prompt)
+
+if (CLR_CMAKE_PLATFORM_ARCH_I386)
+ list (APPEND ASM_OPTIONS /safeseh)
+endif (CLR_CMAKE_PLATFORM_ARCH_I386)
+
+set(ASM_FILE ${CORDBEE_DIR}/${ARCH_SOURCES_DIR}/dbghelpers.asm)
+# asm files require preprocessing using cl.exe on arm64
+if(CLR_CMAKE_PLATFORM_ARCH_ARM64)
+ get_filename_component(name ${ASM_FILE} NAME_WE)
+ set(ASM_PREPROCESSED_FILE ${CMAKE_CURRENT_BINARY_DIR}/${name}.asm)
+ preprocess_def_file(${ASM_FILE} ${ASM_PREPROCESSED_FILE})
+ set(CORDBEE_SOURCES_WKS_PREPROCESSED_ASM ${ASM_PREPROCESSED_FILE})
+ set_property(SOURCE ${CORDBEE_SOURCES_WKS_PREPROCESSED_ASM} PROPERTY COMPILE_DEFINITIONS ${ASM_DEFINITIONS})
+ set_property(SOURCE ${CORDBEE_SOURCES_WKS_PREPROCESSED_ASM} PROPERTY COMPILE_DEFINITIONS ${ASM_OPTIONS})
+ add_library_clr(cordbee_wks ${CORDBEE_SOURCES_WKS} ${CORDBEE_SOURCES_WKS_PREPROCESSED_ASM})
+elseif(CLR_CMAKE_PLATFORM_ARCH_ARM)
+
+ # On Arm32 for Windows, use C++ compiler to process the .asm since it includes C-style headers.
+ set(DBGHELPERS_ASM ${CMAKE_CURRENT_BINARY_DIR}/dbghelpers.asm)
+ set(ASM_OPTIONS " -g ")
+
+ preprocess_def_file(${ASM_FILE} ${DBGHELPERS_ASM})
+
+ # We do not pass any defines since we have already done pre-processing above
+ set (DBGHELPERS_ASM_CMDLINE "-o ${CMAKE_CURRENT_BINARY_DIR}/dbghelpers.obj ${DBGHELPERS_ASM}")
+
+ file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/runasm.cmd"
+ CONTENT "\"${CMAKE_ASM_MASM_COMPILER}\" ${ASM_OPTIONS} ${DBGHELPERS_ASM_CMDLINE}")
+
+ # Need to compile asm file using custom command as include directories are not provided to asm compiler
+ add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/dbghelpers.obj
+ COMMAND ${CMAKE_CURRENT_BINARY_DIR}/runasm.cmd
+ DEPENDS ${DBGHELPERS_ASM}
+ COMMENT "Compiling dbghelpers.asm - ${CMAKE_CURRENT_BINARY_DIR}/runasm.cmd")
+ add_library_clr(cordbee_wks ${CORDBEE_SOURCES_WKS} ${CMAKE_CURRENT_BINARY_DIR}/dbghelpers.obj)
+else ()
+
+ # Need to compile asm file using custom command as include directories are not provided to asm compiler
+ add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/dbghelpers.obj
+ COMMAND ${CMAKE_ASM_MASM_COMPILER} ${ASM_INCLUDE_DIRECTORIES} ${ASM_DEFINITIONS} ${ASM_OPTIONS} /Fo${CMAKE_CURRENT_BINARY_DIR}/dbghelpers.obj /Ta${ASM_FILE}
+ DEPENDS ${ASM_FILE}
+ COMMENT "Compiling dbghelpers.asm")
+
+ add_library_clr(cordbee_wks ${CORDBEE_SOURCES_WKS} ${CMAKE_CURRENT_BINARY_DIR}/dbghelpers.obj)
+endif(CLR_CMAKE_PLATFORM_ARCH_ARM64)
+
+else ()
+
+add_compile_options(-fPIC)
+
+if(CLR_CMAKE_PLATFORM_ARCH_AMD64)
+ add_library_clr(cordbee_wks ${CORDBEE_SOURCES_WKS} ../${ARCH_SOURCES_DIR}/dbghelpers.S)
+elseif(CLR_CMAKE_PLATFORM_ARCH_ARM)
+ add_library_clr(cordbee_wks ${CORDBEE_SOURCES_WKS} ../${ARCH_SOURCES_DIR}/dbghelpers.S)
+elseif(CLR_CMAKE_PLATFORM_ARCH_ARM64)
+ add_library_clr(cordbee_wks ${CORDBEE_SOURCES_WKS})
+else()
+ message(FATAL_ERROR "Only ARM and AMD64 is supported")
+endif()
+
+endif (WIN32)
diff --git a/src/debug/ee/wks/wks.nativeproj b/src/debug/ee/wks/wks.nativeproj
new file mode 100644
index 0000000000..304c591485
--- /dev/null
+++ b/src/debug/ee/wks/wks.nativeproj
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="dogfood">
+ <!--*****************************************************-->
+ <!--This MSBuild project file was automatically generated-->
+ <!--from the original SOURCES/DIRS file by the KBC tool.-->
+ <!--*****************************************************-->
+ <!--Import the settings-->
+ <!--Leaf project Properties-->
+ <PropertyGroup Label="Globals">
+ <SccProjectName>SAK</SccProjectName>
+ <SccAuxPath>SAK</SccAuxPath>
+ <SccLocalPath>SAK</SccLocalPath>
+ <SccProvider>SAK</SccProvider>
+ </PropertyGroup>
+ <PropertyGroup>
+ <BuildCoreBinaries>true</BuildCoreBinaries>
+ <BuildSysBinaries>true</BuildSysBinaries>
+ <OutputPath>$(ClrLibDest)</OutputPath>
+ <OutputName>cordbee_wks</OutputName>
+ <TargetType>LIBRARY</TargetType>
+ <UserAssembleAmd64IncludePath>
+ $(UserAssembleAmd64IncludePath);
+ ..\..\..\vm\AMD64;
+ </UserAssembleAmd64IncludePath>
+ </PropertyGroup>
+ <!--Leaf Project Items-->
+ <Import Project="..\EE.props" />
+ <ItemGroup>
+ <CppCompile Include="@(SourcesNodac)" />
+ <CppCompile Include="@(I386Sources)" />
+ <CppCompile Include="@(Amd64Sources)" />
+ <CppCompile Include="@(ArmSources)" />
+ <CppCompile Include="@(Arm64Sources)" />
+ <PreprocessAssembleArm Condition="'$(BuildArchitecture)' == 'arm'" Include="..\arm\dbghelpers.asm" />
+ <PreprocessAssembleArm Condition="'$(BuildArchitecture)' == 'arm64'" Include="..\arm64\dbghelpers.asm" />
+ <AssembleArm Condition="'$(BuildArchitecture)' == 'arm'" Include="$(IntermediateOutputDirectory)\dbghelpers.i" />
+ <AssembleArm64 Condition="'$(BuildArchitecture)' == 'arm64'" Include="$(IntermediateOutputDirectory)\dbghelpers.i" />
+ <Assemble386 Condition="'$(BuildArchitecture)' == 'i386'" Include="..\i386\dbghelpers.asm" />
+ <AssembleAmd64 Condition="'$(BuildArchitecture)' == 'amd64'" Include="..\amd64\dbghelpers.asm" />
+ </ItemGroup>
+ <!--Import the targets-->
+ <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\clr.targets" />
+</Project>
diff --git a/src/debug/ildbsymlib/.gitmirror b/src/debug/ildbsymlib/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/debug/ildbsymlib/.gitmirror
@@ -0,0 +1 @@
+Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror. \ No newline at end of file
diff --git a/src/debug/ildbsymlib/CMakeLists.txt b/src/debug/ildbsymlib/CMakeLists.txt
new file mode 100644
index 0000000000..1bd1096ed6
--- /dev/null
+++ b/src/debug/ildbsymlib/CMakeLists.txt
@@ -0,0 +1,18 @@
+if(WIN32)
+ #use static crt
+ add_definitions(-MT)
+endif(WIN32)
+
+set( ILDBSYMLIB_SOURCES
+ symread.cpp
+ symbinder.cpp
+ ildbsymbols.cpp
+ symwrite.cpp
+)
+
+if(CLR_CMAKE_PLATFORM_UNIX)
+ add_compile_options(-fPIC)
+endif(CLR_CMAKE_PLATFORM_UNIX)
+
+add_library_clr(ildbsymlib ${ILDBSYMLIB_SOURCES})
+
diff --git a/src/debug/ildbsymlib/classfactory.h b/src/debug/ildbsymlib/classfactory.h
new file mode 100644
index 0000000000..2b38167747
--- /dev/null
+++ b/src/debug/ildbsymlib/classfactory.h
@@ -0,0 +1,95 @@
+// 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.
+// ===========================================================================
+// File: ClassFactory.h
+//
+
+// ===========================================================================
+
+//*****************************************************************************
+// ClassFactory.h
+//
+// Class factories are used by the pluming in COM to activate new objects.
+// This module contains the class factory code to instantiate the
+// ISymUnmanaged Reader,Writer and Binder
+//*****************************************************************************
+#ifndef __ILDBClassFactory__h__
+#define __ILDBClassFactory__h__
+
+// This typedef is for a function which will create a new instance of an object.
+typedef HRESULT (* PFN_CREATE_OBJ)(REFIID riid, void**ppvObject);
+
+//*****************************************************************************
+// This structure is used to declare a global list of coclasses. The class
+// factory object is created with a pointer to the correct one of these, so
+// that when create instance is called, it can be created.
+//*****************************************************************************
+struct COCLASS_REGISTER
+{
+ const GUID *pClsid; // Class ID of the coclass.
+ LPCWSTR szProgID; // Prog ID of the class.
+ PFN_CREATE_OBJ pfnCreateObject; // Creation function for an instance.
+};
+
+
+
+//*****************************************************************************
+// One class factory object satifies all of our clsid's, to reduce overall
+// code bloat.
+//*****************************************************************************
+class CIldbClassFactory :
+ public IClassFactory
+{
+ CIldbClassFactory() { } // Can't use without data.
+
+public:
+ CIldbClassFactory(const COCLASS_REGISTER *pCoClass)
+ : m_cRef(1), m_pCoClass(pCoClass)
+ { }
+
+ virtual ~CIldbClassFactory() {}
+
+ //
+ // IUnknown methods.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE QueryInterface(
+ REFIID riid,
+ void **ppvObject);
+
+ virtual ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return InterlockedIncrement(&m_cRef);
+ }
+
+ virtual ULONG STDMETHODCALLTYPE Release()
+ {
+ LONG cRef = InterlockedDecrement(&m_cRef);
+ if (cRef <= 0)
+ DELETE(this);
+ return (cRef);
+ }
+
+
+ //
+ // IClassFactory methods.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE CreateInstance(
+ IUnknown *pUnkOuter,
+ REFIID riid,
+ void **ppvObject);
+
+ virtual HRESULT STDMETHODCALLTYPE LockServer(
+ BOOL fLock);
+
+
+private:
+ LONG m_cRef; // Reference count.
+ const COCLASS_REGISTER *m_pCoClass; // The class we belong to.
+};
+
+
+
+#endif // __ClassFactory__h__
diff --git a/src/debug/ildbsymlib/dirs.proj b/src/debug/ildbsymlib/dirs.proj
new file mode 100644
index 0000000000..b171f7bca5
--- /dev/null
+++ b/src/debug/ildbsymlib/dirs.proj
@@ -0,0 +1,19 @@
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <!--Import the settings-->
+ <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\clr.props" />
+
+ <PropertyGroup>
+ <BuildInPhase1>true</BuildInPhase1>
+ <BuildInPhaseDefault>false</BuildInPhaseDefault>
+ <BuildCoreBinaries>true</BuildCoreBinaries>
+ <BuildSysBinaries>true</BuildSysBinaries>
+ </PropertyGroup>
+
+ <!--The following projects will build during PHASE 1-->
+ <ItemGroup Condition="'$(BuildExePhase)' == '1'">
+ <ProjectFile Condition="'$(FeatureDbiDebugging)'=='true'" Include="HostLocal\ildbsymlib.nativeproj" />
+ </ItemGroup>
+
+ <!--Import the targets-->
+ <Import Project="$(_NTDRIVE)$(_NTROOT)\tools\Microsoft.DevDiv.Traversal.targets" />
+</Project>
diff --git a/src/debug/ildbsymlib/ildbsymbols.cpp b/src/debug/ildbsymlib/ildbsymbols.cpp
new file mode 100644
index 0000000000..1c9219ce62
--- /dev/null
+++ b/src/debug/ildbsymlib/ildbsymbols.cpp
@@ -0,0 +1,155 @@
+// 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.
+// ===========================================================================
+// File: ildbsymbols.cpp
+//
+
+// ===========================================================================
+
+#include "pch.h"
+
+#include "classfactory.h"
+
+// GUID identifying the ILDB format version.
+extern "C" const GUID ILDB_VERSION_GUID = {0x9e02e5b6, 0x8aef, 0x4d06, { 0x82, 0xe8, 0xe, 0x9b, 0x45, 0x49, 0x97, 0x16} };
+
+// Version used for the "first source release", no longer supported.
+extern "C" const GUID ILDB_VERSION_GUID_FSR = {0xCB2F6723, 0xAB3A, 0x11d, { 0x9C, 0x40, 0x00, 0xC0, 0x4F, 0xA3, 0x0A, 0x3E} };
+
+// This map contains the list of coclasses which are exported from this module.
+const COCLASS_REGISTER g_CoClasses[] =
+{
+// pClsid szProgID pfnCreateObject
+ { &CLSID_CorSymReader_SxS, W("CorSymReader"), SymReader::NewSymReader},
+ { &CLSID_CorSymWriter_SxS, W("CorSymWriter"), SymWriter::NewSymWriter},
+ { &CLSID_CorSymBinder_SxS, W("CorSymBinder"), SymBinder::NewSymBinder},
+ { NULL, NULL, NULL }
+};
+
+STDAPI IldbSymbolsGetClassObject(REFCLSID rclsid, REFIID riid, void** ppvObject)
+{
+ CIldbClassFactory *pClassFactory; // To create class factory object.
+ const COCLASS_REGISTER *pCoClass; // Loop control.
+ HRESULT hr = CLASS_E_CLASSNOTAVAILABLE;
+
+ _ASSERTE(IsValidCLSID(rclsid));
+ _ASSERTE(IsValidIID(riid));
+ _ASSERTE(IsValidWritePtr(ppvObject, void*));
+
+ if (ppvObject)
+ {
+ *ppvObject = NULL;
+
+ // Scan for the right one.
+ for (pCoClass=g_CoClasses; pCoClass->pClsid; pCoClass++)
+ {
+ if (*pCoClass->pClsid == rclsid)
+ {
+ // Allocate the new factory object.
+ pClassFactory = NEW(CIldbClassFactory(pCoClass));
+ if (!pClassFactory)
+ return (E_OUTOFMEMORY);
+
+ // Pick the v-table based on the caller's request.
+ hr = pClassFactory->QueryInterface(riid, ppvObject);
+
+ // Always release the local reference, if QI failed it will be
+ // the only one and the object gets freed.
+ pClassFactory->Release();
+ break;
+ }
+ }
+ }
+ else
+ {
+ hr = E_INVALIDARG;
+ }
+
+ return hr;
+}
+
+/* ------------------------------------------------------------------------- *
+ * CIldbClassFactory class
+ * ------------------------------------------------------------------------- */
+
+//*****************************************************************************
+// QueryInterface is called to pick a v-table on the co-class.
+//*****************************************************************************
+HRESULT STDMETHODCALLTYPE CIldbClassFactory::QueryInterface(
+ REFIID riid,
+ void **ppvObject)
+{
+ HRESULT hr;
+
+ if (ppvObject == NULL)
+ {
+ return E_INVALIDARG;
+ }
+
+ // Avoid confusion.
+ *ppvObject = NULL;
+
+ // Pick the right v-table based on the IID passed in.
+ if (riid == IID_IUnknown)
+ *ppvObject = (IUnknown *) this;
+ else if (riid == IID_IClassFactory)
+ *ppvObject = (IClassFactory *) this;
+
+ // If successful, add a reference for out pointer and return.
+ if (*ppvObject)
+ {
+ hr = S_OK;
+ AddRef();
+ }
+ else
+ hr = E_NOINTERFACE;
+ return (hr);
+}
+
+
+//*****************************************************************************
+// CreateInstance is called to create a new instance of the coclass for which
+// this class was created in the first place. The returned pointer is the
+// v-table matching the IID if there.
+//*****************************************************************************
+HRESULT STDMETHODCALLTYPE CIldbClassFactory::CreateInstance(
+ IUnknown *pUnkOuter,
+ REFIID riid,
+ void **ppvObject)
+{
+ HRESULT hr;
+
+ _ASSERTE(IsValidIID(riid));
+ _ASSERTE(IsValidWritePtr(ppvObject, void*));
+
+ // Avoid confusion.
+ *ppvObject = NULL;
+ _ASSERTE(m_pCoClass);
+
+ // Aggregation is not supported by these objects.
+ if (pUnkOuter)
+ return (CLASS_E_NOAGGREGATION);
+
+ // Ask the object to create an instance of itself, and check the iid.
+ hr = (*m_pCoClass->pfnCreateObject)(riid, ppvObject);
+ return (hr);
+}
+
+// Version of CreateInstance called directly from clients
+STDAPI IldbSymbolsCreateInstance(REFCLSID rclsid, REFIID riid, void** ppvIUnknown)
+{
+ IClassFactory *pClassFactory = NULL;
+ HRESULT hr = IldbSymbolsGetClassObject(rclsid, IID_IClassFactory, (void**)&pClassFactory);
+ if (SUCCEEDED(hr))
+ hr = pClassFactory->CreateInstance(NULL, riid, ppvIUnknown);
+ if (pClassFactory)
+ pClassFactory->Release();
+ return hr;
+}
+
+HRESULT STDMETHODCALLTYPE CIldbClassFactory::LockServer(
+ BOOL fLock)
+{
+ return (S_OK);
+}
diff --git a/src/debug/ildbsymlib/ildbsymlib.props b/src/debug/ildbsymlib/ildbsymlib.props
new file mode 100644
index 0000000000..2a64f2eb7e
--- /dev/null
+++ b/src/debug/ildbsymlib/ildbsymlib.props
@@ -0,0 +1,29 @@
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="dogfood">
+ <!--*****************************************************-->
+ <!--This MSBuild project file was automatically generated-->
+ <!--from the original SOURCES/DIRS file by the KBC tool.-->
+ <!--*****************************************************-->
+ <!--Import the settings-->
+ <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\clr.props" />
+ <!--Leaf project Properties-->
+ <PropertyGroup>
+ <TargetType>LIBRARY</TargetType>
+ <OutputPath>$(ClrLibDest)</OutputPath>
+ <LinkSubsystem>windows</LinkSubsystem>
+ <UseMsvcrt />
+ <ExceptionHandling>$(Sehonly)</ExceptionHandling>
+ <UserIncludes>$(UserIncludes);
+ ..\;
+ ..\..\..\inc;
+ </UserIncludes>
+ <CDefines>$(CDefines);UNICODE;_UNICODE</CDefines>
+ </PropertyGroup>
+ <!--Leaf Project Items-->
+ <ItemGroup>
+ <CppCompile Include="..\symread.cpp" />
+ <CppCompile Include="..\symbinder.cpp" />
+ <CppCompile Include="..\ildbsymbols.cpp" />
+ <CppCompile Include="..\symwrite.cpp" />
+ </ItemGroup>
+
+</Project>
diff --git a/src/debug/ildbsymlib/ildbsymlib.vcproj b/src/debug/ildbsymlib/ildbsymlib.vcproj
new file mode 100644
index 0000000000..07202121d9
--- /dev/null
+++ b/src/debug/ildbsymlib/ildbsymlib.vcproj
@@ -0,0 +1,213 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="9.00"
+ Name="ildbsymlib"
+ ProjectGUID="{08C26436-55DD-4332-804C-C963A859E4DE}"
+ RootNamespace="ildbsymlib"
+ Keyword="Win32Proj"
+ TargetFrameworkVersion="131072"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ OutputDirectory="Debug"
+ IntermediateDirectory="Debug"
+ ConfigurationType="4"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ AdditionalIncludeDirectories=".;..\..\inc"
+ PreprocessorDefinitions="WIN32;_DEBUG;_LIB;-DUNICODE -D_UNICODE"
+ MinimalRebuild="true"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="3"
+ UsePrecompiledHeader="0"
+ WarningLevel="3"
+ Detect64BitPortabilityProblems="true"
+ DebugInformationFormat="4"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLibrarianTool"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ OutputDirectory="Release"
+ IntermediateDirectory="Release"
+ ConfigurationType="4"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=".;..\..\inc"
+ PreprocessorDefinitions="WIN32;NDEBUG;_LIB;-DUNICODE -D_UNICODE"
+ RuntimeLibrary="2"
+ UsePrecompiledHeader="0"
+ WarningLevel="3"
+ Detect64BitPortabilityProblems="true"
+ DebugInformationFormat="3"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLibrarianTool"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="Header Files"
+ Filter="h;hpp;hxx;hm;inl;inc;xsd"
+ UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
+ >
+ <File
+ RelativePath=".\classfactory.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\inc\IldbSymLib.h"
+ >
+ </File>
+ <File
+ RelativePath=".\pch.h"
+ >
+ </File>
+ <File
+ RelativePath=".\pdbdata.h"
+ >
+ </File>
+ <File
+ RelativePath=".\symbinder.h"
+ >
+ </File>
+ <File
+ RelativePath=".\SymRead.h"
+ >
+ </File>
+ <File
+ RelativePath=".\SymWrite.h"
+ >
+ </File>
+ <File
+ RelativePath=".\umisc.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="Resource Files"
+ Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx"
+ UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"
+ >
+ </Filter>
+ <Filter
+ Name="Source Files"
+ Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
+ UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
+ >
+ <File
+ RelativePath=".\ildbsymbols.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\symbinder.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\SymRead.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\symwrite.cpp"
+ >
+ </File>
+ </Filter>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/src/debug/ildbsymlib/pch.h b/src/debug/ildbsymlib/pch.h
new file mode 100644
index 0000000000..ddd4787aa0
--- /dev/null
+++ b/src/debug/ildbsymlib/pch.h
@@ -0,0 +1,41 @@
+// 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.
+// ===========================================================================
+// File: pch.h
+//
+
+// ===========================================================================
+
+#ifndef _ILDBSYMLIB_PCH_H_
+#define _ILDBSYMLIB_PCH_H_
+
+#include "ole2.h"
+
+#include "winwrap.h"
+#include "umisc.h"
+
+#include "corhdr.h"
+#include "corsym.h"
+#include "palclr.h"
+#include "cor.h"
+#include "genericstackprobe.h"
+
+// I'm not sure why this code uses these macros for memory management (they should at least be
+// in-line functions). DELETE is a symbol defined in WinNt.h as an access-type. We're probably
+// not going to try and use that, so we'll just override it for now.
+#ifdef DELETE
+#undef DELETE
+#endif
+
+
+#define NEW( x ) ( ::new (nothrow) x )
+#define DELETE( x ) ( ::delete(x) )
+#define DELETEARRAY( x ) (::delete[] (x))
+
+#include "ildbsymlib.h"
+#include "symwrite.h"
+#include "symread.h"
+#include "symbinder.h"
+
+#endif
diff --git a/src/debug/ildbsymlib/pdbdata.h b/src/debug/ildbsymlib/pdbdata.h
new file mode 100644
index 0000000000..cf09d782e7
--- /dev/null
+++ b/src/debug/ildbsymlib/pdbdata.h
@@ -0,0 +1,92 @@
+// 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.
+// ===========================================================================
+// File: pdbdata.h
+//
+
+// ===========================================================================
+
+#ifndef PDBDATA_H_
+#define PDBDATA_H_
+
+#include "umisc.h"
+#include "palclr.h"
+
+struct SymMethodInfo;
+struct SymLexicalScope;
+struct SymVariable;
+struct SymUsingNamespace;
+struct SymConstant;
+struct SequencePoint;
+struct DocumentInfo;
+struct MethodInfo;
+
+extern "C" const GUID ILDB_VERSION_GUID_FSR;
+extern "C" const GUID ILDB_VERSION_GUID;
+
+#define ILDB_MINOR_VERSION_NUMBER 0
+#define ILDB_SIGNATURE "_ildb_signature"
+#define ILDB_SIGNATURE_SIZE (16)
+
+typedef struct PDBInfo {
+
+ // Entry point of the PE
+ mdMethodDef m_userEntryPoint;
+
+ UINT32 m_CountOfMethods;
+ UINT32 m_CountOfScopes;
+ UINT32 m_CountOfVars;
+ UINT32 m_CountOfUsing;
+ UINT32 m_CountOfConstants;
+ UINT32 m_CountOfDocuments;
+ UINT32 m_CountOfSequencePoints;
+ UINT32 m_CountOfBytes;
+ UINT32 m_CountOfStringBytes;
+
+public:
+ PDBInfo()
+ {
+ memset(this, 0, sizeof(PDBInfo));
+ // Make sure m_userEntryPoint initialized correctly
+ _ASSERTE(mdTokenNil == 0);
+ }
+
+#if BIGENDIAN
+ void ConvertEndianness() {
+ m_userEntryPoint = VAL32(m_userEntryPoint);
+ m_CountOfMethods = VAL32(m_CountOfMethods);
+ m_CountOfScopes = VAL32(m_CountOfScopes);
+ m_CountOfVars = VAL32(m_CountOfVars);
+ m_CountOfUsing = VAL32(m_CountOfUsing);
+ m_CountOfConstants = VAL32(m_CountOfConstants);
+ m_CountOfSequencePoints = VAL32(m_CountOfSequencePoints);
+ m_CountOfDocuments = VAL32(m_CountOfDocuments);
+ m_CountOfBytes = VAL32(m_CountOfBytes);
+ m_CountOfStringBytes = VAL32(m_CountOfStringBytes);
+ }
+#else
+ void ConvertEndianness() {}
+#endif
+
+} PDBInfo;
+
+// The signature, Guid version + PDBInfo data
+#define ILDB_HEADER_SIZE (ILDB_SIGNATURE_SIZE + sizeof(GUID) + sizeof(PDBInfo) )
+
+typedef struct PDBDataPointers
+{
+ SymMethodInfo * m_pMethods; // Method information
+ SymLexicalScope *m_pScopes; // Scopes
+ SymVariable *m_pVars; // Local Variables
+ SymUsingNamespace *m_pUsings; // list of using/imports
+ SymConstant *m_pConstants; // Constants
+ DocumentInfo *m_pDocuments; // Documents
+ SequencePoint *m_pSequencePoints; // Sequence Points
+ // Array of various bytes (variable signature, etc)
+ BYTE *m_pBytes;
+ // Strings
+ BYTE *m_pStringsBytes;
+} PDBDataPointers;
+
+#endif /* PDBDATA_H_ */
diff --git a/src/debug/ildbsymlib/symbinder.cpp b/src/debug/ildbsymlib/symbinder.cpp
new file mode 100644
index 0000000000..d3cf1c647b
--- /dev/null
+++ b/src/debug/ildbsymlib/symbinder.cpp
@@ -0,0 +1,163 @@
+// 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.
+// ===========================================================================
+// File: symbinder.cpp
+//
+
+// ===========================================================================
+
+#include "pch.h"
+#include "symbinder.h"
+
+/* ------------------------------------------------------------------------- *
+ * SymBinder class
+ * ------------------------------------------------------------------------- */
+
+HRESULT
+SymBinder::QueryInterface(
+ REFIID riid,
+ void **ppvObject
+ )
+{
+ HRESULT hr = S_OK;
+
+ _ASSERTE(IsValidIID(riid));
+ _ASSERTE(IsValidWritePtr(ppvObject, void*));
+
+ IfFalseGo( ppvObject, E_INVALIDARG );
+
+ if (riid == IID_ISymUnmanagedBinder)
+ {
+ *ppvObject = (ISymUnmanagedBinder*) this;
+ }
+ else if (riid == IID_ISymUnmanagedBinder2)
+ {
+ *ppvObject = (ISymUnmanagedBinder2*) this;
+ }
+ else if (riid == IID_IUnknown)
+ {
+ *ppvObject = (IUnknown*)this;
+ }
+ else
+ {
+ *ppvObject = NULL;
+ hr = E_NOINTERFACE;
+ }
+
+ if (*ppvObject)
+ {
+ AddRef();
+ }
+
+ErrExit:
+
+ return hr;
+}
+
+HRESULT
+SymBinder::NewSymBinder(
+ REFCLSID clsid,
+ void** ppObj
+ )
+{
+ HRESULT hr = S_OK;
+ SymBinder* pSymBinder = NULL;
+
+ _ASSERTE(IsValidCLSID(clsid));
+ _ASSERTE(IsValidWritePtr(ppObj, IUnknown*));
+
+ if (clsid != IID_ISymUnmanagedBinder)
+ return (E_UNEXPECTED);
+
+ IfFalseGo( ppObj, E_INVALIDARG );
+
+ *ppObj = NULL;
+
+ IfNullGo( pSymBinder = NEW(SymBinder()) );
+ *ppObj = pSymBinder;
+ pSymBinder->AddRef();
+ pSymBinder = NULL;
+
+ErrExit:
+
+ RELEASE( pSymBinder );
+
+ return hr;
+}
+
+//-----------------------------------------------------------
+// GetReaderForFile
+//-----------------------------------------------------------
+HRESULT
+SymBinder::GetReaderForFile(
+ IUnknown *importer, // IMetaDataImporter
+ const WCHAR *fileName, // File we're looking symbols for
+ const WCHAR *searchPath, // Search path for file
+ ISymUnmanagedReader **ppRetVal) // Out: SymReader for file
+{
+ HRESULT hr = S_OK;
+ ISymUnmanagedReader *pSymReader = NULL;
+ IfFalseGo( ppRetVal && fileName && fileName[0] != '\0', E_INVALIDARG );
+
+ // Init Out parameter
+ *ppRetVal = NULL;
+
+ // Call the class factory directly.
+ IfFailGo(IldbSymbolsCreateInstance(CLSID_CorSymReader_SxS,
+ IID_ISymUnmanagedReader,
+ (void**)&pSymReader));
+
+ IfFailGo(pSymReader->Initialize(importer, fileName, searchPath, NULL));
+
+ // Transfer ownership to the out parameter
+ *ppRetVal = pSymReader;
+ pSymReader = NULL;
+
+ErrExit:
+ RELEASE(pSymReader);
+ return hr;
+}
+
+HRESULT
+SymBinder::GetReaderFromStream(
+ IUnknown *importer,
+ IStream *pStream,
+ ISymUnmanagedReader **ppRetVal
+ )
+{
+ HRESULT hr = S_OK;
+ ISymUnmanagedReader *pSymReader = NULL;
+ IfFalseGo( ppRetVal && importer && pStream, E_INVALIDARG );
+
+ // Init Out parameter
+ *ppRetVal = NULL;
+
+ // Call the class factory directly
+ IfFailGo(IldbSymbolsCreateInstance(CLSID_CorSymReader_SxS,
+ IID_ISymUnmanagedReader,
+ (void**)&pSymReader));
+
+ IfFailGo(pSymReader->Initialize(importer, NULL, NULL, pStream));
+
+ // Transfer ownership to the out parameter
+ *ppRetVal = pSymReader;
+ pSymReader = NULL;
+
+ErrExit:
+ RELEASE(pSymReader);
+ return hr;
+}
+
+HRESULT SymBinder::GetReaderForFile2(
+ IUnknown *importer,
+ const WCHAR *fileName,
+ const WCHAR *searchPath,
+ ULONG32 searchPolicy,
+ ISymUnmanagedReader **pRetVal)
+{
+ // This API exists just to allow VS to function properly.
+ // ILDB doesn't support any search policy or search path - we only look
+ // next to the image file.
+ return GetReaderForFile(importer, fileName, searchPath, pRetVal);
+}
diff --git a/src/debug/ildbsymlib/symbinder.h b/src/debug/ildbsymlib/symbinder.h
new file mode 100644
index 0000000000..a0dcf7d4c7
--- /dev/null
+++ b/src/debug/ildbsymlib/symbinder.h
@@ -0,0 +1,73 @@
+// 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.
+// ===========================================================================
+// File: SymBinder.h
+//
+
+// ===========================================================================
+
+#ifndef SYMBINDER_H_
+#define SYMBINDER_H_
+
+/* ------------------------------------------------------------------------- *
+ * SymBinder class
+ * ------------------------------------------------------------------------- */
+
+class SymBinder : ISymUnmanagedBinder2
+{
+// ctor/dtor
+public:
+ SymBinder()
+ {
+ m_refCount = 0;
+ }
+
+ virtual ~SymBinder() {}
+
+ static HRESULT NewSymBinder( REFCLSID clsid, void** ppObj );
+
+// IUnknown methods
+public:
+
+ //-----------------------------------------------------------
+ // IUnknown support
+ //-----------------------------------------------------------
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (InterlockedIncrement((LONG *) &m_refCount));
+ }
+
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ LONG refCount = InterlockedDecrement((LONG *) &m_refCount);
+ if (refCount == 0)
+ DELETE(this);
+
+ return (refCount);
+ }
+ STDMETHOD(QueryInterface)(REFIID riid, void** ppvObject);
+
+ // ISymUnmanagedBinder
+public:
+
+ STDMETHOD(GetReaderForFile)( IUnknown *importer,
+ const WCHAR *fileName,
+ const WCHAR *searchPath,
+ ISymUnmanagedReader **pRetVal);
+ STDMETHOD(GetReaderFromStream)(IUnknown *importer,
+ IStream *pstream,
+ ISymUnmanagedReader **pRetVal);
+
+ // ISymUnmanagedBinder2
+ STDMETHOD(GetReaderForFile2)( IUnknown *importer,
+ const WCHAR *fileName,
+ const WCHAR *searchPath,
+ ULONG32 searchPolicy,
+ ISymUnmanagedReader **pRetVal);
+
+private:
+ SIZE_T m_refCount;
+
+};
+#endif
diff --git a/src/debug/ildbsymlib/symread.cpp b/src/debug/ildbsymlib/symread.cpp
new file mode 100644
index 0000000000..3fe3c8c9cc
--- /dev/null
+++ b/src/debug/ildbsymlib/symread.cpp
@@ -0,0 +1,2765 @@
+// 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.
+// ===========================================================================
+// File: symread.cpp
+//
+
+// ===========================================================================
+#include "pch.h"
+#include "symread.h"
+#include "corimage.h"
+
+#define CODE_WITH_NO_SOURCE 0xfeefee
+// -------------------------------------------------------------------------
+// SymReader class
+// -------------------------------------------------------------------------
+
+//-----------------------------------------------------------
+// NewSymReader
+// Static function used to create a new instance of SymReader
+//-----------------------------------------------------------
+HRESULT
+SymReader::NewSymReader(
+ REFCLSID clsid,
+ void** ppObj
+ )
+{
+ HRESULT hr = S_OK;
+ SymReader* pSymReader = NULL;
+
+ _ASSERTE(IsValidCLSID(clsid));
+ _ASSERTE(IsValidWritePtr(ppObj, IUnknown*));
+
+ if (clsid != IID_ISymUnmanagedReader)
+ return (E_UNEXPECTED);
+
+ IfFalseGo(ppObj, E_INVALIDARG);
+
+ *ppObj = NULL;
+ IfNullGo( pSymReader = NEW(SymReader()));
+
+ *ppObj = pSymReader;
+ pSymReader->AddRef();
+ pSymReader = NULL;
+
+ErrExit:
+
+ RELEASE( pSymReader );
+
+ return hr;
+}
+
+
+//-----------------------------------------------------------
+// ~SymReader
+//-----------------------------------------------------------
+SymReader::~SymReader()
+{
+ Cleanup();
+}
+
+//-----------------------------------------------------------
+// Cleanup
+// Release all memory and clear initialized data structures
+// (eg. as a result of a failed Initialization attempt)
+//-----------------------------------------------------------
+void SymReader::Cleanup()
+{
+ if (m_pDocs)
+ {
+ unsigned i;
+ for(i = 0; i < m_pPDBInfo->m_CountOfDocuments; i++)
+ {
+ RELEASE(m_pDocs[i]);
+ }
+ }
+
+ DELETE(m_pPDBInfo);
+ m_pPDBInfo = NULL;
+
+ // If we loaded from stream, then free the memory we allocated
+ if (m_fInitializeFromStream)
+ {
+ DELETEARRAY(m_DataPointers.m_pBytes);
+ DELETEARRAY(m_DataPointers.m_pConstants);
+ DELETEARRAY(m_DataPointers.m_pDocuments);
+ DELETEARRAY(m_DataPointers.m_pMethods);
+ DELETEARRAY(m_DataPointers.m_pScopes);
+ DELETEARRAY(m_DataPointers.m_pSequencePoints);
+ DELETEARRAY(m_DataPointers.m_pStringsBytes);
+ DELETEARRAY(m_DataPointers.m_pUsings);
+ DELETEARRAY(m_DataPointers.m_pVars);
+ }
+
+ DELETEARRAY(m_pDocs);
+ m_pDocs = NULL;
+
+ RELEASE(m_pImporter);
+ m_pImporter = NULL;
+
+ memset(&m_DataPointers, 0, sizeof(PDBDataPointers));
+ m_szPath[0] = '\0';
+}
+
+//-----------------------------------------------------------
+// ~QueryInterface
+//-----------------------------------------------------------
+HRESULT
+SymReader::QueryInterface(
+ REFIID riid,
+ void **ppvObject
+ )
+{
+ HRESULT hr = S_OK;
+
+ _ASSERTE(IsValidIID(riid));
+ _ASSERTE(IsValidWritePtr(ppvObject, void*));
+
+ IfFalseGo(ppvObject, E_INVALIDARG);
+ if (riid == IID_ISymUnmanagedReader)
+ {
+ *ppvObject = (ISymUnmanagedReader*) this;
+ }
+ else
+ if (riid == IID_IUnknown)
+ {
+ *ppvObject = (IUnknown*)this;
+ }
+ else
+ {
+ *ppvObject = NULL;
+ hr = E_NOINTERFACE;
+ }
+
+ if (*ppvObject)
+ {
+ AddRef();
+ }
+
+ErrExit:
+
+ return hr;
+}
+
+static HRESULT ReadFromStream(IStream *pIStream, void *pv, ULONG cb)
+{
+ HRESULT hr = NOERROR;
+ ULONG ulBytesRead;
+
+ IfFailGo(pIStream->Read(pv, cb, &ulBytesRead));
+ if (ulBytesRead != cb)
+ IfFailGo(HrFromWin32(ERROR_BAD_FORMAT));
+
+ErrExit:
+ return hr;
+}
+
+//-----------------------------------------------------------
+// Initialize
+// Pass in the required information to read in the debug info
+// If a stream is passed in, it is used, otherwise a filename
+// must be passed in
+//-----------------------------------------------------------
+HRESULT SymReader::Initialize(
+ IUnknown *importer, // Cash it to be consistent with CLR
+ const WCHAR* szFileName, // File name of the ildb
+ const WCHAR* szsearchPath, // Search Path
+ IStream *pIStream // IStream
+ )
+{
+ HRESULT hr = NOERROR;
+ _ASSERTE(szFileName || pIStream);
+ IfFalseGo(szFileName || pIStream, E_INVALIDARG );
+
+ _ASSERTE(!m_fInitialized);
+ IfFalseGo(!m_fInitialized, E_UNEXPECTED);
+
+ // If it's passed in, we need to AddRef to be consistent the
+ // desktop version since ReleaseImporterFromISymUnmanagedReader (ceeload.cpp)
+ // assumes there's an addref
+ if (importer)
+ {
+ m_pImporter = importer;
+ m_pImporter->AddRef();
+ }
+
+ // See if we're reading from a file or stream
+ if (pIStream == NULL)
+ {
+ // We're initializing from a file
+ m_fInitializeFromStream = false;
+ IfFailGo(InitializeFromFile(szFileName, szsearchPath));
+ }
+ else
+ {
+ // We're reading in from a stream
+ m_fInitializeFromStream = true;
+ IfFailGo(InitializeFromStream(pIStream));
+ }
+
+ // Note that up to this point, the data we've read in has not been validated. Since we don't trust
+ // our input, it's important that we don't proceed with using this data until validation has been
+ // successful.
+ IfFailGo(ValidateData());
+
+
+ErrExit:
+ // If we have not succeeded, then we need to clean up our data structures. This would allow a client to call
+ // Initialize again, but also ensures we can't possibly use partial or otherwise invalid (possibly
+ // malicious) data.
+ if (FAILED(hr))
+ {
+ Cleanup();
+ }
+ else
+ {
+ // Otherwise we are not properly initialized
+ m_fInitialized = true;
+ }
+
+ return hr;
+}
+
+//-----------------------------------------------------------
+// Initialize the data structures by reading from the supplied stream
+// Note that upon completion the data has not yet been validated for safety.
+//-----------------------------------------------------------
+HRESULT SymReader::InitializeFromStream(
+ IStream *pIStream // IStream
+ )
+{
+ GUID GuidVersion;
+ BYTE *pSignature;
+ HRESULT hr = S_OK;
+
+ // Reset the stream to the begining
+ LARGE_INTEGER li;
+ li.u.HighPart = 0;
+ li.u.LowPart = 0;
+
+ // Make sure we're at the beginning of the stream
+ IfFailGo(pIStream->Seek(li, STREAM_SEEK_SET, NULL));
+
+ IfNullGo(pSignature = (BYTE *)_alloca(ILDB_SIGNATURE_SIZE));
+ IfFailGo(ReadFromStream(pIStream, pSignature, ILDB_SIGNATURE_SIZE));
+
+ // Verify that we're looking at an ILDB File
+ if (memcmp(pSignature, ILDB_SIGNATURE, ILDB_SIGNATURE_SIZE))
+ {
+ IfFailGo(HrFromWin32(ERROR_BAD_FORMAT));
+ }
+
+ IfFailGo(ReadFromStream(pIStream, &GuidVersion, sizeof(GUID)));
+
+ SwapGuid(&GuidVersion);
+
+ if (memcmp(&GuidVersion, &ILDB_VERSION_GUID, sizeof(GUID)))
+ {
+ IfFailGo(HrFromWin32(ERROR_INVALID_DATA));
+ }
+
+ IfNullGo(m_pPDBInfo = NEW(PDBInfo));
+
+ memset(m_pPDBInfo, 0 , sizeof(PDBInfo));
+ IfFailGo(ReadFromStream(pIStream, m_pPDBInfo, sizeof(PDBInfo)));
+
+ // Swap the counts
+ m_pPDBInfo->ConvertEndianness();
+
+ if (m_pPDBInfo->m_CountOfConstants)
+ {
+ IfNullGo(m_DataPointers.m_pConstants = NEW(SymConstant[m_pPDBInfo->m_CountOfConstants]));
+ IfFailGo(ReadFromStream(pIStream, m_DataPointers.m_pConstants, m_pPDBInfo->m_CountOfConstants*sizeof(SymConstant)));
+ }
+
+ if (m_pPDBInfo->m_CountOfMethods)
+ {
+ IfNullGo(m_DataPointers.m_pMethods = NEW(SymMethodInfo[m_pPDBInfo->m_CountOfMethods]));
+ IfFailGo(ReadFromStream(pIStream, m_DataPointers.m_pMethods, m_pPDBInfo->m_CountOfMethods*sizeof(SymMethodInfo)));
+ }
+
+ if (m_pPDBInfo->m_CountOfScopes)
+ {
+ IfNullGo(m_DataPointers.m_pScopes = NEW(SymLexicalScope[m_pPDBInfo->m_CountOfScopes]));
+ IfFailGo(ReadFromStream(pIStream, m_DataPointers.m_pScopes, m_pPDBInfo->m_CountOfScopes*sizeof(SymLexicalScope)));
+ }
+
+ if (m_pPDBInfo->m_CountOfVars)
+ {
+ IfNullGo(m_DataPointers.m_pVars = NEW(SymVariable[m_pPDBInfo->m_CountOfVars]));
+ IfFailGo(ReadFromStream(pIStream, m_DataPointers.m_pVars, m_pPDBInfo->m_CountOfVars*sizeof(SymVariable)));
+ }
+
+ if (m_pPDBInfo->m_CountOfUsing)
+ {
+ IfNullGo(m_DataPointers.m_pUsings = NEW(SymUsingNamespace[m_pPDBInfo->m_CountOfUsing]));
+ IfFailGo(ReadFromStream(pIStream, m_DataPointers.m_pUsings, m_pPDBInfo->m_CountOfUsing*sizeof(SymUsingNamespace)));
+ }
+
+ if (m_pPDBInfo->m_CountOfSequencePoints)
+ {
+ IfNullGo(m_DataPointers.m_pSequencePoints = NEW(SequencePoint[m_pPDBInfo->m_CountOfSequencePoints]));
+ IfFailGo(ReadFromStream(pIStream, m_DataPointers.m_pSequencePoints, m_pPDBInfo->m_CountOfSequencePoints*sizeof(SequencePoint)));
+ }
+
+ if (m_pPDBInfo->m_CountOfDocuments)
+ {
+ IfNullGo(m_DataPointers.m_pDocuments = NEW(DocumentInfo[m_pPDBInfo->m_CountOfDocuments]));
+ IfFailGo(ReadFromStream(pIStream, m_DataPointers.m_pDocuments, m_pPDBInfo->m_CountOfDocuments*sizeof(DocumentInfo)));
+ }
+
+ if (m_pPDBInfo->m_CountOfBytes)
+ {
+ IfNullGo(m_DataPointers.m_pBytes = NEW(BYTE[m_pPDBInfo->m_CountOfBytes]));
+ IfFailGo(ReadFromStream(pIStream, m_DataPointers.m_pBytes, m_pPDBInfo->m_CountOfBytes*sizeof(BYTE)));
+ }
+
+
+ if (m_pPDBInfo->m_CountOfStringBytes)
+ {
+ IfNullGo(m_DataPointers.m_pStringsBytes = NEW(BYTE[m_pPDBInfo->m_CountOfStringBytes]));
+ IfFailGo(ReadFromStream(pIStream, m_DataPointers.m_pStringsBytes, m_pPDBInfo->m_CountOfStringBytes));
+ }
+
+ErrExit:
+ return hr;
+}
+
+//-----------------------------------------------------------
+// ValidateData
+// Checks the contents of everything in m_DataPointers (i.e. all the structures read from the file)
+// to make sure it is valid. Specifically, validates that all indexes are within bounds for the
+// sizes allocated.
+//-----------------------------------------------------------
+HRESULT SymReader::ValidateData()
+{
+ HRESULT hr = S_OK;
+
+ for (UINT32 i = 0; i < m_pPDBInfo->m_CountOfConstants; i++)
+ {
+ SymConstant & c = m_DataPointers.m_pConstants[i];
+ IfFalseGo(c.ParentScope() < m_pPDBInfo->m_CountOfScopes, HrFromWin32(ERROR_BAD_FORMAT));
+ IfFalseGo(c.Name() < m_pPDBInfo->m_CountOfStringBytes, HrFromWin32(ERROR_BAD_FORMAT));
+ IfFailGo(ValidateBytes(c.Signature(), c.SignatureSize()));
+ }
+
+ for (UINT32 i = 0; i < m_pPDBInfo->m_CountOfMethods; i++)
+ {
+ // Note that start/end values may equal the count (i.e. point one past the end) because
+ // the end is the extent, and start can equal end to indicate no entries.
+ SymMethodInfo & m = m_DataPointers.m_pMethods[i];
+ IfFalseGo(m.StartScopes() <= m_pPDBInfo->m_CountOfScopes, HrFromWin32(ERROR_BAD_FORMAT));
+ IfFalseGo(m.EndScopes() <= m_pPDBInfo->m_CountOfScopes, HrFromWin32(ERROR_BAD_FORMAT));
+ IfFalseGo(m.StartScopes() <= m.EndScopes(), HrFromWin32(ERROR_BAD_FORMAT));
+ IfFalseGo(m.StartVars() <= m_pPDBInfo->m_CountOfVars, HrFromWin32(ERROR_BAD_FORMAT));
+ IfFalseGo(m.EndVars() <= m_pPDBInfo->m_CountOfVars, HrFromWin32(ERROR_BAD_FORMAT));
+ IfFalseGo(m.StartVars() <= m.EndVars(), HrFromWin32(ERROR_BAD_FORMAT));
+ IfFalseGo(m.StartUsing() <= m_pPDBInfo->m_CountOfUsing, HrFromWin32(ERROR_BAD_FORMAT));
+ IfFalseGo(m.EndUsing() <= m_pPDBInfo->m_CountOfUsing, HrFromWin32(ERROR_BAD_FORMAT));
+ IfFalseGo(m.StartUsing() <= m.EndUsing(), HrFromWin32(ERROR_BAD_FORMAT));
+ IfFalseGo(m.StartConstant() <= m_pPDBInfo->m_CountOfConstants, HrFromWin32(ERROR_BAD_FORMAT));
+ IfFalseGo(m.EndConstant() <= m_pPDBInfo->m_CountOfConstants, HrFromWin32(ERROR_BAD_FORMAT));
+ IfFalseGo(m.StartConstant() <= m.EndConstant(), HrFromWin32(ERROR_BAD_FORMAT));
+ IfFalseGo(m.StartDocuments() <= m_pPDBInfo->m_CountOfDocuments, HrFromWin32(ERROR_BAD_FORMAT));
+ IfFalseGo(m.EndDocuments() <= m_pPDBInfo->m_CountOfDocuments, HrFromWin32(ERROR_BAD_FORMAT));
+ IfFalseGo(m.StartDocuments() <= m.EndDocuments(), HrFromWin32(ERROR_BAD_FORMAT));
+ IfFalseGo(m.StartSequencePoints() <= m_pPDBInfo->m_CountOfSequencePoints, HrFromWin32(ERROR_BAD_FORMAT));
+ IfFalseGo(m.EndSequencePoints() <= m_pPDBInfo->m_CountOfSequencePoints, HrFromWin32(ERROR_BAD_FORMAT));
+ IfFalseGo(m.StartSequencePoints() <= m.EndSequencePoints(), HrFromWin32(ERROR_BAD_FORMAT));
+ }
+
+ for (UINT32 i = 0; i < m_pPDBInfo->m_CountOfScopes; i++)
+ {
+ SymLexicalScope & s = m_DataPointers.m_pScopes[i];
+ IfFalseGo((s.ParentScope() == (UINT32)-1) || (s.ParentScope() < m_pPDBInfo->m_CountOfScopes), HrFromWin32(ERROR_BAD_FORMAT));
+ }
+
+ for (UINT32 i = 0; i < m_pPDBInfo->m_CountOfVars; i++)
+ {
+ SymVariable & v = m_DataPointers.m_pVars[i];
+ IfFalseGo(v.Scope() < m_pPDBInfo->m_CountOfScopes, HrFromWin32(ERROR_BAD_FORMAT));
+ IfFalseGo(v.Name() < m_pPDBInfo->m_CountOfStringBytes, HrFromWin32(ERROR_BAD_FORMAT));
+ IfFailGo(ValidateBytes(v.Signature(), v.SignatureSize()));
+ }
+
+ for (UINT32 i = 0; i < m_pPDBInfo->m_CountOfUsing; i++)
+ {
+ SymUsingNamespace & u = m_DataPointers.m_pUsings[i];
+ IfFalseGo(u.ParentScope() < m_pPDBInfo->m_CountOfScopes, HrFromWin32(ERROR_BAD_FORMAT));
+ IfFalseGo(u.Name() < m_pPDBInfo->m_CountOfStringBytes, HrFromWin32(ERROR_BAD_FORMAT));
+ }
+
+ for (UINT32 i = 0; i < m_pPDBInfo->m_CountOfSequencePoints; i++)
+ {
+ SequencePoint & s = m_DataPointers.m_pSequencePoints[i];
+ IfFalseGo(s.Document() < m_pPDBInfo->m_CountOfDocuments, HrFromWin32(ERROR_BAD_FORMAT));
+ }
+
+ for (UINT32 i = 0; i < m_pPDBInfo->m_CountOfDocuments; i++)
+ {
+ DocumentInfo & d = m_DataPointers.m_pDocuments[i];
+ IfFailGo(ValidateBytes(d.CheckSumEntry(), d.CheckSumSize()));
+ IfFailGo(ValidateBytes(d.SourceEntry(), d.SourceSize()));
+ IfFalseGo(d.UrlEntry() < m_pPDBInfo->m_CountOfStringBytes, HrFromWin32(ERROR_BAD_FORMAT));
+ }
+
+ // Nothing to validate for the m_pBytes array - each reference must validate above that it's
+ // length doesn't exceed the array
+
+ // We expect all strings to be null terminated. To ensure no string operation overruns the buffer
+ // it sufficies to check that the buffer ends in a null character
+ if (m_pPDBInfo->m_CountOfStringBytes > 0)
+ {
+ IfFalseGo(m_DataPointers.m_pStringsBytes[m_pPDBInfo->m_CountOfStringBytes-1] == '\0', HrFromWin32(ERROR_BAD_FORMAT));
+ }
+
+ErrExit:
+ return hr;
+}
+
+//-----------------------------------------------------------
+// Validate a reference to the bytes array
+//-----------------------------------------------------------
+HRESULT SymReader::ValidateBytes(UINT32 bytesIndex, UINT32 bytesLen)
+{
+ S_UINT32 extent = S_UINT32(bytesIndex) + S_UINT32(bytesLen);
+ if (!extent.IsOverflow() &&
+ (extent.Value() <= m_pPDBInfo->m_CountOfBytes))
+ {
+ return S_OK;
+ }
+
+ return HrFromWin32(ERROR_BAD_FORMAT);
+}
+
+//-----------------------------------------------------------
+// VerifyPEDebugInfo
+// Verify that the debug info in the PE is the one we want
+//-----------------------------------------------------------
+HRESULT SymReader::VerifyPEDebugInfo(const WCHAR* szFileName)
+{
+ HRESULT hr = E_FAIL;
+ HANDLE hFile = INVALID_HANDLE_VALUE;
+ HANDLE hMapFile = INVALID_HANDLE_VALUE;
+ BYTE *pMod = NULL;
+ DWORD dwFileSize;
+ IMAGE_DEBUG_DIRECTORY *pDebugDir;
+ RSDSI *pDebugInfo;
+ DWORD dwUtf8Length;
+ DWORD dwUnicodeLength;
+
+ // We need to change the .pdb extension to .ildb
+ // compatible with VS7
+ wchar_t fullpath[_MAX_PATH];
+ wchar_t drive[_MAX_DRIVE];
+ wchar_t dir[_MAX_DIR];
+ wchar_t fname[_MAX_FNAME];
+
+ IMAGE_NT_HEADERS*pNT;
+
+ hFile = WszCreateFile(szFileName,
+ GENERIC_READ,
+ FILE_SHARE_READ,
+ NULL,
+ OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL,
+ NULL);
+
+ if (hFile == INVALID_HANDLE_VALUE)
+ {
+ // Get the last error if we can
+ return HrFromWin32(GetLastError());
+ }
+
+ dwFileSize = GetFileSize(hFile, NULL);
+ if (dwFileSize < ILDB_HEADER_SIZE)
+ {
+ IfFailGo(HrFromWin32(ERROR_INVALID_DATA));
+ }
+
+ hMapFile = WszCreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
+ if (hMapFile == NULL)
+ IfFailGo(HrFromWin32(GetLastError()));
+
+ pMod = (BYTE *) MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, 0);
+ if (pMod == NULL)
+ IfFailGo(HrFromWin32(GetLastError()));
+
+ pNT = Cor_RtlImageNtHeader(pMod, dwFileSize);
+
+ // If there is no DebugEntry, then just error out
+ if (VAL32(pNT->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].VirtualAddress) == 0)
+ IfFailGo(HrFromWin32(ERROR_BAD_FORMAT));
+
+ // NOTE: This code is not secure against malformed PE files - any of the pointer additions below
+ // may be outside the range of memory mapped for the file. If we ever want to use this code
+ // on untrusted PE files, we should properly validate everything (probably by using PEDecoder).
+
+ DWORD offset;
+ offset = Cor_RtlImageRvaToOffset(pNT, VAL32(pNT->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].VirtualAddress), dwFileSize);
+ if (offset == NULL)
+ IfFailGo(HrFromWin32(ERROR_BAD_FORMAT));
+ pDebugDir = (IMAGE_DEBUG_DIRECTORY *)(pMod + offset);
+ pDebugInfo = (RSDSI *)(pMod + VAL32(pDebugDir->PointerToRawData));
+
+ if (pDebugInfo->dwSig != VAL32(0x53445352)) // "SDSR"
+ {
+ IfFailGo(HrFromWin32(ERROR_BAD_FORMAT));
+ }
+
+
+ // Try the returned Stored Name since it might be a fully qualified path
+ dwUtf8Length = VAL32(pDebugDir->SizeOfData) - sizeof(RSDSI);
+ dwUnicodeLength = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR) pDebugInfo->szPDB, dwUtf8Length, fullpath, COUNTOF(fullpath) - 1);
+
+ // Make sure it's NULL terminated
+ _ASSERTE(dwUnicodeLength < COUNTOF(fullpath));
+ fullpath[dwUnicodeLength]='\0';
+
+ // Replace the extension with ildb
+ if (_wsplitpath_s( fullpath, drive, COUNTOF(drive), dir, COUNTOF(dir), fname, COUNTOF(fname), NULL, 0 ))
+ IfFailGo(HrFromWin32(ERROR_BAD_FORMAT));
+ if (_wmakepath_s(m_szStoredSymbolName, MAX_LONGPATH, drive, dir, fname, W("ildb") ))
+ IfFailGo(HrFromWin32(ERROR_BAD_FORMAT));
+
+ // It looks valid, make sure to set the return code
+ hr = S_OK;
+ErrExit:
+ if (pMod)
+ UnmapViewOfFile(pMod);
+ if (hMapFile != INVALID_HANDLE_VALUE)
+ CloseHandle(hMapFile);
+ if (hFile != INVALID_HANDLE_VALUE)
+ CloseHandle(hFile);
+ return hr;
+
+}
+
+//-----------------------------------------------------------
+// InitializeFromFile
+// Initialize the reader using the passed in file name
+// Note that upon completion the data hasn't yet been validated for safety.
+//-----------------------------------------------------------
+HRESULT
+SymReader::InitializeFromFile(
+ const WCHAR* szFileName,
+ const WCHAR* szsearchPath)
+{
+ HRESULT hr = S_OK;
+ wchar_t fullpath[_MAX_PATH];
+ wchar_t drive[_MAX_DRIVE];
+ wchar_t dir[_MAX_DIR];
+ wchar_t fname[_MAX_FNAME];
+ HANDLE hFile = INVALID_HANDLE_VALUE;
+ HANDLE hMapFile = INVALID_HANDLE_VALUE;
+ HMODULE hMod = NULL;
+ BYTE *CurrentOffset;
+ DWORD dwFileSize;
+ S_UINT32 dwDataSize;
+ GUID VersionInfo;
+
+ _ASSERTE(szFileName);
+ IfFalseGo(szFileName, E_INVALIDARG );
+
+ IfFailGo(VerifyPEDebugInfo(szFileName));
+ // We need to open the exe and check to see if the DebugInfo matches
+
+ if (_wsplitpath_s( szFileName, drive, COUNTOF(drive), dir, COUNTOF(dir), fname, COUNTOF(fname), NULL, 0 ))
+ IfFailGo(HrFromWin32(ERROR_BAD_FORMAT));
+ if (_wmakepath_s( fullpath, _MAX_PATH, drive, dir, fname, W("ildb") ))
+ IfFailGo(HrFromWin32(ERROR_BAD_FORMAT));
+ if (wcsncpy_s( m_szPath, COUNTOF(m_szPath), fullpath, _TRUNCATE) == STRUNCATE)
+ IfFailGo(HrFromWin32(ERROR_INSUFFICIENT_BUFFER));
+
+ hFile = WszCreateFile(m_szPath,
+ GENERIC_READ,
+ FILE_SHARE_READ,
+ NULL,
+ OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL,
+ NULL);
+
+ if (hFile == INVALID_HANDLE_VALUE)
+ {
+
+ // If the stored string is empty, don't do anything
+ if (m_szStoredSymbolName[0] == '\0')
+ {
+ return HrFromWin32(GetLastError());
+ }
+
+ if (_wsplitpath_s( m_szStoredSymbolName, drive, COUNTOF(drive), dir, COUNTOF(dir), fname, COUNTOF(fname), NULL, 0 ))
+ IfFailGo(HrFromWin32(ERROR_BAD_FORMAT));
+ if (_wmakepath_s( fullpath, _MAX_PATH, drive, dir, fname, W("ildb") ))
+ IfFailGo(HrFromWin32(ERROR_BAD_FORMAT));
+ if (wcsncpy_s( m_szPath, COUNTOF(m_szPath), fullpath, _TRUNCATE) == STRUNCATE)
+ IfFailGo(HrFromWin32(ERROR_INSUFFICIENT_BUFFER));
+
+ hFile = WszCreateFile(m_szPath,
+ GENERIC_READ,
+ FILE_SHARE_READ,
+ NULL,
+ OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL,
+ NULL);
+
+ if (hFile == INVALID_HANDLE_VALUE)
+ {
+ return HrFromWin32(GetLastError());
+ }
+ }
+
+ dwFileSize = GetFileSize(hFile, NULL);
+ if (dwFileSize < ILDB_HEADER_SIZE)
+ {
+ IfFailGo(HrFromWin32(ERROR_INVALID_DATA));
+ }
+
+ hMapFile = WszCreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
+ if (hMapFile == NULL)
+ IfFailGo(HrFromWin32(GetLastError()));
+
+ hMod = (HMODULE) MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, 0);
+ if (hMod == NULL)
+ IfFailGo(HrFromWin32(GetLastError()));
+
+ // We've opened the file, now let's get the pertinent info
+ CurrentOffset = (BYTE *)hMod;
+
+ // Verify that we're looking at an ILDB File
+ if (memcmp(CurrentOffset, ILDB_SIGNATURE, ILDB_SIGNATURE_SIZE))
+ {
+ IfFailGo(E_FAIL);
+ }
+ CurrentOffset += ILDB_SIGNATURE_SIZE;
+
+ memcpy( &VersionInfo, CurrentOffset, sizeof(GUID));
+ SwapGuid( &VersionInfo );
+ CurrentOffset += sizeof(GUID);
+
+ if (memcmp(&VersionInfo, &ILDB_VERSION_GUID, sizeof(GUID)))
+ {
+ IfFailGo(HrFromWin32(ERROR_INVALID_DATA));
+ }
+
+ IfNullGo(m_pPDBInfo = NEW(PDBInfo));
+
+ memcpy(m_pPDBInfo, CurrentOffset, sizeof(PDBInfo));
+
+ // Swap the counts
+ m_pPDBInfo->ConvertEndianness();
+
+ // Check to make sure we have enough data to be read in.
+ dwDataSize = S_UINT32(ILDB_HEADER_SIZE);
+ dwDataSize += m_pPDBInfo->m_CountOfConstants*sizeof(SymConstant);
+ dwDataSize += m_pPDBInfo->m_CountOfMethods * sizeof(SymMethodInfo);
+ dwDataSize += m_pPDBInfo->m_CountOfScopes*sizeof(SymLexicalScope);
+ dwDataSize += m_pPDBInfo->m_CountOfVars*sizeof(SymVariable);
+ dwDataSize += m_pPDBInfo->m_CountOfUsing*sizeof(SymUsingNamespace);
+ dwDataSize += m_pPDBInfo->m_CountOfSequencePoints*sizeof(SequencePoint);
+ dwDataSize += m_pPDBInfo->m_CountOfDocuments*sizeof(DocumentInfo);
+ dwDataSize += m_pPDBInfo->m_CountOfBytes*sizeof(BYTE);
+ dwDataSize += m_pPDBInfo->m_CountOfStringBytes*sizeof(BYTE);
+
+ if (dwDataSize.IsOverflow() || dwDataSize.Value() > dwFileSize)
+ {
+ IfFailGo(HrFromWin32(ERROR_INVALID_DATA));
+ }
+
+ CurrentOffset += sizeof(PDBInfo);
+
+ if (m_pPDBInfo->m_CountOfConstants)
+ {
+ m_DataPointers.m_pConstants = (SymConstant*)CurrentOffset;
+ CurrentOffset += (m_pPDBInfo->m_CountOfConstants*sizeof(SymConstant));
+ }
+
+ if (m_pPDBInfo->m_CountOfMethods)
+ {
+ m_DataPointers.m_pMethods = (SymMethodInfo *)CurrentOffset;
+ CurrentOffset += (m_pPDBInfo->m_CountOfMethods*sizeof(SymMethodInfo));
+ }
+
+ if (m_pPDBInfo->m_CountOfScopes)
+ {
+ m_DataPointers.m_pScopes = (SymLexicalScope *)CurrentOffset;
+ CurrentOffset += (m_pPDBInfo->m_CountOfScopes*sizeof(SymLexicalScope));
+ }
+
+ if (m_pPDBInfo->m_CountOfVars)
+ {
+ m_DataPointers.m_pVars = (SymVariable *)CurrentOffset;
+ CurrentOffset += (m_pPDBInfo->m_CountOfVars*sizeof(SymVariable));
+ }
+
+ if (m_pPDBInfo->m_CountOfUsing)
+ {
+ m_DataPointers.m_pUsings = (SymUsingNamespace *)CurrentOffset;
+ CurrentOffset += (m_pPDBInfo->m_CountOfUsing*sizeof(SymUsingNamespace));
+ }
+
+ if (m_pPDBInfo->m_CountOfSequencePoints)
+ {
+ m_DataPointers.m_pSequencePoints = (SequencePoint*)CurrentOffset;
+ CurrentOffset += (m_pPDBInfo->m_CountOfSequencePoints*sizeof(SequencePoint));
+ }
+
+ if (m_pPDBInfo->m_CountOfDocuments)
+ {
+ m_DataPointers.m_pDocuments = (DocumentInfo*)CurrentOffset;
+ CurrentOffset += (m_pPDBInfo->m_CountOfDocuments*sizeof(DocumentInfo));
+ }
+
+ if (m_pPDBInfo->m_CountOfBytes)
+ {
+ m_DataPointers.m_pBytes = (BYTE*)CurrentOffset;
+ CurrentOffset += (m_pPDBInfo->m_CountOfBytes*sizeof(BYTE));
+ }
+
+ if (m_pPDBInfo->m_CountOfStringBytes)
+ {
+ m_DataPointers.m_pStringsBytes = (BYTE*)CurrentOffset;
+ }
+
+ErrExit:
+
+ return hr;
+}
+
+//-----------------------------------------------------------
+// GetDocument
+// Get the document for the passed in information
+//-----------------------------------------------------------
+HRESULT
+SymReader::GetDocument(
+ __in LPWSTR wcsUrl, // URL of the document
+ GUID language, // Language for the file
+ GUID languageVendor, // Language vendor
+ GUID documentType, // Type of document
+ ISymUnmanagedDocument **ppRetVal // [out] Document
+ )
+{
+ HRESULT hr = S_OK;
+ unsigned i;
+ SymDocument* pDoc = NULL;
+ WCHAR *wcsDocumentUrl = NULL;
+ WCHAR *wcsDocumentUrlAlloc = NULL;
+
+ _ASSERTE(m_fInitialized);
+ IfFalseGo(m_fInitialized, E_UNEXPECTED);
+
+ _ASSERTE(ppRetVal && wcsUrl);
+ IfFalseGo(ppRetVal, E_INVALIDARG);
+ IfFalseGo(wcsUrl, E_INVALIDARG);
+
+ // Init Out Parameter
+ *ppRetVal = NULL;
+
+ for (i = 0; i < m_pPDBInfo->m_CountOfDocuments; i++)
+ {
+ int cchName;
+
+ // Convert the UTF8 string to Wide
+ cchName = (ULONG32) MultiByteToWideChar(CP_UTF8,
+ 0,
+ (LPCSTR)&(m_DataPointers.m_pStringsBytes[m_DataPointers.m_pDocuments[i].UrlEntry()]),
+ -1,
+ 0,
+ NULL);
+ IfNullGo( wcsDocumentUrlAlloc = NEW(WCHAR[cchName]) );
+
+ cchName = (ULONG32) MultiByteToWideChar(CP_UTF8,
+ 0,
+ (LPCSTR)&(m_DataPointers.m_pStringsBytes[m_DataPointers.m_pDocuments[i].UrlEntry()]),
+ -1,
+ wcsDocumentUrlAlloc,
+ cchName);
+ wcsDocumentUrl = wcsDocumentUrlAlloc;
+
+ // Compare the url
+ if (wcscmp(wcsUrl, wcsDocumentUrl) == 0)
+ {
+ IfFailGo(GetDocument(i, &pDoc));
+ break;
+ }
+ DELETEARRAY(wcsDocumentUrlAlloc);
+ wcsDocumentUrlAlloc = NULL;
+ }
+
+ if (pDoc)
+ {
+ IfFailGo( pDoc->QueryInterface( IID_ISymUnmanagedDocument,
+ (void**) ppRetVal ) );
+ }
+
+ErrExit:
+ DELETEARRAY(wcsDocumentUrlAlloc);
+
+ RELEASE( pDoc );
+
+ return hr;
+}
+
+//-----------------------------------------------------------
+// GetDocuments
+// Get the documents for this reader
+//-----------------------------------------------------------
+HRESULT
+SymReader::GetDocuments(
+ ULONG32 cDocs,
+ ULONG32 *pcDocs,
+ ISymUnmanagedDocument *pDocs[]
+ )
+{
+ HRESULT hr = S_OK;
+ unsigned cDocCount = 0;
+
+ _ASSERTE(m_fInitialized);
+ IfFalseGo(m_fInitialized, E_UNEXPECTED);
+
+ _ASSERTE(pDocs || pcDocs);
+ IfFalseGo(pDocs || pcDocs, E_INVALIDARG);
+
+ cDocs = min(cDocs, m_pPDBInfo->m_CountOfDocuments);
+
+ for (cDocCount = 0; cDocCount < cDocs; cDocCount++)
+ {
+ if (pDocs)
+ {
+ SymDocument *pDoc;
+ IfFailGo(GetDocument(cDocCount, &pDoc));
+ pDocs[cDocCount] = pDoc;
+ }
+ }
+ if (pcDocs)
+ {
+ *pcDocs = (ULONG32)m_pPDBInfo->m_CountOfDocuments;
+ }
+
+ErrExit:
+ if (FAILED(hr))
+ {
+ unsigned i;
+ for (i = 0; i < cDocCount; i++)
+ {
+ RELEASE(pDocs[cDocCount]);
+ }
+ }
+ return hr;
+}
+
+//-----------------------------------------------------------
+// GetUserEntryPoint
+// Get the entry point for the pe
+//-----------------------------------------------------------
+HRESULT
+SymReader::GetUserEntryPoint(
+ mdMethodDef *pRetVal
+ )
+{
+ HRESULT hr = S_OK;
+
+ _ASSERTE(m_fInitialized);
+ IfFalseGo(m_fInitialized, E_UNEXPECTED);
+
+ _ASSERTE(pRetVal);
+ IfFalseGo(pRetVal, E_INVALIDARG);
+
+ // If it wasn't set then return E_FAIL
+ if (m_pPDBInfo->m_userEntryPoint == mdTokenNil)
+ {
+ hr = E_FAIL;
+ }
+ else
+ {
+ *pRetVal = m_pPDBInfo->m_userEntryPoint;
+ }
+ErrExit:
+ return hr;
+}
+
+// Compare the method token with the SymMethodInfo Entry and return the
+// value expected by bsearch
+int __cdecl CompareMethodToToken(const void *pMethodToken, const void *pMethodInfoEntry)
+{
+ mdMethodDef MethodDef = *(mdMethodDef *)pMethodToken;
+ SymMethodInfo *pMethodInfo = (SymMethodInfo *)pMethodInfoEntry;
+
+ return MethodDef - pMethodInfo->MethodToken();
+}
+
+//-----------------------------------------------------------
+// GetMethod
+// Get the method for the methoddef
+//-----------------------------------------------------------
+HRESULT
+SymReader::GetMethod(
+ mdMethodDef method, // MethodDef
+ ISymUnmanagedMethod **ppRetVal // [out] Method
+ )
+{
+ HRESULT hr = S_OK;
+ UINT32 MethodEntry = 0;
+ SymMethodInfo *pMethodInfo;
+ SymMethod * pMethod = NULL;
+
+ _ASSERTE(m_fInitialized);
+ IfFalseGo(m_fInitialized, E_UNEXPECTED);
+
+ _ASSERTE(ppRetVal);
+ IfFalseGo(ppRetVal, E_INVALIDARG);
+
+ pMethodInfo = (SymMethodInfo *)bsearch(&method, m_DataPointers.m_pMethods, m_pPDBInfo->m_CountOfMethods, sizeof(SymMethodInfo), CompareMethodToToken);
+ IfFalseGo(pMethodInfo, E_FAIL); // no matching method found
+
+ // Found a match
+ MethodEntry = UINT32 (pMethodInfo - m_DataPointers.m_pMethods);
+ _ASSERTE(m_DataPointers.m_pMethods[MethodEntry].MethodToken() == method);
+ IfNullGo( pMethod = NEW(SymMethod(this, &m_DataPointers, MethodEntry)) );
+ *ppRetVal = pMethod;
+ pMethod->AddRef();
+ hr = S_OK;
+
+ErrExit:
+ return hr;
+}
+
+//-----------------------------------------------------------
+// GetMethodByVersion
+//-----------------------------------------------------------
+HRESULT
+SymReader::GetMethodByVersion(
+ mdMethodDef method,
+ int version,
+ ISymUnmanagedMethod **ppRetVal
+ )
+{
+ // Don't support multiple version of the same Method so just
+ // call GetMethod
+ return GetMethod(method, ppRetVal);
+}
+
+
+//-----------------------------------------------------------
+// GetMethodFromDocumentPosition
+//-----------------------------------------------------------
+HRESULT
+SymReader::GetMethodFromDocumentPosition(
+ ISymUnmanagedDocument *document,
+ ULONG32 line,
+ ULONG32 column,
+ ISymUnmanagedMethod **ppRetVal
+)
+{
+ HRESULT hr = S_OK;
+ UINT32 DocumentEntry;
+ UINT32 Method;
+ UINT32 point;
+ SequencePoint *pSequencePointBefore;
+ SequencePoint *pSequencePointAfter;
+ bool found = false;
+
+ _ASSERTE(m_fInitialized);
+ IfFalseGo(m_fInitialized, E_UNEXPECTED);
+
+ _ASSERTE(document && ppRetVal);
+ IfFalseGo(document, E_INVALIDARG);
+ IfFalseGo(ppRetVal, E_INVALIDARG);
+
+ DocumentEntry = ((SymDocument *)document)->GetDocumentEntry();
+
+ // Init out parameter
+ *ppRetVal = NULL;
+
+ // Walk all Methods, check their Document and SequencePoints to see if it's in this doc
+ // and the line/column
+
+ // This function returns the first match if more than one methods cover the specified position.
+ for (Method = 0; Method < m_pPDBInfo->m_CountOfMethods; Method++)
+ {
+ pSequencePointBefore = NULL;
+ pSequencePointAfter = NULL;
+
+ // Walk the sequence points
+ for (point = m_DataPointers.m_pMethods[Method].StartSequencePoints();
+ point < m_DataPointers.m_pMethods[Method].EndSequencePoints();
+ point++)
+ {
+ // Check to see if this sequence point is in this doc
+ if (m_DataPointers.m_pSequencePoints[point].Document() == DocumentEntry)
+ {
+ // If the point is position is within the sequence point then
+ // we're done.
+ if (m_DataPointers.m_pSequencePoints[point].IsWithin(line, column))
+ {
+ IfFailGo(GetMethod(m_DataPointers.m_pMethods[Method].MethodToken(), ppRetVal));
+ found = true;
+ break;
+ }
+
+ // If the sequence is before the point then just remember the point
+ if (m_DataPointers.m_pSequencePoints[point].IsUserLine() &&
+ m_DataPointers.m_pSequencePoints[point].IsLessThan(line, column))
+ {
+ pSequencePointBefore = &m_DataPointers.m_pSequencePoints[point];
+ }
+
+ // If the sequence is before the point then just remember the point
+ if (m_DataPointers.m_pSequencePoints[point].IsUserLine() &&
+ m_DataPointers.m_pSequencePoints[point].IsGreaterThan(line, column))
+ {
+ pSequencePointAfter = &m_DataPointers.m_pSequencePoints[point];
+ }
+ }
+ }
+
+ // If we found an exact match, we're done.
+ if (found)
+ {
+ break;
+ }
+
+ // If we found sequence points within the method before and after
+ // the line/column then we may have found the method. Record the return value, but keep looking
+ // to see if we find an exact match. This may not actually be the right method. Iron Python, for instance,
+ // issues a "method" containing sequence points for all the method headers in a class. Sequence points
+ // in this "method" would then span the sequence points in the bodies of all but the last method.
+ if (pSequencePointBefore && pSequencePointAfter)
+ {
+ IfFailGo(GetMethod(m_DataPointers.m_pMethods[Method].MethodToken(), ppRetVal));
+ }
+ }
+
+ // This function returns E_FAIL if no match is found.
+ // Note that this is different from the behaviour of GetMethodsFromDocumentPosition() (see below).
+ if (*ppRetVal == NULL)
+ {
+ hr = E_FAIL;
+ }
+
+ErrExit:
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Return all methods with sequence points covering the specified source location. This
+// is actually not as straighforward as it sounds, since we need to mimic the behaviour of
+// diasymreader and PDBs here. For PDBs, diasymreader actually does two passes over the
+// sequence points. It tries to find an exact match in the first pass, and if that fails,
+// it'll do a second pass looking for an approximate match. An approximate match is a sequence
+// point which doesn't start on the specified line but covers it. If there's an exact match,
+// then it ignores all the approximate matches. In both cases, diasymreader only checks the
+// start line number of the sequence point and it ignores the column number.
+//
+// For backward compatibility, I'm leaving GetMethodFromDocumentPosition() unchanged.
+//
+
+HRESULT
+SymReader::GetMethodsFromDocumentPosition(
+ ISymUnmanagedDocument *document,
+ ULONG32 line,
+ ULONG32 column,
+ ULONG32 cMethod,
+ ULONG32* pcMethod, //[Optional]: How many method actually returned
+ ISymUnmanagedMethod** ppRetVal
+ )
+{
+ HRESULT hr = S_OK;
+ UINT32 DocumentEntry;
+ UINT32 Method;
+ UINT32 point;
+
+ UINT CurMethod = 0;
+ bool found = false;
+ bool fExactMatch = true;
+
+ ULONG32 maxPreLine = 0;
+
+ _ASSERTE(m_fInitialized);
+ IfFalseGo(m_fInitialized, E_UNEXPECTED);
+
+ _ASSERTE(document);
+ IfFalseGo(document, E_INVALIDARG);
+
+ _ASSERTE((cMethod == 0) || (ppRetVal != NULL));
+ IfFalseGo(cMethod == 0 || ppRetVal != NULL, E_INVALIDARG);
+
+ // Initialize the out parameter if it has been provided.
+ if (pcMethod != NULL)
+ {
+ *pcMethod = 0;
+ }
+
+ DocumentEntry = ((SymDocument *)document)->GetDocumentEntry();
+
+ // Enumerate the sequence points in two passes.
+ while (true)
+ {
+ // Walk all Methods, check their Document and SequencePoints to see if it's in this doc
+ // and the line/column
+
+ for (Method = 0; Method < m_pPDBInfo->m_CountOfMethods; Method++)
+ {
+ found = false;
+
+ // Walk the sequence points
+ for (point = m_DataPointers.m_pMethods[Method].StartSequencePoints();
+ point < m_DataPointers.m_pMethods[Method].EndSequencePoints();
+ point++)
+ {
+ // Check to see if this sequence point is in this doc
+ if (m_DataPointers.m_pSequencePoints[point].Document() == DocumentEntry)
+ {
+ // PDBs (more specifically the DIA APIs) only check the start line number and not the end line number when
+ // trying to determine whether a sequence point covers the specified line number. We need to match this
+ // behaviour here. For backward compatibility reasons, GetMethodFromDocumentPosition() is still checking
+ // against the entire range of a sequence point, but we should revisit that in the next release.
+ ULONG32 curLine = m_DataPointers.m_pSequencePoints[point].StartLine();
+
+ if (fExactMatch)
+ {
+ if (curLine == line)
+ {
+ found = true;
+ }
+ else if ((maxPreLine < curLine) && (curLine < line))
+ {
+ // This is not an exact match, but let's keep track of the sequence point closest to the specified
+ // line. We'll use this info if we have to do a second pass.
+ maxPreLine = curLine;
+ }
+ }
+ else
+ {
+ // We are in the second pass, looking for approximate matches.
+ if ((maxPreLine != 0) && (maxPreLine == curLine))
+ {
+ // Make sure the sequence point covers the specified line.
+ if (m_DataPointers.m_pSequencePoints[point].IsWithinLineOnly(line))
+ {
+ found = true;
+ }
+ }
+ }
+
+ // If we have found a match (whether it's exact or approximate), then save this method unless the caller is
+ // only interested in the number of matching methods or the array provided by the caller isn't big enough.
+ if (found)
+ {
+ if (CurMethod < cMethod)
+ {
+ IfFailGo(GetMethod(m_DataPointers.m_pMethods[Method].MethodToken(), &(ppRetVal[CurMethod])));
+ }
+ CurMethod++;
+ break;
+ }
+ }
+ }
+
+ if (found)
+ {
+ // If we have already filled out the entire array provided by the caller, then we are done.
+ if ((cMethod > 0) && (cMethod == CurMethod))
+ {
+ break;
+ }
+ else
+ {
+ // Otherwise move on to the next method.
+ continue;
+ }
+ }
+ }
+
+ // If we haven't found an exact match, then try it again looking for a sequence point covering the specified line.
+ if (fExactMatch && (CurMethod == 0))
+ {
+ fExactMatch = false;
+ continue;
+ }
+ else
+ {
+ // If we have found an exact match, or if we have done two passes already, then bail.
+ break;
+ }
+ }
+
+ // Note that unlike GetMethodFromDocumentPosition(), this function returns S_OK even if a match is not found.
+ if (SUCCEEDED(hr))
+ {
+ if (pcMethod != NULL)
+ {
+ *pcMethod = CurMethod;
+ }
+ }
+
+ErrExit:
+ return hr;
+}
+
+//-----------------------------------------------------------
+// GetSymbolStoreFileName
+//-----------------------------------------------------------
+HRESULT
+SymReader::GetSymbolStoreFileName(
+ ULONG32 cchName, // Length of szName
+ ULONG32 *pcchName, // [Optional]
+ __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[] // [Optional]
+ )
+{
+ _ASSERTE(m_fInitialized);
+ if (!m_fInitialized)
+ return E_UNEXPECTED;
+
+ if (pcchName)
+ {
+ *pcchName = (ULONG32)(wcslen(m_szPath)+1);
+ }
+
+ if( szName )
+ {
+ if (wcsncpy_s( szName, cchName, m_szPath, _TRUNCATE) == STRUNCATE)
+ return HrFromWin32(ERROR_INSUFFICIENT_BUFFER);
+ }
+
+ return NOERROR;
+}
+
+//-----------------------------------------------------------
+// GetMethodVersion
+//-----------------------------------------------------------
+HRESULT
+SymReader::GetMethodVersion(
+ ISymUnmanagedMethod * pMethod,
+ int* pVersion
+ )
+{
+ HRESULT hr = S_OK;
+
+ _ASSERTE(m_fInitialized);
+ IfFalseGo(m_fInitialized, E_UNEXPECTED);
+
+ _ASSERTE(pMethod && pVersion);
+ IfFalseGo( pMethod && pVersion, E_INVALIDARG);
+ // This symbol store only supports one version of a method
+ *pVersion = 0;
+ErrExit:
+ return hr;
+}
+
+//-----------------------------------------------------------
+// GetDocumentVersion
+//-----------------------------------------------------------
+HRESULT
+SymReader::GetDocumentVersion(
+ ISymUnmanagedDocument* pDoc,
+ int* pVersion,
+ BOOL* pbCurrent // [Optional]
+ )
+{
+ HRESULT hr = S_OK;
+
+ _ASSERTE(m_fInitialized);
+ IfFalseGo(m_fInitialized, E_UNEXPECTED);
+
+ _ASSERTE(pVersion && pDoc);
+ IfFalseGo(pVersion, E_INVALIDARG);
+ IfFalseGo(pDoc, E_INVALIDARG);
+
+ // This symbol store only supports one version of a document
+ *pVersion = 0;
+ if (pbCurrent)
+ {
+ *pbCurrent = TRUE;
+ }
+ErrExit:
+ return hr;
+}
+
+//-----------------------------------------------------------
+// GetDocument
+// Return the document for the given entry
+//-----------------------------------------------------------
+HRESULT SymReader::GetDocument(
+ UINT32 DocumentEntry,
+ SymDocument **ppDocument)
+{
+ HRESULT hr = NOERROR;
+
+ _ASSERTE(m_fInitialized);
+ IfFalseGo(m_fInitialized, E_UNEXPECTED);
+
+ _ASSERTE(ppDocument);
+ IfFalseGo(ppDocument, E_INVALIDARG);
+
+ _ASSERTE(DocumentEntry < m_pPDBInfo->m_CountOfDocuments);
+ IfFalseGo(DocumentEntry < m_pPDBInfo->m_CountOfDocuments, E_INVALIDARG);
+
+ if (m_pDocs == NULL)
+ {
+ IfNullGo(m_pDocs = NEW(SymDocument *[m_pPDBInfo->m_CountOfDocuments]));
+ memset(m_pDocs, 0, m_pPDBInfo->m_CountOfDocuments * sizeof(void *));
+ }
+
+ if (m_pDocs[DocumentEntry] == NULL)
+ {
+ m_pDocs[DocumentEntry] = NEW(SymDocument(this, &m_DataPointers, m_pPDBInfo->m_CountOfMethods, DocumentEntry));
+ IfNullGo(m_pDocs[DocumentEntry]);
+ // AddRef the table version
+ m_pDocs[DocumentEntry]->AddRef();
+
+ }
+
+ //Set and AddRef the Out Parameter
+ *ppDocument = m_pDocs[DocumentEntry];
+ (*ppDocument)->AddRef();
+
+ErrExit:
+ return hr;
+}
+
+HRESULT
+SymReader::UpdateSymbolStore(
+ const WCHAR *filename,
+ IStream *pIStream
+ )
+{
+ // This symbol store doesn't support updating the symbol store.
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+HRESULT
+SymReader::ReplaceSymbolStore(
+ const WCHAR *filename,
+ IStream *pIStream
+ )
+{
+ // This symbol store doesn't support updating the symbol store.
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------
+// GetVariables
+//-----------------------------------------------------------
+HRESULT
+SymReader::GetVariables(
+ mdToken parent,
+ ULONG32 cVars,
+ ULONG32 *pcVars,
+ ISymUnmanagedVariable *pVars[]
+ )
+{
+ //
+ // This symbol reader doesn't support non-local variables.
+ //
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------
+// GetGlobalVariables
+//-----------------------------------------------------------
+HRESULT
+SymReader::GetGlobalVariables(
+ ULONG32 cVars,
+ ULONG32 *pcVars,
+ ISymUnmanagedVariable *pVars[]
+ )
+{
+ //
+ // This symbol reader doesn't support non-local variables.
+ //
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------
+// GetSymAttribute
+//-----------------------------------------------------------
+HRESULT
+SymReader::GetSymAttribute(
+ mdToken parent,
+ __in LPWSTR name,
+ ULONG32 cBuffer,
+ ULONG32 *pcBuffer,
+ __out_bcount_part_opt(cBuffer, *pcBuffer) BYTE buffer[]
+ )
+{
+ // This symbol store doesn't support attributes
+ // VS may query for certain attributes, but will survive without them,
+ // so don't ASSERT here.
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------
+// GetNamespaces
+//-----------------------------------------------------------
+HRESULT
+SymReader::GetNamespaces(
+ ULONG32 cNameSpaces,
+ ULONG32 *pcNameSpaces,
+ ISymUnmanagedNamespace *namespaces[]
+ )
+{
+ // This symbol store doesn't support this
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+/* ------------------------------------------------------------------------- *
+ * SymDocument class
+ * ------------------------------------------------------------------------- */
+
+HRESULT
+SymDocument::QueryInterface(
+ REFIID riid,
+ void **ppInterface
+ )
+{
+ if (ppInterface == NULL)
+ return E_INVALIDARG;
+
+ if (riid == IID_ISymUnmanagedDocument)
+ *ppInterface = (ISymUnmanagedDocument*)this;
+ else if (riid == IID_IUnknown)
+ *ppInterface = (IUnknown*)(ISymUnmanagedDocument*)this;
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+
+//-----------------------------------------------------------
+// GetURL
+//-----------------------------------------------------------
+HRESULT
+SymDocument::GetURL(
+ ULONG32 cchUrl, // The allocated size of the buffer
+ ULONG32 *pcchUrl, // [optional,out] The number of characters available for return
+ __out_ecount_part_opt(cchUrl, *pcchUrl) WCHAR szUrl[] // [optional,out] The string buffer.
+ )
+{
+ if (pcchUrl)
+ {
+ // Convert the UTF8 string to Wide
+ *pcchUrl = (ULONG32) MultiByteToWideChar(CP_UTF8,
+ 0,
+ (LPCSTR)&(m_pData->m_pStringsBytes[m_pData->m_pDocuments[m_DocumentEntry].UrlEntry()]),
+ -1,
+ 0,
+ NULL);
+ }
+
+ if( szUrl )
+ {
+ // Convert the UTF8 string to Wide
+ MultiByteToWideChar(CP_UTF8,
+ 0,
+ (LPCSTR)&(m_pData->m_pStringsBytes[m_pData->m_pDocuments[m_DocumentEntry].UrlEntry()]),
+ -1,
+ szUrl,
+ cchUrl);
+ }
+ return NOERROR;
+}
+
+//-----------------------------------------------------------
+// GetDocumentType
+//-----------------------------------------------------------
+HRESULT
+SymDocument::GetDocumentType(
+ GUID *pRetVal
+ )
+{
+ HRESULT hr = NOERROR;
+ _ASSERTE(pRetVal);
+ IfFalseGo(pRetVal, E_INVALIDARG);
+ *pRetVal = m_pData->m_pDocuments[m_DocumentEntry].DocumentType();
+ SwapGuid(pRetVal);
+ErrExit:
+ return hr;
+}
+
+//-----------------------------------------------------------
+// GetLanguage
+//-----------------------------------------------------------
+HRESULT
+SymDocument::GetLanguage(
+ GUID *pRetVal
+ )
+{
+ HRESULT hr = NOERROR;
+ _ASSERTE(pRetVal);
+ IfFalseGo(pRetVal, E_INVALIDARG);
+
+ *pRetVal = m_pData->m_pDocuments[m_DocumentEntry].Language();
+ SwapGuid(pRetVal);
+ErrExit:
+ return hr;
+}
+
+//-----------------------------------------------------------
+// GetLanguageVendor
+//-----------------------------------------------------------
+HRESULT
+SymDocument::GetLanguageVendor(
+ GUID *pRetVal
+ )
+{
+ HRESULT hr = NOERROR;
+ _ASSERTE(pRetVal);
+ IfFalseGo(pRetVal, E_INVALIDARG);
+ *pRetVal = m_pData->m_pDocuments[m_DocumentEntry].LanguageVendor();
+ SwapGuid(pRetVal);
+ErrExit:
+ return hr;
+}
+
+//-----------------------------------------------------------
+// GetCheckSumAlgorithmId
+//-----------------------------------------------------------
+HRESULT
+SymDocument::GetCheckSumAlgorithmId(
+ GUID *pRetVal
+ )
+{
+ HRESULT hr = NOERROR;
+ _ASSERTE(pRetVal);
+ IfFalseGo(pRetVal, E_INVALIDARG);
+ *pRetVal = m_pData->m_pDocuments[m_DocumentEntry].AlgorithmId();
+ SwapGuid(pRetVal);
+ErrExit:
+ return hr;
+}
+
+//-----------------------------------------------------------
+// GetCheckSum
+//-----------------------------------------------------------
+HRESULT
+SymDocument::GetCheckSum(
+ ULONG32 cData, // The allocated size of the buffer.
+ ULONG32 *pcData, // [optional] The number of bytes available for return
+ BYTE data[]) // [optional] The buffer to receive the checksum.
+{
+ BYTE *pCheckSum = &m_pData->m_pBytes[m_pData->m_pDocuments[m_DocumentEntry].CheckSumEntry()];
+ ULONG32 CheckSumSize = m_pData->m_pDocuments[m_DocumentEntry].CheckSumSize();
+ if (pcData)
+ {
+ *pcData = CheckSumSize;
+ }
+ if(data)
+ {
+ memcpy(data, pCheckSum, min(CheckSumSize, cData));
+ }
+ return NOERROR;
+}
+
+//-----------------------------------------------------------
+// FindClosestLine
+// Search the sequence points looking a line that is closest
+// line following this one that is a sequence point
+//-----------------------------------------------------------
+HRESULT
+SymDocument::FindClosestLine(
+ ULONG32 line,
+ ULONG32 *pRetVal
+ )
+{
+ HRESULT hr = NOERROR;
+ UINT32 Method;
+ UINT32 point;
+ ULONG32 closestLine = 0; // GCC can't tell this isn't used before initialization
+ bool found = false;
+
+ _ASSERTE(pRetVal);
+ IfFalseGo(pRetVal, E_INVALIDARG);
+
+ // Walk all Methods, check their Document and SequencePoints to see if it's in this doc
+ // and the line/column
+ for (Method = 0; Method < m_CountOfMethods; Method++)
+ {
+ // Walk the sequence points
+ for (point = m_pData->m_pMethods[Method].StartSequencePoints();
+ point < m_pData->m_pMethods[Method].EndSequencePoints();
+ point++)
+ {
+ SequencePoint & sp = m_pData->m_pSequencePoints[point];
+ // Check to see if this sequence point is in this doc, and is at or
+ // after the requested line
+ if ((sp.Document() == m_DocumentEntry) && sp.IsUserLine())
+ {
+ if (sp.IsWithin(line, 0) || sp.IsGreaterThan(line, 0))
+ {
+ // This sequence point is at or after the requested line. If we haven't
+ // already found a "closest", or this is even closer than the one we have,
+ // then mark this as the best line so far.
+ if (!found || m_pData->m_pSequencePoints[point].StartLine() < closestLine)
+ {
+ found = true;
+ closestLine = m_pData->m_pSequencePoints[point].StartLine();
+ }
+ }
+ }
+ }
+ }
+
+ if (found)
+ {
+ *pRetVal = closestLine;
+ }
+ else
+ {
+ // Didn't find any lines at or after the one requested.
+ hr = E_FAIL;
+ }
+
+ErrExit:
+ return hr;
+}
+
+//-----------------------------------------------------------
+// SymDocument HasEmbeddedSource
+//-----------------------------------------------------------
+HRESULT
+SymDocument::HasEmbeddedSource(
+ BOOL *pRetVal
+ )
+{
+ //
+ // This symbol reader doesn't support embedded source.
+ //
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------
+// SymDocument GetSourceLength
+//-----------------------------------------------------------
+HRESULT
+SymDocument::GetSourceLength(
+ ULONG32 *pRetVal
+ )
+{
+ //
+ // This symbol reader doesn't support embedded source.
+ //
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------
+// SymDocument GetSourceRange
+//-----------------------------------------------------------
+HRESULT
+SymDocument::GetSourceRange(
+ ULONG32 startLine,
+ ULONG32 startColumn,
+ ULONG32 endLine,
+ ULONG32 endColumn,
+ ULONG32 cSourceBytes,
+ ULONG32 *pcSourceBytes,
+ BYTE source[]
+ )
+{
+ //
+ // This symbol reader doesn't support embedded source.
+ //
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+/* ------------------------------------------------------------------------- *
+ * SymMethod class
+ * ------------------------------------------------------------------------- */
+HRESULT
+SymMethod::QueryInterface(
+ REFIID riid,
+ void **ppInterface
+ )
+{
+ if (ppInterface == NULL)
+ return E_INVALIDARG;
+
+ if (riid == IID_ISymUnmanagedMethod)
+ *ppInterface = (ISymUnmanagedMethod*)this;
+ else if (riid == IID_IUnknown)
+ *ppInterface = (IUnknown*)(ISymUnmanagedMethod*)this;
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+//-----------------------------------------------------------
+// GetToken
+//-----------------------------------------------------------
+HRESULT
+SymMethod::GetToken(
+ mdMethodDef *pRetVal
+)
+{
+ HRESULT hr = S_OK;
+
+ _ASSERTE(pRetVal);
+ IfFalseGo(pRetVal, E_INVALIDARG);
+ *pRetVal = m_pData->m_pMethods[m_MethodEntry].MethodToken();
+ErrExit:
+ return hr;
+}
+
+
+//-----------------------------------------------------------
+// GetSequencePointCount
+//-----------------------------------------------------------
+HRESULT
+SymMethod::GetSequencePointCount(
+ ULONG32* pRetVal
+ )
+{
+
+ HRESULT hr = S_OK;
+ _ASSERTE(pRetVal);
+ IfFalseGo(pRetVal, E_INVALIDARG);
+
+ *pRetVal = (ULONG32)(m_pData->m_pMethods[m_MethodEntry].EndSequencePoints() -
+ m_pData->m_pMethods[m_MethodEntry].StartSequencePoints());
+ErrExit:
+ return hr;
+}
+
+//-----------------------------------------------------------
+// GetSequencePoints
+//-----------------------------------------------------------
+HRESULT
+SymMethod::GetSequencePoints(
+ ULONG32 cPoints, // The size of the allocated arrays.
+ ULONG32* pcPoints, // [optional] The number of sequence points available for return.
+ ULONG32 offsets[], // [optional]
+ ISymUnmanagedDocument *documents[], // [Optional]
+ ULONG32 lines[], // [Optional]
+ ULONG32 columns[], // [Optional]
+ ULONG32 endLines[], // [Optional]
+ ULONG32 endColumns[] // [Optional]
+ )
+{
+ HRESULT hr = NOERROR;
+ UINT32 i = 0;
+ ULONG32 Points = 0;
+
+ for (i = m_pData->m_pMethods[m_MethodEntry].StartSequencePoints();
+ (i < m_pData->m_pMethods[m_MethodEntry].EndSequencePoints());
+ i++, Points++)
+ {
+ if (Points < cPoints)
+ {
+ if (documents)
+ {
+ SymDocument *pDoc;
+ IfFailGo(m_pReader->GetDocument(m_pData->m_pSequencePoints[i].Document(), &pDoc));
+ documents[Points] = pDoc;
+ }
+
+ if (offsets)
+ {
+ offsets[Points] = m_pData->m_pSequencePoints[i].Offset();
+ }
+
+ if (lines)
+ {
+ lines[Points] = m_pData->m_pSequencePoints[i].StartLine();
+ }
+ if (columns)
+ {
+ columns[Points] = m_pData->m_pSequencePoints[i].StartColumn();
+ }
+ if (endLines)
+ {
+ endLines[Points] = m_pData->m_pSequencePoints[i].EndLine();
+ }
+ if (endColumns)
+ {
+ endColumns[Points] = m_pData->m_pSequencePoints[i].EndColumn();
+ }
+ }
+ }
+
+ if (pcPoints)
+ {
+ *pcPoints = Points;
+ }
+
+ErrExit:
+ if (FAILED(hr))
+ {
+ if (documents)
+ {
+ unsigned j;
+ for (j = 0; j < i; j++)
+ {
+ RELEASE(documents[i]);
+ }
+ }
+ }
+ return hr;
+}
+
+//-----------------------------------------------------------
+// GetRootScope
+//-----------------------------------------------------------
+HRESULT
+SymMethod::GetRootScope(
+ ISymUnmanagedScope **ppRetVal
+ )
+{
+ HRESULT hr = S_OK;
+ SymScope *pScope = NULL;
+ _ASSERTE(ppRetVal);
+ IfFalseGo(ppRetVal, E_INVALIDARG);
+
+ // Init Out Param
+ *ppRetVal = NULL;
+ if (m_pData->m_pMethods[m_MethodEntry].EndScopes() - m_pData->m_pMethods[m_MethodEntry].StartScopes())
+ {
+ IfNullGo(pScope = NEW(SymScope(this, m_pData, m_MethodEntry, m_pData->m_pMethods[m_MethodEntry].StartScopes())));
+ pScope->AddRef();
+ *ppRetVal = pScope;
+ }
+ErrExit:
+ return hr;
+}
+
+//-----------------------------------------------------------
+// GetOffset
+// Given a position in a document, gets the offset within the
+// method that corresponds to the position.
+//-----------------------------------------------------------
+HRESULT
+SymMethod::GetOffset(
+ ISymUnmanagedDocument *document,
+ ULONG32 line,
+ ULONG32 column,
+ ULONG32 *pRetVal
+ )
+{
+ HRESULT hr = S_OK;
+ bool fFound = false;
+ _ASSERTE(pRetVal);
+ IfFalseGo(pRetVal, E_INVALIDARG);
+
+ UINT32 point;
+ UINT32 DocumentEntry;
+
+ DocumentEntry = ((SymDocument *)document)->GetDocumentEntry();
+
+ // Walk the sequence points
+ for (point = m_pData->m_pMethods[m_MethodEntry].StartSequencePoints();
+ point < m_pData->m_pMethods[m_MethodEntry].EndSequencePoints();
+ point++)
+ {
+ // Check to see if this sequence point is in this doc
+ if (m_pData->m_pSequencePoints[point].Document() == DocumentEntry)
+ {
+ // Check to see if it's within the sequence point
+ if (m_pData->m_pSequencePoints[point].IsWithin(line, column))
+ {
+ *pRetVal = m_pData->m_pSequencePoints[point].Offset();
+ fFound = true;
+ break;
+ }
+ }
+ }
+ if (!fFound)
+ {
+ hr = E_FAIL;
+ }
+ErrExit:
+ return hr;
+}
+
+//-----------------------------------------------------------
+// GetRanges
+//-----------------------------------------------------------
+HRESULT
+SymMethod::GetRanges(
+ ISymUnmanagedDocument *pDocument, // [in] Document we're working on
+ ULONG32 line, // [in] The document line corresponding to the ranges.
+ ULONG32 column, // [in] Ignored
+ ULONG32 cRanges, // [in] The size of the allocated ranges[] array.
+ ULONG32 *pcRanges, // [out] The number of ranges available for return
+ ULONG32 ranges[] // [out] The range array.
+ )
+{
+ HRESULT hr = NOERROR;
+ DWORD iRange = 0;
+ UINT32 DocumentEntry;
+ UINT32 point;
+ bool fFound = false;
+
+ // Validate some of the parameters
+ _ASSERTE(pDocument && (cRanges % 2) == 0);
+ IfFalseGo(pDocument, E_INVALIDARG);
+ IfFalseGo((cRanges % 2) == 0, E_INVALIDARG);
+
+ // Init out parameter
+ if (pcRanges)
+ {
+ *pcRanges=0;
+ }
+
+ DocumentEntry = ((SymDocument *)pDocument)->GetDocumentEntry();
+
+ // Walk the sequence points
+ for (point = m_pData->m_pMethods[m_MethodEntry].StartSequencePoints();
+ point < m_pData->m_pMethods[m_MethodEntry].EndSequencePoints();
+ point++)
+ {
+ // Check to see if this sequence point is in this doc
+ if (m_pData->m_pSequencePoints[point].Document() == DocumentEntry)
+ {
+ // Check to see if the line is within this sequence
+ // Note, to be compatible with VS7, ignore the column information
+ if (line >= m_pData->m_pSequencePoints[point].StartLine() &&
+ line <= m_pData->m_pSequencePoints[point].EndLine())
+ {
+ fFound = true;
+ break;
+ }
+ }
+ }
+
+ if (fFound)
+ {
+ for (;point < m_pData->m_pMethods[m_MethodEntry].EndSequencePoints(); point++)
+ {
+
+ // Search through all the sequence points since line might have there
+ // IL spread accross multiple ranges (for loops for example)
+ if (m_pData->m_pSequencePoints[point].Document() == DocumentEntry &&
+ line >= m_pData->m_pSequencePoints[point].StartLine() &&
+ line <= m_pData->m_pSequencePoints[point].EndLine())
+ {
+ if (iRange < cRanges)
+ {
+ ranges[iRange] = m_pData->m_pSequencePoints[point].Offset();
+ }
+ iRange++;
+ if (iRange < cRanges)
+ {
+ if (point+1 < m_pData->m_pMethods[m_MethodEntry].EndSequencePoints())
+ {
+ ranges[iRange] = m_pData->m_pSequencePoints[point+1].Offset();
+ }
+ else
+ {
+ // Then it must be till the end of the function which is the root scope's endoffset
+ ranges[iRange] = m_pData->m_pScopes[m_pData->m_pMethods[m_MethodEntry].StartScopes()].EndOffset()+1;
+ }
+ }
+ iRange++;
+ }
+ }
+ if (pcRanges)
+ {
+ // If cRanges passed in, return the number
+ // of elements actually filled in
+ if (cRanges)
+ {
+ *pcRanges = min(iRange, cRanges);
+ }
+ else
+ {
+ // Otherwise return the max number
+ *pcRanges = iRange;
+ }
+ }
+ }
+ else
+ {
+ return E_FAIL;
+ }
+
+ErrExit:
+ return hr;
+}
+
+//-----------------------------------------------------------
+// GetScopeFromOffset
+//-----------------------------------------------------------
+HRESULT
+SymMethod::GetScopeFromOffset(
+ ULONG32 offset,
+ ISymUnmanagedScope **pRetVal
+ )
+{
+ //
+ // This symbol reader doesn't support this functionality
+ //
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------
+// GetParameters
+//-----------------------------------------------------------
+HRESULT
+SymMethod::GetParameters(
+ ULONG32 cParams,
+ ULONG32 *pcParams,
+ ISymUnmanagedVariable *params[]
+ )
+{
+ //
+ // This symbol reader doesn't support parameter access. Parameters
+ // can be found in the normal metadata.
+ //
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------
+// GetNamespace
+//-----------------------------------------------------------
+HRESULT
+SymMethod::GetNamespace(
+ ISymUnmanagedNamespace **ppRetVal
+ )
+{
+ //
+ // This symbol reader doesn't support namespaces
+ //
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------
+// GetSourceStartEnd
+//-----------------------------------------------------------
+HRESULT
+SymMethod::GetSourceStartEnd(
+ ISymUnmanagedDocument *docs[2],
+ ULONG32 lines[2],
+ ULONG32 columns[2],
+ BOOL *pRetVal
+ )
+{
+ //
+ // This symbol reader doesn't support source start/end for methods.
+ //
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+/* ------------------------------------------------------------------------- *
+ * SymScope class
+ * ------------------------------------------------------------------------- */
+
+//-----------------------------------------------------------
+// QueryInterface
+//-----------------------------------------------------------
+HRESULT
+SymScope::QueryInterface(
+ REFIID riid,
+ void **ppInterface
+ )
+{
+ if (ppInterface == NULL)
+ return E_INVALIDARG;
+
+ if (riid == IID_ISymUnmanagedScope)
+ *ppInterface = (ISymUnmanagedScope*)this;
+ else if (riid == IID_IUnknown)
+ *ppInterface = (IUnknown*)(ISymUnmanagedScope*)this;
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+//-----------------------------------------------------------
+// GetMethod
+//-----------------------------------------------------------
+HRESULT
+SymScope::GetMethod(
+ ISymUnmanagedMethod **ppRetVal
+ )
+{
+ HRESULT hr = S_OK;
+
+ _ASSERTE(ppRetVal);
+ IfFalseGo(ppRetVal, E_INVALIDARG);
+
+ *ppRetVal = m_pSymMethod;
+ m_pSymMethod->AddRef();
+
+ErrExit:
+ return hr;
+}
+
+//-----------------------------------------------------------
+// GetParent
+//-----------------------------------------------------------
+HRESULT
+SymScope::GetParent(
+ ISymUnmanagedScope **ppRetVal
+ )
+{
+ HRESULT hr = S_OK;
+ _ASSERTE(ppRetVal);
+ IfFalseGo(ppRetVal, E_INVALIDARG);
+ if (m_pData->m_pScopes[m_ScopeEntry].ParentScope() != (UINT32)-1)
+ {
+ IfNullGo(*ppRetVal = static_cast<ISymUnmanagedScope *>(NEW(SymScope(m_pSymMethod, m_pData, m_MethodEntry,
+ m_pData->m_pScopes[m_ScopeEntry].ParentScope()))));
+ (*ppRetVal)->AddRef();
+ }
+ else
+ {
+ *ppRetVal = NULL;
+ }
+ErrExit:
+ return hr;
+}
+
+//-----------------------------------------------------------
+// GetChildren
+//-----------------------------------------------------------
+HRESULT
+SymScope::GetChildren(
+ ULONG32 cChildren, // [optional] Number of entries in children
+ ULONG32 *pcChildren, // [optional, out] Number of Children available for retur
+ ISymUnmanagedScope *children[] // [optional] array to store children into
+ )
+{
+ HRESULT hr = S_OK;
+ ULONG32 ChildrenCount = 0;
+ _ASSERTE(pcChildren || (children && cChildren));
+ IfFalseGo((pcChildren || (children && cChildren)), E_INVALIDARG);
+
+ if (m_pData->m_pScopes[m_ScopeEntry].HasChildren())
+ {
+ UINT32 ScopeEntry;
+ for(ScopeEntry = m_pData->m_pMethods[m_MethodEntry].StartScopes();
+ (ScopeEntry < m_pData->m_pMethods[m_MethodEntry].EndScopes());
+ ScopeEntry++)
+ {
+ if (m_pData->m_pScopes[ScopeEntry].ParentScope() == m_ScopeEntry)
+ {
+ if (children && ChildrenCount < cChildren)
+ {
+ SymScope *pScope;
+ // Found a child
+ IfNullGo(pScope = NEW(SymScope(m_pSymMethod, m_pData, m_MethodEntry, ScopeEntry)));
+ children[ChildrenCount] = pScope;
+ pScope->AddRef();
+ }
+ ChildrenCount++;
+ }
+ }
+ }
+
+ if (pcChildren)
+ {
+ *pcChildren = ChildrenCount;
+ }
+
+ErrExit:
+ if (FAILED(hr) && ChildrenCount)
+ {
+ unsigned i;
+ for (i =0; i< ChildrenCount; i++)
+ {
+ RELEASE(children[i]);
+ }
+ }
+ return hr;
+}
+
+//-----------------------------------------------------------
+// GetStartOffset
+//-----------------------------------------------------------
+HRESULT
+SymScope::GetStartOffset(
+ ULONG32* pRetVal
+ )
+{
+ HRESULT hr = S_OK;
+ _ASSERTE(pRetVal);
+ IfFalseGo(pRetVal, E_INVALIDARG);
+ *pRetVal = m_pData->m_pScopes[m_ScopeEntry].StartOffset();
+ErrExit:
+ return hr;
+}
+
+//-----------------------------------------------------------
+// GetEndOffset
+//-----------------------------------------------------------
+HRESULT
+SymScope::GetEndOffset(
+ ULONG32* pRetVal
+ )
+{
+ HRESULT hr = S_OK;
+ _ASSERTE(pRetVal);
+ IfFalseGo(pRetVal, E_INVALIDARG);
+ *pRetVal = m_pData->m_pScopes[m_ScopeEntry].EndOffset();
+ErrExit:
+ return hr;
+}
+
+//-----------------------------------------------------------
+// GetLocalCount
+//-----------------------------------------------------------
+HRESULT
+SymScope::GetLocalCount(
+ ULONG32 *pRetVal
+ )
+{
+ HRESULT hr = S_OK;
+ ULONG32 LocalCount = 0;
+ _ASSERTE(pRetVal);
+ IfFalseGo(pRetVal, E_INVALIDARG);
+
+ // Init out parameter
+ *pRetVal = 0;
+ if (m_pData->m_pScopes[m_ScopeEntry].HasVars())
+ {
+ UINT32 var;
+ // Walk and get the locals for this Scope
+ for (var = m_pData->m_pMethods[m_MethodEntry].StartVars();
+ var < m_pData->m_pMethods[m_MethodEntry].EndVars();
+ var++)
+ {
+ if (m_pData->m_pVars[var].Scope() == m_ScopeEntry &&
+ m_pData->m_pVars[var].IsParam() == false)
+ {
+ LocalCount++;
+ }
+ }
+ }
+
+ *pRetVal = LocalCount;
+ErrExit:
+ return hr;
+}
+
+//-----------------------------------------------------------
+// GetLocals
+// Input: either pcLocals or
+// cLocals and pLocals
+//-----------------------------------------------------------
+HRESULT
+SymScope::GetLocals(
+ ULONG32 cLocals, // [optional] available entries in pLocals
+ ULONG32 *pcLocals, // [optional, out] Number of locals returned
+ ISymUnmanagedVariable *pLocals[] // [optional] array to store locals into
+ )
+{
+ HRESULT hr = S_OK;
+
+ ULONG32 LocalCount = 0;
+ _ASSERTE(pcLocals || pLocals);
+ IfFalseGo(pcLocals || pLocals, E_INVALIDARG);
+
+ if (m_pData->m_pScopes[m_ScopeEntry].HasVars())
+ {
+ UINT32 var;
+ // Walk and get the locals for this Scope
+ for (var = m_pData->m_pMethods[m_MethodEntry].StartVars();
+ var < m_pData->m_pMethods[m_MethodEntry].EndVars();
+ var++)
+ {
+ if (m_pData->m_pVars[var].Scope() == m_ScopeEntry &&
+ m_pData->m_pVars[var].IsParam() == false)
+ {
+ if (pLocals && LocalCount < cLocals)
+ {
+ SymReaderVar *pVar;
+ IfNullGo( pVar = NEW(SymReaderVar(this, m_pData, var)));
+ pLocals[LocalCount] = pVar;
+ pVar->AddRef();
+ }
+ LocalCount++;
+ }
+ }
+ }
+ if (pcLocals)
+ {
+ *pcLocals = LocalCount;
+ }
+ErrExit:
+ if (FAILED(hr) && LocalCount != 0)
+ {
+ unsigned i;
+ for (i =0; i < LocalCount; i++)
+ {
+ RELEASE(pLocals[i]);
+ }
+ }
+ return hr;
+}
+
+//-----------------------------------------------------------
+// GetNamespaces
+// Input: either pcNameSpaces or
+// cNameSpaces and pNameSpaces
+//-----------------------------------------------------------
+HRESULT
+SymScope::GetNamespaces(
+ ULONG32 cNameSpaces, // [optional] number of entries pNameSpaces
+ ULONG32 *pcNameSpaces, // [optional, out] Maximum number of Namespace
+ ISymUnmanagedNamespace *pNameSpaces[] // [optinal] array to store namespaces into
+ )
+{
+ HRESULT hr = NOERROR;
+ unsigned i;
+ UINT32 NameSpace;
+ unsigned NameSpaceCount = 0;
+
+ _ASSERTE(pcNameSpaces || (pNameSpaces && cNameSpaces));
+ IfFalseGo(pcNameSpaces || (pNameSpaces && cNameSpaces), E_INVALIDARG);
+
+ for (NameSpace = m_pData->m_pMethods[m_MethodEntry].StartUsing();
+ NameSpace < m_pData->m_pMethods[m_MethodEntry].EndUsing();
+ NameSpace++)
+ {
+ if (m_pData->m_pUsings[NameSpace].ParentScope() == m_ScopeEntry)
+ {
+ if (pNameSpaces && (NameSpaceCount < cNameSpaces) )
+ {
+ IfNullGo(pNameSpaces[NameSpaceCount] = NEW(SymReaderNamespace(this, m_pData, NameSpace)));
+ pNameSpaces[NameSpaceCount]->AddRef();
+ }
+ NameSpaceCount++;
+ }
+ }
+ if (pcNameSpaces)
+ {
+ *pcNameSpaces = NameSpaceCount;
+ }
+ErrExit:
+ if (FAILED(hr) && pNameSpaces)
+ {
+ for (i = 0; (i < cNameSpaces) && (i < NameSpaceCount); i++)
+ {
+ RELEASE(pNameSpaces[i]);
+ }
+ }
+ return hr;
+}
+
+/* ------------------------------------------------------------------------- *
+ * SymReaderVar class
+ * ------------------------------------------------------------------------- */
+
+//-----------------------------------------------------------
+// QueryInterface
+//-----------------------------------------------------------
+HRESULT
+SymReaderVar::QueryInterface(
+ REFIID riid,
+ void **ppInterface
+ )
+{
+ if (ppInterface == NULL)
+ return E_INVALIDARG;
+
+ if (riid == IID_ISymUnmanagedVariable)
+ *ppInterface = (ISymUnmanagedVariable*)this;
+ else if (riid == IID_IUnknown)
+ *ppInterface = (IUnknown*)(ISymUnmanagedVariable*)this;
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+//-----------------------------------------------------------
+// GetName
+//-----------------------------------------------------------
+HRESULT
+SymReaderVar::GetName(
+ ULONG32 cchName, // [optional] Length of szName buffer
+ ULONG32 *pcchName, // [optional, out] Total size needed to return the name
+ __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[] // [optional] Buffer to store the name into.
+ )
+{
+ HRESULT hr = S_OK;
+
+ // We must have at least one combination
+ _ASSERTE(pcchName || (szName && cchName));
+ IfFalseGo( (pcchName || (szName && cchName)), E_INVALIDARG );
+
+ if (pcchName)
+ {
+ // Convert the UTF8 string to Wide
+ *pcchName = (ULONG32) MultiByteToWideChar(CP_UTF8,
+ 0,
+ (LPCSTR)&(m_pData->m_pStringsBytes[m_pData->m_pVars[m_VarEntry].Name()]),
+ -1,
+ 0,
+ NULL);
+
+ }
+ if (szName)
+ {
+ // Convert the UTF8 string to Wide
+ MultiByteToWideChar(CP_UTF8,
+ 0,
+ (LPCSTR)&(m_pData->m_pStringsBytes[m_pData->m_pVars[m_VarEntry].Name()]),
+ -1,
+ szName,
+ cchName);
+ }
+
+ErrExit:
+ return hr;
+}
+
+//-----------------------------------------------------------
+// GetAttributes
+//-----------------------------------------------------------
+HRESULT
+SymReaderVar::GetAttributes(
+ ULONG32 *pRetVal // [out]
+ )
+{
+ if (pRetVal == NULL)
+ return E_INVALIDARG;
+
+ *pRetVal = m_pData->m_pVars[m_VarEntry].Attributes();
+ return S_OK;
+}
+
+//-----------------------------------------------------------
+// GetSignature
+//-----------------------------------------------------------
+HRESULT
+SymReaderVar::GetSignature(
+ ULONG32 cSig, // Size of allocated buffer passed in (sig)
+ ULONG32 *pcSig, // [optional, out] Total size needed to return the signature
+ BYTE sig[] // [Optional] Signature
+ )
+{
+ HRESULT hr = S_OK;
+
+ _ASSERTE(pcSig || sig);
+ IfFalseGo( pcSig || sig, E_INVALIDARG );
+ if (pcSig)
+ {
+ *pcSig = m_pData->m_pVars[m_VarEntry].SignatureSize();
+ }
+ if (sig)
+ {
+ cSig = min(m_pData->m_pVars[m_VarEntry].SignatureSize(), cSig);
+ memcpy(sig, &m_pData->m_pBytes[m_pData->m_pVars[m_VarEntry].Signature()],cSig);
+ }
+
+ErrExit:
+ return hr;
+}
+
+//-----------------------------------------------------------
+// GetAddressKind
+//-----------------------------------------------------------
+HRESULT
+SymReaderVar::GetAddressKind(
+ ULONG32 *pRetVal // [out]
+ )
+{
+ HRESULT hr = S_OK;
+ _ASSERTE(pRetVal);
+ IfFalseGo( pRetVal, E_INVALIDARG );
+ *pRetVal = m_pData->m_pVars[m_VarEntry].AddrKind();
+ErrExit:
+ return S_OK;
+}
+
+//-----------------------------------------------------------
+// GetAddressField1
+//-----------------------------------------------------------
+HRESULT
+SymReaderVar::GetAddressField1(
+ ULONG32 *pRetVal // [out]
+ )
+{
+ HRESULT hr = S_OK;
+
+ _ASSERTE(pRetVal);
+ IfFalseGo( pRetVal, E_INVALIDARG );
+
+ *pRetVal = m_pData->m_pVars[m_VarEntry].Addr1();
+
+ErrExit:
+
+ return hr;
+}
+
+//-----------------------------------------------------------
+// GetAddressField2
+//-----------------------------------------------------------
+HRESULT
+SymReaderVar::GetAddressField2(
+ ULONG32 *pRetVal // [out]
+ )
+{
+ HRESULT hr = S_OK;
+
+ _ASSERTE(pRetVal);
+ IfFalseGo( pRetVal, E_INVALIDARG );
+
+ *pRetVal = m_pData->m_pVars[m_VarEntry].Addr2();
+
+ErrExit:
+
+ return hr;
+}
+
+//-----------------------------------------------------------
+// GetAddressField3
+//-----------------------------------------------------------
+HRESULT
+SymReaderVar::GetAddressField3(
+ ULONG32 *pRetVal // [out]
+ )
+{
+ HRESULT hr = S_OK;
+
+ _ASSERTE(pRetVal);
+ IfFalseGo( pRetVal, E_INVALIDARG );
+
+ *pRetVal = m_pData->m_pVars[m_VarEntry].Addr3();
+
+ErrExit:
+
+ return hr;
+}
+
+//-----------------------------------------------------------
+// GetStartOffset
+//-----------------------------------------------------------
+HRESULT
+SymReaderVar::GetStartOffset(
+ ULONG32 *pRetVal
+ )
+{
+ //
+ // This symbol reader doesn't support variable sub-offsets.
+ //
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------
+// GetEndOffset
+//-----------------------------------------------------------
+HRESULT
+SymReaderVar::GetEndOffset(
+ ULONG32 *pRetVal
+ )
+{
+ //
+ // This symbol reader doesn't support variable sub-offsets.
+ //
+ return E_NOTIMPL;
+}
+
+
+/* ------------------------------------------------------------------------- *
+ * SymReaderNamespace class
+ * ------------------------------------------------------------------------- */
+
+//-----------------------------------------------------------
+// QueryInterface
+//-----------------------------------------------------------
+HRESULT
+SymReaderNamespace::QueryInterface(
+ REFIID riid,
+ void** ppInterface
+ )
+{
+ if (ppInterface == NULL)
+ return E_INVALIDARG;
+
+ if (riid == IID_ISymUnmanagedNamespace)
+ *ppInterface = (ISymUnmanagedNamespace*)this;
+ else if (riid == IID_IUnknown)
+ *ppInterface = (IUnknown*)(ISymUnmanagedNamespace*)this;
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+//-----------------------------------------------------------
+// GetName
+//-----------------------------------------------------------
+HRESULT
+SymReaderNamespace::GetName(
+ ULONG32 cchName, // [optional] Chars available in szName
+ ULONG32 *pcchName, // [optional] Total size needed to return the name
+ __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[] // [optional] Location to store the name into.
+ )
+{
+ HRESULT hr = S_OK;
+ _ASSERTE(pcchName || (szName && cchName));
+ IfFalseGo( (pcchName || (szName && cchName)), E_INVALIDARG );
+
+ if (pcchName)
+ {
+ *pcchName = (ULONG32) MultiByteToWideChar(CP_UTF8,
+ 0,
+ (LPCSTR)&(m_pData->m_pStringsBytes[m_pData->m_pUsings[m_NamespaceEntry].Name()]),
+ -1,
+ 0,
+ NULL);
+ }
+ if (szName)
+ {
+ MultiByteToWideChar(CP_UTF8,
+ 0,
+ (LPCSTR)&(m_pData->m_pStringsBytes[m_pData->m_pUsings[m_NamespaceEntry].Name()]),
+ -1,
+ szName,
+ cchName);
+ }
+
+ErrExit:
+ return hr;
+}
+
+//-----------------------------------------------------------
+// GetNamespaces
+//-----------------------------------------------------------
+HRESULT
+SymReaderNamespace::GetNamespaces(
+ ULONG32 cNamespaces,
+ ULONG32 *pcNamespaces,
+ ISymUnmanagedNamespace* namespaces[]
+ )
+{
+ // This symbol store doesn't support namespaces.
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------
+// GetVariables
+//-----------------------------------------------------------
+HRESULT
+SymReaderNamespace::GetVariables(
+ ULONG32 cVariables,
+ ULONG32 *pcVariables,
+ ISymUnmanagedVariable *pVars[])
+{
+ // This symbol store doesn't support namespaces.
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+}
+
+
+/* ------------------------------------------------------------------------- *
+ * SequencePoint struct functions
+ * ------------------------------------------------------------------------- */
+
+//-----------------------------------------------------------
+// IsWithin - Is the point given within this sequence point
+//-----------------------------------------------------------
+bool SequencePoint::IsWithin(
+ ULONG32 line,
+ ULONG32 column)
+{
+ // If the sequence point starts on the same line
+ // Check the start column (if present)
+ if (StartLine() == line)
+ {
+ if (0 < column && StartColumn() > column)
+ {
+ return false;
+ }
+ }
+
+ // If the sequence point ends on the same line
+ // Check the end column
+ if (EndLine() == line)
+ {
+ if (EndColumn() < column)
+ {
+ return false;
+ }
+ }
+
+ // Make sure the line is within this sequence point
+ if (!((StartLine() <= line) && (EndLine() >= line)))
+ {
+ return false;
+ }
+
+ // Yep it's within this sequence point
+ return true;
+
+}
+
+//-----------------------------------------------------------
+// IsWithinLineOnly - Is the given line within this sequence point
+//-----------------------------------------------------------
+bool SequencePoint::IsWithinLineOnly(
+ ULONG32 line)
+{
+ return ((StartLine() <= line) && (line <= EndLine()));
+}
+
+//-----------------------------------------------------------
+// IsGreaterThan - Is the sequence point greater than the position
+//-----------------------------------------------------------
+bool SequencePoint::IsGreaterThan(
+ ULONG32 line,
+ ULONG32 column)
+{
+ return (StartLine() > line) ||
+ (StartLine() == line && StartColumn() > column);
+}
+
+//-----------------------------------------------------------
+// IsLessThan - Is the sequence point less than the position
+//-----------------------------------------------------------
+bool SequencePoint::IsLessThan
+(
+ ULONG32 line,
+ ULONG32 column
+)
+{
+ return (StartLine() < line) ||
+ (StartLine() == line && StartColumn() < column);
+}
+
+//-----------------------------------------------------------
+// IsUserLine - Is the sequence part of user code
+//-----------------------------------------------------------
+bool SequencePoint::IsUserLine()
+{
+ return StartLine() != CODE_WITH_NO_SOURCE;
+}
diff --git a/src/debug/ildbsymlib/symread.h b/src/debug/ildbsymlib/symread.h
new file mode 100644
index 0000000000..6264e42bdc
--- /dev/null
+++ b/src/debug/ildbsymlib/symread.h
@@ -0,0 +1,554 @@
+// 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.
+// ===========================================================================
+// File: SymRead.h
+//
+
+// ===========================================================================
+
+#ifndef SYMREAD_H_
+#define SYMREAD_H_
+
+class SymScope;
+class SymReaderVar;
+class SymDocument;
+
+// -------------------------------------------------------------------------
+// SymReader class
+// -------------------------------------------------------------------------
+
+class SymReader : public ISymUnmanagedReader
+{
+// ctor/dtor
+public:
+ SymReader()
+ {
+ m_refCount = 0;
+ m_pPDBInfo = NULL;
+ m_pDocs = NULL;
+ m_pImporter = NULL;
+ m_fInitialized = false;
+ m_fInitializeFromStream = false;
+ memset(&m_DataPointers, 0, sizeof(PDBDataPointers));
+ m_szPath[0] = '\0';
+ }
+ virtual ~SymReader();
+ static HRESULT NewSymReader( REFCLSID clsid, void** ppObj );
+
+public:
+ //-----------------------------------------------------------
+ // IUnknown support
+ //-----------------------------------------------------------
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (InterlockedIncrement((LONG *) &m_refCount));
+ }
+
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ LONG refCount = InterlockedDecrement((LONG *) &m_refCount);
+ if (refCount == 0)
+ DELETE(this);
+
+ return (refCount);
+ }
+ STDMETHOD(QueryInterface)(REFIID riid, void** ppvObject);
+
+// ISymUnmanagedReader
+public:
+ STDMETHOD(GetDocument)(__in LPWSTR url,
+ GUID language,
+ GUID languageVendor,
+ GUID documentType,
+ ISymUnmanagedDocument **pRetVal);
+ STDMETHOD(GetDocuments)(ULONG32 cDocs,
+ ULONG32 *pcDocs,
+ ISymUnmanagedDocument *pDocs[]);
+ STDMETHOD(GetUserEntryPoint)(mdMethodDef *pRetVal);
+ STDMETHOD(GetMethod)(mdMethodDef method,
+ ISymUnmanagedMethod **pRetVal);
+ STDMETHOD(GetMethodByVersion)(mdMethodDef method,
+ int version,
+ ISymUnmanagedMethod **pRetVal);
+ STDMETHOD(GetVariables)(mdToken parent,
+ ULONG32 cVars,
+ ULONG32 *pcVars,
+ ISymUnmanagedVariable *pVars[]);
+ STDMETHOD(GetGlobalVariables)(ULONG32 cVars,
+ ULONG32 *pcVars,
+ ISymUnmanagedVariable *pVars[]);
+ STDMETHOD(GetMethodFromDocumentPosition)(ISymUnmanagedDocument *document,
+ ULONG32 line,
+ ULONG32 column,
+ ISymUnmanagedMethod **pRetVal);
+ STDMETHOD(GetSymAttribute)(mdToken parent,
+ __in LPWSTR name,
+ ULONG32 cBuffer,
+ ULONG32 *pcBuffer,
+ __out_bcount_part_opt(cBuffer, *pcBuffer) BYTE buffer[]);
+ STDMETHOD(GetNamespaces)(ULONG32 cNameSpaces,
+ ULONG32 *pcNameSpaces,
+ ISymUnmanagedNamespace *namespaces[]);
+ STDMETHOD(Initialize)(IUnknown *importer,
+ const WCHAR* szFileName,
+ const WCHAR* szsearchPath,
+ IStream *pIStream);
+ STDMETHOD(UpdateSymbolStore)(const WCHAR *filename,
+ IStream *pIStream);
+
+ STDMETHOD(ReplaceSymbolStore)(const WCHAR *filename,
+ IStream *pIStream);
+
+ STDMETHOD(GetSymbolStoreFileName)(ULONG32 cchName,
+ ULONG32 *pcchName,
+ __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[]);
+
+ STDMETHOD(GetMethodsFromDocumentPosition)(ISymUnmanagedDocument* document,
+ ULONG32 line,
+ ULONG32 column,
+ ULONG32 cMethod,
+ ULONG32* pcMethod,
+ ISymUnmanagedMethod* pRetVal[]);
+
+ STDMETHOD(GetDocumentVersion)(ISymUnmanagedDocument *pDoc, int* version, BOOL* pbCurrent);
+
+ STDMETHOD(GetMethodVersion)(ISymUnmanagedMethod* pMethod, int* version);
+
+ //-----------------------------------------------------------
+ // Methods not exposed via a COM interface.
+ //-----------------------------------------------------------
+public:
+ HRESULT GetDocument(UINT32 DocumentEntry, SymDocument **ppDocument);
+private:
+ void Cleanup();
+
+ HRESULT InitializeFromFile(const WCHAR* szFileName,
+ const WCHAR* szsearchPath);
+
+ HRESULT InitializeFromStream(IStream * pIStream);
+
+ HRESULT VerifyPEDebugInfo(const WCHAR* szFileName);
+
+ HRESULT ValidateData();
+
+ HRESULT ValidateBytes(UINT32 bytesIndex, UINT32 bytesLength);
+
+private:
+ // Data Members
+ UINT32 m_refCount;
+
+ // Symbol File Name
+ WCHAR m_szPath[ _MAX_PATH ];
+ WCHAR m_szStoredSymbolName[ _MAX_PATH ];
+
+ PDBInfo *m_pPDBInfo;
+ SymDocument **m_pDocs;
+ IUnknown *m_pImporter;
+ PDBDataPointers m_DataPointers;
+
+ // Are we initialized yet?
+ bool m_fInitialized;
+
+ // Did we initialize from stream
+ bool m_fInitializeFromStream;
+ };
+
+/* ------------------------------------------------------------------------- *
+ * SymDocument class
+ * ------------------------------------------------------------------------- */
+
+class SymDocument : public ISymUnmanagedDocument
+{
+// ctor/dtor
+public:
+ SymDocument(SymReader *pReader,
+ PDBDataPointers *pData,
+ UINT32 CountOfMethods,
+ UINT32 DocumentEntry)
+ {
+ m_refCount = 0;
+ m_pData = pData;
+ m_DocumentEntry = DocumentEntry;
+ m_CountOfMethods = CountOfMethods;
+ m_pReader = pReader;
+ pReader->AddRef();
+
+ }
+ virtual ~SymDocument()
+ {
+ RELEASE(m_pReader);
+ }
+
+// IUnknown
+public:
+ //-----------------------------------------------------------
+ // IUnknown support
+ //-----------------------------------------------------------
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (InterlockedIncrement((LONG *) &m_refCount));
+ }
+
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ LONG refCount = InterlockedDecrement((LONG *) &m_refCount);
+ if (refCount == 0)
+ DELETE(this);
+
+ return (refCount);
+ }
+ STDMETHOD(QueryInterface)(REFIID riid, void** ppvObject);
+
+// ISymUnmanagedDocument
+public:
+ STDMETHOD(GetURL)(ULONG32 cchUrl,
+ ULONG32 *pcchUrl,
+ __out_ecount_part_opt(cchUrl, *pcchUrl) WCHAR szUrl[]);
+ STDMETHOD(GetDocumentType)(GUID *pRetVal);
+ STDMETHOD(GetLanguage)(GUID *pRetVal);
+ STDMETHOD(GetLanguageVendor)(GUID *pRetVal);
+ STDMETHOD(GetCheckSumAlgorithmId)(GUID *pRetVal);
+ STDMETHOD(GetCheckSum)(ULONG32 cData,
+ ULONG32 *pcData,
+ BYTE data[]);
+ STDMETHOD(FindClosestLine)(ULONG32 line, ULONG32 *pRetVal);
+ STDMETHOD(HasEmbeddedSource)(BOOL *pRetVal);
+ STDMETHOD(GetSourceLength)(ULONG32 *pRetVal);
+ STDMETHOD(GetSourceRange)(ULONG32 startLine,
+ ULONG32 startColumn,
+ ULONG32 endLine,
+ ULONG32 endColumn,
+ ULONG32 cSourceBytes,
+ ULONG32 *pcSourceBytes,
+ BYTE source[]);
+
+ //-----------------------------------------------------------
+ // Methods not exposed via a COM interface.
+ //-----------------------------------------------------------
+ UINT32 GetDocumentEntry()
+ {
+ return m_DocumentEntry;
+ }
+
+// Data members
+private:
+ UINT32 m_refCount;
+
+ SymReader *m_pReader;
+
+ // Data Pointer
+ PDBDataPointers *m_pData;
+
+ // Entry into the document array
+ UINT32 m_DocumentEntry;
+
+ // Total number of methods in the ildb
+ UINT32 m_CountOfMethods;
+
+};
+
+/* ------------------------------------------------------------------------- *
+ * SymMethod class
+ * ------------------------------------------------------------------------- */
+
+class SymMethod : public ISymUnmanagedMethod
+{
+// ctor/dtor
+public:
+ SymMethod(SymReader *pSymReader, PDBDataPointers *pData, UINT32 MethodEntry)
+ {
+ m_pData = pData;
+ m_MethodEntry = MethodEntry;
+ m_refCount = 0;
+ m_pReader = pSymReader;
+ pSymReader->AddRef();
+ }
+
+ virtual ~SymMethod()
+ {
+ RELEASE(m_pReader);
+ };
+
+public:
+
+ //-----------------------------------------------------------
+ // IUnknown support
+ //-----------------------------------------------------------
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (InterlockedIncrement((LONG *) &m_refCount));
+ }
+
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ LONG refCount = InterlockedDecrement((LONG *) &m_refCount);
+ if (refCount == 0)
+ DELETE(this);
+
+ return (refCount);
+ }
+ STDMETHOD(QueryInterface)(REFIID riid, void** ppvObject);
+
+// ISymUnmanagedMethod
+public:
+ STDMETHOD(GetToken)(mdMethodDef *pRetVal);
+ STDMETHOD(GetSequencePointCount)(ULONG32 *pRetVal);
+
+ STDMETHOD(GetRootScope)(ISymUnmanagedScope **pRetVal);
+ STDMETHOD(GetScopeFromOffset)(ULONG32 offset,
+ ISymUnmanagedScope **pRetVal);
+ STDMETHOD(GetOffset)(ISymUnmanagedDocument *document,
+ ULONG32 line,
+ ULONG32 column,
+ ULONG32 *pRetVal);
+ STDMETHOD(GetRanges)(ISymUnmanagedDocument *document,
+ ULONG32 line,
+ ULONG32 column,
+ ULONG32 cRanges,
+ ULONG32 *pcRanges,
+ ULONG32 ranges[]);
+ STDMETHOD(GetParameters)(ULONG32 cParams,
+ ULONG32 *pcParams,
+ ISymUnmanagedVariable *params[]);
+ STDMETHOD(GetNamespace)(ISymUnmanagedNamespace **pRetVal);
+ STDMETHOD(GetSourceStartEnd)(ISymUnmanagedDocument *docs[2],
+ ULONG32 lines[2],
+ ULONG32 columns[2],
+ BOOL *pRetVal);
+ STDMETHOD(GetSequencePoints)(ULONG32 cpoints,
+ ULONG32* pcpoints,
+ ULONG32 offsets[],
+ ISymUnmanagedDocument *documents[],
+ ULONG32 lines[],
+ ULONG32 columns[],
+ ULONG32 endlines[],
+ ULONG32 endcolumns[]);
+
+// Data members
+private:
+ // AddRef/Release support
+ UINT32 m_refCount;
+
+ // Data Pointer
+ PDBDataPointers *m_pData;
+
+ // SymReader
+ SymReader *m_pReader;
+
+ // Entry into the SymMethodInfo array
+ UINT32 m_MethodEntry;
+
+};
+
+/* ------------------------------------------------------------------------- *
+ * SymScope class
+ * ------------------------------------------------------------------------- */
+
+class SymScope : public ISymUnmanagedScope
+{
+// ctor/dtor
+public:
+ SymScope(
+ ISymUnmanagedMethod *pSymMethod,
+ PDBDataPointers *pData,
+ UINT32 MethodEntry,
+ UINT32 ScopeEntry)
+ {
+ m_pSymMethod = pSymMethod;
+ m_pSymMethod->AddRef();
+ m_pData = pData;
+ m_MethodEntry = MethodEntry;
+ m_ScopeEntry = ScopeEntry;
+ m_refCount = 0;
+ }
+ virtual ~SymScope()
+ {
+ RELEASE(m_pSymMethod);
+ }
+
+public:
+ //-----------------------------------------------------------
+ // IUnknown support
+ //-----------------------------------------------------------
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (InterlockedIncrement((LONG *) &m_refCount));
+ }
+
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ LONG refCount = InterlockedDecrement((LONG *) &m_refCount);
+ if (refCount == 0)
+ DELETE(this);
+
+ return (refCount);
+ }
+ STDMETHOD(QueryInterface)(REFIID riid, void** ppvObject);
+
+// ISymUnmanagedScope
+public:
+ STDMETHOD(GetMethod)(ISymUnmanagedMethod **pRetVal);
+ STDMETHOD(GetParent)(ISymUnmanagedScope **pRetVal);
+ STDMETHOD(GetChildren)(ULONG32 cChildren,
+ ULONG32 *pcChildren,
+ ISymUnmanagedScope *children[]);
+ STDMETHOD(GetStartOffset)(ULONG32 *pRetVal);
+ STDMETHOD(GetEndOffset)(ULONG32 *pRetVal);
+ STDMETHOD(GetLocalCount)(ULONG32 *pRetVal);
+ STDMETHOD(GetLocals)(ULONG32 cLocals,
+ ULONG32 *pcLocals,
+ ISymUnmanagedVariable *locals[]);
+ STDMETHOD(GetNamespaces)(ULONG32 cNameSpaces,
+ ULONG32 *pcNameSpaces,
+ ISymUnmanagedNamespace *namespaces[]);
+
+// Data members
+private:
+
+ UINT32 m_refCount; // Add/Ref Release
+
+ ISymUnmanagedMethod *m_pSymMethod;
+
+ // Data Pointer
+ PDBDataPointers *m_pData;
+ // Entry into the SymMethodInfo array
+ UINT32 m_MethodEntry;
+ // Entry into the scope array
+ UINT32 m_ScopeEntry;
+};
+
+/* ------------------------------------------------------------------------- *
+ * SymReaderVar class
+ * ------------------------------------------------------------------------- */
+
+class SymReaderVar : public ISymUnmanagedVariable
+{
+// ctor/dtor
+public:
+ SymReaderVar(SymScope *pScope, PDBDataPointers *pData, UINT32 VarEntry)
+ {
+ m_pData = pData;
+ m_VarEntry = VarEntry;
+ m_refCount = 0;
+ m_pScope = pScope;
+ pScope->AddRef();
+ }
+ virtual ~SymReaderVar()
+ {
+ RELEASE(m_pScope);
+ }
+
+public:
+ //-----------------------------------------------------------
+ // IUnknown support
+ //-----------------------------------------------------------
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (InterlockedIncrement((LONG *) &m_refCount));
+ }
+
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ LONG refCount = InterlockedDecrement((LONG *) &m_refCount);
+ if (refCount == 0)
+ DELETE(this);
+
+ return (refCount);
+ }
+ STDMETHOD(QueryInterface)(REFIID riid, void** ppvObject);
+
+// ISymUnmanagedReaderVar
+public:
+ STDMETHOD(GetName)(ULONG32 cchName,
+ ULONG32 *pcchName,
+ __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[]);
+ STDMETHOD(GetAttributes)(ULONG32 *pRetVal);
+ STDMETHOD(GetSignature)(ULONG32 cSig,
+ ULONG32 *pcSig,
+ BYTE sig[]);
+ STDMETHOD(GetAddressKind)(ULONG32 *pRetVal);
+ STDMETHOD(GetAddressField1)(ULONG32 *pRetVal);
+ STDMETHOD(GetAddressField2)(ULONG32 *pRetVal);
+ STDMETHOD(GetAddressField3)(ULONG32 *pRetVal);
+ STDMETHOD(GetStartOffset)(ULONG32 *pRetVal);
+ STDMETHOD(GetEndOffset)(ULONG32 *pRetVal);
+
+
+// Data members
+private:
+ UINT32 m_refCount; // Add/Ref Release
+
+ // Data Pointer
+ PDBDataPointers *m_pData;
+
+ // Scope of the variable
+ SymScope *m_pScope;
+
+ // Entry into the SymMethodInfo array
+ UINT32 m_VarEntry;
+};
+
+class SymReaderNamespace : public ISymUnmanagedNamespace
+{
+
+public:
+ SymReaderNamespace(SymScope *pScope, PDBDataPointers *pData, UINT32 NamespaceEntry)
+ {
+ m_pData = pData;
+ m_NamespaceEntry = NamespaceEntry;
+ m_refCount = 0;
+ m_pScope = pScope;
+ pScope->AddRef();
+ }
+ virtual ~SymReaderNamespace()
+ {
+ }
+
+public:
+ //-----------------------------------------------------------
+ // IUnknown support
+ //-----------------------------------------------------------
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (InterlockedIncrement((LONG *) &m_refCount));
+ }
+
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ LONG refCount = InterlockedDecrement((LONG *) &m_refCount);
+ if (refCount == 0)
+ DELETE(this);
+
+ return (refCount);
+ }
+ STDMETHOD(QueryInterface)(REFIID riid, void** ppvObject);
+
+public:
+ //-----------------------------------------------------------
+ // ISymUnmanagedNamespace support
+ //-----------------------------------------------------------
+ STDMETHOD(GetName)(ULONG32 cchName,
+ ULONG32 *pcchName,
+ __out_ecount_part_opt(cchName, *pcchName) WCHAR szName[]);
+ STDMETHOD(GetNamespaces)(ULONG32 cNamespaces,
+ ULONG32 *pcNamespaces,
+ ISymUnmanagedNamespace* namespaces[]);
+ STDMETHOD(GetVariables)(ULONG32 cchName,
+ ULONG32 *pcchName,
+ ISymUnmanagedVariable *pVars[]);
+
+private:
+ UINT32 m_refCount; // Add/Ref Release
+
+ // Owning scope
+ SymScope *m_pScope;
+
+ // Data Pointer
+ PDBDataPointers *m_pData;
+ // Entry into the NameSpace array
+ UINT32 m_NamespaceEntry;
+
+};
+
+#endif
diff --git a/src/debug/ildbsymlib/symwrite.cpp b/src/debug/ildbsymlib/symwrite.cpp
new file mode 100644
index 0000000000..c09fd37498
--- /dev/null
+++ b/src/debug/ildbsymlib/symwrite.cpp
@@ -0,0 +1,1553 @@
+// 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.
+// ===========================================================================
+// File: symwrite.cpp
+//
+
+//
+// Note: The various SymWriter_* and SymDocumentWriter_* are entry points
+// called via PInvoke from the managed symbol wrapper used by managed languages
+// to emit debug information (such as jscript)
+// ===========================================================================
+
+#include "pch.h"
+#include "symwrite.h"
+
+
+// -------------------------------------------------------------------------
+// SymWriter class
+// -------------------------------------------------------------------------
+
+// This is a COM object which is called both directly from the runtime, and from managed code
+// via PInvoke (CoreSymWrapper) and IJW (ISymWrapper). This is an unusual pattern, and it's not
+// clear exactly how best to address it. Eg., should we be using BEGIN_EXTERNAL_ENTRYPOINT
+// macros? Conceptually this is just a drop-in replacement for diasymreader.dll, and could
+// live in a different DLL instead of being statically linked into the runtime. But since it
+// relies on utilcode (and actually gets the runtime utilcode, not the nohost utilcode like
+// other external tools), it does have some properties of runtime code.
+//
+
+//-----------------------------------------------------------
+// NewSymWriter
+// Static function used to create a new instance of SymWriter
+//-----------------------------------------------------------
+HRESULT SymWriter::NewSymWriter(const GUID& id, void **object)
+{
+ if (id != IID_ISymUnmanagedWriter)
+ return (E_UNEXPECTED);
+
+ SymWriter *writer = NEW(SymWriter());
+
+ if (writer == NULL)
+ return (E_OUTOFMEMORY);
+
+ *object = (ISymUnmanagedWriter*)writer;
+ writer->AddRef();
+
+ return (S_OK);
+}
+
+//-----------------------------------------------------------
+// SymWriter Constuctor
+//-----------------------------------------------------------
+SymWriter::SymWriter() :
+ m_refCount(0),
+ m_openMethodToken(mdMethodDefNil),
+ m_LargestMethodToken(mdMethodDefNil),
+ m_pmethod(NULL),
+ m_currentScope(k_noScope),
+ m_hFile(NULL),
+ m_pIStream(NULL),
+ m_pStringPool(NULL),
+ m_closed( false ),
+ m_sortLines (false),
+ m_sortMethodEntries(false)
+{
+ memset(m_szPath, 0, sizeof(m_szPath));
+ memset(&ModuleLevelInfo, 0, sizeof(PDBInfo));
+}
+
+//-----------------------------------------------------------
+// SymWriter QI
+//-----------------------------------------------------------
+COM_METHOD SymWriter::QueryInterface(REFIID riid, void **ppInterface)
+{
+ if (ppInterface == NULL)
+ return E_INVALIDARG;
+
+ if (riid == IID_ISymUnmanagedWriter )
+ *ppInterface = (ISymUnmanagedWriter*)this;
+ /* ROTORTODO: Pretend that we do not implement ISymUnmanagedWriter2 to prevent C# compiler from using it.
+ This is against COM rules since ISymUnmanagedWriter3 inherits from ISymUnmanagedWriter2.
+ else if (riid == IID_ISymUnmanagedWriter2 )
+ *ppInterface = (ISymUnmanagedWriter2*)this;
+ */
+ else if (riid == IID_ISymUnmanagedWriter3 )
+ *ppInterface = (ISymUnmanagedWriter3*)this;
+ else if (riid == IID_IUnknown)
+ *ppInterface = (IUnknown*)(ISymUnmanagedWriter*)this;
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+//-----------------------------------------------------------
+// SymWriter Destructor
+//-----------------------------------------------------------
+SymWriter::~SymWriter()
+{
+ // Note that this must be thread-safe - it may be invoked on the finalizer thread
+ // But since this dtor can only be invoked when all references have been released,
+ // no other threads can be manipulating the writer.
+ // Ideally we'd probably just add locking to all methods, but this is low-priority
+ // because diasymreader.dll isn't thread-safe and so we need to ensure the CLR's use
+ // of these interfaces are properly syncrhonized.
+ if ( !m_closed )
+ Close();
+ RELEASE(m_pIStream);
+ DELETE(m_pStringPool);
+}
+
+//-----------------------------------------------------------
+// SymWriter Initialize the SymWriter
+//-----------------------------------------------------------
+COM_METHOD SymWriter::Initialize
+(
+ IUnknown *emitter, // Emitter (IMetaData Emit/Import) - unused by ILDB
+ const WCHAR *szFilename, // FileName of the exe we're creating
+ IStream *pIStream, // Stream to store into
+ BOOL fFullBuild // Is this a full build or an incremental build
+)
+{
+ HRESULT hr = S_OK;
+
+ // Incremental compile not implemented in Rotor
+ _ASSERTE(fFullBuild);
+
+ if (emitter == NULL)
+ return E_INVALIDARG;
+
+ if (pIStream != NULL)
+ {
+ m_pIStream = pIStream;
+ pIStream->AddRef();
+ }
+ else
+ {
+ if (szFilename == NULL)
+ {
+ IfFailRet(E_INVALIDARG);
+ }
+ }
+
+ m_pStringPool = NEW(StgStringPool());
+ IfFailRet(m_pStringPool->InitNew());
+
+ if (szFilename != NULL)
+ {
+ wchar_t fullpath[_MAX_PATH];
+ wchar_t drive[_MAX_DRIVE];
+ wchar_t dir[_MAX_DIR];
+ wchar_t fname[_MAX_FNAME];
+ _wsplitpath_s( szFilename, drive, COUNTOF(drive), dir, COUNTOF(dir), fname, COUNTOF(fname), NULL, 0 );
+ _wmakepath_s( fullpath, COUNTOF(fullpath), drive, dir, fname, W("ildb") );
+ if (wcsncpy_s( m_szPath, COUNTOF(m_szPath), fullpath, _TRUNCATE) == STRUNCATE)
+ return HrFromWin32(ERROR_INSUFFICIENT_BUFFER);
+ }
+
+ // Note that we don't need the emitter - ILDB is agnostic to the module metadata.
+
+ return hr;
+}
+
+//-----------------------------------------------------------
+// SymWriter Initialize2 the SymWriter
+// Delegate to Initialize then use the szFullPathName param
+//-----------------------------------------------------------
+COM_METHOD SymWriter::Initialize2
+(
+ IUnknown *emitter, // Emitter (IMetaData Emit/Import)
+ const WCHAR *szTempPath, // Location of the file
+ IStream *pIStream, // Stream to store into
+ BOOL fFullBuild, // Full build or not
+ const WCHAR *szFullPathName // Final destination of the ildb
+)
+{
+ HRESULT hr = S_OK;
+ IfFailGo( Initialize( emitter, szTempPath, pIStream, fFullBuild ) );
+ // We don't need the final location of the ildb
+
+ErrExit:
+ return hr;
+}
+
+//-----------------------------------------------------------
+// SymWriter GetorCreateDocument
+// creates a new symbol document writer for a specified source
+// Arguments:
+// input: wcsUrl - The source file name
+// output: ppRetVal - The new document writer
+// Return Value: hr - S_OK if success, OOM otherwise
+//-----------------------------------------------------------
+HRESULT SymWriter::GetOrCreateDocument(
+ const WCHAR *wcsUrl, // Document name
+ const GUID *pLanguage, // What Language we're compiling
+ const GUID *pLanguageVendor, // What vendor
+ const GUID *pDocumentType, // Type
+ ISymUnmanagedDocumentWriter **ppRetVal // [out] Created DocumentWriter
+)
+{
+ ULONG UrlEntry;
+ DWORD strLength = WszWideCharToMultiByte(CP_UTF8, 0, wcsUrl, -1, 0, 0, 0, 0);
+ LPSTR multiByteURL = (LPSTR) new char [strLength+1];
+ HRESULT hr = S_OK;
+
+ if (multiByteURL == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ WszWideCharToMultiByte(CP_UTF8, 0, wcsUrl, -1, multiByteURL, strLength+1, 0, 0);
+
+ if (m_pStringPool->FindString(multiByteURL, &UrlEntry) == S_FALSE) // no file of that name has been seen before
+ {
+ hr = CreateDocument(wcsUrl, pLanguage, pLanguageVendor, pDocumentType, ppRetVal);
+ }
+ else // we already have a writer for this file
+ {
+ UINT32 docInfo = 0;
+
+ CRITSEC_COOKIE cs = ClrCreateCriticalSection(CrstLeafLock, CRST_DEFAULT);
+
+ ClrEnterCriticalSection(cs);
+
+ while ((docInfo < m_MethodInfo.m_documents.count()) && (m_MethodInfo.m_documents[docInfo].UrlEntry() != UrlEntry))
+ {
+ docInfo++;
+ }
+
+ if (docInfo == m_MethodInfo.m_documents.count()) // something went wrong and we didn't find the writer
+ {
+ hr = CreateDocument(wcsUrl, pLanguage, pLanguageVendor, pDocumentType, ppRetVal);
+ }
+ else
+ {
+ *ppRetVal = m_MethodInfo.m_documents[docInfo].DocumentWriter();
+ (*ppRetVal)->AddRef();
+ }
+ ClrLeaveCriticalSection(cs);
+ }
+
+ delete [] multiByteURL;
+ return hr;
+
+} // SymWriter::GetOrCreateDocument
+
+//-----------------------------------------------------------
+// SymWriter CreateDocument
+// creates a new symbol document writer for a specified source
+// Arguments:
+// input: wcsUrl - The source file name
+// output: ppRetVal - The new document writer
+// Return Value: hr - S_OK if success, OOM otherwise
+//-----------------------------------------------------------
+HRESULT SymWriter::CreateDocument(const WCHAR *wcsUrl, // Document name
+ const GUID *pLanguage, // What Language we're compiling
+ const GUID *pLanguageVendor, // What vendor
+ const GUID *pDocumentType, // Type
+ ISymUnmanagedDocumentWriter **ppRetVal // [out] Created DocumentWriter
+)
+
+{
+ DocumentInfo* pDocument = NULL;
+ SymDocumentWriter *sdw = NULL;
+ UINT32 DocumentEntry;
+ ULONG UrlEntry;
+ HRESULT hr = NOERROR;
+
+ DocumentEntry = m_MethodInfo.m_documents.count();
+ IfNullGo(pDocument = m_MethodInfo.m_documents.next());
+ memset(pDocument, 0, sizeof(DocumentInfo));
+
+ // Create the new document writer.
+ sdw = NEW(SymDocumentWriter(DocumentEntry, this));
+ IfNullGo(sdw);
+
+ pDocument->SetLanguage(*pLanguage);
+ pDocument->SetLanguageVendor(*pLanguageVendor);
+ pDocument->SetDocumentType(*pDocumentType);
+ pDocument->SetDocumentWriter(sdw);
+
+ // stack check needed to call back into utilcode
+ BEGIN_SO_INTOLERANT_CODE_NO_THROW_CHECK_THREAD_FORCE_SO();
+ hr = m_pStringPool->AddStringW(wcsUrl, (UINT32 *)&UrlEntry);
+ END_SO_INTOLERANT_CODE;
+ IfFailGo(hr);
+
+ pDocument->SetUrlEntry(UrlEntry);
+
+ // Pass out the new ISymUnmanagedDocumentWriter.
+ sdw->AddRef();
+ *ppRetVal = (ISymUnmanagedDocumentWriter*)sdw;
+ sdw = NULL;
+
+ErrExit:
+ DELETE(sdw);
+ return hr;
+}
+
+//-----------------------------------------------------------
+// SymWriter DefineDocument
+//-----------------------------------------------------------
+COM_METHOD SymWriter::DefineDocument(
+ const WCHAR *wcsUrl, // Document name
+ const GUID *pLanguage, // What Language we're compiling
+ const GUID *pLanguageVendor, // What vendor
+ const GUID *pDocumentType, // Type
+ ISymUnmanagedDocumentWriter **ppRetVal // [out] Created DocumentWriter
+)
+{
+ HRESULT hr = NOERROR;
+
+ IfFalseGo(wcsUrl, E_INVALIDARG);
+ IfFalseGo(pLanguage, E_INVALIDARG);
+ IfFalseGo(pLanguageVendor, E_INVALIDARG);
+ IfFalseGo(pDocumentType, E_INVALIDARG);
+ IfFalseGo(ppRetVal, E_INVALIDARG);
+
+ // Init out parameter
+ *ppRetVal = NULL;
+
+ hr = GetOrCreateDocument(wcsUrl, pLanguage, pLanguageVendor, pDocumentType, ppRetVal);
+ErrExit:
+ return hr;
+}
+
+
+//-----------------------------------------------------------
+// SymWriter SetDocumentSrc
+//-----------------------------------------------------------
+HRESULT SymWriter::SetDocumentSrc(
+ UINT32 DocumentEntry,
+ DWORD SourceSize,
+ BYTE* pSource
+)
+{
+ DocumentInfo* pDocument = NULL;
+ HRESULT hr = S_OK;
+
+ IfFalseGo( SourceSize == 0 || pSource, E_INVALIDARG);
+ IfFalseGo( DocumentEntry < m_MethodInfo.m_documents.count(), E_INVALIDARG);
+
+ pDocument = &m_MethodInfo.m_documents[DocumentEntry];
+
+ if (pSource)
+ {
+ UINT32 i;
+ IfFalseGo( m_MethodInfo.m_bytes.grab(SourceSize, &i), E_OUTOFMEMORY);
+ memcpy(&m_MethodInfo.m_bytes[i], pSource, SourceSize);
+ pDocument->SetSourceEntry(i);
+ pDocument->SetSourceSize(SourceSize);
+ }
+
+ErrExit:
+ return hr;
+}
+
+//-----------------------------------------------------------
+// SymWriter SetDocumentCheckSum
+//-----------------------------------------------------------
+HRESULT SymWriter::SetDocumentCheckSum(
+ UINT32 DocumentEntry,
+ GUID AlgorithmId,
+ DWORD CheckSumSize,
+ BYTE* pCheckSum
+)
+{
+ DocumentInfo* pDocument = NULL;
+ HRESULT hr = S_OK;
+
+ IfFalseGo( CheckSumSize == 0 || pCheckSum, E_INVALIDARG);
+ IfFalseGo( DocumentEntry < m_MethodInfo.m_documents.count(), E_INVALIDARG);
+
+ pDocument = &m_MethodInfo.m_documents[DocumentEntry];
+
+ if (pCheckSum)
+ {
+ UINT32 i;
+ IfFalseGo( m_MethodInfo.m_bytes.grab(CheckSumSize, &i), E_OUTOFMEMORY);
+ memcpy(&m_MethodInfo.m_bytes[i], pCheckSum, CheckSumSize);
+ pDocument->SetCheckSumEntry(i);
+ pDocument->SetCheckSymSize(CheckSumSize);
+ }
+
+ pDocument->SetAlgorithmId(AlgorithmId);
+
+ErrExit:
+ return hr;
+}
+
+//-----------------------------------------------------------
+// SymWriter SetUserEntryPoint
+//-----------------------------------------------------------
+COM_METHOD SymWriter::SetUserEntryPoint(mdMethodDef entryMethod)
+{
+ HRESULT hr = S_OK;
+
+ // Make sure that an entry point hasn't already been set.
+ if (ModuleLevelInfo.m_userEntryPoint == 0)
+ ModuleLevelInfo.m_userEntryPoint = entryMethod;
+
+ return hr;
+}
+
+//-----------------------------------------------------------
+// SymWriter OpenMethod
+// Get ready to get information about a new method
+//-----------------------------------------------------------
+COM_METHOD SymWriter::OpenMethod(mdMethodDef method)
+{
+ HRESULT hr = S_OK;
+
+ // We can only have one open method at a time.
+ if (m_openMethodToken != mdMethodDefNil)
+ return E_INVALIDARG;
+
+ m_LargestMethodToken = max(method, m_LargestMethodToken);
+
+ if (m_LargestMethodToken != method)
+ {
+ m_sortMethodEntries = true;
+ // Check to see if we're trying to open a method we've already done
+ unsigned i;
+ for (i = 0; i < m_MethodInfo.m_methods.count(); i++)
+ {
+ if (m_MethodInfo.m_methods[i].MethodToken() == method)
+ {
+ return E_INVALIDARG;
+ }
+ }
+ }
+
+ // Remember the token for this method.
+ m_openMethodToken = method;
+
+ IfNullGo( m_pmethod = m_MethodInfo.m_methods.next() );
+ m_pmethod->SetMethodToken(m_openMethodToken);
+ m_pmethod->SetStartScopes(m_MethodInfo.m_scopes.count());
+ m_pmethod->SetStartVars(m_MethodInfo.m_vars.count());
+ m_pmethod->SetStartUsing(m_MethodInfo.m_usings.count());
+ m_pmethod->SetStartConstant(m_MethodInfo.m_constants.count());
+ m_pmethod->SetStartDocuments(m_MethodInfo.m_documents.count());
+ m_pmethod->SetStartSequencePoints(m_MethodInfo.m_auxSequencePoints.count());
+
+ // By default assume the lines are inserted in the correct order
+ m_sortLines = false;
+
+ // Initialize the maximum scope end offset for this method
+ m_maxScopeEnd = 1;
+
+ // Open the implicit root scope for the method
+ _ASSERTE(m_currentScope == k_noScope);
+
+ IfFailRet(OpenScope(0, NULL));
+
+ _ASSERTE(m_currentScope != k_noScope);
+
+ErrExit:
+ return hr;
+}
+
+COM_METHOD SymWriter::OpenMethod2(
+ mdMethodDef method,
+ ULONG32 isect,
+ ULONG32 offset)
+{
+ // This symbol writer doesn't support section offsets
+ _ASSERTE(FALSE);
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------
+// compareAuxLines
+// Used to sort SequencePoint
+//-----------------------------------------------------------
+int __cdecl SequencePoint::compareAuxLines(const void *elem1, const void *elem2 )
+{
+ SequencePoint* p1 = (SequencePoint*)elem1;
+ SequencePoint* p2 = (SequencePoint*)elem2;
+ return p1->Offset() - p2->Offset();
+}
+
+//-----------------------------------------------------------
+// SymWriter CloseMethod
+// We're done with this function, write it out.
+//-----------------------------------------------------------
+COM_METHOD SymWriter::CloseMethod()
+{
+ HRESULT hr = S_OK;
+ UINT32 CountOfSequencePoints;
+
+ // Must have an open method.
+ if (m_openMethodToken == mdMethodDefNil)
+ return E_UNEXPECTED;
+
+ // All scopes up to the root must have been closed (and the root must not have been closed).
+ _ASSERTE(m_currentScope != k_noScope);
+ if (m_MethodInfo.m_scopes[m_currentScope].ParentScope() != k_noScope)
+ return E_FAIL;
+
+ // Close the implicit root scope using the largest end offset we've seen in this method, or 1 if none.
+ IfFailRet(CloseScopeInternal(m_maxScopeEnd));
+
+ m_pmethod->SetEndScopes(m_MethodInfo.m_scopes.count());
+ m_pmethod->SetEndVars(m_MethodInfo.m_vars.count());
+ m_pmethod->SetEndUsing(m_MethodInfo.m_usings.count());
+ m_pmethod->SetEndConstant(m_MethodInfo.m_constants.count());
+ m_pmethod->SetEndDocuments(m_MethodInfo.m_documents.count());
+ m_pmethod->SetEndSequencePoints(m_MethodInfo.m_auxSequencePoints.count());
+
+ CountOfSequencePoints = m_pmethod->EndSequencePoints() - m_pmethod->StartSequencePoints();
+ // Write any sequence points.
+ if (CountOfSequencePoints > 0 ) {
+ // sort the sequence points
+ if ( m_sortLines )
+ {
+ qsort(&m_MethodInfo.m_auxSequencePoints[m_pmethod->StartSequencePoints()],
+ CountOfSequencePoints,
+ sizeof( SequencePoint ),
+ SequencePoint::compareAuxLines );
+ }
+ }
+
+ // All done with this method.
+ m_openMethodToken = mdMethodDefNil;
+
+ return hr;
+}
+
+//-----------------------------------------------------------
+// SymWriter DefineSequencePoints
+// Define the sequence points for this function
+//-----------------------------------------------------------
+COM_METHOD SymWriter::DefineSequencePoints(
+ ISymUnmanagedDocumentWriter *document, //
+ ULONG32 spCount, // Count of sequence points
+ ULONG32 offsets[], // Offsets
+ ULONG32 lines[], // Beginning Lines
+ ULONG32 columns[], // [optional] Columns
+ ULONG32 endLines[], // [optional] End Lines
+ ULONG32 endColumns[] // [optional] End Columns
+)
+{
+ HRESULT hr = S_OK;
+ DWORD docnum;
+
+ // We must have a document, offsets, and lines.
+ IfFalseGo(document && offsets && lines, E_INVALIDARG);
+ // Must have some sequence points
+ IfFalseGo(spCount != 0, E_INVALIDARG);
+ // Must have an open method.
+ IfFalseGo(m_openMethodToken != mdMethodDefNil, E_INVALIDARG);
+
+ // Remember that we've loaded the sequence points and
+ // which document they were for.
+ docnum = (DWORD)((SymDocumentWriter *)document)->GetDocumentEntry();;
+
+ // if sets of lines have been inserted out-of-order, remember to sort when emitting
+ if ( m_MethodInfo.m_auxSequencePoints.count() > 0 && m_MethodInfo.m_auxSequencePoints[ m_MethodInfo.m_auxSequencePoints.count()-1 ].Offset() > offsets[0] )
+ m_sortLines = true;
+
+ // Copy the incomming arrays into the internal format.
+
+ for ( UINT32 i = 0; i < spCount; i++)
+ {
+ SequencePoint * paux;
+ IfNullGo(paux = m_MethodInfo.m_auxSequencePoints.next());
+ paux->SetOffset(offsets[i]);
+ paux->SetStartLine(lines[i]);
+ paux->SetStartColumn(columns ? columns[i] : 0);
+ // If no endLines specified, assume same as start
+ paux->SetEndLine(endLines ? endLines[i] : lines[i]);
+ paux->SetEndColumn(endColumns ? endColumns[i]: 0);
+ paux->SetDocument(docnum);
+ }
+
+ErrExit:
+ return hr;
+}
+
+//-----------------------------------------------------------
+// SymWriter OpenScope
+// Open a new scope for this function
+//-----------------------------------------------------------
+COM_METHOD SymWriter::OpenScope(ULONG32 startOffset, ULONG32 *scopeID)
+{
+ HRESULT hr = S_OK;
+
+ // Make sure the startOffset is within the current scope.
+ if ((m_currentScope != k_noScope) &&
+ (unsigned int)startOffset < m_MethodInfo.m_scopes[m_currentScope].StartOffset())
+ return E_INVALIDARG;
+
+ // Fill in the new scope.
+ UINT32 newScope = m_MethodInfo.m_scopes.count();
+
+ // Make sure that adding 1 below won't overflow (although "next" should fail much
+ // sooner if we were anywhere near close enough).
+ if (newScope >= UINT_MAX)
+ return E_UNEXPECTED;
+
+ SymLexicalScope *sc;
+ IfNullGo( sc = m_MethodInfo.m_scopes.next());
+ sc->SetParentScope(m_currentScope); // parent is the current scope.
+ sc->SetStartOffset(startOffset);
+ sc->SetHasChildren(FALSE);
+ sc->SetHasVars(FALSE);
+ sc->SetEndOffset(0);
+
+ // The current scope has a child now.
+ if (m_currentScope != k_noScope)
+ m_MethodInfo.m_scopes[m_currentScope].SetHasChildren(TRUE);
+
+ // The new scope is now the current scope.
+ m_currentScope = newScope;
+ _ASSERTE(m_currentScope != k_noScope);
+
+ // Pass out the "scope id", which is a _1_ based id for the scope.
+ if (scopeID)
+ *scopeID = m_currentScope + 1;
+
+ErrExit:
+ return hr;
+}
+
+//-----------------------------------------------------------
+// SymWriter CloseScope
+//-----------------------------------------------------------
+COM_METHOD SymWriter::CloseScope(
+ ULONG32 endOffset // Closing offset of scope
+)
+{
+ // This API can only be used to close explicit user scopes.
+ // The implicit root scope is only closed internally by CloseMethod.
+ if ((m_currentScope == k_noScope) || (m_MethodInfo.m_scopes[m_currentScope].ParentScope() == k_noScope))
+ return E_FAIL;
+
+ HRESULT hr = CloseScopeInternal(endOffset);
+
+ _ASSERTE(m_currentScope != k_noScope);
+
+ return hr;
+}
+
+//-----------------------------------------------------------
+// CloseScopeInternal
+// Implementation for ISymUnmanagedWriter::CloseScope but can be called even to
+// close the implicit root scope.
+//-----------------------------------------------------------
+COM_METHOD SymWriter::CloseScopeInternal(
+ ULONG32 endOffset // Closing offset of scope
+)
+{
+ _ASSERTE(m_currentScope != k_noScope);
+
+ // Capture the end offset
+ m_MethodInfo.m_scopes[m_currentScope].SetEndOffset(endOffset);
+
+ // The current scope is now the parent scope.
+ m_currentScope = m_MethodInfo.m_scopes[m_currentScope].ParentScope();
+
+ // Update the maximum scope end offset for this method
+ if (endOffset > m_maxScopeEnd)
+ m_maxScopeEnd = endOffset;
+
+ return S_OK;
+}
+
+//-----------------------------------------------------------
+// SymWriter SetScopeRange
+// Set the Start/End Offset for this scope
+//-----------------------------------------------------------
+COM_METHOD SymWriter::SetScopeRange(
+ ULONG32 scopeID, // ID for the scope
+ ULONG32 startOffset, // Start Offset
+ ULONG32 endOffset // End Offset
+)
+{
+ if (scopeID <= 0)
+ return E_INVALIDARG;
+
+ if (scopeID > m_MethodInfo.m_scopes.count() )
+ return E_INVALIDARG;
+
+ // Remember the new start and end offsets. Also remember that the
+ // scopeID is _1_ based!!!
+ SymLexicalScope *sc = &(m_MethodInfo.m_scopes[scopeID - 1]);
+ sc->SetStartOffset(startOffset);
+ sc->SetEndOffset(endOffset);
+
+ // Update the maximum scope end offset for this method
+ if (endOffset > m_maxScopeEnd)
+ m_maxScopeEnd = endOffset;
+
+ return S_OK;
+}
+
+//-----------------------------------------------------------
+// SymWriter DefineLocalVariable
+//-----------------------------------------------------------
+COM_METHOD SymWriter::DefineLocalVariable(
+ const WCHAR *name, // Name of the variable
+ ULONG32 attributes, // Attributes for the var
+ ULONG32 cSig, // Signature for the variable
+ BYTE signature[],
+ ULONG32 addrKind,
+ ULONG32 addr1, ULONG32 addr2, ULONG32 addr3,
+ ULONG32 startOffset, ULONG32 endOffset)
+{
+ HRESULT hr = S_OK;
+ ULONG NameEntry;
+
+ // We must have a current scope.
+ if (m_currentScope == k_noScope)
+ return E_FAIL;
+
+ // We must have a name and a signature.
+ if (!name || !signature)
+ return E_INVALIDARG;
+
+ if (cSig == 0)
+ return E_INVALIDARG;
+
+ // Make a new local variable and copy the data.
+ SymVariable *var;
+ IfNullGo( var = m_MethodInfo.m_vars.next());
+ var->SetIsParam(FALSE);
+ var->SetAttributes(attributes);
+ var->SetAddrKind(addrKind);
+ var->SetIsHidden(attributes & VAR_IS_COMP_GEN);
+ var->SetAddr1(addr1);
+ var->SetAddr2(addr2);
+ var->SetAddr3(addr3);
+
+
+ // Length of the sig?
+ ULONG32 sigLen;
+ sigLen = cSig;
+
+ // stack check needed to call back into utilcode
+ BEGIN_SO_INTOLERANT_CODE_NO_THROW_CHECK_THREAD_FORCE_SO();
+ // Copy the name.
+ hr = m_pStringPool->AddStringW(name, (UINT32 *)&NameEntry);
+ END_SO_INTOLERANT_CODE;
+ IfFailGo(hr);
+ var->SetName(NameEntry);
+
+ // Copy the signature
+ // Note that we give this back exactly as-is, but callers typically remove any calling
+ // convention prefix.
+ UINT32 i;
+ IfFalseGo(m_MethodInfo.m_bytes.grab(sigLen, &i), E_OUTOFMEMORY);
+ memcpy(&m_MethodInfo.m_bytes[i], signature, sigLen);
+ var->SetSignature(i);
+ var->SetSignatureSize(sigLen);
+
+ // This var is in the current scope
+ var->SetScope(m_currentScope);
+ m_MethodInfo.m_scopes[m_currentScope].SetHasVars(TRUE);
+
+ var->SetStartOffset(startOffset);
+ var->SetEndOffset(endOffset);
+
+ErrExit:
+ return hr;
+}
+
+COM_METHOD SymWriter::DefineLocalVariable2(
+ const WCHAR *name,
+ ULONG32 attributes,
+ mdSignature sigToken,
+ ULONG32 addrKind,
+ ULONG32 addr1, ULONG32 addr2, ULONG32 addr3,
+ ULONG32 startOffset, ULONG32 endOffset)
+{
+ // This symbol writer doesn't support definiting signatures via tokens
+ _ASSERTE(FALSE);
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------
+// SymWriter DefineParameter
+//-----------------------------------------------------------
+COM_METHOD SymWriter::DefineParameter(
+ const WCHAR *name, // Param name
+ ULONG32 attributes, // Attribute for the parameter
+ ULONG32 sequence,
+ ULONG32 addrKind,
+ ULONG32 addr1, ULONG32 addr2, ULONG32 addr3)
+{
+ HRESULT hr = S_OK;
+ ULONG NameEntry;
+
+ // We must have a method.
+ if (m_openMethodToken == mdMethodDefNil)
+ return E_INVALIDARG;
+
+ // We must have a name.
+ if (!name)
+ return E_INVALIDARG;
+
+ SymVariable *var;
+ IfNullGo( var = m_MethodInfo.m_vars.next());
+ var->SetIsParam(TRUE);
+ var->SetAttributes(attributes);
+ var->SetAddrKind(addrKind);
+ var->SetIsHidden(attributes & VAR_IS_COMP_GEN);
+ var->SetAddr1(addr1);
+ var->SetAddr2(addr2);
+ var->SetAddr3(addr3);
+ var->SetSequence(sequence);
+
+
+ // stack check needed to call back into utilcode
+ BEGIN_SO_INTOLERANT_CODE_NO_THROW_CHECK_THREAD_FORCE_SO();
+ // Copy the name.
+ hr = m_pStringPool->AddStringW(name, (UINT32 *)&NameEntry);
+ END_SO_INTOLERANT_CODE;
+ IfFailGo(hr);
+ var->SetName(NameEntry);
+
+ // This var is in the current scope
+ if (m_currentScope != k_noScope)
+ m_MethodInfo.m_scopes[m_currentScope].SetHasVars(TRUE);
+
+ var->SetStartOffset(0);
+ var->SetEndOffset(0);
+
+ErrExit:
+ return hr;
+}
+
+//-----------------------------------------------------------
+// verifyConstTypes
+// Verify that the type is a type we support
+//-----------------------------------------------------------
+static bool verifyConstTypes( DWORD vt )
+{
+ switch ( vt ) {
+ case VT_UI8:
+ case VT_I8:
+ case VT_I4:
+ case VT_UI1: // value < LF_NUMERIC
+ case VT_I2:
+ case VT_R4:
+ case VT_R8:
+ case VT_BOOL: // value < LF_NUMERIC
+ case VT_DATE:
+ case VT_BSTR:
+ case VT_I1:
+ case VT_UI2:
+ case VT_UI4:
+ case VT_INT:
+ case VT_UINT:
+ case VT_DECIMAL:
+ return true;
+ }
+ return false;
+}
+
+//-----------------------------------------------------------
+// SymWriter DefineConstant
+//-----------------------------------------------------------
+COM_METHOD SymWriter::DefineConstant(
+ const WCHAR __RPC_FAR *name,
+ VARIANT value,
+ ULONG32 cSig,
+ unsigned char __RPC_FAR signature[])
+{
+ HRESULT hr = S_OK;
+ ULONG ValueBstr = 0;
+ ULONG Name;
+
+ // currently we only support local constants
+
+ // We must have a method.
+ if (m_openMethodToken == mdMethodDefNil)
+ return E_INVALIDARG;
+
+ // We must have a name and signature.
+ IfFalseGo(name, E_INVALIDARG);
+ IfFalseGo(signature, E_INVALIDARG);
+ IfFalseGo(cSig > 0, E_INVALIDARG);
+
+ //
+ // Support byref decimal values
+ //
+ if ( (V_VT(&value)) == ( VT_BYREF | VT_DECIMAL ) ) {
+ if ( V_DECIMALREF(&value) == NULL )
+ return E_INVALIDARG;
+ V_DECIMAL(&value) = *V_DECIMALREF(&value);
+ V_VT(&value) = VT_DECIMAL;
+ }
+
+ // we only support non-ref constants
+ if ( ( V_VT(&value) & VT_BYREF ) != 0 )
+ return E_INVALIDARG;
+
+ if ( !verifyConstTypes( V_VT(&value) ) )
+ return E_INVALIDARG;
+
+ // If it's a BSTR, we need to persist the Bstr as an entry into
+ // the stringpool
+ if (V_VT(&value) == VT_BSTR)
+ {
+ // stack check needed to call back into utilcode
+ BEGIN_SO_INTOLERANT_CODE_NO_THROW_CHECK_THREAD_FORCE_SO();
+ // Copy the bstrValue.
+ hr = m_pStringPool->AddStringW(V_BSTR(&value), (UINT32 *)&ValueBstr);
+ END_SO_INTOLERANT_CODE;
+ IfFailGo(hr);
+ V_BSTR(&value) = NULL;
+ }
+
+ SymConstant *con;
+ IfNullGo( con = m_MethodInfo.m_constants.next());
+ con->SetValue(value, ValueBstr);
+
+
+ // stack check needed to call back into utilcode
+ BEGIN_SO_INTOLERANT_CODE_NO_THROW_CHECK_THREAD_FORCE_SO();
+ // Copy the name.
+ hr = m_pStringPool->AddStringW(name, (UINT32 *)&Name);
+ END_SO_INTOLERANT_CODE;
+ IfFailGo(hr);
+ con->SetName(Name);
+
+ // Copy the signature
+ UINT32 i;
+ IfFalseGo(m_MethodInfo.m_bytes.grab(cSig, &i), E_OUTOFMEMORY);
+ memcpy(&m_MethodInfo.m_bytes[i], signature, cSig);
+ con->SetSignature(i);
+ con->SetSignatureSize(cSig);
+
+ // This const is in the current scope
+ con->SetParentScope(m_currentScope);
+ m_MethodInfo.m_scopes[m_currentScope].SetHasVars(TRUE);
+
+ErrExit:
+ return hr;
+}
+
+COM_METHOD SymWriter::DefineConstant2(
+ const WCHAR *name,
+ VARIANT value,
+ mdSignature sigToken)
+{
+ // This symbol writer doesn't support definiting signatures via tokens
+ _ASSERTE(FALSE);
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------
+// SymWriter Abort
+//-----------------------------------------------------------
+COM_METHOD SymWriter::Abort(void)
+{
+ m_closed = true;
+ return S_OK;
+}
+
+//-----------------------------------------------------------
+// SymWriter DefineField
+//-----------------------------------------------------------
+COM_METHOD SymWriter::DefineField(
+ mdTypeDef parent,
+ const WCHAR *name,
+ ULONG32 attributes,
+ ULONG32 csig,
+ BYTE signature[],
+ ULONG32 addrKind,
+ ULONG32 addr1, ULONG32 addr2, ULONG32 addr3)
+{
+ // This symbol store doesn't support extra random variable
+ // definitions.
+ return S_OK;
+}
+
+//-----------------------------------------------------------
+// SymWriter DefineGlobalVariable
+//-----------------------------------------------------------
+COM_METHOD SymWriter::DefineGlobalVariable(
+ const WCHAR *name,
+ ULONG32 attributes,
+ ULONG32 csig,
+ BYTE signature[],
+ ULONG32 addrKind,
+ ULONG32 addr1, ULONG32 addr2, ULONG32 addr3)
+{
+ // This symbol writer doesn't support global variables
+ _ASSERTE(FALSE);
+ return E_NOTIMPL;
+}
+
+COM_METHOD SymWriter::DefineGlobalVariable2(
+ const WCHAR *name,
+ ULONG32 attributes,
+ mdSignature sigToken,
+ ULONG32 addrKind,
+ ULONG32 addr1, ULONG32 addr2, ULONG32 addr3)
+{
+ // This symbol writer doesn't support global variables
+ _ASSERTE(FALSE);
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------
+// compareMethods
+// Used to sort method entries
+//-----------------------------------------------------------
+int __cdecl SymMethodInfo::compareMethods(const void *elem1, const void *elem2 )
+{
+ SymMethodInfo* p1 = (SymMethodInfo*)elem1;
+ SymMethodInfo* p2 = (SymMethodInfo*)elem2;
+ return p1->MethodToken() - p2->MethodToken();
+}
+
+//-----------------------------------------------------------
+// SymWriter Close
+//-----------------------------------------------------------
+COM_METHOD SymWriter::Close()
+{
+ HRESULT hr = Commit();
+ m_closed = true;
+ for (UINT32 docInfo = 0; docInfo < m_MethodInfo.m_documents.count(); docInfo++)
+ {
+ m_MethodInfo.m_documents[docInfo].SetDocumentWriter(NULL);
+ }
+ return hr;
+}
+
+//-----------------------------------------------------------
+// SymWriter Commit
+//-----------------------------------------------------------
+COM_METHOD SymWriter::Commit(void)
+{
+ // Sort the entries if need be
+ if (m_sortMethodEntries)
+ {
+ // First remap any tokens we need to
+ if (m_MethodMap.count())
+ {
+ unsigned i;
+ for (i = 0; i< m_MethodMap.count(); i++)
+ {
+ m_MethodInfo.m_methods[m_MethodMap[i].MethodEntry].SetMethodToken(m_MethodMap[i].m_MethodToken);
+ }
+ }
+
+ // Now sort the array
+ qsort(&m_MethodInfo.m_methods[0],
+ m_MethodInfo.m_methods.count(),
+ sizeof( SymMethodInfo ),
+ SymMethodInfo::compareMethods );
+ m_sortMethodEntries = false;
+ }
+ return WritePDB();
+}
+
+//-----------------------------------------------------------
+// SymWriter SetSymAttribute
+//-----------------------------------------------------------
+COM_METHOD SymWriter::SetSymAttribute(
+ mdToken parent,
+ const WCHAR *name,
+ ULONG32 cData,
+ BYTE data[])
+{
+ // Setting attributes on the symbol isn't supported
+
+ // ROTORTODO: #156785 in PS
+ return S_OK;
+}
+
+//-----------------------------------------------------------
+// SymWriter OpenNamespace
+//-----------------------------------------------------------
+COM_METHOD SymWriter::OpenNamespace(const WCHAR *name)
+{
+ // This symbol store doesn't support namespaces.
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------
+// SymWriter OpenNamespace
+//-----------------------------------------------------------
+COM_METHOD SymWriter::CloseNamespace()
+{
+ // This symbol store doesn't support namespaces.
+ return S_OK;
+}
+
+//-----------------------------------------------------------
+// SymWriter UsingNamespace
+// Add a Namespace to the list of namespace for this method
+//-----------------------------------------------------------
+COM_METHOD SymWriter::UsingNamespace(const WCHAR *fullName)
+{
+ HRESULT hr = S_OK;
+ ULONG Name;
+
+ // We must have a current scope.
+ if (m_currentScope == k_noScope)
+ return E_FAIL;
+
+ // We must have a name.
+ if (!fullName)
+ return E_INVALIDARG;
+
+
+ SymUsingNamespace *use;
+ IfNullGo( use = m_MethodInfo.m_usings.next());
+
+ // stack check needed to call back into utilcode
+ BEGIN_SO_INTOLERANT_CODE_NO_THROW_CHECK_THREAD_FORCE_SO();
+ // Copy the name.
+ hr = m_pStringPool->AddStringW(fullName, (UINT32 *)&Name);
+ END_SO_INTOLERANT_CODE;
+ IfFailGo(hr);
+ use->SetName(Name);
+
+ use->SetParentScope(m_currentScope);
+
+ErrExit:
+ return hr;
+}
+
+//-----------------------------------------------------------
+// SymWriter SetMethodSourceRange
+//-----------------------------------------------------------
+COM_METHOD SymWriter::SetMethodSourceRange(
+ ISymUnmanagedDocumentWriter *startDoc,
+ ULONG32 startLine,
+ ULONG32 startColumn,
+ ISymUnmanagedDocumentWriter *endDoc,
+ ULONG32 endLine,
+ ULONG32 endColumn)
+{
+ // This symbol store doesn't support source ranges.
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------
+// UnicodeToUTF8
+// Translate the Unicode string to a UTF8 string
+// Return the length in UTF8 of the Unicode string
+// Including NULL terminator
+//-----------------------------------------------------------
+inline int WINAPI UnicodeToUTF8(
+ LPCWSTR pUni, // Unicode string
+ __out_bcount_opt(cbUTF) PSTR pUTF8, // [optional, out] Buffer for UTF8 string
+ int cbUTF // length of UTF8 buffer
+)
+{
+ // Pass in the length including the NULL terminator
+ int cchSrc = (int)wcslen(pUni)+1;
+ return WideCharToMultiByte(CP_UTF8, 0, pUni, cchSrc, pUTF8, cbUTF, NULL, NULL);
+}
+
+//-----------------------------------------------------------
+// SymWriter GetDebugCVInfo
+// Get the size and potentially the debug info
+//-----------------------------------------------------------
+COM_METHOD SymWriter::GetDebugCVInfo(
+ DWORD cbBuf, // [optional] Size of buf
+ DWORD *pcbBuf, // [out] Size needed for the DebugInfo
+ BYTE buf[]) // [optional, out] Buffer for DebugInfo
+{
+
+ if ( m_szPath == NULL || *m_szPath == 0 )
+ return E_UNEXPECTED;
+
+ // We need to change the .ildb extension to .pdb to be
+ // compatible with VS7
+ wchar_t fullpath[_MAX_PATH];
+ wchar_t drive[_MAX_DRIVE];
+ wchar_t dir[_MAX_DIR];
+ wchar_t fname[_MAX_FNAME];
+ if (_wsplitpath_s( m_szPath, drive, COUNTOF(drive), dir, COUNTOF(dir), fname, COUNTOF(fname), NULL, 0 ))
+ return E_FAIL;
+ if (_wmakepath_s( fullpath, COUNTOF(fullpath), drive, dir, fname, W("pdb") ))
+ return E_FAIL;
+
+ // Get UTF-8 string size, including the Null Terminator
+ int Utf8Length = UnicodeToUTF8( fullpath, NULL, 0 );
+ if (Utf8Length < 0 )
+ return HRESULT_FROM_GetLastError();
+
+ DWORD dwSize = sizeof(RSDSI) + DWORD(Utf8Length);
+
+ // If the caller is just checking for the size
+ if ( cbBuf == 0 && pcbBuf != NULL )
+ {
+ *pcbBuf = dwSize;
+ return S_OK;
+ }
+
+ if (cbBuf < dwSize)
+ {
+ return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
+ }
+
+ if ( buf == NULL )
+ {
+ return E_INVALIDARG;
+ }
+
+ RSDSI* pRsdsi = (RSDSI*)buf;
+ pRsdsi->dwSig = VAL32(0x53445352); // "SDSR";
+ pRsdsi->guidSig = ILDB_VERSION_GUID;
+ SwapGuid(&(pRsdsi->guidSig));
+ // Age of 0 represent VC6.0 format so make sure it's 1
+ pRsdsi->age = VAL32(1);
+ UnicodeToUTF8( fullpath, pRsdsi->szPDB, Utf8Length );
+ if ( pcbBuf )
+ *pcbBuf = dwSize;
+ return S_OK;
+}
+
+//-----------------------------------------------------------
+// SymWriter GetDebugInfo
+// Get the size and potentially the debug info
+//-----------------------------------------------------------
+COM_METHOD SymWriter::GetDebugInfo(
+ IMAGE_DEBUG_DIRECTORY *pIDD, // [out] IDD to fill in
+ DWORD cData, // [optional] size of data
+ DWORD *pcData, // [optional, out] return needed size for DebugInfo
+ BYTE data[]) // [optional] Buffer to store into
+{
+ HRESULT hr = S_OK;
+ if ( cData == 0 && pcData != NULL )
+ {
+ // just checking for the size
+ return GetDebugCVInfo( 0, pcData, NULL );
+ }
+
+ if ( pIDD == NULL )
+ return E_INVALIDARG;
+
+ DWORD cTheData = 0;
+ IfFailGo( GetDebugCVInfo( cData, &cTheData, data ) );
+
+ memset( pIDD, 0, sizeof( *pIDD ) );
+ pIDD->Type = VAL32(IMAGE_DEBUG_TYPE_CODEVIEW);
+ pIDD->SizeOfData = VAL32(cTheData);
+
+ if ( pcData ) {
+ *pcData = cTheData;
+ }
+
+ErrExit:
+ return hr;
+}
+
+COM_METHOD SymWriter::RemapToken(mdToken oldToken, mdToken newToken)
+{
+ HRESULT hr = NOERROR;
+ if (oldToken != newToken)
+ {
+ // We only care about methods
+ if ((TypeFromToken(oldToken) == mdtMethodDef) ||
+ (TypeFromToken(newToken) == mdtMethodDef))
+ {
+ // Make sure they are both methods
+ _ASSERTE(TypeFromToken(newToken) == mdtMethodDef);
+ _ASSERTE(TypeFromToken(oldToken) == mdtMethodDef);
+
+ // Make sure we sort before saving
+ m_sortMethodEntries = true;
+
+ // Check to see if we're trying to map a token we know about
+ unsigned i;
+ for (i = 0; i < m_MethodInfo.m_methods.count(); i++)
+ {
+ if (m_MethodInfo.m_methods[i].MethodToken() == oldToken)
+ {
+ // Remember the map, we need to actually do the actual
+ // mapping later because we might already have a function
+ // with a token 'newToken'
+ SymMap *pMethodMap;
+ IfNullGo( pMethodMap = m_MethodMap.next() );
+ pMethodMap->m_MethodToken = newToken;
+ pMethodMap->MethodEntry = i;
+ break;
+ }
+ }
+ }
+ }
+ErrExit:
+ return hr;
+}
+
+//-----------------------------------------------------------
+// SymWriter Write
+// Write the information to a file or to a stream
+//-----------------------------------------------------------
+COM_METHOD SymWriter::Write(void *pData, DWORD SizeOfData)
+{
+ HRESULT hr = NOERROR;
+ DWORD NumberOfBytesWritten = 0;
+ if (m_pIStream)
+ {
+ IfFailGo(m_pIStream->Write(pData,
+ SizeOfData,
+ &NumberOfBytesWritten));
+ }
+ else
+ {
+ // Write out a signature to recognize that we're an ildb
+ if (!WriteFile(m_hFile, pData, SizeOfData, &NumberOfBytesWritten, NULL))
+ return HrFromWin32(GetLastError());
+ }
+ _ASSERTE(NumberOfBytesWritten == SizeOfData);
+ErrExit:
+ return hr;
+}
+
+//-----------------------------------------------------------
+// SymWriter WriteStringPool
+// Write the information to a file or to a stream
+//-----------------------------------------------------------
+COM_METHOD SymWriter::WriteStringPool()
+{
+ IStream *pIStream = NULL;
+ BYTE *pStreamMem = NULL;
+
+ HRESULT hr = NOERROR;
+ if (m_pIStream)
+ {
+ IfFailGo(m_pStringPool->PersistToStream(m_pIStream));
+ }
+ else
+ {
+ LARGE_INTEGER disp = { {0, 0} };
+ DWORD NumberOfBytes;
+ DWORD SizeOfData;
+ STATSTG statStg;
+
+ IfFailGo(CreateStreamOnHGlobal(NULL,
+ TRUE,
+ &pIStream));
+
+ IfFailGo(m_pStringPool->PersistToStream(pIStream));
+
+ IfFailGo(pIStream->Stat(&statStg, STATFLAG_NONAME));
+ SizeOfData = statStg.cbSize.u.LowPart;
+
+ IfFailGo(pIStream->Seek(disp, STREAM_SEEK_SET, NULL));
+
+ pStreamMem = NEW(BYTE[SizeOfData]);
+ IfFailGo(pIStream->Read(pStreamMem, SizeOfData, &NumberOfBytes));
+
+ if (!WriteFile(m_hFile, pStreamMem, SizeOfData, &NumberOfBytes, NULL))
+ return HrFromWin32(GetLastError());
+
+ _ASSERTE(NumberOfBytes == SizeOfData);
+
+ }
+ErrExit:
+ RELEASE(pIStream);
+ DELETEARRAY(pStreamMem);
+ return hr;
+}
+
+//-----------------------------------------------------------
+// SymWriter WritePDB
+// Write the PDB information to a file or to a stream
+//-----------------------------------------------------------
+COM_METHOD SymWriter::WritePDB()
+{
+
+ HRESULT hr = NOERROR;
+ GUID ildb_guid = ILDB_VERSION_GUID;
+
+ // Make sure the ModuleLevelInfo is set
+ ModuleLevelInfo.m_CountOfVars = VAL32(m_MethodInfo.m_vars.count());
+ ModuleLevelInfo.m_CountOfBytes = VAL32(m_MethodInfo.m_bytes.count());
+ ModuleLevelInfo.m_CountOfUsing = VAL32(m_MethodInfo.m_usings.count());
+ ModuleLevelInfo.m_CountOfScopes = VAL32(m_MethodInfo.m_scopes.count());
+ ModuleLevelInfo.m_CountOfMethods = VAL32(m_MethodInfo.m_methods.count());
+ if (m_pStringPool)
+ {
+ DWORD dwSaveSize;
+ IfFailGo(m_pStringPool->GetSaveSize((UINT32 *)&dwSaveSize));
+ ModuleLevelInfo.m_CountOfStringBytes = VAL32(dwSaveSize);
+ }
+ else
+ {
+ ModuleLevelInfo.m_CountOfStringBytes = 0;
+ }
+ ModuleLevelInfo.m_CountOfConstants = VAL32(m_MethodInfo.m_constants.count());
+ ModuleLevelInfo.m_CountOfDocuments = VAL32(m_MethodInfo.m_documents.count());
+ ModuleLevelInfo.m_CountOfSequencePoints = VAL32(m_MethodInfo.m_auxSequencePoints.count());
+
+ // Open the file
+ if (m_pIStream == NULL)
+ {
+ // We need to open the output file.
+ m_hFile = WszCreateFile(m_szPath,
+ GENERIC_WRITE,
+ 0,
+ NULL,
+ CREATE_ALWAYS,
+ FILE_ATTRIBUTE_NORMAL,
+ NULL);
+
+ if (m_hFile == INVALID_HANDLE_VALUE)
+ {
+ IfFailGo(HrFromWin32(GetLastError()));
+ }
+ }
+ else
+ {
+ // We're writing to a stream. Make sure we're at the beginning
+ // (eg. if this is being called more than once).
+ // Note that technically we should probably call SetSize to truncate the
+ // stream to ensure we don't leave reminants of the previous contents
+ // at the end of the new stream. But with our current CGrowableStream
+ // implementation, this would have a big performance impact (causing us to
+ // do linear growth and lots of reallocations at every write). We only
+ // ever add data to a symbol writer (don't remove anything), and so subsequent
+ // streams should always get larger. Regardless, ILDB supports trailing garbage
+ // without a problem (we used to always have the remainder of a page at the end
+ // of the stream), and so this is not an issue of correctness.
+ LARGE_INTEGER pos0;
+ pos0.QuadPart = 0;
+ IfFailGo(m_pIStream->Seek(pos0, STREAM_SEEK_SET, NULL));
+ }
+
+#if _DEBUG
+ // We need to make sure the Variant entry in the constants is 8 byte
+ // aligned so make sure everything up to the there is aligned correctly
+ if ((ILDB_SIGNATURE_SIZE % 8) ||
+ (sizeof(PDBInfo) % 8) ||
+ (sizeof(GUID) % 8))
+ {
+ _ASSERTE(!"We need to safe the data in an aligned format");
+ }
+#endif
+
+ // Write out a signature to recognize that we're an ildb
+ IfFailGo(Write((void *)ILDB_SIGNATURE, ILDB_SIGNATURE_SIZE));
+ // Write out a guid representing the version
+ SwapGuid(&ildb_guid);
+ IfFailGo(Write((void *)&ildb_guid, sizeof(GUID)));
+
+ // Now we need to write the Project level
+ IfFailGo(Write(&ModuleLevelInfo, sizeof(PDBInfo)));
+
+ // Now we have to write out each array as appropriate
+ IfFailGo(Write(m_MethodInfo.m_constants.m_array, sizeof(SymConstant) * m_MethodInfo.m_constants.count()));
+
+ // These members are all 4 byte aligned
+ IfFailGo(Write(m_MethodInfo.m_methods.m_array, sizeof(SymMethodInfo) * m_MethodInfo.m_methods.count()));
+ IfFailGo(Write(m_MethodInfo.m_scopes.m_array, sizeof(SymLexicalScope) * m_MethodInfo.m_scopes.count()));
+ IfFailGo(Write(m_MethodInfo.m_vars.m_array, sizeof(SymVariable) * m_MethodInfo.m_vars.count()));
+ IfFailGo(Write(m_MethodInfo.m_usings.m_array, sizeof(SymUsingNamespace) * m_MethodInfo.m_usings.count()));
+ IfFailGo(Write(m_MethodInfo.m_auxSequencePoints.m_array, sizeof(SequencePoint) * m_MethodInfo.m_auxSequencePoints.count()));
+ IfFailGo(Write(m_MethodInfo.m_documents.m_array, sizeof(DocumentInfo) * m_MethodInfo.m_documents.count()));
+ IfFailGo(Write(m_MethodInfo.m_bytes.m_array, sizeof(BYTE) * m_MethodInfo.m_bytes.count()));
+ IfFailGo(WriteStringPool());
+
+ErrExit:
+ if (m_hFile)
+ CloseHandle(m_hFile);
+ return hr;
+}
+
+/* ------------------------------------------------------------------------- *
+ * SymDocumentWriter class
+ * ------------------------------------------------------------------------- */
+SymDocumentWriter::SymDocumentWriter(
+ UINT32 DocumentEntry,
+ SymWriter *pEmitter
+) :
+ m_refCount ( 0 ),
+ m_DocumentEntry ( DocumentEntry ),
+ m_pEmitter( pEmitter )
+{
+ _ASSERTE(pEmitter);
+ m_pEmitter->AddRef();
+}
+
+SymDocumentWriter::~SymDocumentWriter()
+{
+ // Note that this must be thread-safe - it may be invoked on the finalizer thread
+ RELEASE(m_pEmitter);
+}
+
+COM_METHOD SymDocumentWriter::QueryInterface(REFIID riid, void **ppInterface)
+{
+ if (ppInterface == NULL)
+ return E_INVALIDARG;
+
+ if (riid == IID_ISymUnmanagedDocumentWriter)
+ *ppInterface = (ISymUnmanagedDocumentWriter*)this;
+ else if (riid == IID_IUnknown)
+ *ppInterface = (IUnknown*)(ISymUnmanagedDocumentWriter*)this;
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+//-----------------------------------------------------------
+// SymDocumentWriter SetSource
+//-----------------------------------------------------------
+COM_METHOD SymDocumentWriter::SetSource(ULONG32 sourceSize,
+ BYTE source[])
+{
+ return m_pEmitter->SetDocumentSrc(m_DocumentEntry, sourceSize, source);
+}
+
+//-----------------------------------------------------------
+// SymDocumentWriter SetCheckSum
+//-----------------------------------------------------------
+COM_METHOD SymDocumentWriter::SetCheckSum(GUID algorithmId,
+ ULONG32 checkSumSize,
+ BYTE checkSum[])
+{
+ return m_pEmitter->SetDocumentCheckSum(m_DocumentEntry, algorithmId, checkSumSize, checkSum);
+}
+
+
+//-----------------------------------------------------------
+// DocumentInfo SetDocumentWriter
+//-----------------------------------------------------------
+// Set the pointer to the SymDocumentWriter instance corresponding to this instance of DocumentInfo
+// An argument of NULL will call Release
+// Arguments
+// input: pDoc - pointer to the associated SymDocumentWriter or NULL
+
+void DocumentInfo::SetDocumentWriter(SymDocumentWriter * pDoc)
+{
+ if (m_pDocumentWriter != NULL)
+ {
+ m_pDocumentWriter->Release();
+ }
+ m_pDocumentWriter = pDoc;
+ if (m_pDocumentWriter != NULL)
+ {
+ pDoc->AddRef();
+ }
+}
diff --git a/src/debug/ildbsymlib/symwrite.h b/src/debug/ildbsymlib/symwrite.h
new file mode 100644
index 0000000000..055b8ec21f
--- /dev/null
+++ b/src/debug/ildbsymlib/symwrite.h
@@ -0,0 +1,1226 @@
+// 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.
+// ===========================================================================
+// File: SymWrite.h
+//
+
+// ===========================================================================
+
+#ifndef SYMWRITE_H_
+#define SYMWRITE_H_
+#ifdef _MSC_VER
+#pragma warning(disable:4786)
+#endif
+
+#include <windows.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "cor.h"
+#include "umisc.h"
+#include "stgpool.h"
+#include "safemath.h"
+
+#include <corsym.h>
+#include "pdbdata.h"
+
+class SymDocumentWriter;
+
+#if BIGENDIAN
+/***
+*PUBLIC void VariantSwap
+*Purpose:
+* Swap the Variant members
+*
+*Entry:
+* SrcInBigEndian = whether pvarg is in BIGENDIAN or not
+* pvargDest = Destination variant
+* pvarg = pointer to a VARIANT to swap
+*
+*Exit:
+* Filled in pvarDest
+*
+***********************************************************************/
+inline HRESULT VariantSwap(bool SrcInBigEndian, VARIANT FAR *pvargDest, VARIANT FAR* pvarg)
+{
+ if (pvargDest == NULL || pvarg == NULL)
+ return E_INVALIDARG;
+ VARTYPE vt = VT_EMPTY;
+
+ if (SrcInBigEndian)
+ {
+ vt = V_VT(pvarg);
+ }
+ *(UINT32*)pvargDest = VAL32(*(UINT32*)pvarg);
+ if (!SrcInBigEndian)
+ {
+ vt = V_VT(pvargDest);
+ }
+
+ switch (vt)
+ {
+ case VT_EMPTY:
+ case VT_NULL:
+ // No Value to swap
+ break;
+
+ // 1 byte
+ case VT_I1:
+ case VT_UI1:
+ V_I1(pvargDest) = V_I1(pvarg);
+ break;
+
+ // 2 bytes
+ case VT_I2:
+ case VT_UI2:
+ case VT_INT:
+ case VT_UINT:
+ case VT_BOOL:
+ V_I2(pvargDest) = VAL16(V_I2(pvarg));
+ break;
+
+ // 4 bytes
+ case VT_I4:
+ case VT_UI4:
+ case VT_R4:
+ V_I4(pvargDest) = VAL32(V_I4(pvarg));
+ break;
+
+ // 8 bytes
+ case VT_I8:
+ case VT_UI8:
+ case VT_R8:
+ case VT_DATE:
+ V_I8(pvargDest) = VAL64(V_I8(pvarg));
+ break;
+
+ case VT_DECIMAL:
+ DECIMAL_HI32(V_DECIMAL(pvargDest)) = VAL32(DECIMAL_HI32(V_DECIMAL(pvarg)));
+ DECIMAL_LO32(V_DECIMAL(pvargDest)) = VAL32(DECIMAL_LO32(V_DECIMAL(pvarg)));
+ DECIMAL_MID32(V_DECIMAL(pvargDest)) = VAL32(DECIMAL_MID32(V_DECIMAL(pvarg)));
+ break;
+
+ // These aren't currently supported
+ case VT_CY: //6
+ case VT_BSTR: //8
+ case VT_DISPATCH: //9
+ case VT_ERROR: //10
+ case VT_VARIANT: //12
+ case VT_UNKNOWN: //13
+ case VT_VOID: //24
+ case VT_HRESULT: //25
+ case VT_PTR: //26
+ case VT_SAFEARRAY: //27
+ case VT_CARRAY: //28
+ case VT_USERDEFINED://29
+ case VT_LPSTR: //30
+ case VT_LPWSTR: //31
+ case VT_FILETIME: //64
+ case VT_BLOB: //65
+ case VT_STREAM: //66
+ case VT_STORAGE: //67
+ case VT_STREAMED_OBJECT: //68
+ case VT_STORED_OBJECT: //69
+ case VT_BLOB_OBJECT: //70
+ case VT_CF: //71
+ case VT_CLSID: //72
+ default:
+ _ASSERTE(!"NYI");
+ break;
+ }
+ return NOERROR;
+}
+#endif // BIGENDIAN
+
+// Default space sizes for the various arrays. Make it too small in a
+// checked build so we exercise the growing code.
+#ifdef _DEBUG
+#define DEF_LOCAL_SPACE 2
+#define DEF_MISC_SPACE 64
+#else
+#define DEF_LOCAL_SPACE 64
+#define DEF_MISC_SPACE 1024
+#endif
+
+/* ------------------------------------------------------------------------- *
+ * SymVariable struct
+ * ------------------------------------------------------------------------- */
+struct SymVariable
+{
+private:
+ UINT32 m_Scope; // index of parent scope
+ UINT32 m_Name; // index into misc byte array
+ ULONG32 m_Attributes; // Attributes
+ UINT32 m_Signature; // index into misc byte array
+ ULONG32 m_SignatureSize; // Signature size
+ ULONG32 m_AddrKind; // Address Kind
+ ULONG32 m_Addr1; // Additional info
+ ULONG32 m_Addr2;
+ ULONG32 m_Addr3;
+ ULONG32 m_StartOffset; // StartOffset
+ ULONG32 m_EndOffset; // EndOffset
+ ULONG32 m_Sequence;
+ BOOL m_IsParam; // parameter?
+ BOOL m_IsHidden; // Is not visible to the user
+
+public:
+ UINT32 Scope()
+ {
+ return VAL32(m_Scope);
+ }
+ void SetScope(UINT32 Scope)
+ {
+ m_Scope = VAL32(Scope);
+ }
+
+ UINT32 Name()
+ {
+ return VAL32(m_Name);
+ }
+ void SetName(UINT32 Name)
+ {
+ m_Name = VAL32(Name);
+ }
+
+ ULONG32 Attributes()
+ {
+ return VAL32(m_Attributes);
+ }
+ void SetAttributes(ULONG32 Attributes)
+ {
+ m_Attributes = VAL32(Attributes);
+ }
+
+ UINT32 Signature()
+ {
+ return VAL32(m_Signature);
+ }
+ void SetSignature(UINT32 Signature)
+ {
+ m_Signature = VAL32(Signature);
+ }
+ ULONG32 SignatureSize()
+ {
+ return VAL32(m_SignatureSize);
+ }
+ void SetSignatureSize(ULONG32 SignatureSize)
+ {
+ m_SignatureSize = VAL32(SignatureSize);
+ }
+
+ ULONG32 AddrKind()
+ {
+ return VAL32(m_AddrKind);
+ }
+ void SetAddrKind(ULONG32 AddrKind)
+ {
+ m_AddrKind = VAL32(AddrKind);
+ }
+ ULONG32 Addr1()
+ {
+ return VAL32(m_Addr1);
+ }
+ void SetAddr1(ULONG32 Addr1)
+ {
+ m_Addr1 = VAL32(Addr1);
+ }
+
+ ULONG32 Addr2()
+ {
+ return VAL32(m_Addr2);
+ }
+ void SetAddr2(ULONG32 Addr2)
+ {
+ m_Addr2 = VAL32(Addr2);
+ }
+
+ ULONG32 Addr3()
+ {
+ return VAL32(m_Addr3);
+ }
+ void SetAddr3(ULONG32 Addr3)
+ {
+ m_Addr3 = VAL32(Addr3);
+ }
+
+ ULONG32 StartOffset()
+ {
+ return VAL32(m_StartOffset);
+ }
+ void SetStartOffset(ULONG32 StartOffset)
+ {
+ m_StartOffset = VAL32(StartOffset);
+ }
+ ULONG32 EndOffset()
+ {
+ return VAL32(m_EndOffset);
+ }
+ void SetEndOffset(ULONG EndOffset)
+ {
+ m_EndOffset = VAL32(EndOffset);
+ }
+ ULONG32 Sequence()
+ {
+ return VAL32(m_Sequence);
+ }
+ void SetSequence(ULONG32 Sequence)
+ {
+ m_Sequence = VAL32(Sequence);
+ }
+
+ BOOL IsParam()
+ {
+ return VAL32(m_IsParam);
+ }
+ void SetIsParam(BOOL IsParam)
+ {
+ m_IsParam = IsParam;
+ }
+ BOOL IsHidden()
+ {
+ return VAL32(m_IsHidden);
+ }
+ void SetIsHidden(BOOL IsHidden)
+ {
+ m_IsHidden = IsHidden;
+ }
+};
+
+/* ------------------------------------------------------------------------- *
+ * SymLexicalScope struct
+ * ------------------------------------------------------------------------- */
+struct SymLexicalScope
+{
+private:
+
+ UINT32 m_ParentScope; // parent index (-1 for no parent)
+ ULONG32 m_StartOffset; // start offset
+ ULONG32 m_EndOffset; // end offset
+ BOOL m_HasChildren; // scope has children
+ BOOL m_HasVars; // scope has vars?
+public:
+ UINT32 ParentScope()
+ {
+ return VAL32(m_ParentScope);
+ }
+ void SetParentScope(UINT32 ParentScope)
+ {
+ m_ParentScope = VAL32(ParentScope);
+ }
+
+ ULONG32 StartOffset()
+ {
+ return VAL32(m_StartOffset);
+ }
+ void SetStartOffset(ULONG32 StartOffset)
+ {
+ m_StartOffset = VAL32(StartOffset);
+ }
+ ULONG32 EndOffset()
+ {
+ return VAL32(m_EndOffset);
+ }
+ void SetEndOffset(ULONG32 EndOffset)
+ {
+ m_EndOffset = VAL32(EndOffset);
+ }
+ BOOL HasChildren()
+ {
+ return m_HasChildren;
+ }
+ void SetHasChildren(BOOL HasChildren)
+ {
+ m_HasChildren = HasChildren;
+ }
+ BOOL HasVars()
+ {
+ return m_HasVars;
+ }
+ void SetHasVars(BOOL HasVars)
+ {
+ m_HasVars = HasVars;
+ }
+
+};
+
+/* ------------------------------------------------------------------------- *
+ * SymUsingNamespace struct
+ * ------------------------------------------------------------------------- */
+struct SymUsingNamespace
+{
+private:
+
+ UINT32 m_ParentScope; // index of parent scope
+ UINT32 m_Name; // Index of name
+public:
+ UINT32 ParentScope()
+ {
+ return VAL32(m_ParentScope);
+ }
+ void SetParentScope(UINT32 ParentScope)
+ {
+ m_ParentScope = VAL32(ParentScope);
+ }
+ UINT32 Name()
+ {
+ return VAL32(m_Name);
+ }
+ void SetName(UINT32 Name)
+ {
+ m_Name = VAL32(Name);
+ }
+};
+
+/* ------------------------------------------------------------------------- *
+ * SymConstant struct
+ * ------------------------------------------------------------------------- */
+struct SymConstant
+{
+private:
+
+ VARIANT m_Value; // Constant Value
+ UINT32 m_ParentScope; // Parent scope
+ UINT32 m_Name; // Name index
+ UINT32 m_Signature; // Signature index
+ ULONG32 m_SignatureSize;// Signature size
+ UINT32 m_ValueBstr; // If the variant is a bstr, store the string
+
+public:
+ UINT32 ParentScope()
+ {
+ return VAL32(m_ParentScope);
+ }
+ void SetParentScope(UINT32 ParentScope)
+ {
+ m_ParentScope = VAL32(ParentScope);
+ }
+ UINT32 Name()
+ {
+ return VAL32(m_Name);
+ }
+ void SetName(UINT32 Name)
+ {
+ m_Name = VAL32(Name);
+ }
+ UINT32 Signature()
+ {
+ return VAL32(m_Signature);
+ }
+ void SetSignature(UINT32 Signature)
+ {
+ m_Signature = VAL32(Signature);
+ }
+ ULONG32 SignatureSize()
+ {
+ return VAL32(m_SignatureSize);
+ }
+ void SetSignatureSize(ULONG32 SignatureSize)
+ {
+ m_SignatureSize = VAL32(SignatureSize);
+ }
+ VARIANT Value(UINT32 *pValueBstr)
+ {
+ *pValueBstr = VAL32(m_ValueBstr);
+#if BIGENDIAN
+ VARIANT VariantValue;
+ VariantInit(&VariantValue);
+ // VT_BSTR's are dealt with ValueBStr
+ if (m_ValueBstr)
+ {
+ V_VT(&VariantValue) = VT_BSTR;
+ }
+ else
+ {
+ VariantSwap(false, &VariantValue, &m_Value);
+ }
+ return VariantValue;
+#else
+ return m_Value;
+#endif
+ }
+ void SetValue(VARIANT VariantValue, UINT32 ValueBstr)
+ {
+ m_Value = VariantValue;
+ m_ValueBstr = VAL32(ValueBstr);
+#if BIGENDIAN
+ // VT_BSTR's are dealt with ValueBStr
+ if (m_ValueBstr)
+ {
+ V_VT(&m_Value) = VAL16(VT_BSTR);
+ }
+ else
+ {
+ VariantSwap(true, &m_Value, &VariantValue);
+ }
+#endif
+ }
+};
+
+/* ------------------------------------------------------------------------- *
+ * SymMethodInfo struct
+ * ------------------------------------------------------------------------- */
+struct SymMethodInfo
+{
+private:
+
+ mdMethodDef m_MethodToken; // Method token
+
+ // Start/End Entries into the respective tables
+ // End values are extents - one past the last index (and so may actually be an index off
+ // the end of the array). Start may equal end if the method has none of the item.
+ UINT32 m_StartScopes;
+ UINT32 m_EndScopes;
+ UINT32 m_StartVars;
+ UINT32 m_EndVars;
+ UINT32 m_StartUsing;
+ UINT32 m_EndUsing;
+ UINT32 m_StartConstant;
+ UINT32 m_EndConstant;
+ UINT32 m_StartDocuments;
+ UINT32 m_EndDocuments;
+ UINT32 m_StartSequencePoints;
+ UINT32 m_EndSequencePoints;
+
+public:
+ static int __cdecl compareMethods(const void *elem1, const void *elem2 );
+
+ mdMethodDef MethodToken()
+ {
+ return VAL32(m_MethodToken);
+ }
+ void SetMethodToken(mdMethodDef MethodToken)
+ {
+ m_MethodToken = VAL32(MethodToken);
+ }
+ UINT32 StartScopes()
+ {
+ return VAL32(m_StartScopes);
+ }
+ void SetStartScopes(UINT32 StartScopes)
+ {
+ m_StartScopes = VAL32(StartScopes);
+ }
+ UINT32 EndScopes()
+ {
+ return VAL32(m_EndScopes);
+ }
+ void SetEndScopes(UINT32 EndScopes)
+ {
+ m_EndScopes = VAL32(EndScopes);
+ }
+ UINT32 StartVars()
+ {
+ return VAL32(m_StartVars);
+ }
+ void SetStartVars(UINT32 StartVars)
+ {
+ m_StartVars = VAL32(StartVars);
+ }
+ UINT32 EndVars()
+ {
+ return VAL32(m_EndVars);
+ }
+ void SetEndVars(UINT32 EndVars)
+ {
+ m_EndVars = VAL32(EndVars);
+ }
+ UINT32 StartUsing()
+ {
+ return VAL32(m_StartUsing);
+ }
+ void SetStartUsing(UINT32 StartUsing)
+ {
+ m_StartUsing = VAL32(StartUsing);
+ }
+ UINT32 EndUsing()
+ {
+ return VAL32(m_EndUsing);
+ }
+ void SetEndUsing(UINT32 EndUsing)
+ {
+ m_EndUsing = VAL32(EndUsing);
+ }
+ UINT32 StartConstant()
+ {
+ return VAL32(m_StartConstant);
+ }
+ void SetStartConstant(UINT32 StartConstant)
+ {
+ m_StartConstant = VAL32(StartConstant);
+ }
+ UINT32 EndConstant()
+ {
+ return VAL32(m_EndConstant);
+ }
+ void SetEndConstant(UINT32 EndConstant)
+ {
+ m_EndConstant = VAL32(EndConstant);
+ }
+ UINT32 StartDocuments()
+ {
+ return VAL32(m_StartDocuments);
+ }
+ void SetStartDocuments(UINT32 StartDocuments)
+ {
+ m_StartDocuments = VAL32(StartDocuments);
+ }
+ UINT32 EndDocuments()
+ {
+ return VAL32(m_EndDocuments);
+ }
+ void SetEndDocuments(UINT32 EndDocuments)
+ {
+ m_EndDocuments = VAL32(EndDocuments);
+ }
+ UINT32 StartSequencePoints()
+ {
+ return VAL32(m_StartSequencePoints);
+ }
+ void SetStartSequencePoints(UINT32 StartSequencePoints)
+ {
+ m_StartSequencePoints = VAL32(StartSequencePoints);
+ }
+ UINT32 EndSequencePoints()
+ {
+ return VAL32(m_EndSequencePoints);
+ }
+ void SetEndSequencePoints(UINT32 EndSequencePoints)
+ {
+ m_EndSequencePoints = VAL32(EndSequencePoints);
+ }
+};
+
+/* ------------------------------------------------------------------------- *
+ * SymMap struct
+ * ------------------------------------------------------------------------- */
+struct SymMap
+{
+ mdMethodDef m_MethodToken; // New Method token
+ UINT32 MethodEntry; // Method Entry
+};
+
+/* ------------------------------------------------------------------------- *
+ * SequencePoint struct
+ * ------------------------------------------------------------------------- */
+struct SequencePoint {
+
+private:
+
+ DWORD m_Offset;
+ DWORD m_StartLine;
+ DWORD m_StartColumn;
+ DWORD m_EndLine;
+ DWORD m_EndColumn;
+ DWORD m_Document;
+
+public:
+ bool IsWithin(ULONG32 line, ULONG32 column);
+ bool IsWithinLineOnly(ULONG32 line);
+ bool IsGreaterThan(ULONG32 line, ULONG32 column);
+ bool IsLessThan(ULONG32 line, ULONG32 column);
+ bool IsUserLine();
+ static int __cdecl compareAuxLines(const void *elem1, const void *elem2 );
+
+ DWORD Offset()
+ {
+ return VAL32(m_Offset);
+ }
+ void SetOffset(DWORD Offset)
+ {
+ m_Offset = VAL32(Offset);
+ }
+ DWORD StartLine()
+ {
+ return VAL32(m_StartLine);
+ }
+ void SetStartLine(DWORD StartLine)
+ {
+ m_StartLine = VAL32(StartLine);
+ }
+
+ DWORD StartColumn()
+ {
+ return VAL32(m_StartColumn);
+ }
+ void SetStartColumn(DWORD StartColumn)
+ {
+ m_StartColumn = VAL32(StartColumn);
+ }
+
+ DWORD EndLine()
+ {
+ return VAL32(m_EndLine);
+ }
+ void SetEndLine(DWORD EndLine)
+ {
+ m_EndLine = VAL32(EndLine);
+ }
+ DWORD EndColumn()
+ {
+ return VAL32(m_EndColumn);
+ }
+ void SetEndColumn(DWORD EndColumn)
+ {
+ m_EndColumn = VAL32(EndColumn);
+ }
+ DWORD Document()
+ {
+ return VAL32(m_Document);
+ }
+ void SetDocument(DWORD Document)
+ {
+ m_Document = VAL32(Document);
+ }
+};
+
+
+/* ------------------------------------------------------------------------- *
+ * DocumentInfo struct
+ * ------------------------------------------------------------------------- */
+typedef struct DocumentInfo {
+
+private:
+
+ GUID m_Language;
+ GUID m_LanguageVendor;
+ GUID m_DocumentType;
+ GUID m_AlgorithmId;
+ DWORD m_CheckSumSize;
+ UINT32 m_CheckSumEntry;
+ UINT32 m_SourceSize;
+ UINT32 m_SourceEntry;
+ UINT32 m_UrlEntry;
+ SymDocumentWriter * m_pDocumentWriter;
+
+public:
+
+ GUID Language()
+ {
+ GUID TmpGuid = m_Language;
+ SwapGuid(&TmpGuid);
+ return TmpGuid;
+ }
+ void SetLanguage(GUID Language)
+ {
+ SwapGuid(&Language);
+ m_Language = Language;
+ }
+ GUID LanguageVendor()
+ {
+ GUID TmpGuid = m_LanguageVendor;
+ SwapGuid(&TmpGuid);
+ return TmpGuid;
+ }
+ void SetLanguageVendor(GUID LanguageVendor)
+ {
+ SwapGuid(&LanguageVendor);
+ m_LanguageVendor = LanguageVendor;
+ }
+ GUID DocumentType()
+ {
+ GUID TmpGuid = m_DocumentType;
+ SwapGuid(&TmpGuid);
+ return TmpGuid;
+ }
+ void SetDocumentType(GUID DocumentType)
+ {
+ SwapGuid(&DocumentType);
+ m_DocumentType = DocumentType;
+ }
+
+ // Set the pointer to the SymDocumentWriter instance corresponding to this instance of DocumentInfo
+ // An argument of NULL will call Release
+ void SetDocumentWriter(SymDocumentWriter * pDoc);
+
+ // get the associated SymDocumentWriter
+ SymDocumentWriter * DocumentWriter()
+ {
+ return m_pDocumentWriter;
+ }
+
+ GUID AlgorithmId()
+ {
+ GUID TmpGuid = m_AlgorithmId;
+ SwapGuid(&TmpGuid);
+ return TmpGuid;
+ }
+ void SetAlgorithmId(GUID AlgorithmId)
+ {
+ SwapGuid(&AlgorithmId);
+ m_AlgorithmId = AlgorithmId;
+ }
+
+ DWORD CheckSumSize()
+ {
+ return VAL32(m_CheckSumSize);
+ }
+ void SetCheckSymSize(DWORD CheckSumSize)
+ {
+ m_CheckSumSize = VAL32(CheckSumSize);
+ }
+ UINT32 CheckSumEntry()
+ {
+ return VAL32(m_CheckSumEntry);
+ }
+ void SetCheckSumEntry(UINT32 CheckSumEntry)
+ {
+ m_CheckSumEntry = VAL32(CheckSumEntry);
+ }
+ UINT32 SourceSize()
+ {
+ return VAL32(m_SourceSize);
+ }
+ void SetSourceSize(UINT32 SourceSize)
+ {
+ m_SourceSize = VAL32(SourceSize);
+ }
+ UINT32 SourceEntry()
+ {
+ return VAL32(m_SourceEntry);
+ }
+ void SetSourceEntry(UINT32 SourceEntry)
+ {
+ m_SourceEntry = VAL32(SourceEntry);
+ }
+ UINT32 UrlEntry()
+ {
+ return VAL32(m_UrlEntry);
+ }
+ void SetUrlEntry(UINT32 UrlEntry)
+ {
+ m_UrlEntry = VAL32(UrlEntry);
+ }
+
+} DocumentInfo;
+
+template <class T>
+class ArrayStorage
+{
+public:
+
+ ArrayStorage( int initialSize = 0 )
+ : m_spaceSize(0), m_instanceCount( 0 ), m_array( NULL )
+ {
+ grow( initialSize );
+ }
+ ~ArrayStorage()
+ {
+
+ if ( m_array )
+ DELETEARRAY(m_array);
+ m_array = NULL;
+ m_spaceSize = 0;
+ m_instanceCount = 0;
+ }
+ T* next()
+ {
+ if( !grow ( m_instanceCount ) )
+ return NULL;
+ _ASSERTE( m_instanceCount < m_spaceSize );
+ return &m_array[ m_instanceCount++ ];
+ }
+ bool grab(UINT32 n, UINT32 * pIndex)
+ {
+ S_UINT32 newSize = S_UINT32(m_instanceCount) + S_UINT32(n);
+ if (newSize.IsOverflow())
+ return false;
+ if (!grow(newSize.Value()))
+ return false;
+ _ASSERTE( m_instanceCount < m_spaceSize );
+ *pIndex = m_instanceCount;
+ m_instanceCount += n;
+ return true;
+ }
+
+ T& operator[]( UINT32 i ) {
+ _ASSERTE( i < m_instanceCount );
+ if (i >= m_instanceCount)
+ {
+ // Help mitigate the impact of buffer overflow
+ // Fail fast with a null-reference AV
+ return *(static_cast<T*>(0)) ;
+ }
+ return m_array[ i ];
+ }
+ void reset() {
+ m_instanceCount = 0;
+ }
+ UINT32 size() {
+ return m_spaceSize;
+ }
+ UINT32 count() {
+ return m_instanceCount;
+ }
+
+ UINT32 m_spaceSize; // Total size of array in elements
+ UINT32 m_instanceCount; // total T's in the file
+ T *m_array; // array of T's
+private:
+ bool grow( UINT32 n )
+ {
+ if (n >= m_spaceSize)
+ {
+ // Make a new, bigger array.
+ UINT32 newSpaceSize;
+
+ if (n == 0)
+ newSpaceSize = DEF_LOCAL_SPACE;
+ else
+ newSpaceSize = max( m_spaceSize * 2, n);
+
+ // Make sure we're not asking for more than 4GB of bytes to ensure no integer-overflow attacks are possible
+ S_UINT32 newBytes = S_UINT32(newSpaceSize) * S_UINT32(sizeof(T));
+ if (newBytes.IsOverflow())
+ return false;
+
+ T *newTs;
+ newTs = NEW(T[newSpaceSize]);
+ if ( newTs == NULL )
+ return false;
+
+ // Copy over the old Ts.
+ memcpy(newTs, m_array,
+ sizeof(T) * m_spaceSize);
+
+ // Delete the old Ts.
+ DELETEARRAY(m_array);
+
+ // Hang onto the new array.
+ m_array = newTs;
+ m_spaceSize = newSpaceSize;
+ }
+ return true;
+ }
+
+};
+
+typedef struct MethodInfo {
+
+ ArrayStorage<SymMethodInfo> m_methods; // Methods information
+ ArrayStorage<SymLexicalScope> m_scopes; // Scope information for the method
+ ArrayStorage<SymVariable> m_vars; // Variables
+ ArrayStorage<SymUsingNamespace> m_usings; // using/imports
+ ArrayStorage<SymConstant> m_constants; // Constants
+ ArrayStorage<DocumentInfo> m_documents; // Document Source Format
+ ArrayStorage<SequencePoint> m_auxSequencePoints; // Sequence Points
+ // Array of various bytes (variable signature, etc)
+ ArrayStorage<BYTE> m_bytes;
+
+
+public:
+
+ MethodInfo() :
+ m_bytes( DEF_MISC_SPACE )
+ {
+ }
+} MethodInfo;
+
+/* ------------------------------------------------------------------------- *
+ * SymWriter class
+ * ------------------------------------------------------------------------- */
+
+class SymWriter : public ISymUnmanagedWriter3
+{
+public:
+ SymWriter();
+ virtual ~SymWriter();
+
+ //-----------------------------------------------------------
+ // IUnknown support
+ //-----------------------------------------------------------
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (InterlockedIncrement((LONG *) &m_refCount));
+ }
+
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ // Note that this must be thread-safe - it may be invoked on the finalizer thread
+ LONG refCount = InterlockedDecrement((LONG *) &m_refCount);
+ if (refCount == 0)
+ DELETE(this);
+
+ return (refCount);
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ISymUnmanagedWriter
+ //-----------------------------------------------------------
+ COM_METHOD DefineDocument(const WCHAR *url,
+ const GUID *language,
+ const GUID *languageVendor,
+ const GUID *documentType,
+ ISymUnmanagedDocumentWriter **pRetVal);
+ COM_METHOD SetUserEntryPoint(mdMethodDef entryMethod);
+ COM_METHOD OpenMethod(mdMethodDef method);
+ COM_METHOD CloseMethod();
+ COM_METHOD DefineSequencePoints(ISymUnmanagedDocumentWriter *document,
+ ULONG32 spCount,
+ ULONG32 offsets[],
+ ULONG32 lines[],
+ ULONG32 columns[],
+ ULONG32 endLines[],
+ ULONG32 encColumns[]);
+ COM_METHOD OpenScope(ULONG32 startOffset, ULONG32 *scopeID);
+ COM_METHOD CloseScope(ULONG32 endOffset);
+ COM_METHOD SetScopeRange(ULONG32 scopeID, ULONG32 startOffset, ULONG32 endOffset);
+ COM_METHOD DefineLocalVariable(const WCHAR *name,
+ ULONG32 attributes,
+ ULONG32 cSig,
+ BYTE signature[],
+ ULONG32 addrKind,
+ ULONG32 addr1, ULONG32 addr2, ULONG32 addr3,
+ ULONG32 startOffset, ULONG32 endOffset);
+ COM_METHOD DefineParameter(const WCHAR *name,
+ ULONG32 attributes,
+ ULONG32 sequence,
+ ULONG32 addrKind,
+ ULONG32 addr1, ULONG32 addr2, ULONG32 addr3);
+ COM_METHOD DefineField(mdTypeDef parent,
+ const WCHAR *name,
+ ULONG32 attributes,
+ ULONG32 cSig,
+ BYTE signature[],
+ ULONG32 addrKind,
+ ULONG32 addr1, ULONG32 addr2, ULONG32 addr3);
+ COM_METHOD DefineGlobalVariable(const WCHAR *name,
+ ULONG32 attributes,
+ ULONG32 cSig,
+ BYTE signature[],
+ ULONG32 addrKind,
+ ULONG32 addr1, ULONG32 addr2, ULONG32 addr3);
+ COM_METHOD Close();
+ COM_METHOD SetSymAttribute(mdToken parent,
+ const WCHAR *name,
+ ULONG32 cData,
+ BYTE data[]);
+ COM_METHOD OpenNamespace(const WCHAR *name);
+ COM_METHOD CloseNamespace();
+ COM_METHOD UsingNamespace(const WCHAR *fullName);
+ COM_METHOD SetMethodSourceRange(ISymUnmanagedDocumentWriter *startDoc,
+ ULONG32 startLine,
+ ULONG32 startColumn,
+ ISymUnmanagedDocumentWriter *endDoc,
+ ULONG32 endLine,
+ ULONG32 endColumn);
+ COM_METHOD GetDebugCVInfo(DWORD cData,
+ DWORD *pcData,
+ BYTE data[]);
+
+ COM_METHOD Initialize(IUnknown *emitter,
+ const WCHAR *filename,
+ IStream *pIStream,
+ BOOL fFullBuild);
+
+ COM_METHOD Initialize2(IUnknown *emitter,
+ const WCHAR *pdbTempPath, // location to write pdb file
+ IStream *pIStream,
+ BOOL fFullBuild,
+ const WCHAR *pdbFinalPath); // location exe should contain for pdb file
+
+ COM_METHOD GetDebugInfo(IMAGE_DEBUG_DIRECTORY *pIDD,
+ DWORD cData,
+ DWORD *pcData,
+ BYTE data[]);
+
+ COM_METHOD RemapToken(mdToken oldToken,
+ mdToken newToken);
+
+ COM_METHOD DefineConstant(const WCHAR __RPC_FAR *name,
+ VARIANT value,
+ ULONG32 cSig,
+ unsigned char __RPC_FAR signature[ ]);
+
+ COM_METHOD Abort(void);
+
+ //-----------------------------------------------------------
+ // ISymUnmanagedWriter2
+ //-----------------------------------------------------------
+ COM_METHOD DefineLocalVariable2(const WCHAR *name,
+ ULONG32 attributes,
+ mdSignature sigToken,
+ ULONG32 addrKind,
+ ULONG32 addr1,
+ ULONG32 addr2,
+ ULONG32 addr3,
+ ULONG32 startOffset,
+ ULONG32 endOffset);
+
+ COM_METHOD DefineGlobalVariable2(const WCHAR *name,
+ ULONG32 attributes,
+ mdSignature sigToken,
+ ULONG32 addrKind,
+ ULONG32 addr1,
+ ULONG32 addr2,
+ ULONG32 addr3);
+
+ COM_METHOD DefineConstant2(const WCHAR *name,
+ VARIANT value,
+ mdSignature sigToken);
+
+ //-----------------------------------------------------------
+ // ISymUnmanagedWriter3
+ //-----------------------------------------------------------
+
+ COM_METHOD OpenMethod2(mdMethodDef method,
+ ULONG32 isect,
+ ULONG32 offset);
+
+ COM_METHOD Commit();
+
+ //-----------------------------------------------------------
+ // Methods not exposed via a COM interface.
+ //-----------------------------------------------------------
+
+ static HRESULT NewSymWriter(REFIID clsid, void** ppObj);
+ HRESULT SetDocumentCheckSum(
+ UINT32 DocumentEntry,
+ GUID AlgorithmId,
+ DWORD CheckSumSize,
+ BYTE* pCheckSum);
+ HRESULT SetDocumentSrc(UINT32 DocumentEntry,
+ DWORD SourceSize,
+ BYTE* pSource);
+
+ COM_METHOD Write(void *pData, DWORD SizeOfData);
+ COM_METHOD WriteStringPool();
+ COM_METHOD WritePDB();
+
+ COM_METHOD Initialize(const WCHAR *szFilename, IStream *pIStream);
+
+ void SetFullPathName(const WCHAR *szFullPathName)
+ {
+
+ }
+
+private:
+ // Helper API for CloserScope
+ COM_METHOD CloseScopeInternal(ULONG32 endOffset);
+ HRESULT GetOrCreateDocument(
+ const WCHAR *wcsUrl, // Document name
+ const GUID *pLanguage, // What Language we're compiling
+ const GUID *pLanguageVendor, // What vendor
+ const GUID *pDocumentType, // Type
+ ISymUnmanagedDocumentWriter **ppRetVal // [out] Created DocumentWriter
+ );
+ HRESULT CreateDocument(
+ const WCHAR *wcsUrl, // Document name
+ const GUID *pLanguage, // What Language we're compiling
+ const GUID *pLanguageVendor, // What vendor
+ const GUID *pDocumentType, // Type
+ ISymUnmanagedDocumentWriter **ppRetVal // [out] Created DocumentWriter
+ );
+
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+private:
+ UINT32 m_refCount; // AddRef/Release
+
+ mdMethodDef m_openMethodToken;
+ mdMethodDef m_LargestMethodToken;
+ SymMethodInfo * m_pmethod;
+
+ // index of currently open scope
+ UINT32 m_currentScope;
+
+ // special scope "index" meaning there is no such scope
+ static const UINT32 k_noScope = (UINT32)-1;
+
+ // maximum scope end offset seen so far in this method
+ ULONG32 m_maxScopeEnd;
+
+ MethodInfo m_MethodInfo;
+ ArrayStorage<SymMap> m_MethodMap; // Methods information
+
+ // Symbol File Name
+ WCHAR m_szPath[ _MAX_PATH ];
+ // File Handle
+ HANDLE m_hFile;
+ // Stream we're storing into if asked to.
+ IStream* m_pIStream;
+
+ // StringPool we use to store the string into
+ StgStringPool *m_pStringPool;
+
+ // Project level symbol information
+ PDBInfo ModuleLevelInfo;
+
+ bool m_closed; // Have we closed the file yet?
+ bool m_sortLines; // sort the line for current method
+ bool m_sortMethodEntries; // Sort the method entries
+
+
+};
+
+/* ------------------------------------------------------------------------- *
+ * SymDocumentWriter class
+ * ------------------------------------------------------------------------- */
+
+class SymDocumentWriter : public ISymUnmanagedDocumentWriter
+{
+public:
+ SymDocumentWriter(UINT32 DocumentEntry,
+ SymWriter *pEmitter);
+
+ virtual ~SymDocumentWriter();
+
+ //-----------------------------------------------------------
+ // IUnknown support
+ //-----------------------------------------------------------
+ ULONG STDMETHODCALLTYPE AddRef()
+ {
+ return (InterlockedIncrement((LONG *) &m_refCount));
+ }
+
+ ULONG STDMETHODCALLTYPE Release()
+ {
+ // Note that this must be thread-safe - it may be invoked on the finalizer thread
+ LONG refCount = InterlockedDecrement((LONG *) &m_refCount);
+ if (refCount == 0)
+ DELETE(this);
+
+ return (refCount);
+ }
+ COM_METHOD QueryInterface(REFIID riid, void **ppInterface);
+
+ //-----------------------------------------------------------
+ // ISymUnmanagedDocumentWriter
+ //-----------------------------------------------------------
+ COM_METHOD SetSource(ULONG32 sourceSize, BYTE source[]);
+ COM_METHOD SetCheckSum(GUID algorithmId,
+ ULONG32 checkSumSize, BYTE checkSum[]);
+
+ //-----------------------------------------------------------
+ // Methods not exposed via a COM interface.
+ //-----------------------------------------------------------
+ //
+ // Commit the doc to the pdb
+ //
+ UINT32 GetDocumentEntry()
+ {
+ return m_DocumentEntry;
+ }
+
+ //-----------------------------------------------------------
+ // Data members
+ //-----------------------------------------------------------
+private:
+ UINT32 m_refCount; // AddRef/Release
+ UINT32 m_DocumentEntry; // Entry into the documents array
+ SymWriter *m_pEmitter; // Associated SymWriter
+};
+
+// Debug Info
+struct RSDSI // RSDS debug info
+{
+ DWORD dwSig; // RSDS
+ GUID guidSig;
+ DWORD age;
+ char szPDB[0]; // followed by a zero-terminated UTF8 file name
+};
+
+#endif /* SYMWRITE_H_ */
diff --git a/src/debug/ildbsymlib/umisc.h b/src/debug/ildbsymlib/umisc.h
new file mode 100644
index 0000000000..fda40474ff
--- /dev/null
+++ b/src/debug/ildbsymlib/umisc.h
@@ -0,0 +1,69 @@
+// 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.
+// ===========================================================================
+// File: umisc.h
+//
+
+// ===========================================================================
+
+
+// Abstract:
+//
+// A collection of utility macros.
+//
+
+#ifndef UMISC_H
+#define UMISC_H
+
+#define COM_METHOD HRESULT STDMETHODCALLTYPE
+
+inline HRESULT HrFromWin32(DWORD dwWin32Error)
+{
+ return HRESULT_FROM_WIN32(dwWin32Error);
+}
+
+// Some helper #def's to safely Release, close & delete Objects under
+// failure conditions
+
+#define RELEASE(x) \
+ do \
+ { \
+ if (x) \
+ { \
+ IUnknown *punk = x; \
+ x = NULL; \
+ punk->Release(); \
+ } \
+ } while (0)
+
+
+#include "debugmacros.h"
+//
+// Good for verifying params withing range.
+//
+#define IfFalseGo(expr, HR) IfFailGo((expr) ? S_OK : (HR))
+
+// ----------------------------------------------------------------------------
+// Validation macros
+// Note that the Win32 APIs like IsBadReadPtr are banned
+//
+#define IsValidReadPtr(ptr, type) ((ptr)!=NULL)
+
+#define IsValidWritePtr(ptr, type) ((ptr)!=NULL)
+
+#define IsValidReadBufferPtr(ptr, type, len) ((ptr)!=NULL)
+
+#define IsValidWriteBufferPtr(ptr, type, len) ((ptr)!=NULL)
+
+#define IsValidInterfacePtr(ptr, type) ((ptr)!=NULL)
+
+#define IsValidCodePtr(ptr) ((ptr)!=NULL)
+
+#define IsValidStringPtr(ptr) ((ptr)!=NULL)
+
+#define IsValidIID(iid) TRUE
+
+#define IsValidCLSID(clsid) TRUE
+
+#endif
diff --git a/src/debug/inc/.gitmirror b/src/debug/inc/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/debug/inc/.gitmirror
@@ -0,0 +1 @@
+Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror. \ No newline at end of file
diff --git a/src/debug/inc/amd64/.gitmirror b/src/debug/inc/amd64/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/debug/inc/amd64/.gitmirror
@@ -0,0 +1 @@
+Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror. \ No newline at end of file
diff --git a/src/debug/inc/amd64/primitives.h b/src/debug/inc/amd64/primitives.h
new file mode 100644
index 0000000000..0b13670c4a
--- /dev/null
+++ b/src/debug/inc/amd64/primitives.h
@@ -0,0 +1,257 @@
+// 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.
+//*****************************************************************************
+// File: primitives.h
+//
+
+//
+// Platform-specific debugger primitives
+//
+//*****************************************************************************
+
+#ifndef PRIMITIVES_H_
+#define PRIMITIVES_H_
+
+#ifndef CORDB_ADDRESS_TYPE
+typedef const BYTE CORDB_ADDRESS_TYPE;
+typedef DPTR(CORDB_ADDRESS_TYPE) PTR_CORDB_ADDRESS_TYPE;
+#endif
+//This is an abstraction to keep x86/ia64 patch data separate
+#ifndef PRD_TYPE
+#define PRD_TYPE DWORD_PTR
+#endif
+
+typedef M128A FPRegister64;
+
+// From section 1.1 of AMD64 Programmers Manual Vol 3.
+#define MAX_INSTRUCTION_LENGTH 15
+
+// Given a return address retrieved during stackwalk,
+// this is the offset by which it should be decremented to lend somewhere in a call instruction.
+#define STACKWALK_CONTROLPC_ADJUST_OFFSET 1
+
+#define CORDbg_BREAK_INSTRUCTION_SIZE 1
+#define CORDbg_BREAK_INSTRUCTION (BYTE)0xCC
+
+inline CORDB_ADDRESS GetPatchEndAddr(CORDB_ADDRESS patchAddr)
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return patchAddr + CORDbg_BREAK_INSTRUCTION_SIZE;
+}
+
+
+#define InitializePRDToBreakInst(_pPRD) *(_pPRD) = CORDbg_BREAK_INSTRUCTION
+#define PRDIsBreakInst(_pPRD) (*(_pPRD) == CORDbg_BREAK_INSTRUCTION)
+
+#define CORDbgGetInstructionEx(_buffer, _requestedAddr, _patchAddr, _dummy1, _dummy2) \
+ CORDbgGetInstruction((CORDB_ADDRESS_TYPE *)((_buffer) + ((_patchAddr) - (_requestedAddr))));
+
+#define CORDbgSetInstructionEx(_buffer, _requestedAddr, _patchAddr, _opcode, _dummy2) \
+ CORDbgSetInstruction((CORDB_ADDRESS_TYPE *)((_buffer) + ((_patchAddr) - (_requestedAddr))), (_opcode));
+
+#define CORDbgInsertBreakpointEx(_buffer, _requestedAddr, _patchAddr, _dummy1, _dummy2) \
+ CORDbgInsertBreakpoint((CORDB_ADDRESS_TYPE *)((_buffer) + ((_patchAddr) - (_requestedAddr))));
+
+
+SELECTANY const CorDebugRegister g_JITToCorDbgReg[] =
+{
+ REGISTER_AMD64_RAX,
+ REGISTER_AMD64_RCX,
+ REGISTER_AMD64_RDX,
+ REGISTER_AMD64_RBX,
+ REGISTER_AMD64_RSP,
+ REGISTER_AMD64_RBP,
+ REGISTER_AMD64_RSI,
+ REGISTER_AMD64_RDI,
+ REGISTER_AMD64_R8,
+ REGISTER_AMD64_R9,
+ REGISTER_AMD64_R10,
+ REGISTER_AMD64_R11,
+ REGISTER_AMD64_R12,
+ REGISTER_AMD64_R13,
+ REGISTER_AMD64_R14,
+ REGISTER_AMD64_R15
+};
+
+//
+// Mapping from ICorDebugInfo register numbers to CorDebugRegister
+// numbers. Note: this must match the order in corinfo.h.
+//
+inline CorDebugRegister ConvertRegNumToCorDebugRegister(ICorDebugInfo::RegNum reg)
+{
+ _ASSERTE(reg >= 0);
+ _ASSERTE(static_cast<size_t>(reg) < _countof(g_JITToCorDbgReg));
+ return g_JITToCorDbgReg[reg];
+}
+
+
+//
+// inline function to access/modify the CONTEXT
+//
+inline LPVOID CORDbgGetIP(DT_CONTEXT* context)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+
+ PRECONDITION(CheckPointer(context));
+ }
+ CONTRACTL_END;
+
+ return (LPVOID) context->Rip;
+}
+
+inline void CORDbgSetIP(DT_CONTEXT* context, LPVOID rip)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+
+ PRECONDITION(CheckPointer(context));
+ }
+ CONTRACTL_END;
+
+ context->Rip = (DWORD64) rip;
+}
+
+inline LPVOID CORDbgGetSP(const DT_CONTEXT * context)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+
+ PRECONDITION(CheckPointer(context));
+ }
+ CONTRACTL_END;
+
+ return (LPVOID)context->Rsp;
+}
+inline void CORDbgSetSP(DT_CONTEXT *context, LPVOID rsp)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+
+ PRECONDITION(CheckPointer(context));
+ }
+ CONTRACTL_END;
+
+ context->Rsp = (UINT_PTR)rsp;
+}
+
+// AMD64 has no frame pointer stored in RBP
+#define CORDbgSetFP(context, rbp)
+#define CORDbgGetFP(context) 0
+
+// compare the RIP, RSP, and RBP
+inline BOOL CompareControlRegisters(const DT_CONTEXT * pCtx1, const DT_CONTEXT * pCtx2)
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+
+ if ((pCtx1->Rip == pCtx2->Rip) &&
+ (pCtx1->Rsp == pCtx2->Rsp) &&
+ (pCtx1->Rbp == pCtx2->Rbp))
+ {
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+/* ========================================================================= */
+//
+// Routines used by debugger support functions such as codepatch.cpp or
+// exception handling code.
+//
+// GetInstruction, InsertBreakpoint, and SetInstruction all operate on
+// a _single_ byte of memory. This is really important. If you only
+// save one byte from the instruction stream before placing a breakpoint,
+// you need to make sure to only replace one byte later on.
+//
+
+
+inline PRD_TYPE CORDbgGetInstruction(UNALIGNED CORDB_ADDRESS_TYPE* address)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ return *address; // retrieving only one byte is important
+}
+
+inline void CORDbgInsertBreakpoint(UNALIGNED CORDB_ADDRESS_TYPE *address)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ *((unsigned char*)address) = 0xCC; // int 3 (single byte patch)
+ FlushInstructionCache(GetCurrentProcess(), address, 1);
+
+}
+
+inline void CORDbgSetInstruction(UNALIGNED CORDB_ADDRESS_TYPE* address,
+ PRD_TYPE instruction)
+{
+ // In a DAC build, this function assumes the input is an host address.
+ LIMITED_METHOD_DAC_CONTRACT;
+
+ *((unsigned char*)address) =
+ (unsigned char) instruction; // setting one byte is important
+ FlushInstructionCache(GetCurrentProcess(), address, 1);
+
+}
+
+
+inline void CORDbgAdjustPCForBreakInstruction(DT_CONTEXT* pContext)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ pContext->Rip -= 1;
+}
+
+inline bool AddressIsBreakpoint(CORDB_ADDRESS_TYPE *address)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ return *address == CORDbg_BREAK_INSTRUCTION;
+}
+
+inline BOOL IsRunningOnWin95() {
+ return false;
+}
+
+inline void SetSSFlag(DT_CONTEXT *pContext)
+{
+ _ASSERTE(pContext != NULL);
+ pContext->EFlags |= 0x100;
+}
+
+inline void UnsetSSFlag(DT_CONTEXT *pContext)
+{
+ _ASSERTE(pContext != NULL);
+ pContext->EFlags &= ~0x100;
+}
+
+inline bool IsSSFlagEnabled(DT_CONTEXT * context)
+{
+ _ASSERTE(context != NULL);
+ return (context->EFlags & 0x100) != 0;
+}
+
+
+inline bool PRDIsEqual(PRD_TYPE p1, PRD_TYPE p2){
+ return p1 == p2;
+}
+inline void InitializePRD(PRD_TYPE *p1) {
+ *p1 = 0;
+}
+
+inline bool PRDIsEmpty(PRD_TYPE p1) {
+ return p1 == 0;
+}
+
+#endif // PRIMITIVES_H_
diff --git a/src/debug/inc/arm/.gitmirror b/src/debug/inc/arm/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/debug/inc/arm/.gitmirror
@@ -0,0 +1 @@
+Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror. \ No newline at end of file
diff --git a/src/debug/inc/arm/primitives.h b/src/debug/inc/arm/primitives.h
new file mode 100644
index 0000000000..0bac542667
--- /dev/null
+++ b/src/debug/inc/arm/primitives.h
@@ -0,0 +1,179 @@
+// 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.
+//*****************************************************************************
+// File: primitives.h
+//
+
+//
+// Platform-specific debugger primitives
+//
+//*****************************************************************************
+
+#ifndef PRIMITIVES_H_
+#define PRIMITIVES_H_
+
+#ifndef THUMB_CODE
+#define THUMB_CODE 1
+#endif
+
+typedef const BYTE CORDB_ADDRESS_TYPE;
+typedef DPTR(CORDB_ADDRESS_TYPE) PTR_CORDB_ADDRESS_TYPE;
+
+//This is an abstraction to keep x86/ia64 patch data separate
+#define PRD_TYPE USHORT
+
+#define MAX_INSTRUCTION_LENGTH 4
+
+// Given a return address retrieved during stackwalk,
+// this is the offset by which it should be decremented to lend somewhere in a call instruction.
+#define STACKWALK_CONTROLPC_ADJUST_OFFSET 2
+
+#define CORDbg_BREAK_INSTRUCTION_SIZE 2
+#define CORDbg_BREAK_INSTRUCTION (USHORT)0xdefe
+
+inline CORDB_ADDRESS GetPatchEndAddr(CORDB_ADDRESS patchAddr)
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return patchAddr + CORDbg_BREAK_INSTRUCTION_SIZE;
+}
+
+
+#define InitializePRDToBreakInst(_pPRD) *(_pPRD) = CORDbg_BREAK_INSTRUCTION
+#define PRDIsBreakInst(_pPRD) (*(_pPRD) == CORDbg_BREAK_INSTRUCTION)
+
+template <class T>
+inline T _ClearThumbBit(T addr)
+{
+ return (T)(((DWORD)addr) & ~THUMB_CODE);
+}
+
+
+#define CORDbgGetInstructionEx(_buffer, _requestedAddr, _patchAddr, _dummy1, _dummy2) \
+ CORDbgGetInstructionExImpl((CORDB_ADDRESS_TYPE *)((_buffer) + (_ClearThumbBit(_patchAddr) - (_requestedAddr))));
+
+#define CORDbgSetInstructionEx(_buffer, _requestedAddr, _patchAddr, _opcode, _dummy2) \
+ CORDbgSetInstructionExImpl((CORDB_ADDRESS_TYPE *)((_buffer) + (_ClearThumbBit(_patchAddr) - (_requestedAddr))), (_opcode));
+
+#define CORDbgInsertBreakpointEx(_buffer, _requestedAddr, _patchAddr, _dummy1, _dummy2) \
+ CORDbgInsertBreakpointExImpl((CORDB_ADDRESS_TYPE *)((_buffer) + (_ClearThumbBit(_patchAddr) - (_requestedAddr))));
+
+
+SELECTANY const CorDebugRegister g_JITToCorDbgReg[] =
+{
+ REGISTER_ARM_R0,
+ REGISTER_ARM_R1,
+ REGISTER_ARM_R2,
+ REGISTER_ARM_R3,
+ REGISTER_ARM_R4,
+ REGISTER_ARM_R5,
+ REGISTER_ARM_R6,
+ REGISTER_ARM_R7,
+ REGISTER_ARM_R8,
+ REGISTER_ARM_R9,
+ REGISTER_ARM_R10,
+ REGISTER_ARM_R11,
+ REGISTER_ARM_R12,
+ REGISTER_ARM_SP,
+ REGISTER_ARM_LR,
+ REGISTER_ARM_PC
+};
+
+//
+// inline function to access/modify the CONTEXT
+//
+inline void CORDbgSetIP(DT_CONTEXT *context, LPVOID eip) {
+ LIMITED_METHOD_CONTRACT;
+
+ context->Pc = (UINT32)(size_t)eip;
+}
+
+inline LPVOID CORDbgGetSP(const DT_CONTEXT * context) {
+ LIMITED_METHOD_CONTRACT;
+
+ return (LPVOID)(size_t)(context->Sp);
+}
+
+inline void CORDbgSetSP(DT_CONTEXT *context, LPVOID esp) {
+ LIMITED_METHOD_CONTRACT;
+
+ context->Sp = (UINT32)(size_t)esp;
+}
+
+inline void CORDbgSetFP(DT_CONTEXT *context, LPVOID ebp) {
+ LIMITED_METHOD_CONTRACT;
+
+ _ASSERTE(FALSE); // @ARMTODO
+}
+inline LPVOID CORDbgGetFP(DT_CONTEXT* context)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ _ASSERTE(FALSE); // @ARMTODO
+ return (LPVOID)(UINT_PTR)0;
+}
+
+// compare the EIP, ESP, and EBP
+inline BOOL CompareControlRegisters(const DT_CONTEXT * pCtx1, const DT_CONTEXT * pCtx2)
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+
+ // @ARMTODO: Sort out frame registers
+
+ if ((pCtx1->Pc == pCtx2->Pc) &&
+ (pCtx1->Sp == pCtx2->Sp))
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/* ========================================================================= */
+//
+// Routines used by debugger support functions such as codepatch.cpp or
+// exception handling code.
+//
+// GetInstruction, InsertBreakpoint, and SetInstruction all operate on
+// a _single_ PRD_TYPE unit of memory. This is really important. If you only
+// save one PRD_TYPE from the instruction stream before placing a breakpoint,
+// you need to make sure to only replace one PRD_TYPE later on.
+//
+inline PRD_TYPE CORDbgGetInstruction(UNALIGNED CORDB_ADDRESS_TYPE* address)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ ULONG ptraddr = dac_cast<ULONG>(address);
+ _ASSERTE(ptraddr & THUMB_CODE);
+ ptraddr &= ~THUMB_CODE;
+ return *(PRD_TYPE *)ptraddr;
+}
+
+inline void CORDbgSetInstruction(CORDB_ADDRESS_TYPE* address,
+ PRD_TYPE instruction)
+{
+ // In a DAC build, this function assumes the input is an host address.
+ LIMITED_METHOD_DAC_CONTRACT;
+
+ ULONG ptraddr = dac_cast<ULONG>(address);
+ _ASSERTE(ptraddr & THUMB_CODE);
+ ptraddr &= ~THUMB_CODE;
+
+ *(PRD_TYPE *)ptraddr = instruction;
+ FlushInstructionCache(GetCurrentProcess(),
+ address,
+ sizeof(PRD_TYPE));
+}
+
+class Thread;
+// Enable single stepping.
+void SetSSFlag(DT_CONTEXT *pCtx, Thread *pThread);
+
+// Disable single stepping
+void UnsetSSFlag(DT_CONTEXT *pCtx, Thread *pThread);
+
+// Check if single stepping is enabled.
+bool IsSSFlagEnabled(DT_CONTEXT *pCtx, Thread *pThread);
+
+#include "arm_primitives.h"
+#endif // PRIMITIVES_H_
diff --git a/src/debug/inc/arm64/.gitmirror b/src/debug/inc/arm64/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/debug/inc/arm64/.gitmirror
@@ -0,0 +1 @@
+Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror. \ No newline at end of file
diff --git a/src/debug/inc/arm64/primitives.h b/src/debug/inc/arm64/primitives.h
new file mode 100644
index 0000000000..e9e04378f7
--- /dev/null
+++ b/src/debug/inc/arm64/primitives.h
@@ -0,0 +1,186 @@
+// 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.
+//*****************************************************************************
+// File: primitives.h
+//
+
+//
+// Platform-specific debugger primitives
+//
+//*****************************************************************************
+
+#ifndef PRIMITIVES_H_
+#define PRIMITIVES_H_
+
+typedef NEON128 FPRegister64;
+typedef const BYTE CORDB_ADDRESS_TYPE;
+typedef DPTR(CORDB_ADDRESS_TYPE) PTR_CORDB_ADDRESS_TYPE;
+
+#define MAX_INSTRUCTION_LENGTH 4
+
+// Given a return address retrieved during stackwalk,
+// this is the offset by which it should be decremented to land at the call instruction.
+#define STACKWALK_CONTROLPC_ADJUST_OFFSET 4
+
+#define PRD_TYPE LONG
+#define CORDbg_BREAK_INSTRUCTION_SIZE 4
+#define CORDbg_BREAK_INSTRUCTION (LONG)0xD43E0000
+
+#define NZCV_N 0x80000000
+#define NZCV_Z 0x40000000
+#define NZCV_C 0x20000000
+#define NZCV_V 0x10000000
+
+#define NZCV_N_BIT 0x1f
+#define NZCV_Z_BIT 0x1e
+#define NZCV_C_BIT 0x1d
+#define NZCV_V_BIT 0x1c
+
+inline CORDB_ADDRESS GetPatchEndAddr(CORDB_ADDRESS patchAddr)
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return patchAddr + CORDbg_BREAK_INSTRUCTION_SIZE;
+}
+
+#define InitializePRDToBreakInst(_pPRD) *(_pPRD) = CORDbg_BREAK_INSTRUCTION
+#define PRDIsBreakInst(_pPRD) (*(_pPRD) == CORDbg_BREAK_INSTRUCTION)
+
+
+#define CORDbgGetInstructionEx(_buffer, _requestedAddr, _patchAddr, _dummy1, _dummy2) \
+ CORDbgGetInstructionExImpl((CORDB_ADDRESS_TYPE *)((_buffer) + (_patchAddr) - (_requestedAddr)));
+
+#define CORDbgSetInstructionEx(_buffer, _requestedAddr, _patchAddr, _opcode, _dummy2) \
+ CORDbgSetInstructionExImpl((CORDB_ADDRESS_TYPE *)((_buffer) + (_patchAddr) - (_requestedAddr)), (_opcode));
+
+#define CORDbgInsertBreakpointEx(_buffer, _requestedAddr, _patchAddr, _dummy1, _dummy2) \
+ CORDbgInsertBreakpointExImpl((CORDB_ADDRESS_TYPE *)((_buffer) + (_patchAddr) - (_requestedAddr)));
+
+
+SELECTANY const CorDebugRegister g_JITToCorDbgReg[] =
+{
+ REGISTER_ARM64_X0,
+ REGISTER_ARM64_X1,
+ REGISTER_ARM64_X2,
+ REGISTER_ARM64_X3,
+ REGISTER_ARM64_X4,
+ REGISTER_ARM64_X5,
+ REGISTER_ARM64_X6,
+ REGISTER_ARM64_X7,
+ REGISTER_ARM64_X8,
+ REGISTER_ARM64_X9,
+ REGISTER_ARM64_X10,
+ REGISTER_ARM64_X11,
+ REGISTER_ARM64_X12,
+ REGISTER_ARM64_X13,
+ REGISTER_ARM64_X14,
+ REGISTER_ARM64_X15,
+ REGISTER_ARM64_X16,
+ REGISTER_ARM64_X17,
+ REGISTER_ARM64_X18,
+ REGISTER_ARM64_X19,
+ REGISTER_ARM64_X20,
+ REGISTER_ARM64_X21,
+ REGISTER_ARM64_X22,
+ REGISTER_ARM64_X23,
+ REGISTER_ARM64_X24,
+ REGISTER_ARM64_X25,
+ REGISTER_ARM64_X26,
+ REGISTER_ARM64_X27,
+ REGISTER_ARM64_X28,
+ REGISTER_ARM64_FP,
+ REGISTER_ARM64_LR,
+ REGISTER_ARM64_SP,
+ REGISTER_ARM64_PC
+};
+
+inline void CORDbgSetIP(DT_CONTEXT *context, LPVOID eip) {
+ LIMITED_METHOD_CONTRACT;
+
+ context->Pc = (DWORD64)eip;
+}
+
+inline LPVOID CORDbgGetSP(const DT_CONTEXT * context) {
+ LIMITED_METHOD_CONTRACT;
+
+ return (LPVOID)(size_t)(context->Sp);
+}
+
+inline void CORDbgSetSP(DT_CONTEXT *context, LPVOID esp) {
+ LIMITED_METHOD_CONTRACT;
+
+ context->Sp = (DWORD64)esp;
+}
+
+inline LPVOID CORDbgGetFP(const DT_CONTEXT * context) {
+ LIMITED_METHOD_CONTRACT;
+
+ return (LPVOID)(size_t)(context->Fp);
+}
+
+inline void CORDbgSetFP(DT_CONTEXT *context, LPVOID fp) {
+ LIMITED_METHOD_CONTRACT;
+
+ context->Fp = (DWORD64)fp;
+}
+
+
+inline BOOL CompareControlRegisters(const DT_CONTEXT * pCtx1, const DT_CONTEXT * pCtx2)
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+
+ // @ARMTODO: Sort out frame registers
+
+ if ((pCtx1->Pc == pCtx2->Pc) &&
+ (pCtx1->Sp == pCtx2->Sp) &&
+ (pCtx1->Fp == pCtx2->Fp))
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+inline void CORDbgSetInstruction(CORDB_ADDRESS_TYPE* address,
+ PRD_TYPE instruction)
+{
+ // In a DAC build, this function assumes the input is an host address.
+ LIMITED_METHOD_DAC_CONTRACT;
+
+ ULONGLONG ptraddr = dac_cast<ULONGLONG>(address);
+ *(PRD_TYPE *)ptraddr = instruction;
+ FlushInstructionCache(GetCurrentProcess(),
+ address,
+ sizeof(PRD_TYPE));
+}
+
+inline PRD_TYPE CORDbgGetInstruction(UNALIGNED CORDB_ADDRESS_TYPE* address)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ ULONGLONG ptraddr = dac_cast<ULONGLONG>(address);
+ return *(PRD_TYPE *)ptraddr;
+}
+
+
+inline void SetSSFlag(DT_CONTEXT *pContext)
+{
+ _ASSERTE(pContext != NULL);
+ pContext->Cpsr |= 0x00200000;
+}
+
+inline void UnsetSSFlag(DT_CONTEXT *pContext)
+{
+ _ASSERTE(pContext != NULL);
+ pContext->Cpsr &= ~0x00200000;
+}
+
+inline bool IsSSFlagEnabled(DT_CONTEXT * pContext)
+{
+ _ASSERTE(pContext != NULL);
+ return (pContext->Cpsr & 0x00200000) != 0;
+}
+
+
+#include "arm_primitives.h"
+#endif // PRIMITIVES_H_
diff --git a/src/debug/inc/arm_primitives.h b/src/debug/inc/arm_primitives.h
new file mode 100644
index 0000000000..7c536353e5
--- /dev/null
+++ b/src/debug/inc/arm_primitives.h
@@ -0,0 +1,113 @@
+// 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.
+//*****************************************************************************
+// File: arm_primitives.h
+//
+
+//
+// ARM/ARM64-specific debugger primitives
+//
+//*****************************************************************************
+
+#ifndef ARM_PRIMITIVES_H_
+#define ARM_PRIMITIVES_H_
+
+//
+// Mapping from ICorDebugInfo register numbers to CorDebugRegister
+// numbers. Note: this must match the order in corinfo.h.
+//
+inline CorDebugRegister ConvertRegNumToCorDebugRegister(ICorDebugInfo::RegNum reg)
+{
+ LIMITED_METHOD_CONTRACT;
+ _ASSERTE(reg >= 0);
+ _ASSERTE(static_cast<size_t>(reg) < _countof(g_JITToCorDbgReg));
+ return g_JITToCorDbgReg[reg];
+}
+
+inline LPVOID CORDbgGetIP(DT_CONTEXT *context)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ return (LPVOID)(size_t)(context->Pc);
+}
+
+inline void CORDbgSetInstructionExImpl(CORDB_ADDRESS_TYPE* address,
+ PRD_TYPE instruction)
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+
+ *(PRD_TYPE *)address = instruction;
+ FlushInstructionCache(GetCurrentProcess(),
+ address,
+ sizeof(PRD_TYPE));
+}
+
+inline PRD_TYPE CORDbgGetInstructionExImpl(UNALIGNED CORDB_ADDRESS_TYPE* address)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ return *(PRD_TYPE *)address;
+}
+
+inline void CORDbgInsertBreakpoint(UNALIGNED CORDB_ADDRESS_TYPE *address)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ CORDbgSetInstruction(address, CORDbg_BREAK_INSTRUCTION);
+}
+
+inline void CORDbgInsertBreakpointExImpl(UNALIGNED CORDB_ADDRESS_TYPE *address)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ CORDbgSetInstruction(address, CORDbg_BREAK_INSTRUCTION);
+}
+
+// After a breakpoint exception, the CPU points to _after_ the break instruction.
+// Adjust the IP so that it points at the break instruction. This lets us patch that
+// opcode and re-excute what was underneath the bp.
+inline void CORDbgAdjustPCForBreakInstruction(DT_CONTEXT* pContext)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ // @ARMTODO: ARM appears to leave the PC at the start of the breakpoint (at least according to Windbg,
+ // which may be adjusting the view).
+ return;
+}
+
+inline bool AddressIsBreakpoint(CORDB_ADDRESS_TYPE* address)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ return CORDbgGetInstruction(address) == CORDbg_BREAK_INSTRUCTION;
+}
+
+class Thread;
+// Enable single stepping.
+void SetSSFlag(DT_CONTEXT *pCtx, Thread *pThread);
+
+// Disable single stepping
+void UnsetSSFlag(DT_CONTEXT *pCtx, Thread *pThread);
+
+// Check if single stepping is enabled.
+bool IsSSFlagEnabled(DT_CONTEXT *pCtx, Thread *pThread);
+
+inline bool PRDIsEqual(PRD_TYPE p1, PRD_TYPE p2)
+{
+ return p1 == p2;
+}
+
+inline void InitializePRD(PRD_TYPE *p1)
+{
+ *p1 = 0;
+}
+
+inline bool PRDIsEmpty(PRD_TYPE p1)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ return p1 == 0;
+}
+
+#endif // ARM_PRIMITIVES_H_
diff --git a/src/debug/inc/common.h b/src/debug/inc/common.h
new file mode 100644
index 0000000000..77fe27a4b3
--- /dev/null
+++ b/src/debug/inc/common.h
@@ -0,0 +1,323 @@
+// 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 DEBUGGER_COMMON_H
+#define DEBUGGER_COMMON_H
+
+//
+// Conversions between pointers and CORDB_ADDRESS
+// These are 3gb safe - we use zero-extension for CORDB_ADDRESS.
+// Note that this is a different semantics from CLRDATA_ADDRESS which is sign-extended.
+//
+// @dbgtodo : This confuses the host and target address spaces. Ideally we'd have
+// conversions between PTR types (eg. DPTR) and CORDB_ADDRESS, and not need conversions
+// from host pointer types to CORDB_ADDRESS.
+//
+#if defined(_TARGET_X86_) || defined(_TARGET_ARM_)
+inline CORDB_ADDRESS PTR_TO_CORDB_ADDRESS(const void* ptr)
+{
+ SUPPORTS_DAC;
+ // Cast a void* to a ULONG is not 64-bit safe and triggers compiler warning C3411.
+ // But this is x86 only, so we know it's ok. Use PtrToUlong to do the conversion
+ // without invoking the error.
+ return (CORDB_ADDRESS)(PtrToUlong(ptr));
+}
+inline CORDB_ADDRESS PTR_TO_CORDB_ADDRESS(UINT_PTR ptr)
+{
+ SUPPORTS_DAC;
+ // PtrToUlong
+ return (CORDB_ADDRESS)(ULONG)(ptr);
+}
+#else
+#define PTR_TO_CORDB_ADDRESS(_ptr) (CORDB_ADDRESS)(ULONG_PTR)(_ptr)
+#endif //_TARGET_X86_ || _TARGET_ARM_
+
+#define CORDB_ADDRESS_TO_PTR(_cordb_addr) ((LPVOID)(SIZE_T)(_cordb_addr))
+
+
+// Determine if an exception record is for a CLR debug event, and get the payload.
+CORDB_ADDRESS IsEventDebuggerNotification(const EXCEPTION_RECORD * pRecord, CORDB_ADDRESS pClrBaseAddress);
+#if defined(FEATURE_DBGIPC_TRANSPORT_DI) || defined(FEATURE_DBGIPC_TRANSPORT_VM)
+struct DebuggerIPCEvent;
+void InitEventForDebuggerNotification(DEBUG_EVENT * pDebugEvent,
+ CORDB_ADDRESS pClrBaseAddress,
+ DebuggerIPCEvent * pIPCEvent);
+#endif // (FEATURE_DBGIPC_TRANSPORT_DI || FEATURE_DBGIPC_TRANSPORT_VM)
+
+
+void GetPidDecoratedName(__out_z __out_ecount(cBufSizeInChars) WCHAR * pBuf,
+ int cBufSizeInChars,
+ const WCHAR * pPrefix,
+ DWORD pid);
+
+
+//
+// This macro is used in CORDbgCopyThreadContext().
+//
+// CORDbgCopyThreadContext() does an intelligent copy
+// from pSrc to pDst, respecting the ContextFlags of both contexts.
+//
+#define CopyContextChunk(_t, _f, _end, _flag) \
+{ \
+ LOG((LF_CORDB, LL_INFO1000000, \
+ "CP::CTC: copying " #_flag ":" FMT_ADDR "<---" FMT_ADDR "(%d)\n", \
+ DBG_ADDR(_t), DBG_ADDR(_f), ((UINT_PTR)(_end) - (UINT_PTR)_t))); \
+ memcpy((_t), (_f), ((UINT_PTR)(_end) - (UINT_PTR)(_t))); \
+}
+
+//
+// CORDbgCopyThreadContext() does an intelligent copy from pSrc to pDst,
+// respecting the ContextFlags of both contexts.
+//
+struct DebuggerREGDISPLAY;
+
+extern void CORDbgCopyThreadContext(DT_CONTEXT* pDst, const DT_CONTEXT* pSrc);
+extern void CORDbgSetDebuggerREGDISPLAYFromContext(DebuggerREGDISPLAY *pDRD,
+ DT_CONTEXT* pContext);
+
+//---------------------------------------------------------------------------------------
+//
+// Return the size of the CONTEXT required for the specified context flags.
+//
+// Arguments:
+// flags - this is the equivalent of the ContextFlags field of a CONTEXT
+//
+// Return Value:
+// size of the CONTEXT required
+//
+// Notes:
+// On WIN64 platforms this function will always return sizeof(CONTEXT).
+//
+
+inline
+ULONG32 ContextSizeForFlags(ULONG32 flags)
+{
+#if defined(CONTEXT_EXTENDED_REGISTERS) && defined(_TARGET_X86_)
+ // Older platforms didn't have extended registers in
+ // the context definition so only enforce that size
+ // if the extended register flag is set.
+ if ((flags & CONTEXT_EXTENDED_REGISTERS) != CONTEXT_EXTENDED_REGISTERS)
+ {
+ return offsetof(T_CONTEXT, ExtendedRegisters);
+ }
+ else
+#endif // _TARGET_X86_
+ {
+ return sizeof(T_CONTEXT);
+ }
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Given the size of a buffer and the context flags, check whether the buffer is sufficient large
+// to hold the CONTEXT.
+//
+// Arguments:
+// size - size of a buffer
+// flags - this is the equivalent of the ContextFlags field of a CONTEXT
+//
+// Return Value:
+// TRUE if the buffer is large enough to hold the CONTEXT
+//
+
+inline
+BOOL CheckContextSizeForFlags(ULONG32 size, ULONG32 flags)
+{
+ return (size >= ContextSizeForFlags(flags));
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Given the size of a buffer and the BYTE array representation of a CONTEXT,
+// check whether the buffer is sufficient large to hold the CONTEXT.
+//
+// Arguments:
+// size - size of a buffer
+// flags - this is the equivalent of the ContextFlags field of a CONTEXT
+//
+// Return Value:
+// TRUE if the buffer is large enough to hold the CONTEXT
+//
+
+inline
+BOOL CheckContextSizeForBuffer(ULONG32 size, const BYTE * pbBuffer)
+{
+ return ( ( size >= (offsetof(T_CONTEXT, ContextFlags) + sizeof(ULONG32)) ) &&
+ CheckContextSizeForFlags(size, (reinterpret_cast<const T_CONTEXT *>(pbBuffer))->ContextFlags) );
+}
+
+/* ------------------------------------------------------------------------- *
+ * Constant declarations
+ * ------------------------------------------------------------------------- */
+
+enum
+{
+ NULL_THREAD_ID = -1,
+ NULL_PROCESS_ID = -1
+};
+
+/* ------------------------------------------------------------------------- *
+ * Macros
+ * ------------------------------------------------------------------------- */
+
+//
+// CANNOT USE IsBad*Ptr() methods here. They are *banned* APIs because of various
+// reasons (see http://winweb/wincet/bannedapis.htm).
+//
+
+#define VALIDATE_POINTER_TO_OBJECT(ptr, type) \
+if ((ptr) == NULL) \
+{ \
+ return E_INVALIDARG; \
+}
+
+#define VALIDATE_POINTER_TO_OBJECT_OR_NULL(ptr, type)
+
+//
+// CANNOT USE IsBad*Ptr() methods here. They are *banned* APIs because of various
+// reasons (see http://winweb/wincet/bannedapis.htm).
+//
+#define VALIDATE_POINTER_TO_OBJECT_ARRAY(ptr, type, cElt, fRead, fWrite) \
+if ((ptr) == NULL) \
+{ \
+ return E_INVALIDARG; \
+}
+
+#define VALIDATE_POINTER_TO_OBJECT_ARRAY_OR_NULL(ptr, type,cElt,fRead,fWrite)
+
+/* ------------------------------------------------------------------------- *
+ * Function Prototypes
+ * ------------------------------------------------------------------------- */
+
+
+
+// Linear search through an array of NativeVarInfos, to find
+// the variable of index dwIndex, valid at the given ip.
+//
+// returns CORDBG_E_IL_VAR_NOT_AVAILABLE if the variable isn't
+// valid at the given ip.
+//
+// This should be inlined
+HRESULT FindNativeInfoInILVariableArray(DWORD dwIndex,
+ SIZE_T ip,
+ ICorDebugInfo::NativeVarInfo **ppNativeInfo,
+ unsigned int nativeInfoCount,
+ ICorDebugInfo::NativeVarInfo *nativeInfo);
+
+
+#define VALIDATE_HEAP
+//HeapValidate(GetProcessHeap(), 0, NULL);
+
+// struct DebuggerILToNativeMap: Holds the IL to Native offset map
+// Great pains are taken to ensure that this each entry corresponds to the
+// first IL instruction in a source line. It isn't actually a mapping
+// of _every_ IL instruction in a method, just those for source lines.
+// SIZE_T ilOffset: IL offset of a source line.
+// SIZE_T nativeStartOffset: Offset within the method where the native
+// instructions corresponding to the IL offset begin.
+// SIZE_T nativeEndOffset: Offset within the method where the native
+// instructions corresponding to the IL offset end.
+//
+// Note: any changes to this struct need to be reflected in
+// COR_DEBUG_IL_TO_NATIVE_MAP in CorDebug.idl. These structs must
+// match exactly.
+//
+struct DebuggerILToNativeMap
+{
+ ULONG ilOffset;
+ ULONG nativeStartOffset;
+ ULONG nativeEndOffset;
+ ICorDebugInfo::SourceTypes source;
+};
+
+void ExportILToNativeMap(ULONG32 cMap,
+ COR_DEBUG_IL_TO_NATIVE_MAP mapExt[],
+ struct DebuggerILToNativeMap mapInt[],
+ SIZE_T sizeOfCode);
+
+#include <primitives.h>
+
+// ----------------------------------------------------------------------------
+// IsPatchInRequestedRange
+//
+// Description:
+// This function checks if a patch falls (fully or partially) in the requested range of memory.
+//
+// Arguments:
+// * requestedAddr - the address of the memory range
+// * requestedSize - the size of the memory range
+// * patchAddr - the address of the patch
+// * pPRD - the opcode of the patch
+//
+// Return Value:
+// Return TRUE if the patch is fully or partially in the requested memory range.
+//
+// Notes:
+// Currently this function is called both from the RS (via code:CordbProcess.ReadMemory and
+// code:CordbProcess.WriteMemory) and from DAC. When we DACize the two functions mentioned above,
+// this function should be called from DAC only, and we should use a MemoryRange here.
+//
+
+inline bool IsPatchInRequestedRange(CORDB_ADDRESS requestedAddr,
+ SIZE_T requestedSize,
+ CORDB_ADDRESS patchAddr)
+{
+ SUPPORTS_DAC;
+
+ if (requestedAddr == 0)
+ return false;
+
+ // Note that patchEnd points to the byte immediately AFTER the patch, so patchEnd is NOT
+ // part of the patch.
+ CORDB_ADDRESS patchEnd = GetPatchEndAddr(patchAddr);
+
+ // We have three cases:
+ // 1) the entire patch is in the requested range
+ // 2) the beginning of the requested range is covered by the patch
+ // 3) the end of the requested range is covered by the patch
+ //
+ // Note that on x86, since the break instruction only takes up one byte, the following condition
+ // degenerates to case 1 only.
+ return (((requestedAddr <= patchAddr) && (patchEnd <= (requestedAddr + requestedSize))) ||
+ ((patchAddr <= requestedAddr) && (requestedAddr < patchEnd)) ||
+ ((patchAddr <= (requestedAddr + requestedSize - 1)) && ((requestedAddr + requestedSize - 1) < patchEnd)));
+}
+
+inline CORDB_ADDRESS ALIGN_ADDRESS( CORDB_ADDRESS val, CORDB_ADDRESS alignment )
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+
+ // alignment must be a power of 2 for this implementation to work (need modulo otherwise)
+ _ASSERTE( 0 == (alignment & (alignment - 1)) );
+ CORDB_ADDRESS result = (val + (alignment - 1)) & ~(alignment - 1);
+ _ASSERTE( result >= val ); // check for overflow
+ return result;
+}
+
+//
+// Whenever a structure is marshalled between different platforms, we need to ensure the
+// layout is the same in both cases. We tell GCC to use the MSVC-style packing with
+// the following attribute. The main thing this appears to control is whether
+// 8-byte values are aligned at 4-bytes (GCC default) or 8-bytes (MSVC default).
+// This attribute affects only the immediate struct it is applied to, you must also apply
+// it to any nested structs if you want their layout affected as well. You also must
+// apply this to unions embedded in other structures, since it can influence the starting
+// alignment.
+//
+// Note that there doesn't appear to be any disadvantage to applying this a little
+// more agressively than necessary, so we generally use it on all classes / structures
+// defined in a file that defines marshalled data types (eg. DacDbiStructures.h)
+// The -mms-bitfields compiler option also does this for the whole file, but we don't
+// want to go changing the layout of, for example, structures defined in OS header files
+// so we explicitly opt-in with this attribute.
+//
+#ifdef __GNUC__
+#define MSLAYOUT __attribute__((__ms_struct__))
+#else
+#define MSLAYOUT
+#endif
+
+#include "dumpcommon.h"
+
+#endif //DEBUGGER_COMMON_H
diff --git a/src/debug/inc/coreclrremotedebugginginterfaces.h b/src/debug/inc/coreclrremotedebugginginterfaces.h
new file mode 100644
index 0000000000..ac7ddb68ab
--- /dev/null
+++ b/src/debug/inc/coreclrremotedebugginginterfaces.h
@@ -0,0 +1,20 @@
+// 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.
+
+
+//
+// Defines interfaces shared between mscordbi and the Visual Studio debugger port supplier plugin that we
+// provide for debugging remote CoreCLR instances on the Mac.
+//
+
+#ifndef __PORT_SUPPLIER_INTERFACES_INCLUDED
+#define __PORT_SUPPLIER_INTERFACES_INCLUDED
+
+
+
+
+class ICoreClrDebugTarget;
+
+
+#endif // __PORT_SUPPLIER_INTERFACES_INCLUDED
diff --git a/src/debug/inc/dacdbiinterface.h b/src/debug/inc/dacdbiinterface.h
new file mode 100644
index 0000000000..fe58724fc5
--- /dev/null
+++ b/src/debug/inc/dacdbiinterface.h
@@ -0,0 +1,2726 @@
+// 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.
+//*****************************************************************************
+// DacDbiInterface.h
+//
+
+//
+// Define the interface between the DAC and DBI.
+//*****************************************************************************
+
+#ifndef _DACDBI_INTERFACE_H_
+#define _DACDBI_INTERFACE_H_
+
+#include <metahost.h>
+
+// The DAC/DBI interface can use structures and LSPTR declarations from the
+// existing V2 interfaces
+#include "dbgipcevents.h"
+
+//-----------------------------------------------------------------------------
+// Deallocation function for memory allocated with the global IAllocator object.
+//
+// Arguments:
+// p - pointer to delete. Allocated with IAllocator::Alloc
+//
+// Notes:
+// This should invoke the dtor and then call IAllocator::Free.
+// In the DAC implementation, this will call via IAllocator.
+// In the DBI implementation, this can directly call delete (assuming the IAllocator::Free
+// directly called new).
+template<class T> void DeleteDbiMemory(T *p);
+// Need a class to serve as a tag that we can use to overload New/Delete.
+class forDbiWorker {};
+#define forDbi (*(forDbiWorker *)NULL)
+extern void * operator new(size_t lenBytes, const forDbiWorker &);
+extern void * operator new[](size_t lenBytes, const forDbiWorker &);
+extern void operator delete(void *p, const forDbiWorker &);
+extern void operator delete[](void *p, const forDbiWorker &);
+
+// The dac exposes a way to walk all GC references in the process. This
+// includes both strong references and weak references. This is done
+// through a referece walk.
+typedef void* * RefWalkHandle;
+
+#include "dacdbistructures.h"
+
+// This is the current format of code:DbiVersion. It needs to be rev'ed when we decide to store something
+// else other than the product version of the DBI in DbiVersion (e.g. a timestamp). See
+// code:CordbProcess::CordbProcess#DBIVersionChecking for more information.
+const DWORD kCurrentDbiVersionFormat = 1;
+
+//-----------------------------------------------------------------------------
+// This is a low-level interface between DAC and DBI.
+// The DAC is the raw DAC-ized code from the EE.
+// DBI is the implementation of ICorDebug on top of that.
+//
+// This interface should be:
+// - Stateless: The DAC component should not have any persistent state. It should not have any resources
+// that it needs to clean up. DBI can store all the state (eg, list of of modules).
+// Using IAllocator/IStringHolder interfaces to allocate data to pass back out is ok because DBI owns
+// the resources, not the DAC layer.
+// - blittable: The types on the interface should be blittable. For example, use TIDs instead of OS Thread handles.
+// Passing pointers to be used as out-parameters is ok.
+// - lightweight: it will inevitably have many methods on it and should be very fluid to use.
+// - very descriptive: heavily call out liabilities on the runtime. For example, don't just have a method like
+// "GetName" where Name is ambiguous. Heavily comment exactly what Name is, when it may fail, if it's 0-length,
+// if it's unique, etc. This serves two purposes:
+// a) it helps ensure the right invariants flow up to the public API level.
+// b) it helps ensure that the debugger is making the right assumptions about the runtime's behavior.
+//
+// #Marshaling:
+// This interface should be marshalable such that the caller (the Right Side) can exist in one
+// process, while the implementation of Dac could be on another machine.
+// - All types need to be marshable.
+// - Use OUT and OPTIONAL as defined in windef.h to guide the marshaler. Here are how types are marshaled:
+// T : value-type, copied on input.
+// T* : will be marshaled as non-null by-ref (copy on input, copy on return),
+// const T*: non-null, copy on input only.
+// OUT T*: non-null copy-on-return only.
+// OPTIONAL T*: by-ref, could be null.
+// - The marshaler has special knowledge of IStringHolder and DacDbiArrayList<T>.
+// - You can write custom marshalers for non-blittable structures defined in DacDbiStructures.h.
+// - There is custom handling for marshalling callbacks.
+//
+//
+// Threading: The interface (and the underlying DataTarget) are free-threaded to leverage
+// concurrency.
+//
+// Allocation:
+// This interface can use IAllocator to allocate objects and hand them back. The allocated objects should be:
+// - closed, serializable object graphs.
+// - should have private fields and public accessors
+// - have dtors that free any allocated the memory via calling DeleteDbiMemory.
+// Objects can be declared in a header and shared between both dbi and dac.
+// Consider using DacDbiArrayList<T> instead of custom allocations.
+
+// Error handling:
+// Any call on the interface may fail. For example, the data-target may not have access to the necessary memory.
+// Methods should throw on error.
+//
+// #Enumeration
+// General rules about Enumerations:
+// - Do not assume that enumerations exposed here are in any particular order.
+// - many enumerations also correspond to Load/Unload events. Since load/unload aren't atomic with publishing
+// in an enumeration, this is a Total Ordering of things:
+// a) object shows up in enumeration
+// b) load event.
+// c) ... steady state ...
+// d) object removed from DacDbi enumeration;
+// Any existing handles we get beyond this are explicitly associated with a Cordb* object; which can be
+// neutered on the unload event by Dbi.
+// e) unload event.
+// - Send after it's reachability from other objects is broken. (Eg, For AppDomain unload
+// means no threads left in that appdomain)
+// - Send before it's deleted (so VMPTR is still valid; not yet recycled).
+// - Send early enough that property access can at least gracefully fail. (eg,
+// Module::GetName should either return the name, or fail)
+//
+// Cordb must neuter any Cordb objects that have any pre-existing handles to the object.
+// After this point, gauranteed that nobody can discover the VMPTR any more:
+// - doesn't show up in enumerations (so can't be discoverered implicitly)
+// - object should not be discoverable by other objects in VM.
+// - any Cordb object that already had it would be neutered by Dbi.
+// - Therefore nothing should even be asking Dac for it.
+// f) object deleted.
+// Think of it like this: The event occurs to let you know that the enumeration has been updated.
+//
+// A robust debugger should not rely on events for correctness. For example,
+// a corrupt debuggee may send:
+// 1) multiple load events. (if target repeats due to an issue)
+// 2) no load event and only an unload event. (if target fails inbetween
+// publish (a) and load (b), and then backout code sends the unload).
+// 3) no unload event. (eg, if target is rudely killed)
+// 4) multiple unload events (if target repeats due to bug)
+//
+// This satisfies the following rules:
+// - once you get the load event, you can find the object via enumeration
+// - once an item is discoverable, it must immediately show up in the enumeration.
+// - once you get the unload event, the object is dead and can't be rediscovered via enumeration.
+//
+// This is an issue even for well-behaved targets. Imagine if a debugger attaches right after
+// an unload event is sent. We don't want the debugger to enumerate and re-discover the
+// unloaded object because now that the unload event is already sent, the debugger won't get
+// any further notification of when the object is deleted in the target.
+// Thus it's valuable for the debugger to have debug-only checks after unload events to assert
+// that the object is no longer discoverable.
+//
+//.............................................................................
+// The purpose of this object is to provide EE funcationality back to
+// the debugger. This represents the entire set of EE functions used
+// by the debugger.
+//
+// We will make this interface larger over time to grow the functionality
+// between the EE and the Debugger.
+//
+//
+//-----------------------------------------------------------------------------
+class IDacDbiInterface
+{
+public:
+ class IStringHolder;
+
+ // The following tag tells the DD-marshalling tool to start scanning.
+ // BEGIN_MARSHAL
+
+ //-----------------------------------------------------------------------------
+ // Functions to control the behavior of the DacDbi implementation itself.
+ //-----------------------------------------------------------------------------
+
+ //
+ // Check whether the version of the DBI matches the version of the runtime.
+ // This is only called when we are remote debugging. On Windows, we should have checked all the
+ // versions before we call any API on the IDacDbiInterface. See
+ // code:CordbProcess::CordbProcess#DBIVersionChecking for more information on version checks.
+ //
+ // Return Value:
+ // S_OK on success.
+ //
+ // Notes:
+ // THIS MUST BE THE FIRST API ON THE INTERFACE!
+ //
+ virtual
+ HRESULT CheckDbiVersion(const DbiVersion * pVersion) = 0;
+
+ //
+ // Flush the DAC cache. This should be called when target memory changes.
+ //
+ //
+ // Return Value:
+ // S_OK on success.
+ //
+ // Notes:
+ // If this fails, the interface is in an undefined state.
+ // This must be called anytime target memory changes, else all other functions
+ // (besides Destroy) may yield out-of-date or semantically incorrect results.
+ //
+ virtual
+ HRESULT FlushCache() = 0;
+
+ //
+ // Control DAC's checking of the target's consistency. Specifically, if this is disabled then
+ // ASSERTs in VM code are ignored. The default is disabled, since DAC should do it's best to
+ // return results even with a corrupt or unsyncrhonized target. See
+ // code:ClrDataAccess::TargetConsistencyAssertsEnabled for more details.
+ //
+ // When testing with a non-corrupt and properly syncrhonized target, this should be enabled to
+ // help catch bugs.
+ //
+ // Arguments:
+ // fEnableAsserts - whether ASSERTs should be raised when consistency checks fail (_DEBUG
+ // builds only)
+ //
+ // Notes:
+ // In the future we may want to extend DAC target consistency checks to be retail checks
+ // (exceptions) as well. We'll also need a mechanism for disabling them (eg. when an advanced
+ // user wants to try to get a result anyway even though the target is inconsistent). In that
+ // case we'll want an additional argument here for enabling/disabling the throwing of
+ // consistency failures exceptions (this is independent from asserts - there are legitimate
+ // scenarios for all 4 combinations).
+ //
+ virtual
+ void DacSetTargetConsistencyChecks(bool fEnableAsserts) = 0;
+
+ //
+ // Destroy the interface object. The client should call this when it's done
+ // with the IDacDbiInterface to free up any resources.
+ //
+ // Return Value:
+ // None.
+ //
+ // Notes:
+ // The client should not call anything else on this interface after Destroy.
+ //
+ virtual
+ void Destroy() = 0;
+
+ //-----------------------------------------------------------------------------
+ // General purpose target inspection functions
+ //-----------------------------------------------------------------------------
+
+ //
+ // Query if Left-side is started up?
+ //
+ //
+ // Return Value:
+ // BOOL whether Left-side is intialized.
+ //
+ // Notes:
+ // If the Left-side is not yet started up, then data in the LS is not yet initialized enough
+ // for us to make meaningful queries, but the runtime will fire "Startup Exception" when it is.
+ //
+ // If the left-side is started up, then data is ready. (Although data may be temporarily inconsistent,
+ // see DataSafe). We may still get a Startup Exception in these cases, but it can be ignored.
+ //
+ virtual
+ BOOL IsLeftSideInitialized() = 0;
+
+
+ //
+ // Get an LS Appdomain via an AppDomain unique ID.
+ // Fails if the AD is not found or if the ID is invalid.
+ //
+ // Arguments:
+ // appdomainId - "unique appdomain ID". Must be a valid Id.
+ //
+ // Return Value:
+ // VMPTR_AppDomain for the corresponding AppDomain ID. Else throws.
+ //
+ // Notes:
+ // This query is based off the lifespan of the AppDomain from the VM's perspective.
+ // The AppDomainId is most likely obtained from an AppDomain-Created debug events.
+ // An AppDomainId is unique for the lifetime of the VM.
+ // This is the inverse function of GetAppDomainId().
+ //
+ virtual
+ VMPTR_AppDomain GetAppDomainFromId(ULONG appdomainId) = 0;
+
+
+ //
+ // Get the AppDomain ID for an AppDomain.
+ //
+ // Arguments:
+ // vmAppDomain - VM pointer to the AppDomain object of interest
+ //
+ // Return Value:
+ // AppDomain ID for appdomain. Else throws.
+ //
+ // Notes:
+ // An AppDomainId is unique for the lifetime of the VM. It is non-zero.
+ //
+ virtual
+ ULONG GetAppDomainId(VMPTR_AppDomain vmAppDomain) = 0;
+
+ //
+ // Get the managed AppDomain object for an AppDomain.
+ //
+ // Arguments:
+ // vmAppDomain - VM pointer to the AppDomain object of interest
+ //
+ // Return Value:
+ // objecthandle for the managed app domain object or the Null VMPTR if there is no
+ // object created yet
+ //
+ // Notes:
+ // The AppDomain managed object is lazily constructed on the AppDomain the first time
+ // it is requested. It may be NULL.
+ //
+ virtual
+ VMPTR_OBJECTHANDLE GetAppDomainObject(VMPTR_AppDomain vmAppDomain) = 0;
+
+ //
+ // Determine if the specified AppDomain is the default domain
+ //
+ // Arguments:
+ // vmAppDomain - VM pointer to the AppDomain ojbect of interest
+ //
+ // Return Value:
+ // TRUE if this is the default appdomain, else FALSE.
+ //
+ // Notes:
+ // The default domain is the only one which cannot be unloaded and exists for the life
+ // of the process.
+ // A well behaved target only has 1 default domain.
+ //
+ virtual
+ BOOL IsDefaultDomain(VMPTR_AppDomain vmAppDomain) = 0;
+
+
+ virtual
+ void GetAssemblyFromDomainAssembly(VMPTR_DomainAssembly vmDomainAssembly, OUT VMPTR_Assembly * vmAssembly) = 0;
+
+ //
+ // Determines whether the runtime security system has assigned full-trust to this assembly.
+ //
+ // Arguments:
+ // vmDomainAssembly - VM pointer to the assembly in question.
+ //
+ // Return Value:
+ // Returns trust status for the assembly.
+ // Throws on error.
+ //
+ // Notes:
+ // Of course trusted malicious code in the process could always cause this API to lie. However,
+ // an assembly loaded without full-trust should have no way of causing this API to return true.
+ //
+ virtual
+ BOOL IsAssemblyFullyTrusted(VMPTR_DomainAssembly vmDomainAssembly) = 0;
+
+
+ //
+ // Get the full AD friendly name for the given EE AppDomain.
+ //
+ // Arguments:
+ // vmAppDomain - VM pointer to the AppDomain.
+ // pStrName - required out parameter where the name will be stored.
+ //
+ // Return Value:
+ // None. On success, sets the string via the holder. Throws on error.
+ // This either sets pStrName or Throws. It won't do both.
+ //
+ // Notes:
+ // AD names have an unbounded length. AppDomain friendly names can also change, and
+ // so callers should be prepared to listen for name-change events and requery.
+ // AD names are specified by the user.
+ //
+ virtual
+ void GetAppDomainFullName(
+ VMPTR_AppDomain vmAppDomain,
+ IStringHolder * pStrName) = 0;
+
+
+ //
+ // #ModuleNames
+ //
+ // Modules / Assemblies have many different naming schemes:
+ //
+ // 1) Metadata Scope name: All modules have metadata, and each metadata scope has a name assigned
+ // by the creator of that scope (eg, the compiler). This usually is similar to the filename, but could
+ // be arbitrary.
+ // eg: "Foo"
+ //
+ // 2) FileRecord: the File record entry in the manifest module's metadata (table 0x26) for this module.
+ // eg: "Foo"
+ //
+ // 3) Managed module path: This is path that the image was loaded from. Eg, "c:\foo.dll". For non-file
+ // based modules (like in-memory, dynamic), there is no file path. The specific path is determined by
+ // fusion / loader policy.
+ // eg: "c:\foo.dll"
+ //
+ // 4) GAC path: If the module is loaded from the GAC, this is the path on disk into the gac cache that
+ // the image was pulled from.
+ // eg: "
+ //
+ // 5) Ngen path: If the module was ngenned, this is the path on disk into the ngen cache that the image
+ // was pulled from.
+ // eg:
+ //
+ // 6) Fully Qualified Assembly Name: this is an abstract name, which the CLR (fusion / loader) will
+ // resolve (to a filename for file-based modules). Managed apps may need to deal in terms of FQN,
+ // but the debugging services generally avoid them.
+ // eg: "Foo, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL".
+ //
+
+
+ //
+ // Get the "simple name" of a module. This is a heuristic within the CLR to return a simple,
+ // not-well-specified, but meaningful, name for a module.
+ //
+ // Arguments:
+ // vmModule - module to query
+ // pStrFileName - string holder to get simple name.
+ //
+ // Return Value:
+ // None, but pStrFilename will be initialized upon return.
+ // Throws if there was a problem reading the data with DAC or if there is an OOM exception,
+ // in which case no string was stored into pStrFilename.
+ //
+ // Notes:
+ // See code:#ModuleNames for an overview on module names.
+ //
+ // This is really just using code:Module::GetSimpleName.
+ // This gives back a meaningful name, which is generally some combination of the metadata
+ // name of the FileRecord name. This is important because it's valid even when a module
+ // doesn't have a filename.
+ //
+ // The simple name does not have any meaning. It is not a filename, does not necessarily have any
+ // relationship to the filename, and it's not necesarily the metadata name.
+ // Do not use the simple name for anything other than as a pretty string to give the an end user.
+ //
+ virtual
+ void GetModuleSimpleName(VMPTR_Module vmModule, IStringHolder * pStrFilename) = 0;
+
+
+ //
+ // Get the full path and file name to the assembly's manifest module.
+ //
+ // Arguments:
+ // vmAssembly - VM pointer to the Assembly.
+ // pStrFilename - required out parameter where the filename will be stored.
+ //
+ // Return Value:
+ // TRUE on success, in which case the filename was stored into pStrFilename
+ // FALSE if the assembly has no filename (eg. for in-memory assemblies), in which
+ // case an empty string was stored into pStrFilename.
+ // Throws if there was a problem reading the data with DAC, in which case
+ // no string was stored into pStrFilename.
+ //
+ // Notes:
+ // See code:#ModuleNames for an overview on module names.
+ //
+ // Normally this is just the filename from which the dll containing the assembly was
+ // loaded. In the case of multi-module assemblies, this is the filename for the
+ // manifest module (the one containing the assembly manifest). For in-memory
+ // assemblies (eg. those loaded from a Byte[], and those created by Reflection.Emit
+ // which will not be saved to disk) there is no filename. In that case this API
+ // returns an empty string.
+ //
+ virtual
+ BOOL GetAssemblyPath(VMPTR_Assembly vmAssembly,
+ IStringHolder * pStrFilename) = 0;
+
+
+ // get a type def resolved across modules
+ // Arguments:
+ // input: pTypeRefInfo - domain file and type ref from the referencing module
+ // output: pTargetRefInfo - domain file and type def from the referenced type (this may
+ // come from a module other than the referencing module)
+ // Note: throws
+ virtual
+ void ResolveTypeReference(const TypeRefData * pTypeRefInfo,
+ TypeRefData * pTargetRefInfo) = 0;
+ //
+ // Get the full path and file name to the module (if any).
+ //
+ // Arguments:
+ // vmModule - VM pointer to the module.
+ // pStrFilename - required out parameter where the filename will be stored.
+ //
+ // Return Value:
+ // TRUE on success, in which case the filename was stored into pStrFilename
+ // FALSE the module has no filename (eg. for in-memory assemblies), in which
+ // case an empty string was stored into pStrFilename.
+ // Throws an exception if there was a problem reading the data with DAC, in which case
+ // no string was stored into pStrFilename.
+ //
+ // Notes:
+ // See code:#ModuleNames for an overview on module names.
+ //
+ // Normally this is just the filename from which the module was loaded.
+ // For in-memory module (eg. those loaded from a Byte[], and those created by Reflection.Emit
+ // which will not be saved to disk) there is no filename. In that case this API
+ // returns an empty string. Consider GetModuleSimpleName in those cases.
+ //
+ // We intentionally don't use the function name "GetModuleFileName" here because
+ // winbase #defines that token (along with many others) to have an A or W suffix.
+ //
+ virtual
+ BOOL GetModulePath(VMPTR_Module vmModule,
+ IStringHolder * pStrFilename) = 0;
+
+
+ //
+ // Get the full path and file name to the ngen image for the module (if any).
+ //
+ // Arguments:
+ // vmModule - VM pointer to the module.
+ // pStrFilename - required out parameter where the filename will be stored.
+ //
+ // Return Value:
+ // TRUE on success, in which case the filename was stored into pStrFilename
+ // FALSE the module has no filename (eg. for in-memory assemblies), in which
+ // case an empty string was stored into pStrFilename.
+ // Throws an exception if there was a problem reading the data with DAC, in which case
+ // no string was stored into pStrFilename.
+ //
+ // Notes:
+ // See code:#ModuleNames for an overview on module names.
+ //
+ virtual
+ BOOL GetModuleNGenPath(VMPTR_Module vmModule,
+ IStringHolder * pStrFilename) = 0;
+
+
+
+ // Get the metadata for the target module
+ //
+ // Arguments:
+ // vmModule - target module to get metadata for.
+ // pTargetBuffer - Out parameter to get target-buffer for metadata. Gauranteed to be non-empty on
+ // return. This will throw CORDBG_E_MISSING_METADATA hr if the buffer is empty.
+ // This does not gaurantee that the buffer is readable. For example, in a minidump, buffer's
+ // memory may not be present.
+ //
+ // Notes:
+ // Each module's metadata exists as a raw buffer in the target. This finds that target buffer and
+ // returns it. The host can then use OpenScopeOnMemory to create an instance of the metadata in
+ // the host process space.
+ //
+ // For dynamic modules, the CLR will eagerly serialize the metadata at "debuggable" points. This
+ // could be after each type is loaded; or after a bulk update.
+ // For non-dynamic modules (both in-memory and file-based), the metadata exists in the PEFile's image.
+ //
+ // Failure cases:
+ // This should succeed in normal, live-debugging scenarios. However, common failure paths here would be:
+ //
+ // 1. Data structures are intact, but Unable to even find the TargetBuffer in the target. In this
+ // case Metadata is truly missing. Likely means:
+ // - target is in the middle of generating metadata for a large bulk operation. (For example, attach
+ // to a TypeLibConverter using Ref.Emit to emit a module for a very large .tlb file).
+ // - corrupted target,
+ // - or the target had some error(out-of-memory?) generating the metadata.
+ // This throws CORDBG_E_MISSING_METADATA.
+ //
+ // 2. Target buffer is found, but memory it describes is not present. Likely means a minidump
+ // scenario with missing memory. Client should use alternative metadata location techniques (such as
+ // an ImagePath to locate the original image and then pulling metadata from that file).
+ //
+ virtual
+ void GetMetadata(VMPTR_Module vmModule, OUT TargetBuffer * pTargetBuffer) = 0;
+
+
+ // Definitions for possible symbol formats
+ // This is equivalent to code:ESymbolFormat in the runtime
+ typedef enum
+ {
+ kSymbolFormatNone, // No symbols available
+ kSymbolFormatPDB, // PDB symbol format - use diasymreader.dll
+ kSymbolFormatILDB, // ILDB symbol format - use ildbsymlib
+ } SymbolFormat;
+
+ //
+ // Get the in-memory symbol (PDB/ILDB) buffer in the target if present.
+ //
+ // Arguments:
+ // vmModule- module to query for.
+ // pTargetBuffer - out parameter to get buffer in target of symbols. If no symbols, pTargetBuffer is empty on return.
+ // pSymbolFormat - out parameter to get the format of the symbols.
+ //
+ // Returns:
+ // 1) If there are in-memory symbols for the given module, pTargetBuffer is set to the buffer describing
+ // the symbols and pSymbolFormat is set to indicate PDB or ILDB format. This buffer can then be read,
+ // converted into an IStream, and passed to ISymUnmanagedBinder::CreateReaderForStream.
+ // 2) If the target is valid, but there is no symbols for the module, then pTargetBuffer->IsEmpty() == true
+ // and *pSymbolFormat == kSymbolFormatNone.
+ // 3) Else, throws exception.
+ //
+ //
+ // Notes:
+ // For file-based modules, PDBs are normally on disk and the debugger retreieves them via a symbol
+ // path without any help from ICorDebug.
+ // However, in some cases, the PDB is stored in-memory and so the debugger needs ICorDebug. Common
+ // cases include:
+ // - dynamic modules generated with reflection-emit.
+ // - in-memory modules loaded by Load(Byte[],Byte[]), which provide the PDB as a byte[].
+ // - hosted modules where the host (such as SQL) store the PDB.
+ //
+ // In all cases, this can commonly fail. Executable code does not need to have a PDB.
+ virtual
+ void GetSymbolsBuffer(VMPTR_Module vmModule, OUT TargetBuffer * pTargetBuffer, OUT SymbolFormat * pSymbolFormat) = 0;
+
+ //
+ // Get properties for a module
+ //
+ // Arguments:
+ // vmModule - vm handle to a module
+ // pData - required out parameter which will be filled out with module properties
+ //
+ // Notes:
+ // See definition of DomainFileInfo for more details about what properties
+ // this gives back.
+ virtual
+ void GetModuleData(VMPTR_Module vmModule, OUT ModuleInfo * pData) = 0;
+
+
+ //
+ // Get properties for a DomainFile
+ //
+ // Arguments:
+ // vmDomainFile - vm handle to a DomainFile
+ // pData - required out parameter which will be filled out with module properties
+ //
+ // Notes:
+ // See definition of DomainFileInfo for more details about what properties
+ // this gives back.
+ virtual
+ void GetDomainFileData(VMPTR_DomainFile vmDomainFile, OUT DomainFileInfo * pData) = 0;
+
+ virtual
+ void GetModuleForDomainFile(VMPTR_DomainFile vmDomainFile, OUT VMPTR_Module * pModule) = 0;
+
+ //.........................................................................
+ // These methods were the methods that DBI was calling from IXClrData in V2.
+ // We imported them over to this V3 interface so that we can sever all ties between DBI and the
+ // old IXClrData.
+ //
+ // The exact semantics of these are whatever their V2 IXClrData counterpart did.
+ // We may eventually migrate these to their real V3 replacements.
+ //.........................................................................
+
+ // "types" of addresses. This is taken exactly from the definition, but renamed to match
+ // CLR coding conventions.
+ typedef enum
+ {
+ kAddressUnrecognized,
+ kAddressManagedMethod,
+ kAddressRuntimeManagedCode,
+ kAddressRuntimeUnmanagedCode,
+ kAddressGcData,
+ kAddressRuntimeManagedStub,
+ kAddressRuntimeUnmanagedStub,
+ } AddressType;
+
+ //
+ // Get the "type" of address.
+ //
+ // Arguments:
+ // address - address to query type.
+ //
+ // Return Value:
+ // Type of address. Throws on error.
+ //
+ // Notes:
+ // This is taken exactly from the IXClrData definition.
+ // This is provided for V3 compatibility to support Interop-debugging.
+ // This should eventually be deprecated.
+ //
+ virtual
+ AddressType GetAddressType(CORDB_ADDRESS address) = 0;
+
+
+ //
+ // Query if address is a CLR stub.
+ //
+ // Arguments:
+ // address - Target address to query for.
+ //
+ //
+ // Return Value:
+ // true if the address is a CLR stub.
+ //
+ // Notes:
+ // This is used to implement ICorDebugProcess::IsTransitionStub
+ // This yields true if the address is claimed by a CLR stub manager, or if the IP is in mscorwks.
+ // Conceptually, This should eventually be merged with GetAddressType().
+ //
+ virtual
+ BOOL IsTransitionStub(CORDB_ADDRESS address) = 0;
+
+ //.........................................................................
+ // Get the values of the JIT Optimization and EnC flags.
+ //
+ // Arguments:
+ // vmDomainFile - (input) VM DomainFile (module) for which we are retrieving flags
+ // pfAllowJITOpts - (mandatory output) true iff this is not compiled for debug,
+ // i.e., without optimization
+ // pfEnableEnc - (mandatory output) true iff this module has EnC enabled
+ //
+ // Return Value:
+ // Returns on success. Throws on failure.
+ //
+ // Notes:
+ // This is used to implement both ICorDebugModule2::GetJitCompilerFlags and
+ // ICorDebugCode2::GetCompilerFlags.
+ //.........................................................................
+
+ virtual
+ void GetCompilerFlags(
+ VMPTR_DomainFile vmDomainFile,
+ OUT BOOL * pfAllowJITOpts,
+ OUT BOOL * pfEnableEnC) = 0;
+
+ //.........................................................................
+ // Set the values of the JIT optimization and EnC flags.
+ //
+ // Arguments:
+ // vmDomainFile - (input) VM DomainFile (module) for which we are retrieving flags
+ // pfAllowJITOpts - (input) true iff this should not be compiled for debug,
+ // i.e., without optimization
+ // pfEnableEnc - (input) true iff this module should have EnC enabled. If this is
+ // false, no change is made to the EnC flags. In other words, once EnC is enabled,
+ // there is no way to disable it.
+ //
+ // Return Value:
+ // S_OK on success and all bits were set.
+ // CORDBG_S_NOT_ALL_BITS_SET - if not all bits are set. Must use GetCompileFlags to
+ // determine which bits were set.
+ // CORDBG_E_CANT_CHANGE_JIT_SETTING_FOR_ZAP_MODULE - if module is ngenned.
+ // Throw on other errors.
+ //
+ // Notes:
+ // Caller can only use this at module-load before any methods are jitted.
+ // This may be called multiple times.
+ // This is used to implement both ICorDebugModule2::SetJitCompilerFlags and
+ // ICorDebugModule::EnableJITDebugging.
+ //.........................................................................
+
+ virtual
+ HRESULT SetCompilerFlags(VMPTR_DomainFile vmDomainFile,
+ BOOL fAllowJitOpts,
+ BOOL fEnableEnC) = 0;
+
+ //
+ // Enumerate all AppDomains in the process.
+ //
+ // Arguments:
+ // fpCallback - callback to invoke on each appdomain
+ // pUserData - user data to supply for each callback.
+ //
+ // Return Value:
+ // Returns on success. Throws on error.
+ //
+ // Notes:
+ // Enumerates all appdomains in the process, including the Default-domain.
+ // Appdomains must show up in this list before the AD Load event is sent, and before
+ // that appdomain is discoverable from the debugger.
+ // See enumeration rules for details.
+ //
+ typedef void (*FP_APPDOMAIN_ENUMERATION_CALLBACK)(VMPTR_AppDomain vmAppDomain, CALLBACK_DATA pUserData);
+ virtual
+ void EnumerateAppDomains(FP_APPDOMAIN_ENUMERATION_CALLBACK fpCallback,
+ CALLBACK_DATA pUserData) = 0;
+
+
+ //
+ // Eunmerate all Assemblies in an appdomain. Enumerations is in load-order
+ //
+ // Arguments:
+ // vmAppDomain - domain in which to enumerate
+ // fpCallback - address to query type.
+ // pUserData - required out parameter for type of address.
+ //
+ // Return Value:
+ // Returns on success. Throws on error.
+ //
+ // Notes:
+ // Enumerates all executable assemblies (both shared and unshared) within an appdomain.
+ // This does not include inspection-only assemblies because those are just data and
+ // not executable (eg, they'll never show up on the stack and you can't set a breakpoint in them).
+ // This enumeration needs to be consistent with load/unload events.
+ // See enumeration rules for details.
+ //
+ // The order of the enumeration is the order the assemblies where loaded.
+ // Ultimately, the debugger needs to be able to tell the user the load
+ // order of assemblies (it can do this with native dlls). Since
+ // managed assembliees don't 1:1 correspond to native dlls, debuggers
+ // need this information from the runtime.
+ //
+
+ typedef void (*FP_ASSEMBLY_ENUMERATION_CALLBACK)(VMPTR_DomainAssembly vmDomainAssembly, CALLBACK_DATA pUserData);
+ virtual
+ void EnumerateAssembliesInAppDomain(VMPTR_AppDomain vmAppDomain,
+ FP_ASSEMBLY_ENUMERATION_CALLBACK fpCallback,
+ CALLBACK_DATA pUserData) = 0;
+
+
+
+ //
+ // Callback function for EnumerateModulesInAssembly
+ //
+ // This can throw on error.
+ //
+ // Arguments:
+ // vmModule - new module from the enumeration
+ // pUserData - user data passed to EnumerateModulesInAssembly
+ typedef void (*FP_MODULE_ENUMERATION_CALLBACK)(VMPTR_DomainFile vmModule, CALLBACK_DATA pUserData);
+
+ //
+ // Enumerates all the code Modules in an assembly.
+ //
+ // Arguments:
+ // vmAssembly - assembly to enumerate within
+ // fpCallback - callback function to invoke on each module
+ // pUserData - arbitrary data passed to the callback
+ //
+ // Notes:
+ // This only enumerates "code" modules (ie, modules that have executable code in them). That
+ // includes normal file-based, ngenned, in-memory, and even dynamic modules.
+ // That excludes:
+ // - Resource modules (which have no code or metadata)
+ // - Inspection-only modules. These are viewed as pure data from the debugger's perspective.
+ //
+ virtual
+ void EnumerateModulesInAssembly(
+ VMPTR_DomainAssembly vmAssembly,
+ FP_MODULE_ENUMERATION_CALLBACK fpCallback,
+ CALLBACK_DATA pUserData) = 0;
+
+
+
+ //
+ // When stopped at an event, request a synchronization.
+ //
+ //
+ // Return Value:
+ // Returns on success. Throws on error.
+ //
+ // Notes:
+ // Call this when an event is dispatched (eg, LoadModule) to request the runtime
+ // synchronize. This does a cooperative sync with the LS. This is not an async break
+ // and can not be called at arbitrary points.
+ // This primitive lets the LS always take the V3 codepath and defer decision making to the RS.
+ // The V2 behavior is to call this after every event (Since that's what V2 did).
+ // The V3 behavior is to never call this.
+ //
+ // If this is called, the LS will sync and we will get a SyncComplete.
+ //
+ // This is also like a precursor to "AsyncBreakAllOtherThreads"
+ //
+ virtual
+ void RequestSyncAtEvent() = 0;
+
+ // Sets a flag inside LS.Debugger that indicates that
+ // 1. all "first chance exception" events should not be sent to the debugger
+ // 2. "exception handler found" events for exceptions never crossing JMC frames should not be sent to the debugger
+ //
+ // Arguments:
+ // sendExceptionsOutsideOfJMC - new value for the flag Debugger::m_sendExceptionsOutsideOfJMC.
+ //
+ // Return Value:
+ // Returns error code, never throws.
+ //
+ // Note: This call is used by ICorDebugProcess8.EnableExceptionCallbacksOutsideOfMyCode.
+ virtual
+ HRESULT SetSendExceptionsOutsideOfJMC(BOOL sendExceptionsOutsideOfJMC) = 0;
+
+ //
+ // Notify the debuggee that a debugger atach is pending.
+ //
+ // Arguments:
+ // None
+ //
+ // Return Value:
+ // Returns on success. Throws on error.
+ //
+ // Notes:
+ // Attaching means that CORDebuggerPendingAttach() will now return true.
+ // This doesn't do anything else (eg, no fake events).
+ //
+ // @dbgtodo- still an open Feature-Crew decision how this is exposed publicly.
+ virtual
+ void MarkDebuggerAttachPending() = 0;
+
+ //
+ // Notify the debuggee that a debugger is attached / detached.
+ //
+ // Arguments:
+ // fAttached - true if we're attaching, false if we're detaching.
+ //
+ // Return Value:
+ // Returns on success. Throws on error.
+ //
+ // Notes:
+ // Attaching means that CorDebuggerAttached() will now return true.
+ // This doesn't do anything else (eg, no fake events).
+ // This lets the V3 codepaths invade the LS to subscribe to events.
+ //
+ // @dbgtodo- still an open Feature-Crew decision how this is exposed publicly.
+ virtual
+ void MarkDebuggerAttached(BOOL fAttached) = 0;
+
+
+
+ //
+ // Hijack a thread. This will effectively do a native func-eval of the thread to set the IP
+ // to a hijack stub and push the parameters.
+ //
+ // Arguments:
+ // dwThreadId - OS thread to hijack. This must be consistent with pRecord and pOriginalContext
+ // pRecord - optional pointer to Exception record. Required if this is hijacked at an exception.
+ // NULL if this is hijacked at a managed IP.
+ // pOriginalContext - optional pointer to buffer to receive the context that the thread is hijacked from.
+ // The caller can use this to either restore the hijack or walk the hijack.
+ // cbSizeContext - size in bytes of buffer pointed to by pContext
+ // reason - reason code for the hijack. The hijack stub can then delegate to the proper hijack.
+ // pUserData - arbitrary data passed through to hijack. This is reason-depedendent.
+ // pRemoteContextAddr - If non-NULL this receives the remote address where the CONTEXT was written in the
+ // in the debuggee.
+ //
+ // Assumptions:
+ // Caller must guarantee this is safe.
+ // This is intended to be used at a thread that either just had an exception or is at a managed IP.
+ // If this is hijacked at an exception, client must cancel the exception (gh / DBG_CONTINUE)
+ // so that the OS exception processing doesn't interfere with the hijack.
+ //
+ // Notes:
+ // Hijack is hard, so we want 1 hijack stub that handles all our hijacking needs.
+ // This lets us share:
+ // - assembly stubs (which are very platform specific)
+ // - hijacking / restoration mechanics,
+ // - making the hijack walkable via the stackwalker.
+ //
+ // Hijacking can be used to implement: func-eval, FE abort, Synchronizing,
+ // dispatching Unhandled Exception notifications.
+ //
+ // Nesting: Since Hijacking passes the key state off to the hijacked thread, (such as original
+ // context to be used with restoring the hijack), the raw hijacking nests just like function
+ // calls. However, the client may need to keep additional state to handle nesting. For example,
+ // nested hijacks will require the client to track multiple CONTEXT*.
+ //
+ // If the thread is in jitted code, then the hijack needs to cooperate with the in-process
+ // stackwalker that the GC uses. It must be in cooperative mode, and push a Frame on the
+ // frame chain to protect the managed frames it hijacked from before it goes to preemptive mode.
+
+ virtual
+ void Hijack(
+ VMPTR_Thread vmThread,
+ ULONG32 dwThreadId,
+ const EXCEPTION_RECORD * pRecord,
+ T_CONTEXT * pOriginalContext,
+ ULONG32 cbSizeContext,
+ EHijackReason::EHijackReason reason,
+ void * pUserData,
+ CORDB_ADDRESS * pRemoteContextAddr) = 0;
+
+
+ //
+ // Callback function for connection enumeration.
+ //
+ // Arguments:
+ // id - the connection ID.
+ // pName - the name of the connection.
+ // pUserData - user data supplied to EnumerateConnections
+ typedef void (*FP_CONNECTION_CALLBACK)(DWORD id, LPCWSTR pName, CALLBACK_DATA pUserData);
+
+ //
+ // Enumerate all the Connections in the process.
+ //
+ // Arguments:
+ // fpCallback - callback to invoke for each connection
+ // pUserData - random user data to pass to callback.
+ //
+ // Notes:
+ // This enumerates all the connections. The host notifies the debugger of Connections
+ // via the ICLRDebugManager interface.
+ // ICorDebug has no interest in connections. It's merely the transport between the host and the debugger.
+ // Ideally, that transport would be more general.
+ //
+ // V2 Attach would provide faked up CreateConnection, ChangeConnection events on attach.
+ // This enumeration ability allows V3 to emulate that behavior.
+ //
+#ifdef FEATURE_INCLUDE_ALL_INTERFACES
+ virtual void EnumerateConnections(FP_CONNECTION_CALLBACK fpCallback, CALLBACK_DATA pUserData) = 0;
+#endif //FEATURE_INCLUDE_ALL_INTERFACES
+
+ //
+ // Enumerate all threads in the target.
+ //
+ // Arguments:
+ // fpCallback - callback function to invoke on each thread.
+ // pUserData - arbitrary user data supplied to each callback.
+ //
+ // Notes:
+ // This enumerates the ThreadStore in the target, which is all the Thread* objects.
+ // This includes threads that have entered the runtime. This may include threads
+ // even before that thread has executed IL and after that thread no longer has managed
+ // code on its stack.
+
+ // Callback invoked for each thread.
+ typedef void (*FP_THREAD_ENUMERATION_CALLBACK)(VMPTR_Thread vmThread, CALLBACK_DATA pUserData);
+
+ virtual
+ void EnumerateThreads(FP_THREAD_ENUMERATION_CALLBACK fpCallback, CALLBACK_DATA pUserData) = 0;
+
+
+ // Check if the thread is dead
+ //
+ // Arguments:
+ // vmThread - valid thread to check if it's dead.
+ //
+ // Returns: true if the thread is "dead", which means it can never call managed code again.
+ //
+ // Notes:
+ // #IsThreadMarkedDead
+ // Threads shutdown states are:
+ // 1) Thread is running managed code normally. Thread eventually exits all managed code and
+ // gets to a point where it will never call managed code again.
+ // 2) Thread is marked as dead.
+ // - For threads created outside of the runtime (such as a native thread that wanders into
+ // managed code), this mark can happen in DllMain(ThreadDetach)
+ // - For threads created by the runtime (eg, System.Threading.Thread.Start), this may be done
+ // at the top of the threads stack after it calls the user's Thread-Proc.
+ // 3) MAYBE Native thread exits at this point (or it may not). This would be the common case
+ // for threads created outside the runtime.
+ // 4) Thread exit event is sent.
+ // - For threads created by the runtime, this may be sent at the top of the thread's
+ // stack (or even when we know that the thread will never execute managed code again)
+ // - For threads created outside the runtime, this is more difficult. A thread can
+ // call into managed code and then return, and then call back into managed code at a
+ // later time (The finalizer does this!). So it's not clear when the native thread
+ // actually exits and will never call managed code again. The only hook we have for
+ // this is DllMain(Thread-Detach). We can mark bits in DllMain, but we can't send
+ // debugger notifications (too dangerous from such a restricted context).
+ // So we may mark the thread as dead, but then sweep later (perhaps on the finalizer
+ // thread), and thus send the Exit events later.
+ // 5) Native thread may exit at this point. This is the common case for threads created by
+ // the runtime.
+ //
+ // The underlying native thread may have exited at eitehr #3 or #5. Because of this
+ // flexibility, we don't want to rely on native thread exit events.
+ // This function checks if a Thread is passed state #2 (marked as dead). The key invariant
+ // is that once a thread is marked as dead:
+ // - it can never call managed code again.
+ // - it should not be discoverable by DacDbi enumerations.
+ //
+ // DBI should prefer relying on IsThreadMarkedDead rather than event notifications (either
+ // managed or native) because tracking events requires that DBI maintain state, which means
+ // that attach + dump cases may break. For example, we want a full dump at the ExitThread
+ // event to have the same view as a live process at the ExitThread event.
+ //
+ // We avoid relying on the native thread exit notifications because:
+ // - that's a specific feature of the Win32 debugging API that may not be available on other platforms.
+ // - the only native events the pipeline gets are Exceptions.
+ //
+ // Whether a thread is dead can be inferred from the ICorDebug API. However, we have this
+ // on DacDbi to ensure that this definition is consistent with the other DacDbi methods,
+ // especially the enumeration and discovery rules.
+ virtual
+ bool IsThreadMarkedDead(VMPTR_Thread vmThread) = 0;
+
+
+ //
+ // Return the handle of the specified thread.
+ //
+ // Arguments:
+ // vmThread - the specified thread
+ //
+ // Return Value:
+ // the handle of the specified thread
+ //
+ // @dbgtodo- this should go away in V3. This is useless on a dump.
+
+ virtual
+ HANDLE GetThreadHandle(VMPTR_Thread vmThread) = 0;
+
+ //
+ // Return the object handle for the managed Thread object corresponding to the specified thread.
+ //
+ // Arguments:
+ // vmThread - the specified thread
+ //
+ // Return Value:
+ // This function returns the object handle for the managed Thread object corresponding to the
+ // specified thread. The return value may be NULL if a managed Thread object has not been created
+ // for the specified thread yet.
+ //
+
+ virtual
+ VMPTR_OBJECTHANDLE GetThreadObject(VMPTR_Thread vmThread) = 0;
+
+ //
+ // Set and reset the TSNC_DebuggerUserSuspend bit on the state of the specified thread
+ // according to the CorDebugThreadState.
+ //
+ // Arguments:
+ // vmThread - the specified thread
+ // debugState - the desired CorDebugThreadState
+ //
+
+ virtual
+ void SetDebugState(VMPTR_Thread vmThread,
+ CorDebugThreadState debugState) = 0;
+
+ //
+ // Returns TRUE if this thread has an unhandled exception
+ //
+ // Arguments:
+ // vmThread - the thread to query
+ //
+ // Return Value
+ // TRUE iff this thread has an unhandled exception
+ //
+ virtual
+ BOOL HasUnhandledException(VMPTR_Thread vmThread) = 0;
+
+ //
+ // Return the user state of the specified thread. Most of the state are derived from
+ // the ThreadState of the specified thread, e.g. TS_Background, TS_Unstarted, etc.
+ // The exception is USER_UNSAFE_POINT, which we need to do a one-frame stackwalk to figure out.
+ //
+ // Arguments:
+ // vmThread - the specified thread
+ //
+ // Return Value:
+ // the user state of the specified thread
+ //
+
+ virtual
+ CorDebugUserState GetUserState(VMPTR_Thread vmThread) = 0;
+
+
+ //
+ // Returns most of the user state of the specified thread,
+ // i.e. flags which can be derived from the ThreadState:
+ // USER_STOP_REQUESTED, USER_SUSPEND_REQUESTED, USER_BACKGROUND, USER_UNSTARTED
+ // USER_STOPPED, USER_WAIT_SLEEP_JOIN, USER_SUSPENDED, USER_THREADPOOL
+ //
+ // Only USER_UNSAFE_POINT is always set to 0, since it takes additional stackwalk.
+ // If you need USER_UNSAFE_POINT, use GetUserState(VMPTR_Thread);
+ //
+ // Arguments:
+ // vmThread - the specified thread
+ //
+ // Return Value:
+ // the user state of the specified thread
+ //
+ virtual
+ CorDebugUserState GetPartialUserState(VMPTR_Thread vmThread) = 0;
+
+
+ //
+ // Return the connection ID of the specified thread.
+ //
+ // Arguments:
+ // vmThread - the specified thread
+ //
+ // Return Value:
+ // the connection ID of the specified thread
+ //
+
+ virtual
+ CONNID GetConnectionID(VMPTR_Thread vmThread) = 0;
+
+ //
+ // Return the task ID of the specified thread.
+ //
+ // Arguments:
+ // vmThread - the specified thread
+ //
+ // Return Value:
+ // the task ID of the specified thread
+ //
+
+ virtual
+ TASKID GetTaskID(VMPTR_Thread vmThread) = 0;
+
+ //
+ // Return the OS thread ID of the specified thread
+ //
+ // Arguments:
+ // vmThread - the specified thread; cannot be NULL
+ //
+ // Return Value:
+ // the OS thread ID of the specified thread. Returns 0 if not scheduled.
+ //
+
+ virtual
+ DWORD TryGetVolatileOSThreadID(VMPTR_Thread vmThread) = 0;
+
+ //
+ // Return the unique thread ID of the specified thread. The value used for the thread ID changes
+ // depending on whether the runtime is being hosted. In non-hosted scenarios, a managed thread will
+ // always be associated with the same native thread, and so we can use the OS thread ID as the thread ID
+ // for the managed thread. In hosted scenarios, however, a managed thread may run on multiple native
+ // threads. It may not even have a backing native thread if it's switched out. Therefore, we can't use
+ // the OS thread ID as the thread ID. Instead, we use the internal managed thread ID.
+ //
+ // Arguments:
+ // vmThread - the specified thread; cannot be NULL
+ //
+ // Return Value:
+ // Returns a stable and unique thread ID for the lifetime of the specified managed thread.
+ //
+
+ virtual
+ DWORD GetUniqueThreadID(VMPTR_Thread vmThread) = 0;
+
+ //
+ // Return the object handle to the managed Exception object of the current exception
+ // on the specified thread. The return value could be NULL if there is no current exception.
+ //
+ // Arguments:
+ // vmThread - the specified thread
+ //
+ // Return Value:
+ // This function returns the object handle to the managed Exception object of the current exception.
+ // The return value may be NULL if there is no exception being processed, or if the specified thread
+ // is an unmanaged thread which has entered and exited the runtime.
+ //
+
+ virtual
+ VMPTR_OBJECTHANDLE GetCurrentException(VMPTR_Thread vmThread) = 0;
+
+ //
+ // Return the object handle to the managed object for a given CCW pointer.
+ //
+ // Arguments:
+ // ccwPtr - the specified ccw pointer
+ //
+ // Return Value:
+ // This function returns the object handle to the managed object for a given CCW pointer.
+ //
+
+ virtual
+ VMPTR_OBJECTHANDLE GetObjectForCCW(CORDB_ADDRESS ccwPtr) = 0;
+
+ //
+ // Return the object handle to the managed CustomNotification object of the current notification
+ // on the specified thread. The return value could be NULL if there is no current notification.
+ //
+ // Arguments:
+ // vmThread - the specified thread on which the notification occurred
+ //
+ // Return Value:
+ // This function returns the object handle to the managed CustomNotification object of the current notification.
+ // The return value may be NULL if there is no current notification.
+ //
+
+ virtual
+ VMPTR_OBJECTHANDLE GetCurrentCustomDebuggerNotification(VMPTR_Thread vmThread) = 0;
+
+
+ //
+ // Return the current appdomain the specified thread is in.
+ //
+ // Arguments:
+ // vmThread - the specified thread
+ //
+ // Return Value:
+ // the current appdomain of the specified thread
+ //
+ // Notes:
+ // This function throws if the current appdomain is NULL for whatever reason.
+ //
+
+ virtual
+ VMPTR_AppDomain GetCurrentAppDomain(VMPTR_Thread vmThread) = 0;
+
+
+ //
+ // Resolve an assembly
+ //
+ // Arguments:
+ // vmScope - module containing metadata that the token is scoped to.
+ // tkAssemblyRef - assembly ref token to lookup.
+ //
+ // Returns:
+ // Assembly that the loader/fusion has bound to the given assembly ref.
+ // Returns NULL if the assembly has not yet been loaded (a common case).
+ // Throws on error.
+ //
+ // Notes:
+ // A single module has metadata that specifies references via tokens. The
+ // loader/fusion goes through tremendous and random policy hoops to determine
+ // which specific file actually gets bound to the reference. This policy includes
+ // things like config files, registry settings, and many other knobs.
+ //
+ // The debugger can't duplicate this policy with 100% accuracy, and
+ // so we need DAC to lookup the assembly that was actually loaded.
+ virtual
+ VMPTR_DomainAssembly ResolveAssembly(VMPTR_DomainFile vmScope, mdToken tkAssemblyRef) = 0;
+
+ //-----------------------------------------------------------------------------
+ // Interface for initializing the native/IL sequence points and native var info
+ // for a function.
+ // Arguments:
+ // input:
+ // vmMethodDesc MethodDesc of the function
+ // startAddr starting address of the function--this serves to
+ // differentiate various EnC versions of the function
+ // fCodePitched indicates whether code for the function has been pitched
+ // fJitComplete indicates whether the function has been jitted
+ // output:
+ // pNativeVarData space for the native code offset information for locals
+ // pSequencePoints space for the IL/native sequence points
+ // Return value:
+ // none, but may throw an exception
+ // Assumptions:
+ // vmMethodDesc, pNativeVarInfo and pSequencePoints are non-NULL
+
+ // Notes:
+ //-----------------------------------------------------------------------------
+
+ virtual
+ void GetNativeCodeSequencePointsAndVarInfo(VMPTR_MethodDesc vmMethodDesc,
+ CORDB_ADDRESS startAddress,
+ BOOL fCodeAvailabe,
+ OUT NativeVarData * pNativeVarData,
+ OUT SequencePoints * pSequencePoints) = 0;
+
+ //
+ // Return the filter CONTEXT on the LS. Once we move entirely over to the new managed pipeline
+ // built on top of the Win32 debugging API, this won't be necessary.
+ //
+ // Arguments:
+ // vmThread - the specified thread
+ //
+ // Return Value:
+ // the filter CONTEXT of the specified thread
+ //
+ // Notes:
+ // This function should go away when everything is moved OOP and
+ // we don't have a filter CONTEXT on the LS anymore.
+ //
+
+ virtual
+ VMPTR_CONTEXT GetManagedStoppedContext(VMPTR_Thread vmThread) = 0;
+
+ typedef enum
+ {
+ kInvalid,
+ kManagedStackFrame,
+ kExplicitFrame,
+ kNativeStackFrame,
+ kNativeRuntimeUnwindableStackFrame,
+ kAtEndOfStack,
+ } FrameType;
+
+ // The stackwalker functions allocate persistent state within DDImpl. Clients can hold onto
+ // this via an opaque StackWalkHandle.
+ typedef void* * StackWalkHandle;
+
+ //
+ // Create a stackwalker on the specified thread and return a handle to it.
+ // Initially, the stackwalker is at the filter CONTEXT if there is one.
+ // Otherwise it is at the leaf CONTEXT. It DOES NOT fast forward to the first frame of interest.
+ //
+ // Arguments:
+ // vmThread - the specified thread
+ // pInternalContextBuffer - a CONTEXT buffer for the stackwalker to work with
+ // ppSFIHandle - out parameter; return a handle to the stackwalker
+ //
+ // Notes:
+ // Call DeleteStackWalk() to delete the stackwalk buffer.
+ // This is a special case that violates the 'no state' tenant.
+ //
+
+ virtual
+ void CreateStackWalk(VMPTR_Thread vmThread,
+ DT_CONTEXT * pInternalContextBuffer,
+ OUT StackWalkHandle * ppSFIHandle) = 0;
+
+ // Delete the stackwalk object created from CreateStackWalk.
+ virtual
+ void DeleteStackWalk(StackWalkHandle ppSFIHandle) = 0;
+
+ //
+ // Get the CONTEXT of the current frame where the stackwalker is stopped at.
+ //
+ // Arguments:
+ // pSFIHandle - the handle to the stackwalker
+ // pContext - OUT: the CONTEXT to be filled out. The context control flags are ignored.
+ //
+
+ virtual
+ void GetStackWalkCurrentContext(StackWalkHandle pSFIHandle,
+ DT_CONTEXT * pContext) = 0;
+
+ //
+ // Set the stackwalker to the given CONTEXT. The CorDebugSetContextFlag indicates whether
+ // the CONTEXT is "active", meaning that the IP is point at the current instruction,
+ // not the return address of some function call.
+ //
+ // Arguments:
+ // vmThread - the current thread
+ // pSFIHandle - the handle to the stackwalker
+ // flag - flag to indicate whether the specified CONTEXT is "active"
+ // pContext - the specified CONTEXT. This may make correctional adjustments to the context's IP.
+ //
+
+ virtual
+ void SetStackWalkCurrentContext(VMPTR_Thread vmThread,
+ StackWalkHandle pSFIHandle,
+ CorDebugSetContextFlag flag,
+ DT_CONTEXT * pContext) = 0;
+
+ //
+ // Unwind the stackwalker to the next frame. The next frame could be any actual stack frame,
+ // explicit frame, native marker frame, etc. Call GetStackWalkCurrentFrameInfo() to find out
+ // more about the frame.
+ //
+ // Arguments:
+ // pSFIHandle - the handle to the stackwalker
+ //
+ // Return Value:
+ // Return TRUE if we successfully unwind to the next frame.
+ // Return FALSE if there is no more frames to walk.
+ // Throw on error.
+ //
+
+ virtual
+ BOOL UnwindStackWalkFrame(StackWalkHandle pSFIHandle) = 0;
+
+ //
+ // Check whether the specified CONTEXT is valid. The only check we perform right now is whether the
+ // SP in the specified CONTEXT is in the stack range of the thread.
+ //
+ // Arguments:
+ // vmThread - the specified thread
+ // pContext - the CONTEXT to be checked
+ //
+ // Return Value:
+ // Return S_OK if the CONTEXT passes our checks.
+ // Returns CORDBG_E_NON_MATCHING_CONTEXT if the SP in the specified CONTEXT doesn't fall in the stack
+ // range of the thread.
+ // Throws on error.
+ //
+
+ virtual
+ HRESULT CheckContext(VMPTR_Thread vmThread,
+ const DT_CONTEXT * pContext) = 0;
+
+ //
+ // Fill in the DebuggerIPCE_STRData structure with information about the current frame
+ // where the stackwalker is stopped at.
+ //
+ // Arguments:
+ // pSFIHandle - the handle to the stackwalker
+ // pFrameData - the DebuggerIPCE_STRData to be filled out;
+ // it can be NULL if you just want to know the frame type
+ //
+ // Return Value:
+ // Return the type of the current frame
+ //
+
+ virtual
+ FrameType GetStackWalkCurrentFrameInfo(StackWalkHandle pSFIHandle,
+ OPTIONAL DebuggerIPCE_STRData * pFrameData) = 0;
+
+ //
+ // Return the number of internal frames on the specified thread.
+ //
+ // Arguments:
+ // vmThread - the thread whose internal frames are being retrieved
+ //
+ // Return Value:
+ // Return the number of internal frames.
+ //
+ // Notes:
+ // Explicit frames are "marker objects" the runtime pushes on the stack to mark special places, e.g.
+ // appdomain transition, managed-to- unmanaged transition, etc. Internal frames are only a subset of
+ // explicit frames. Explicit frames which are not interesting to the debugger are not exposed (e.g.
+ // GCFrame). Internal frames are interesting to the debugger if they have a CorDebugInternalFrameType
+ // other than STUBFRAME_NONE.
+ //
+ // The user should call this function before code:IDacDbiInterface::EnumerateInternalFrames to figure
+ // out how many interesting internal frames there are.
+ //
+
+ virtual
+ ULONG32 GetCountOfInternalFrames(VMPTR_Thread vmThread) = 0;
+
+ //
+ // Enumerate the internal frames on the specified thread and invoke the provided callback on each of
+ // them. Information about the internal frame is stored in the DebuggerIPCE_STRData.
+ //
+ // Arguments:
+ // vmThread - the thread to be walked fpCallback - callback function invoked on each internal frame
+ // pUserData - user-specified custom data
+ //
+ // Notes:
+ // The user can call code:IDacDbiInterface::GetCountOfInternalFrames to figure out how many internal
+ // frames are on the thread before calling this function. Also, refer to the comment of that function
+ // to find out more about internal frames.
+ //
+
+ typedef void (*FP_INTERNAL_FRAME_ENUMERATION_CALLBACK)(const DebuggerIPCE_STRData * pFrameData, CALLBACK_DATA pUserData);
+
+ virtual
+ void EnumerateInternalFrames(VMPTR_Thread vmThread,
+ FP_INTERNAL_FRAME_ENUMERATION_CALLBACK fpCallback,
+ CALLBACK_DATA pUserData) = 0;
+
+ //
+ // Given the FramePointer of the parent frame and the FramePointer of the current frame,
+ // check if the current frame is the parent frame. fpParent should have been returned
+ // previously by the DacDbiInterface via GetStackWalkCurrentFrameInfo().
+ //
+ // Arguments:
+ // fpToCheck - the FramePointer of the current frame
+ // fpParent - the FramePointer of the parent frame; should have been returned earlier by the DDI
+ //
+ // Return Value:
+ // Return TRUE if the current frame is indeed the parent frame
+ //
+ // Note:
+ // Because of the complexity involved in checking for the parent frame, we should always
+ // ask the ExceptionTracker to do it.
+ //
+
+ virtual
+ BOOL IsMatchingParentFrame(FramePointer fpToCheck, FramePointer fpParent) = 0;
+
+ //
+ // Return the stack parameter size of a given method. This is necessary on x86 for unwinding.
+ //
+ // Arguments:
+ // controlPC - any address in the specified method; you can use the current PC of the stack frame
+ //
+ // Return Value:
+ // Return the size of the stack parameters of the given method.
+ // Return 0 for vararg methods.
+ //
+ // Assumptions:
+ // The callee stack parameter size is constant throughout a method.
+ //
+
+ virtual
+ ULONG32 GetStackParameterSize(CORDB_ADDRESS controlPC) = 0;
+
+ //
+ // Return the FramePointer of the current frame where the stackwalker is stopped at.
+ //
+ // Arguments:
+ // pSFIHandle - the handle to the stackwalker
+ //
+ // Return Value:
+ // the FramePointer of the current frame
+ //
+ // Notes:
+ // The FramePointer of a stack frame is:
+ // the stack address of the return address on x86,
+ // the current SP on AMD64,
+ //
+ // On x86, to get the stack address of the return address, we need to unwind one more frame
+ // and use the SP of the caller frame as the FramePointer of the callee frame. This
+ // function does NOT do that. It just returns the SP. The caller needs to handle the
+ // unwinding.
+ //
+ // The FramePointer of an explicit frame is just the stack address of the explicit frame.
+ //
+
+ virtual
+ FramePointer GetFramePointer(StackWalkHandle pSFIHandle) = 0;
+
+ //
+ // Check whether the specified CONTEXT is the CONTEXT of the leaf frame. This function doesn't care
+ // whether the leaf frame is native or managed.
+ //
+ // Arguments:
+ // vmThread - the specified thread
+ // pContext - the CONTEXT to check
+ //
+ // Return Value:
+ // Return TRUE if the specified CONTEXT is the leaf CONTEXT.
+ //
+ // Notes:
+ // Currently we check the specified CONTEXT against the filter CONTEXT first.
+ // This will be deprecated in V3.
+ //
+
+ virtual
+ BOOL IsLeafFrame(VMPTR_Thread vmThread,
+ const DT_CONTEXT * pContext) = 0;
+
+ // Get the context for a particular thread of the target process.
+ // Arguments:
+ // input: vmThread - the thread for which the context is required
+ // output: pContextBuffer - the address of the CONTEXT to be initialized.
+ // The memory for this belongs to the caller. It must not be NULL.
+ // Note: throws
+ virtual
+ void GetContext(VMPTR_Thread vmThread, DT_CONTEXT * pContextBuffer) = 0;
+
+ //
+ // This is a simple helper function to convert a CONTEXT to a DebuggerREGDISPLAY. We need to do this
+ // inside DDI because the RS has no notion of REGDISPLAY.
+ //
+ // Arguments:
+ // pInContext - the CONTEXT to be converted
+ // pOutDRD - the converted DebuggerREGDISPLAY
+ // fActive - Indicate whether the CONTEXT is active or not. An active CONTEXT means that the
+ // IP is the next instruction to be executed, not the return address of a function call.
+ // The opposite of an active CONTEXT is an unwind CONTEXT, which is obtained from
+ // unwinding.
+ //
+
+ virtual
+ void ConvertContextToDebuggerRegDisplay(const DT_CONTEXT * pInContext,
+ DebuggerREGDISPLAY * pOutDRD,
+ BOOL fActive) = 0;
+
+ typedef enum
+ {
+ kNone,
+ kILStub,
+ kLCGMethod,
+ } DynamicMethodType;
+
+ //
+ // Check whether the specified method is an IL stub or an LCG method. This answer determines if we
+ // need to expose the method in a V2-style stackwalk.
+ //
+ // Arguments:
+ // vmMethodDesc - the method to be checked
+ //
+ // Return Value:
+ // Return kNone if the method is neither an IL stub or an LCG method.
+ // Return kILStub if the method is an IL stub.
+ // Return kLCGMethod if the method is an LCG method.
+ //
+
+ virtual
+ DynamicMethodType IsILStubOrLCGMethod(VMPTR_MethodDesc vmMethodDesc) = 0;
+
+ //
+ // Return a TargetBuffer for the raw vararg signature.
+ // Also return the address of the first argument in the vararg signature.
+ //
+ // Arguments:
+ // VASigCookieAddr - the target address of the VASigCookie pointer (double indirection)
+ // pArgBase - out parameter; return the target address of the first word of the arguments
+ //
+ // Return Value:
+ // Return a TargetBuffer for the raw vararg signature.
+ //
+ // Notes:
+ // We can't take a VMPTR here because VASigCookieAddr does not come from the DDI. Instead,
+ // we use the native variable information to figure out which stack slot contains the
+ // VASigCookie pointer. So a remote address is all we have got.
+ //
+ // Ideally we should be able to return just a SigParser, but doing so has a not-so-trivial problem.
+ // The memory used for the signature pointed to by the SigParser cannot be allocated in the DAC cache,
+ // since it'll be used by mscordbi. We don't have a clean way to allocate memory in mscordbi without
+ // breaking the Signature abstraction.
+ //
+ // The other option would be to create a new sub-type like "SignatureCopy" which allocates and frees
+ // its own backing memory. Currently we don't want to share heaps between mscordacwks.dll and
+ // mscordbi.dll, and so we would have to jump through some hoops to allocate with an allocator
+ // in mscordbi.dll.
+ //
+
+ virtual
+ TargetBuffer GetVarArgSig(CORDB_ADDRESS VASigCookieAddr,
+ OUT CORDB_ADDRESS * pArgBase) = 0;
+
+ //
+ // Indicates if the specified type requires 8-byte alignment.
+ //
+ // Arguments:
+ // thExact - the exact TypeHandle of the type to query
+ //
+ // Return Value:
+ // TRUE if the type requires 8-byte alignment.
+ //
+
+ virtual
+ BOOL RequiresAlign8(VMPTR_TypeHandle thExact) = 0;
+
+ //
+ // Resolve the raw generics token to the real generics type token. The resolution is based on the
+ // given index. See Notes below.
+ //
+ // Arguments:
+ // dwExactGenericArgsTokenIndex - the variable index of the generics type token
+ // rawToken - the raw token to be resolved
+ //
+ // Return Value:
+ // Return the actual generics type token.
+ //
+ // Notes:
+ // DDI tells the RS which variable stores the generics type token, but DDI doesn't retrieve the value
+ // of the variable itself. Instead, the RS retrieves the value of the variable. However,
+ // in some cases, the variable value is not the generics type token. In this case, we need to
+ // "resolve" the variable value to the generics type token. The RS should call this API to do that.
+ //
+ // If the index is 0, then the generics type token is the MethodTable of the "this" object.
+ // rawToken will be the address of the "this" object.
+ //
+ // If the index is TYPECTXT_ILNUM, the generics type token is a secret argument.
+ // It could be a MethodDesc or a MethodTable, and in this case no resolution is actually necessary.
+ // rawToken will be the actual secret argument, and this API really is just a nop.
+ //
+ // However, we don't want the RS to know all this logic.
+ //
+
+ virtual
+ GENERICS_TYPE_TOKEN ResolveExactGenericArgsToken(DWORD dwExactGenericArgsTokenIndex,
+ GENERICS_TYPE_TOKEN rawToken) = 0;
+
+ //-----------------------------------------------------------------------------
+ // Functions to get information about code objects
+ //-----------------------------------------------------------------------------
+
+ // GetILCodeAndSig returns the function's ILCode and SigToken given
+ // a module and a token. The info will come from a MethodDesc, if
+ // one exists or from metadata.
+ //
+ // Arguments:
+ // Input:
+ // vmDomainFile - module containing metadata for the method
+ // functionToken - metadata token for the function
+ // Output (required):
+ // codeInfo - start address and size of the IL
+ // pLocalSigToken - signature token for the method
+ virtual
+ void GetILCodeAndSig(VMPTR_DomainFile vmDomainFile,
+ mdToken functionToken,
+ OUT TargetBuffer * pCodeInfo,
+ OUT mdToken * pLocalSigToken) = 0;
+
+ // Gets information about a native code blob:
+ // it's method desc, whether it's an instantiated generic, its EnC version number
+ // and hot and cold region information.
+ // Arguments:
+ // Input:
+ // vmDomainFile - module containing metadata for the method
+ // functionToken - token for the function for which we need code info
+ // Output (required):
+ // pCodeInfo - data structure describing the native code regions.
+ // Notes: If the function is unjitted, the method desc will be NULL and the
+ // output parameter will be invalid. In general, if the native start address
+ // is unavailable for any reason, the output parameter will also be
+ // invalid (i.e., pCodeInfo->IsValid is false).
+
+ virtual
+ void GetNativeCodeInfo(VMPTR_DomainFile vmDomainFile,
+ mdToken functionToken,
+ OUT NativeCodeFunctionData * pCodeInfo) = 0;
+
+ // Gets information about a native code blob:
+ // it's method desc, whether it's an instantiated generic, its EnC version number
+ // and hot and cold region information.
+ // This is similar to function above, just works from a different starting point
+ // Also this version can get info for any particular EnC version instance
+ // because they all have different start addresses whereas the above version gets
+ // the most recent one
+ // Arguments:
+ // Input:
+ // hotCodeStartAddr - the beginning of the code hot code region
+ // Output (required):
+ // pCodeInfo - data structure describing the native code regions.
+
+ virtual
+ void GetNativeCodeInfoForAddr(VMPTR_MethodDesc vmMethodDesc,
+ CORDB_ADDRESS hotCodeStartAddr,
+ NativeCodeFunctionData * pCodeInfo) = 0;
+
+ //-----------------------------------------------------------------------------
+ // Functions to get information about types
+ //-----------------------------------------------------------------------------
+
+ // Determine if a type is a ValueType
+ //
+ // Arguments:
+ // input: vmTypeHandle - the type being checked (works even on unrestored types)
+ //
+ // Return:
+ // TRUE iff the type is a ValueType
+
+ virtual
+ BOOL IsValueType (VMPTR_TypeHandle th) = 0;
+
+ // Determine if a type has generic parameters
+ //
+ // Arguments:
+ // input: vmTypeHandle - the type being checked (works even on unrestored types)
+ //
+ // Return:
+ // TRUE iff the type has generic parameters
+
+ virtual
+ BOOL HasTypeParams (VMPTR_TypeHandle th) = 0;
+
+ // Get type information for a class
+ //
+ // Arguments:
+ // input: vmAppDomain - appdomain where we will fetch field data for the type
+ // thExact - exact type handle for type
+ // output:
+ // pData - structure containing information about the class and its
+ // fields
+
+ virtual
+ void GetClassInfo (VMPTR_AppDomain vmAppDomain,
+ VMPTR_TypeHandle thExact,
+ ClassInfo * pData) = 0;
+
+ // get field information and object size for an instantiated generic
+ //
+ // Arguments:
+ // input: vmDomainFile - module containing metadata for the type
+ // thExact - exact type handle for type (may be NULL)
+ // thApprox - approximate type handle for the type
+ // output:
+ // pFieldList - array of structures containing information about the fields. Clears any previous
+ // contents. Allocated and initialized by this function.
+ // pObjectSize - size of the instantiated object
+ //
+ virtual
+ void GetInstantiationFieldInfo (VMPTR_DomainFile vmDomainFile,
+ VMPTR_TypeHandle vmThExact,
+ VMPTR_TypeHandle vmThApprox,
+ OUT DacDbiArrayList<FieldData> * pFieldList,
+ OUT SIZE_T * pObjectSize) = 0;
+
+ // use a type handle to get the information needed to create the corresponding RS CordbType instance
+ //
+ // Arguments:
+ // input: boxed - indicates what, if anything, is boxed. See code:AreValueTypesBoxed for more
+ // specific information
+ // vmAppDomain - module containing metadata for the type
+ // vmTypeHandle - type handle for the type
+ // output: pTypeInfo - holds information needed to build the corresponding CordbType
+ //
+ virtual
+ void TypeHandleToExpandedTypeInfo(AreValueTypesBoxed boxed,
+ VMPTR_AppDomain vmAppDomain,
+ VMPTR_TypeHandle vmTypeHandle,
+ DebuggerIPCE_ExpandedTypeData * pTypeInfo) = 0;
+
+ virtual
+ void GetObjectExpandedTypeInfo(AreValueTypesBoxed boxed,
+ VMPTR_AppDomain vmAppDomain,
+ CORDB_ADDRESS addr,
+ OUT DebuggerIPCE_ExpandedTypeData * pTypeInfo) = 0;
+
+
+ virtual
+ void GetObjectExpandedTypeInfoFromID(AreValueTypesBoxed boxed,
+ VMPTR_AppDomain vmAppDomain,
+ COR_TYPEID id,
+ OUT DebuggerIPCE_ExpandedTypeData * pTypeInfo) = 0;
+
+
+ // Get type handle for a TypeDef token, if one exists. For generics this returns the open type.
+ // Note there is no guarantee the returned handle will be fully restored (in pre-jit scenarios),
+ // only that it exists. Later functions that use this type handle should fail if they require
+ // information not yet available at the current restoration level
+ //
+ // Arguments:
+ // input: vmModule - the module scope in which to look up the type def
+ // metadataToken - the type definition to retrieve
+ //
+ // Return value: the type handle if it exists or throws CORDBG_E_CLASS_NOT_LOADED if it isn't loaded
+ //
+ virtual
+ VMPTR_TypeHandle GetTypeHandle(VMPTR_Module vmModule,
+ mdTypeDef metadataToken) = 0;
+
+ // Get the approximate type handle for an instantiated type. This may be identical to the exact type handle,
+ // but if we have code sharing for generics, it may differ in that it may have canonical type parameters.
+ // This will occur if we have not yet loaded an exact type but we have loaded the canonical form of the
+ // type.
+ //
+ // Arguments:
+ // input: pTypeData - information needed to get the type handle, this includes a list of type parameters
+ // and the number of entries in the list. Allocated and initialized by the caller.
+ // Return value: the approximate type handle
+ //
+ virtual
+ VMPTR_TypeHandle GetApproxTypeHandle(TypeInfoList * pTypeData) = 0;
+
+ // Get the exact type handle from type data.
+ // Arguments:
+ // input: pTypeData - type information for the type. includes information about
+ // the top-level type as well as information
+ // about the element type for array types, the referent for
+ // pointer types, or actual parameters for generic class or
+ // valuetypes, as appropriate for the top-level type.
+ // pArgInfo - This is preallocated and initialized by the caller and contains two fields:
+ // genericArgsCount - number of type parameters (these may be actual type parameters
+ // for generics or they may represent the element type or referent
+ // type.
+ // pGenericArgData - list of type parameters
+ // vmTypeHandle - the exact type handle derived from the type information
+ // Return Value: an HRESULT indicating the result of the operation
+ virtual
+ HRESULT GetExactTypeHandle(DebuggerIPCE_ExpandedTypeData * pTypeData,
+ ArgInfoList * pArgInfo,
+ VMPTR_TypeHandle& vmTypeHandle) = 0;
+
+ //
+ // Retrieve the generic type params for a given MethodDesc. This function is specifically
+ // for stackwalking because it requires the generic type token on the stack.
+ //
+ // Arguments:
+ // vmAppDomain - the appdomain of the MethodDesc
+ // vmMethodDesc - the method in question
+ // genericsToken - the generic type token in the stack frame owned by the method
+ //
+ // pcGenericClassTypeParams - out parameter; returns the number of type parameters for the class
+ // containing the method in question; must not be NULL
+ // pGenericTypeParams - out parameter; returns an array of type parameters and
+ // the count of the total number of type parameters; must not be NULL
+ //
+ // Notes:
+ // The memory for the array is allocated by this function on the Dbi heap.
+ // The caller is responsible for releasing it.
+ //
+
+ virtual
+ void GetMethodDescParams(VMPTR_AppDomain vmAppDomain,
+ VMPTR_MethodDesc vmMethodDesc,
+ GENERICS_TYPE_TOKEN genericsToken,
+ OUT UINT32 * pcGenericClassTypeParams,
+ OUT TypeParamsList * pGenericTypeParams) = 0;
+
+ // Get the target field address of a context or thread local static.
+ // Arguments:
+ // input: vmField - pointer to the field descriptor for the static field
+ // vmRuntimeThread - thread to which the static field belongs. This must
+ // NOT be NULL
+ // Return Value: The target address of the field if the field is allocated.
+ // NULL if the field storage is not yet allocated.
+ //
+ // Note:
+ // Static field storage is lazily allocated, so this may commonly return NULL.
+ // This is an inspection only method and can not allocate the static storage.
+ // Field storage is constant once allocated, so this value can be cached.
+
+ virtual
+ CORDB_ADDRESS GetThreadOrContextStaticAddress(VMPTR_FieldDesc vmField,
+ VMPTR_Thread vmRuntimeThread) = 0;
+
+ // Get the target field address of a collectible types static.
+ // Arguments:
+ // input: vmField - pointer to the field descriptor for the static field
+ // vmAppDomain - AppDomain to which the static field belongs. This must
+ // NOT be NULL
+ // Return Value: The target address of the field if the field is allocated.
+ // NULL if the field storage is not yet allocated.
+ //
+ // Note:
+ // Static field storage may not exist yet, so this may commonly return NULL.
+ // This is an inspection only method and can not allocate the static storage.
+ // Field storage is not constant once allocated so this value can not be cached
+ // across a Continue
+
+ virtual
+ CORDB_ADDRESS GetCollectibleTypeStaticAddress(VMPTR_FieldDesc vmField,
+ VMPTR_AppDomain vmAppDomain) = 0;
+
+ // Get information about a field added with Edit And Continue.
+ // Arguments:
+ // intput: pEnCFieldInfo - information about the EnC added field including:
+ // object to which it belongs (if this is null the field is static)
+ // the field token
+ // the class token for the class to which the field was added
+ // the offset to the fields
+ // the domain file
+ // an indication of the type: whether it's a class or value type
+ // output: pFieldData - information about the EnC added field
+ // pfStatic - flag to indicate whether the field is static
+ virtual
+ void GetEnCHangingFieldInfo(const EnCHangingFieldInfo * pEnCFieldInfo,
+ OUT FieldData * pFieldData,
+ OUT BOOL * pfStatic) = 0;
+
+
+ // GetTypeHandleParams gets the necessary data for a type handle, i.e. its
+ // type parameters, e.g. "String" and "List<int>" from the type handle
+ // for "Dict<String,List<int>>", and sends it back to the right side.
+ // Arguments:
+ // input: vmAppDomain - app domain to which the type belongs
+ // vmTypeHandle - type handle for the type
+ // output: pParams - list of instances of DebuggerIPCE_ExpandedTypeData,
+ // one for each type parameter. These will be used on the
+ // RS to build up an instantiation which will allow
+ // building an instance of CordbType for the top-level
+ // type. The memory for this list is allocated on the dbi
+ // heap in this function.
+ // This will not fail except for OOM
+
+ virtual
+ void GetTypeHandleParams(VMPTR_AppDomain vmAppDomain,
+ VMPTR_TypeHandle vmTypeHandle,
+ OUT TypeParamsList * pParams) = 0;
+
+ // GetSimpleType
+ // gets the metadata token and domain file corresponding to a simple type
+ // Arguments:
+ // input: vmAppDomain - Appdomain in which simpleType resides
+ // simpleType - CorElementType value corresponding to a simple type
+ // output: pMetadataToken - the metadata token corresponding to simpleType,
+ // in the scope of vmDomainFile.
+ // vmDomainFile - the domainFile for simpleType
+ // Notes:
+ // This is inspection-only. If the type is not yet loaded, it will throw CORDBG_E_CLASS_NOT_LOADED.
+ // It will not try to load a type.
+ // If the type has been loaded, vmDomainFile will be non-null unless the target is somehow corrupted.
+ // In that case, we will throw CORDBG_E_TARGET_INCONSISTENT.
+
+ virtual
+ void GetSimpleType(VMPTR_AppDomain vmAppDomain,
+ CorElementType simpleType,
+ OUT mdTypeDef * pMetadataToken,
+ OUT VMPTR_Module * pVmModule,
+ OUT VMPTR_DomainFile * pVmDomainFile) = 0;
+
+ // for the specified object returns TRUE if the object derives from System.Exception
+ virtual
+ BOOL IsExceptionObject(VMPTR_Object vmObject) = 0;
+
+ // gets the list of raw stack frames for the specified exception object
+ virtual
+ void GetStackFramesFromException(VMPTR_Object vmObject, DacDbiArrayList<DacExceptionCallStackData>& dacStackFrames) = 0;
+
+ // Returns true if the argument is a runtime callable wrapper
+ virtual
+ BOOL IsRcw(VMPTR_Object vmObject) = 0;
+
+ // retrieves the list of COM interfaces implemented by vmObject, as it is known at
+ // the time of the call (the list may change as new interface types become available
+ // in the runtime)
+ virtual
+ void GetRcwCachedInterfaceTypes(
+ VMPTR_Object vmObject,
+ VMPTR_AppDomain vmAppDomain,
+ BOOL bIInspectableOnly,
+ OUT DacDbiArrayList<DebuggerIPCE_ExpandedTypeData> * pDacInterfaces) = 0;
+
+ // retrieves the list of interfaces pointers implemented by vmObject, as it is known at
+ // the time of the call (the list may change as new interface types become available
+ // in the runtime)
+ virtual
+ void GetRcwCachedInterfacePointers(
+ VMPTR_Object vmObject,
+ BOOL bIInspectableOnly,
+ OUT DacDbiArrayList<CORDB_ADDRESS> * pDacItfPtrs) = 0;
+
+ // retrieves a list of interface types corresponding to the passed in
+ // list of IIDs. the interface types are retrieved from an app domain
+ // IID / Type cache, that is updated as new types are loaded. will
+ // have NULL entries corresponding to unknown IIDs in "iids"
+ virtual
+ void GetCachedWinRTTypesForIIDs(
+ VMPTR_AppDomain vmAppDomain,
+ DacDbiArrayList<GUID> & iids,
+ OUT DacDbiArrayList<DebuggerIPCE_ExpandedTypeData> * pTypes) = 0;
+
+ // retrieves the whole app domain cache of IID / Type mappings.
+ virtual
+ void GetCachedWinRTTypes(
+ VMPTR_AppDomain vmAppDomain,
+ OUT DacDbiArrayList<GUID> * piids,
+ OUT DacDbiArrayList<DebuggerIPCE_ExpandedTypeData> * pTypes) = 0;
+
+
+ // ----------------------------------------------------------------------------
+ // functions to get information about reference/handle referents for ICDValue
+ // ----------------------------------------------------------------------------
+
+ // Get object information for a TypedByRef object. Initializes the objRef and typedByRefType fields of
+ // pObjectData (type info for the referent).
+ // Arguments:
+ // input: pTypedByRef - pointer to a TypedByRef struct
+ // vmAppDomain - AppDomain for the type of the object referenced
+ // output: pObjectData - information about the object referenced by pTypedByRef
+ // Note: Throws
+ virtual
+ void GetTypedByRefInfo(CORDB_ADDRESS pTypedByRef,
+ VMPTR_AppDomain vmAppDomain,
+ DebuggerIPCE_ObjectData * pObjectData) = 0;
+
+ // Get the string length and offset to string base for a string object
+ // Arguments:
+ // input: objPtr - address of a string object
+ // output: pObjectData - fills in the string fields stringInfo.offsetToStringBase and
+ // stringInfo.length
+ // Note: throws
+ virtual
+ void GetStringData(CORDB_ADDRESS objectAddress, DebuggerIPCE_ObjectData * pObjectData) = 0;
+
+ // Get information for an array type referent of an objRef, including rank, upper and lower bounds,
+ // element size and type, and the number of elements.
+ // Arguments:
+ // input: objectAddress - the address of an array object
+ // output: pObjectData - fills in the array-related fields:
+ // arrayInfo.offsetToArrayBase,
+ // arrayInfo.offsetToLowerBounds,
+ // arrayInfo.offsetToUpperBounds,
+ // arrayInfo.componentCount,
+ // arrayInfo.rank,
+ // arrayInfo.elementSize,
+ // Note: throws
+ virtual
+ void GetArrayData(CORDB_ADDRESS objectAddress, DebuggerIPCE_ObjectData * pObjectData) = 0;
+
+ // Get information about an object for which we have a reference, including the object size and
+ // type information.
+ // Arguments:
+ // input: objectAddress - address of the object for which we want information
+ // type - the basic type of the object (we may find more specific type
+ // information for the object)
+ // vmAppDomain - the appdomain to which the object belong
+ // output: pObjectData - fills in the size and type information fields
+ // Note: throws
+ virtual
+ void GetBasicObjectInfo(CORDB_ADDRESS objectAddress,
+ CorElementType type,
+ VMPTR_AppDomain vmAppDomain,
+ DebuggerIPCE_ObjectData * pObjectData) = 0;
+
+ // --------------------------------------------------------------------------------------------
+#ifdef TEST_DATA_CONSISTENCY
+ // Determine whether a crst is held by the left side. When the DAC is executing VM code that takes a
+ // lock, we want to know whether the LS already holds that lock. If it does, we will assume the locked
+ // data is in an inconsistent state and will throw an exception, rather than relying on this data. This
+ // function is part of a self-test that will ensure we are correctly detecting when the LS holds a lock
+ // on data the RS is trying to inspect.
+ // Argument:
+ // input: vmCrst - the lock to test
+ // output: none
+ // Notes:
+ // Throws
+ // For this code to run, the environment variable TestDataConsistency must be set to 1.
+ virtual
+ void TestCrst(VMPTR_Crst vmCrst) = 0;
+
+ // Determine whether a crst is held by the left side. When the DAC is executing VM code that takes a
+ // lock, we want to know whether the LS already holds that lock. If it does, we will assume the locked
+ // data is in an inconsistent state and will throw an exception, rather than relying on this data. This
+ // function is part of a self-test that will ensure we are correctly detecting when the LS holds a lock
+ // on data the RS is trying to inspect.
+ // Argument:
+ // input: vmRWLock - the lock to test
+ // output: none
+ // Notes:
+ // Throws
+ // For this code to run, the environment variable TestDataConsistency must be set to 1.
+
+ virtual
+ void TestRWLock(VMPTR_SimpleRWLock vmRWLock) = 0;
+#endif
+ // --------------------------------------------------------------------------------------------
+ // Get the address of the Debugger control block on the helper thread. The debugger control block
+ // contains information about the status of the debugger, handles to various events and space to hold
+ // information sent back and forth between the debugger and the debuggee's helper thread.
+ // Arguments: none
+ // Return Value: The remote address of the Debugger control block allocated on the helper thread
+ // if it has been successfully allocated or NULL otherwise.
+ virtual
+ CORDB_ADDRESS GetDebuggerControlBlockAddress() = 0;
+
+ // Creates a VMPTR of an Object. The Object is found by dereferencing ptr
+ // as though it is a target address to an OBJECTREF. This is similar to
+ // GetObject with another level of indirection.
+ //
+ // Arguments:
+ // ptr - A target address pointing to an OBJECTREF
+ //
+ // Return Value:
+ // A VMPTR to the Object which ptr points to
+ //
+ // Notes:
+ // The VMPTR this produces can be deconstructed by GetObjectContents.
+ // This function will throw if given a NULL or otherwise invalid pointer,
+ // but if given a valid address to an invalid pointer, it will produce
+ // a VMPTR_Object which points to invalid memory.
+ virtual
+ VMPTR_Object GetObjectFromRefPtr(CORDB_ADDRESS ptr) = 0;
+
+ // Creates a VMPTR of an Object. The Object is assumed to be at the target
+ // address supplied by ptr
+ //
+ // Arguments:
+ // ptr - A target address to an Object
+ //
+ // Return Value:
+ // A VMPTR to the Object which was at ptr
+ //
+ // Notes:
+ // The VMPTR this produces can be deconstructed by GetObjectContents.
+ // This will produce a VMPTR_Object regardless of whether the pointer is
+ // valid or not.
+ virtual
+ VMPTR_Object GetObject(CORDB_ADDRESS ptr) = 0;
+
+ // Sets state in the native binder.
+ //
+ // Arguments:
+ // ePolicy - the NGEN policy to change
+ //
+ // Return Value:
+ // HRESULT indicating if the state was successfully updated
+ //
+ virtual
+ HRESULT EnableNGENPolicy(CorDebugNGENPolicy ePolicy) = 0;
+
+ // Sets the NGEN compiler flags. This restricts NGEN to only use images with certain
+ // types of pregenerated code. With respect to debugging this is used to specify that
+ // the NGEN image must be debuggable aka non-optimized code. Note that these flags
+ // are merged with other sources of configuration so it is possible that the final
+ // result retrieved from GetDesiredNGENCompilerFlags does not match what was specfied
+ // in this call.
+ //
+ // If an NGEN image of the appropriate type isn't available then one of two things happens:
+ // a) the NGEN image isn't loaded and CLR loads the MSIL image instead
+ // b) the NGEN image is loaded, but we don't use the pregenerated code it contains
+ // and instead use only the MSIL and metadata
+ //
+ // This function is only legal to call at app startup before any decisions have been
+ // made about NGEN image loading. Once we begin loading this configuration is immutable.
+ //
+ //
+ // Arguments:
+ // dwFlags - the new NGEN compiler flags that should go into effect
+ //
+ // Return Value:
+ // HRESULT indicating if the state was successfully updated. On error the
+ // current flags in effect will not have changed.
+ //
+ virtual
+ HRESULT SetNGENCompilerFlags(DWORD dwFlags) = 0;
+
+ // Gets the NGEN compiler flags currently in effect. This accounts for settings that
+ // were caused by SetDesiredNGENCompilerFlags as well as other configuration sources.
+ // See SetDesiredNGENCompilerFlags for more info
+ //
+ // Arguments:
+ // pdwFlags - the NGEN compiler flags currently in effect
+ //
+ // Return Value:
+ // HRESULT indicating if the state was successfully retrieved.
+ //
+ virtual
+ HRESULT GetNGENCompilerFlags(DWORD *pdwFlags) = 0;
+
+ // Create a VMPTR_OBJECTHANDLE from a CORDB_ADDRESS pointing to an object handle
+ //
+ // Arguments:
+ // handle: target address of a GC handle
+ //
+ // ReturnValue:
+ // returns a VMPTR_OBJECTHANDLE with the handle as the m_addr field
+ //
+ // Notes:
+ // This will produce a VMPTR_OBJECTHANDLE regardless of whether handle is
+ // valid.
+ // Ideally we'd be using only strongly-typed variables on the RS, and then this would be unnecessary
+ virtual
+ VMPTR_OBJECTHANDLE GetVmObjectHandle(CORDB_ADDRESS handleAddress) = 0;
+
+ // Validate that the VMPTR_OBJECTHANDLE refers to a legitimate managed object
+ //
+ // Arguments:
+ // handle: the GC handle to be validated
+ //
+ // Return value:
+ // TRUE if the object appears to be valid (its a heuristic), FALSE if it definately is not valid
+ //
+ virtual
+ BOOL IsVmObjectHandleValid(VMPTR_OBJECTHANDLE vmHandle) = 0;
+
+ // indicates if the specified module is a WinRT module
+ //
+ // Arguments:
+ // vmModule: the module to check
+ // isWinRT: out parameter indicating state of module
+ //
+ // Return value:
+ // S_OK indicating that the operation succeeded
+ //
+ virtual
+ HRESULT IsWinRTModule(VMPTR_Module vmModule, BOOL& isWinRT) = 0;
+
+ // Determines the app domain id for the object refered to by a given VMPTR_OBJECTHANDLE
+ //
+ // Arguments:
+ // handle: the GC handle which refers to the object of interest
+ //
+ // Return value:
+ // The app domain id of the object of interest
+ //
+ // This may throw if the object handle is corrupt (it doesn't refer to a managed object)
+ virtual
+ ULONG GetAppDomainIdFromVmObjectHandle(VMPTR_OBJECTHANDLE vmHandle) = 0;
+
+
+ // Get the target address from a VMPTR_OBJECTHANDLE, i.e., the handle address
+ // Arguments:
+ // vmHandle - (input) the VMPTR_OBJECTHANDLE from which we need the target address
+ // Return value: the target address from the VMPTR_OBJECTHANDLE
+ //
+ virtual
+ CORDB_ADDRESS GetHandleAddressFromVmHandle(VMPTR_OBJECTHANDLE vmHandle) = 0;
+
+ // Given a VMPTR to an Object return the target address
+ //
+ // Arguments:
+ // obj - the Object VMPTR to get the address from
+ //
+ // Return Value:
+ // Return the target address which obj is using
+ //
+ // Notes:
+ // The VMPTR this consumes can be reconstructed using GetObject and
+ // providing the address stored in the returned TargetBuffer. This has
+ // undefined behavior for invalid VMPTR_Objects.
+
+ virtual
+ TargetBuffer GetObjectContents(VMPTR_Object obj) = 0;
+
+ // The callback used to enumerate blocking objects
+ typedef void (*FP_BLOCKINGOBJECT_ENUMERATION_CALLBACK)(DacBlockingObject blockingObject,
+ CALLBACK_DATA pUserData);
+
+ //
+ // Enumerate all monitors blocking a thread
+ //
+ // Arguments:
+ // vmThread - the thread to get monitor data for
+ // fpCallback - callback to invoke on the blocking data for each monitor
+ // pUserData - user data to supply for each callback.
+ //
+ // Return Value:
+ // Returns on success. Throws on error.
+ //
+ //
+ virtual
+ void EnumerateBlockingObjects(VMPTR_Thread vmThread,
+ FP_BLOCKINGOBJECT_ENUMERATION_CALLBACK fpCallback,
+ CALLBACK_DATA pUserData) = 0;
+
+
+
+ //
+ // Returns the thread which owns the monitor lock on an object and the acquisition
+ // count
+ //
+ // Arguments:
+ // vmObject - The object to check for ownership
+
+ //
+ // Return Value:
+ // Throws on error. Inside the structure we have:
+ // pVmThread - the owning or thread or VMPTR_Thread::NullPtr() if unowned
+ // pAcquisitionCount - the number of times the lock would need to be released in
+ // order for it to be unowned
+ //
+ virtual
+ MonitorLockInfo GetThreadOwningMonitorLock(VMPTR_Object vmObject) = 0;
+
+ //
+ // Enumerate all threads waiting on the monitor event for an object
+ //
+ // Arguments:
+ // vmObject - the object whose monitor event we are interested in
+ // fpCallback - callback to invoke on each thread in the queue
+ // pUserData - user data to supply for each callback.
+ //
+ // Return Value:
+ // Returns on success. Throws on error.
+ //
+ //
+ virtual
+ void EnumerateMonitorEventWaitList(VMPTR_Object vmObject,
+ FP_THREAD_ENUMERATION_CALLBACK fpCallback,
+ CALLBACK_DATA pUserData) = 0;
+
+ //
+ // Returns the managed debugging flags for the process (a combination
+ // of the CLR_DEBUGGING_PROCESS_FLAGS flags). This function specifies,
+ // beyond whether or not a managed debug event is pending, also if the
+ // event (if one exists) is caused by a Debugger.Launch(). This is
+ // important b/c Debugger.Launch calls should *NOT* cause the debugger
+ // to terminate the process when the attach is canceled.
+ virtual
+ CLR_DEBUGGING_PROCESS_FLAGS GetAttachStateFlags() = 0;
+
+ virtual
+ bool GetMetaDataFileInfoFromPEFile(VMPTR_PEFile vmPEFile,
+ DWORD & dwTimeStamp,
+ DWORD & dwImageSize,
+ bool & isNGEN,
+ IStringHolder* pStrFilename) = 0;
+
+ virtual
+ bool GetILImageInfoFromNgenPEFile(VMPTR_PEFile vmPEFile,
+ DWORD & dwTimeStamp,
+ DWORD & dwSize,
+ IStringHolder* pStrFilename) = 0;
+
+
+ virtual
+ bool IsThreadSuspendedOrHijacked(VMPTR_Thread vmThread) = 0;
+
+
+ typedef void* * HeapWalkHandle;
+
+ // Returns true if it is safe to walk the heap. If this function returns false,
+ // you could still create a heap walk and attempt to walk it, but there's no
+ // telling how much of the heap will be available.
+ virtual
+ bool AreGCStructuresValid() = 0;
+
+ // Creates a HeapWalkHandle which can be used to walk the managed heap with the
+ // WalkHeap function. Note if this function completes successfully you will need
+ // to delete the handle by passing it into DeleteHeapWalk.
+ //
+ // Arguments:
+ // pHandle - the location to store the heap walk handle in
+ //
+ // Returns:
+ // S_OK on success, an error code on failure.
+ virtual
+ HRESULT CreateHeapWalk(OUT HeapWalkHandle * pHandle) = 0;
+
+
+ // Deletes the give HeapWalkHandle. Note you must call this function if
+ // CreateHeapWalk returns success.
+ virtual
+ void DeleteHeapWalk(HeapWalkHandle handle) = 0;
+
+ // Walks the heap using the given heap walk handle, enumerating objects
+ // on the managed heap. Note that walking the heap requires that the GC
+ // data structures be in a valid state, which you can find by calling
+ // AreGCStructuresValid.
+ //
+ // Arguments:
+ // handle - a HeapWalkHandle obtained from CreateHeapWalk
+ // count - the number of object addresses to obtain; pValues must
+ // be at least as large as count
+ // objects - the location to stuff the object addresses found during
+ // the heap walk; this array should be at least "count" in
+ // length; this field must not be null
+ // pFetched - a location to store the actual number of values filled
+ // into pValues; this field must not be null
+ //
+ // Returns:
+ // S_OK on success, a failure HRESULT otherwise.
+ //
+ // Note:
+ // You should iteratively call WalkHeap requesting more values until
+ // *pFetched != count.. This signifies that we have reached the end
+ // of the heap walk.
+ virtual
+ HRESULT WalkHeap(HeapWalkHandle handle,
+ ULONG count,
+ OUT COR_HEAPOBJECT * objects,
+ OUT ULONG * pFetched) = 0;
+
+ virtual
+ HRESULT GetHeapSegments(OUT DacDbiArrayList<COR_SEGMENT> * pSegments) = 0;
+
+ virtual
+ bool IsValidObject(CORDB_ADDRESS obj) = 0;
+
+ virtual
+ bool GetAppDomainForObject(CORDB_ADDRESS obj, OUT VMPTR_AppDomain * pApp,
+ OUT VMPTR_Module * pModule,
+ OUT VMPTR_DomainFile * pDomainFile) = 0;
+
+
+ // Reference Walking.
+
+ // Creates a reference walk.
+ // Parameters:
+ // pHandle - out - the reference walk handle to create
+ // walkStacks - in - whether or not to report stack references
+ // walkFQ - in - whether or not to report references from the finalizer queue
+ // handleWalkMask - in - the types of handles report (see CorGCReferenceType, cordebug.idl)
+ // Returns:
+ // An HRESULT indicating whether it succeded or failed.
+ // Exceptions:
+ // Does not throw, but does not catch exceptions either.
+ virtual
+ HRESULT CreateRefWalk(OUT RefWalkHandle * pHandle, BOOL walkStacks, BOOL walkFQ, UINT32 handleWalkMask) = 0;
+
+ // Deletes a reference walk.
+ // Parameters:
+ // handle - in - the handle of the reference walk to delete
+ // Excecptions:
+ // Does not throw, but does not catch exceptions either.
+ virtual
+ void DeleteRefWalk(RefWalkHandle handle) = 0;
+
+ // Enumerates GC references in the process based on the parameters passed to CreateRefWalk.
+ // Parameters:
+ // handle - in - the RefWalkHandle to enumerate
+ // count - in - the capacity of "refs"
+ // refs - in/out - an array to write the references to
+ // pFetched - out - the number of references written
+ virtual
+ HRESULT WalkRefs(RefWalkHandle handle, ULONG count, OUT DacGcReference * refs, OUT ULONG * pFetched) = 0;
+
+ virtual
+ HRESULT GetTypeID(CORDB_ADDRESS obj, COR_TYPEID * pType) = 0;
+
+ virtual
+ HRESULT GetObjectFields(COR_TYPEID id, ULONG32 celt, OUT COR_FIELD * layout, OUT ULONG32 * pceltFetched) = 0;
+
+ virtual
+ HRESULT GetTypeLayout(COR_TYPEID id, COR_TYPE_LAYOUT * pLayout) = 0;
+
+ virtual
+ HRESULT GetArrayLayout(COR_TYPEID id, COR_ARRAY_LAYOUT * pLayout) = 0;
+
+ virtual
+ void GetGCHeapInformation(OUT COR_HEAPINFO * pHeapInfo) = 0;
+
+ // If a PEFile has an RW capable IMDInternalImport, this returns the address of the MDInternalRW
+ // object which implements it.
+ //
+ //
+ // Arguments:
+ // vmPEFile - target PEFile to get metadata MDInternalRW for.
+ // pAddrMDInternalRW - If a PEFile has an RW capable IMDInternalImport, this will be set to the address
+ // of the MDInternalRW object which implements it. Otherwise it will be NULL.
+ //
+ virtual
+ HRESULT GetPEFileMDInternalRW(VMPTR_PEFile vmPEFile, OUT TADDR* pAddrMDInternalRW) = 0;
+
+ // Retrieves the active ReJitInfo for a given module/methodDef, if it exists.
+ // Active is defined as after GetReJitParameters returns from the profiler dll and
+ // no call to Revert has completed yet.
+ //
+ //
+ // Arguments:
+ // vmModule - The module to search in
+ // methodTk - The methodDef token indicates the method within the module to check
+ // pReJitInfo - [out] The RejitInfo request, if any, that is active on this method. If no request
+ // is active this will be pReJitInfo->IsNull() == TRUE.
+ //
+ // Returns:
+ // S_OK regardless of whether a rejit request is active or not, as long as the answer is certain
+ // error HRESULTs such as CORDBG_READ_VIRTUAL_FAILURE are possible
+ //
+ virtual
+ HRESULT GetReJitInfo(VMPTR_Module vmModule, mdMethodDef methodTk, OUT VMPTR_ReJitInfo* pReJitInfo) = 0;
+
+ // Retrieves the active ReJitInfo for a given MethodDesc/code address, if it exists.
+ // Active is defined as after GetReJitParameters returns from the profiler dll and
+ // no call to Revert has completed yet.
+ //
+ //
+ // Arguments:
+ // vmMethod - The method to look for
+ // codeStartAddress - The code start address disambiguates between multiple rejitted instances
+ // of the method.
+ // pReJitInfo - [out] The RejitInfo request, if any, that is active on this method. If no request
+ // is active this will be pReJitInfo->IsNull() == TRUE.
+ //
+ // Returns:
+ // S_OK regardless of whether a rejit request is active or not, as long as the answer is certain
+ // error HRESULTs such as CORDBG_READ_VIRTUAL_FAILURE are possible
+ //
+ virtual
+ HRESULT GetReJitInfo(VMPTR_MethodDesc vmMethod, CORDB_ADDRESS codeStartAddress, OUT VMPTR_ReJitInfo* pReJitInfo) = 0;
+
+
+ // Retrieves the SharedReJitInfo for a given ReJitInfo.
+ //
+ //
+ // Arguments:
+ // vmReJitInfo - The ReJitInfo to inspect
+ // pSharedReJitInfo - [out] The SharedReJitInfo that is pointed to by vmReJitInfo.
+ //
+ // Returns:
+ // S_OK if no error
+ // error HRESULTs such as CORDBG_READ_VIRTUAL_FAILURE are possible
+ //
+ virtual
+ HRESULT GetSharedReJitInfo(VMPTR_ReJitInfo vmReJitInfo, VMPTR_SharedReJitInfo* pSharedReJitInfo) = 0;
+
+ // Retrieves useful data from a SharedReJitInfo such as IL code and IL mapping.
+ //
+ //
+ // Arguments:
+ // sharedReJitInfo - The SharedReJitInfo to inspect
+ // pData - [out] Various properties of the SharedReJitInfo such as IL code and IL mapping.
+ //
+ // Returns:
+ // S_OK if no error
+ // error HRESULTs such as CORDBG_READ_VIRTUAL_FAILURE are possible
+ //
+ virtual
+ HRESULT GetSharedReJitInfoData(VMPTR_SharedReJitInfo sharedReJitInfo, DacSharedReJitInfo* pData) = 0;
+
+ // Retrieves a bit field indicating which defines were in use when clr was built. This only includes
+ // defines that are specified in the Debugger::_Target_Defines enumeration, which is a small subset of
+ // all defines.
+ //
+ //
+ // Arguments:
+ // pDefines - [out] The set of defines clr.dll was built with. Bit offsets are encoded using the
+ // enumeration Debugger::_Target_Defines
+ //
+ // Returns:
+ // S_OK if no error
+ // error HRESULTs such as CORDBG_READ_VIRTUAL_FAILURE are possible
+ //
+ virtual
+ HRESULT GetDefinesBitField(ULONG32 *pDefines) = 0;
+
+ // Retrieves a version number indicating the shape of the data structures used in the Metadata implementation
+ // inside clr.dll. This number changes anytime a datatype layout changes so that they can be correctly
+ // deserialized from out of process
+ //
+ //
+ // Arguments:
+ // pMDStructuresVersion - [out] The layout version number for metadata data structures. See
+ // Debugger::Debugger() in Debug\ee\Debugger.cpp for a description of the options.
+ //
+ // Returns:
+ // S_OK if no error
+ // error HRESULTs such as CORDBG_READ_VIRTUAL_FAILURE are possible
+ //
+ virtual
+ HRESULT GetMDStructuresVersion(ULONG32* pMDStructuresVersion) = 0;
+
+ // The following tag tells the DD-marshalling tool to stop scanning.
+ // END_MARSHAL
+
+ //-----------------------------------------------------------------------------
+ // Utility interface used for passing strings out of these APIs. The caller
+ // provides an implementation of this that uses whatever memory allocation
+ // strategy it desires, and IDacDbiInterface APIs will call AssignCopy in order
+ // to pass back the contents of strings.
+ //
+ // This permits the client and implementation of IDacDbiInterface to be in
+ // different DLLs with their own heap allocation mechanism, while avoiding
+ // the ugly and verbose 2-call C-style string passing API pattern.
+ //-----------------------------------------------------------------------------
+ class IStringHolder
+ {
+ public:
+ //
+ // Store a copy of of the provided string.
+ //
+ // Arguments:
+ // psz - The null-terminated unicode string to copy.
+ //
+ // Return Value:
+ // S_OK on success, typical HRESULT return values on failure.
+ //
+ // Notes:
+ // The underlying object is responsible for allocating and freeing the
+ // memory for this copy. The object must not store the value of psz,
+ // it is no longer valid after this call returns.
+ //
+ virtual
+ HRESULT AssignCopy(const WCHAR * psz) = 0;
+ };
+
+
+ //-----------------------------------------------------------------------------
+ // Interface for allocations
+ // This lets DD allocate buffers to pass back to DBI; and thus avoids
+ // the common 2-step (query size/allocate/query data) pattern.
+ //
+ // Note that mscordacwks.dll and clients cannot share the same heap allocator,
+ // DAC statically links the CRT to avoid run-time dependencies on non-OS libraries.
+ //-----------------------------------------------------------------------------
+ class IAllocator
+ {
+ public:
+ // Allocate
+ // Expected to throw on error.
+ virtual
+ void * Alloc(SIZE_T lenBytes) = 0;
+
+ // Free. This shouldn't throw.
+ virtual
+ void Free(void * p) = 0;
+ };
+
+
+ //-----------------------------------------------------------------------------
+ // Callback interface to provide Metadata lookup.
+ //-----------------------------------------------------------------------------
+ class IMetaDataLookup
+ {
+ public:
+ //
+ // Lookup a metadata importer via PEFile.
+ //
+ // Returns:
+ // A IMDInternalImport used by dac-ized VM code. The object is NOT addref-ed. See lifespan notes below.
+ // Returns NULL if no importer is available.
+ // Throws on exceptional circumstances (eg, detects the debuggee is corrupted).
+ //
+ // Notes:
+ // IMDInternalImport is a property of PEFile. The DAC-ized code uses it as a weak reference,
+ // and so we avoid doing an AddRef() here because that would mean we need to add Release() calls
+ // in DAC-only paths.
+ // The metadata importers are not DAC-ized, and thus we have a local copy in the host.
+ // If it was dac-ized, then DAC would get the importer just like any other field.
+ //
+ // lifespan of returned object:
+ // - DBI owns the metadata importers.
+ // - DBI must not free the importer without calling Flush() on DAC first.
+ // - DAC will only invoke this when in a DD primitive, which was in turn invoked by DBI.
+ // - For performance reasons, we want to allow DAC to cache this between Flush() calls.
+ // - If DAC caches the importer, it will only use it when DBI invokes a DD primitive.
+ // - the reference count of the returned object is not adjusted.
+ //
+ virtual
+ IMDInternalImport * LookupMetaData(VMPTR_PEFile addressPEFile, bool &isILMetaDataForNGENImage) = 0;
+ };
+
+}; // end IDacDbiInterface
+
+
+#endif // _DACDBI_INTERFACE_H_
diff --git a/src/debug/inc/dacdbistructures.h b/src/debug/inc/dacdbistructures.h
new file mode 100644
index 0000000000..d6f2c0b943
--- /dev/null
+++ b/src/debug/inc/dacdbistructures.h
@@ -0,0 +1,790 @@
+// 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.
+//*****************************************************************************
+// File: DacDbiStructures.h
+//
+
+//
+// Declarations and inline functions for data structures shared between by the
+// DAC/DBI interface functions and the right side.
+//
+// Note that for MAC these structures are marshalled between Windows and Mac
+// and so their layout and size must be identical in both builds. Use the
+// MSLAYOUT macro on every structure to avoid compiler packing differences.
+//
+//*****************************************************************************
+
+#ifndef DACDBISTRUCTURES_H_
+#define DACDBISTRUCTURES_H_
+
+#include "./common.h"
+
+//-------------------------------------------------------------------------------
+// classes shared by the DAC/DBI interface functions and the right side
+//-------------------------------------------------------------------------------
+
+// DacDbiArrayList encapsulates an array and the number of elements in the array.
+// Notes:
+// - storage is always on the DacDbi heap
+// - this class owns the memory. Its dtor will free.
+// - Operations that initialize list elements use the assignment
+// operator defined for type T. If T is a pointer type or has pointer
+// type components and no assignment operator override, this will make a shallow copy of
+// the element. If T has an assignment operator override that makes a deep copy of pointer
+// types, T must also have a destructor that will deallocate any memory allocated.
+// - this is NOT thread safe!!!
+// - the array elements are always mutable, but the number of elements is fixed between allocations
+// - you can gain access to the array using &(list[0]) but this is NOT safe if the array is empty. You
+// can call IsEmpty to determine if it is safe to access the array portion
+// This list is not designed to have unused elements at the end of the array (extra space) nor to be growable
+
+// usage examples:
+// typedef DacDbiArrayList<Bar> BarList; // handy typedef
+// void GetAListOfBars(BarList * pBarList)
+// {
+// DacDbiArrayList<Foo> fooList; // fooList is an empty array of objects of type Foo
+// int elementCount = GetNumberOfFoos();
+// Bar * pBars = new Bar[elementCount];
+//
+// fooList.Alloc(elementCount); // get space for the list of Foo instances
+// for (int i = 0; i < fooList.Count(); ++i)
+// {
+// fooList[i] = GetNextFoo(); // copy elements into the list
+// }
+// ConvertFoosToBars(pBars, &fooList); // always pass by reference
+// pBarList->Init(pBars, fooList.Count()); // initialize a list
+// }
+//
+// void ConvertFoosToBars(Bar * pBars, DacDbiArrayList<Foo> * pFooList)
+// {
+// for (int i = 0; i < pFooList->Count(); ++i)
+// {
+// if ((*pFooList)[i].IsBaz())
+// pBars [i] = ConvertBazToBar(&(*pFooList)[i]);
+// else pBars [i] = (*pFooList)[i].barPart;
+// }
+// }
+//
+template<class T>
+class MSLAYOUT DacDbiArrayList
+{
+public:
+ // construct an empty list
+ DacDbiArrayList();
+
+ // deep copy constructor
+ DacDbiArrayList(const T * list, int count);
+
+ // destructor--sets list to empty state
+ ~DacDbiArrayList();
+
+ // explicitly deallocate the list and set it back to the empty state
+ void Dealloc();
+
+ // allocate a list with space for nElements items
+ void Alloc(int nElements);
+
+ // allocate and initialize a DacDbiArrayList from an array of type T and a count
+ void Init(const T * list, int count);
+
+ // predicate to indicate if the list is empty
+ bool IsEmpty() { return m_nEntries == 0; }
+
+ // read-only element accessor
+ const T & operator [](int index) const;
+
+ // writeable element accessor
+ T & operator [](int index);
+
+
+ // returns the number of elements in the list
+ int Count() const;
+
+ // @dbgtodo Mac - cleaner way to expose this for serialization?
+ void PrepareForDeserialize()
+ {
+ m_pList = NULL;
+ }
+private:
+ // because these are private (and unimplemented), calls will generate a compiler (or linker) error.
+ // This prevents accidentally invoking the default (shallow) copy ctor or assignment operator.
+ // This prevents having multiple instances point to the same list memory (eg. due to passing by value),
+ // which would result in memory corruption when the first copy is destroyed and the list memory is deallocated.
+ DacDbiArrayList(const DacDbiArrayList<T> & sourceList);
+ T & operator = (const DacDbiArrayList<T> & rhs);
+
+// data members
+protected:
+ T * m_pList; // the list
+
+ // - the count is managed by the member functions and is not settable, so (m_pList == NULL) == (m_nEntries == 0)
+ // is always true.
+ int m_nEntries; // the number of items in the list
+
+};
+
+
+// Describes a buffer in the target
+struct MSLAYOUT TargetBuffer
+{
+ TargetBuffer();
+ TargetBuffer(CORDB_ADDRESS pBuffer, ULONG cbSizeInput);
+
+ // @dbgtodo : This ctor form confuses target and host address spaces. This should probably be PTR_VOID instead of void*
+ TargetBuffer(void * pBuffer, ULONG cbSizeInput);
+
+ //
+ // Helper methods
+ //
+
+ // Return a sub-buffer that's starts at byteOffset within this buffer and runs to the end.
+ TargetBuffer SubBuffer(ULONG byteOffset) const;
+
+ // Return a sub-buffer that starts at byteOffset within this buffer and is byteLength long.
+ TargetBuffer SubBuffer(ULONG byteOffset, ULONG byteLength) const;
+
+ // Returns true if the buffer length is 0.
+ bool IsEmpty() const;
+
+ // Sets address to NULL and size to 0
+ // IsEmpty() will be true after this.
+ void Clear();
+
+ // Initialize fields
+ void Init(CORDB_ADDRESS address, ULONG size);
+
+ // Target address of buffer
+ CORDB_ADDRESS pAddress;
+
+ // Size of buffer in bytes
+ ULONG cbSize;
+};
+
+//===================================================================================
+// Module properties, retrieved by DAC.
+// Describes a VMPTR_DomainFile representing a module.
+// In the VM, a raw Module may be domain neutral and shared by many appdomains.
+// Whereas a DomainFile is like a { AppDomain, Module} pair. DomainFile corresponds
+// much more to ICorDebugModule (which also has appdomain affinity).
+//===================================================================================
+struct MSLAYOUT DomainFileInfo
+{
+ // The appdomain that the DomainFile is associated with.
+ // Although VMPTR_Module may be shared across multiple domains, a DomainFile has appdomain affinity.
+ VMPTR_AppDomain vmAppDomain;
+
+ // The assembly this module belongs to. All modules live in an assembly.
+ VMPTR_DomainAssembly vmDomainAssembly;
+};
+
+struct MSLAYOUT ModuleInfo
+{
+ // The non-domain specific assembly which this module resides in.
+ VMPTR_Assembly vmAssembly;
+
+ // The PE Base address and size of the module. These may be 0 if there is no image
+ // (such as for a dynamic module that's not persisted to disk).
+ CORDB_ADDRESS pPEBaseAddress;
+
+ // The PEFile associated with the module. Every module (even non-file-based ones) has a PEFile.
+ // This is critical because DAC may ask for a metadata importer via PE-file.
+ // a PEFile may have 1 or more PEImage child objects (1 for IL, 1 for native image, etc)
+ VMPTR_PEFile vmPEFile;
+
+ // The PE Base address and size of the module. These may be 0 if there is no image
+ // (such as for a dynamic module that's not persisted to disk).
+ ULONG nPESize;
+
+ // Is this a dynamic (reflection.emit) module?
+ // This means that new classes can be added to the module; and so
+ // the module's metadata and symbols can be updated. Debugger will have to do extra work
+ // to keep up with the updates.
+ // Dynamic modules may be transient (entirely in-memory) or persisted to disk (have a file associated with them).
+ BOOL fIsDynamic;
+
+ // Is this an inmemory module?
+ // Assemblies can be instantiated purely in-memory from just a Byte[].
+ // This means the module (and pdb) are not in files, and thus the debugger
+ // needs to do extra work to retrieve them from the Target's memory.
+ BOOL fInMemory;
+};
+
+// the following two classes track native offsets for local variables and sequence
+// points. This information is initialized on demand.
+
+
+//===================================================================================
+// NativeVarData holds a list of structs that provide the following information for
+// each local variable and fixed argument in a function: the offsets between which the
+// variable or argument lives in a particular location, the location itself, and the
+// variable number (ID). This allows us to determine where a value is at any given IP.
+
+// Lifetime management of the list is the responsibility of the NativeVarData class.
+// Callers that allocate memory for a new list should NOT maintain a separate pointer
+// to the list.
+
+// The arguments we track are the "fixed" arguments, specifically, the explicit arguments
+// that appear in the source code and the "this" pointer for non-static methods.
+// Varargs and other implicit arguments, such as the generic handle are counted in
+// CordbJITILFrame::m_allArgsCount.
+
+// Although logically, we really don't differentiate between arguments and locals when
+// all we want to know is where to find a value, we need to have two
+// separate counts. The full explanation is in the comment in rsthread.cpp in
+// CordbJITILFrame::ILVariableToNative, but the short version is that it allows us to
+// compute the correct ID for a value.
+
+// m_fixedArgsCount, accessed through GetFixedArgCount, is the actual number of fixed
+// arguments.
+// m_allArgsCount, accessed through GetAllArgsCount, is the number of fixed args plus the
+// number of varargs.
+
+// The number of entries in m_offsetInfo, accessed through Count(), is NOT the
+// number of locals, nor the number of locals plus the number of arguments. It is the
+// number of entries in the list. Any particular value may have an arbitrary number of
+// entries, depending on how many different places it is stored during the execution of
+// the method. The list is not sorted, so searches for data within it must be linear.
+//===================================================================================
+class MSLAYOUT NativeVarData
+{
+public:
+ // constructor
+ NativeVarData();
+ // destructor
+ ~NativeVarData();
+
+
+ // initialize the list of native var information structures, including the starting address of the list
+ // (m_pOffsetInfo, the number of entries (m_count) and the number of fixed args (m_fixedArgsCount).
+ // NativeVarData will manage the lifetime of the allocated memory for the list, so the caller should not
+ // hold on to its address.
+ void InitVarDataList(ICorDebugInfo::NativeVarInfo * plistStart, int fixedArgCount, int entryCount);
+
+private:
+ // non-existent copy constructor to disable the (shallow) compiler-generated
+ // one. If you attempt to use this, you will get a compiler or linker error.
+ NativeVarData(const NativeVarData & rhs) {};
+
+ // non-existent assignment operator to disable the (shallow) compiler-generated
+ // one. If you attempt to use this, you will get a compiler or linker error.
+ NativeVarData & operator=(const NativeVarData & rhs);
+
+//----------------------------------------------------------------------------------
+// Accessor Functions
+//----------------------------------------------------------------------------------
+public:
+
+ // get the list of native offset info
+ const DacDbiArrayList<ICorDebugInfo::NativeVarInfo> * GetOffsetInfoList() const
+ {
+ _ASSERTE(m_fInitialized);
+ return &m_offsetInfo;
+ }
+
+ // get the number of explicit arguments for this function--this
+ // includes the fixed arguments for vararg methods, but not the variable ones
+ ULONG32 GetFixedArgCount()
+ {
+ _ASSERTE(IsInitialized());
+ // this count includes explicit arguments plus one for the "this" pointer
+ // but doesn't count varargs
+ return m_fixedArgsCount;
+ }
+
+ // get the number of all arguments, including varargs
+ ULONG32 GetAllArgsCount()
+ {
+ _ASSERTE(IsInitialized());
+ return m_allArgsCount;
+ }
+
+ // set the number of all arguments, including varargs
+ void SetAllArgsCount(ULONG32 count)
+ {
+ m_allArgsCount = count;
+ }
+
+ // determine whether we have successfully initialized this
+ BOOL IsInitialized()
+ {
+ return m_fInitialized == true;
+ }
+
+
+//----------------------------------------------------------------------------------
+// Data Members
+//----------------------------------------------------------------------------------
+
+// @dbgtodo Mac - making this public for serializing for remote DAC on mac. Need to make this private again.
+public:
+ // contains a list of structs providing information about the location of a local
+ // variable or argument between a pair of offsets and the number of entries in the list
+ DacDbiArrayList<ICorDebugInfo::NativeVarInfo> m_offsetInfo;
+
+ // number of fixed arguments to the function i.e., the explicit arguments and "this" pointer
+ ULONG32 m_fixedArgsCount;
+
+ // number of fixed arguments plus number of varargs
+ ULONG32 m_allArgsCount;
+
+ // indicates whether an attempt has been made toinitialize the var data already
+ bool m_fInitialized;
+}; // class NativeVarData
+
+//===================================================================================
+// SequencePoints holds a list of sequence points that map IL offsets to native offsets. In addition,
+// it keeps track of the number of entries in the list and whether the list is sorted.
+//===================================================================================
+class MSLAYOUT SequencePoints
+{
+public:
+ SequencePoints();
+
+ ~SequencePoints();
+
+ // Initialize the m_pMap data member to the address of an allocated chunk
+ // of memory (or to NULL if the count is zero). Set m_count as the
+ // number of entries in the map.
+ void InitSequencePoints(ULONG32 count);
+
+private:
+ // non-existent copy constructor to disable the (shallow) compiler-generated
+ // one. If you attempt to use this, you will get a compiler or linker error.
+ SequencePoints(const SequencePoints & rhs) {};
+
+ // non-existent assignment operator to disable the (shallow) compiler-generated
+ // one. If you attempt to use this, you will get a compiler or linker error.
+ SequencePoints & operator=(const SequencePoints & rhs);
+
+ //----------------------------------------------------------------------------------
+ // class MapSortILMap: A template class that will sort an array of DebuggerILToNativeMap.
+ // This class is intended to be instantiated on the stack / in temporary storage, and used
+ // to reorder the sequence map.
+ //----------------------------------------------------------------------------------
+ class MapSortILMap : public CQuickSort<DebuggerILToNativeMap>
+ {
+ public:
+ //Constructor
+ MapSortILMap(DebuggerILToNativeMap * map,
+ int count)
+ : CQuickSort<DebuggerILToNativeMap>(map, count) {}
+
+ // secondary key comparison--if two IL offsets are the same,
+ // we determine order based on native offset
+ int CompareInternal(DebuggerILToNativeMap * first,
+ DebuggerILToNativeMap * second);
+
+ //Comparison operator
+ int Compare(DebuggerILToNativeMap * first,
+ DebuggerILToNativeMap * second);
+ };
+
+//----------------------------------------------------------------------------------
+// Accessor Functions
+//----------------------------------------------------------------------------------
+public:
+ // @dbgtodo Microsoft inspection: It would be very nice not to need this at all. Ideally,
+ // it would be better to make ExportILToNativeMap expect a DacDbiArrayList instead of the
+ // array and size. At present, there's a call to ExportILToNativeMap in debugger.cpp where
+ // DacDbiArrayLists aren't available, so at present, we need to pass the array and size.
+ // We should be able to eliminate the debugger.cpp call when we get rid of in-proc
+ // inspection. At that point, we can delete this function too, as well as GetEntryCount.
+ // In the meantime, it would be great if no one else took a dependency on this.
+
+ // get value of m_pMap
+ DebuggerILToNativeMap * GetMapAddr()
+ {
+ // Please don't call this function
+ _ASSERTE(m_fInitialized);
+ return &(m_map[0]);
+ }
+
+ // get value of m_count
+ ULONG32 GetEntryCount()
+ {
+ _ASSERTE(m_fInitialized);
+ return m_mapCount;
+ }
+
+ ULONG32 GetCallsiteEntryCount()
+ {
+ _ASSERTE(m_fInitialized);
+ return m_map.Count() - m_mapCount; //m_map.Count();
+ }
+
+ DebuggerILToNativeMap * GetCallsiteMapAddr()
+ {
+ // Please don't call this function
+ _ASSERTE(m_fInitialized);
+
+ if (m_map.Count() == m_mapCount)
+ return NULL;
+
+ return &(m_map[m_mapCount]);
+ }
+
+
+
+ // determine whether we have initialized this
+ BOOL IsInitialized()
+ {
+ return m_fInitialized == true;
+ }
+
+ // Copy data from the VM map data to our own map structure and sort. The
+ // information comes to us in a data structure that differs slightly from the
+ // one we use out of process, so we have to copy it to the right-side struct.
+ void CopyAndSortSequencePoints(const ICorDebugInfo::OffsetMapping mapCopy[]);
+
+
+ // Set the IL offset of the last sequence point before the epilog.
+ // If a native offset maps to the epilog, we will return the this IL offset.
+ void SetLastILOffset(ULONG32 lastILOffset)
+ {
+ _ASSERTE(m_fInitialized);
+ m_lastILOffset = lastILOffset;
+ }
+
+ // Map the given native offset to IL offset. Also return the mapping type.
+ DWORD MapNativeOffsetToIL(DWORD dwNativeOffset,
+ CorDebugMappingResult *pMapType);
+
+//----------------------------------------------------------------------------------
+// Data Members
+//----------------------------------------------------------------------------------
+
+ // @dbgtodo Mac - making this public for serializing for remote DAC on mac. Need to make this private again.
+public:
+
+ // map of IL to native offsets for sequence points
+ DacDbiArrayList<DebuggerILToNativeMap> m_map;
+
+ //
+ ULONG32 m_mapCount;
+
+ // the IL offset of the last sequence point before the epilog
+ ULONG32 m_lastILOffset;
+ // indicates whether an attempt has been made to initialize the sequence points already
+ bool m_fInitialized;
+}; // class SequencePoints
+
+//----------------------------------------------------------------------------------
+// declarations needed for getting native code regions
+//----------------------------------------------------------------------------------
+
+// Code may be split into Hot & Cold regions, so we need an extra address & size.
+// The jitter doesn't do this optimization w/ debuggable code, so we'll
+// rarely see the cold region information as non-null values.
+
+// This enumeration provides symbolic indices into m_rgCodeRegions.
+typedef enum {kHot = 0, kCold, MAX_REGIONS} CodeBlobRegion;
+
+// This contains the information we need to initialize a CordbNativeCode object
+class MSLAYOUT NativeCodeFunctionData
+{
+public:
+ // set all fields to default values (NULL, FALSE, or zero as appropriate)
+ NativeCodeFunctionData();
+
+ // conversion constructor to convert from an instance of DebuggerIPCE_JITFUncData to an instance of
+ // NativeCodeFunctionData.
+ NativeCodeFunctionData(DebuggerIPCE_JITFuncData * source);
+
+ // The hot region start address could be NULL in the following circumstances:
+ // 1. We haven't yet tried to get the information
+ // 2. We tried to get the information, but the function hasn't been jitted yet
+ // 3. We tried to get the information, but the MethodDesc wasn't available yet (very early in
+ // module initialization), which implies that the code isn't available either.
+ // 4. We tried to get the information, but a method edit has reset the MethodDesc, but the
+ // method hasn't been jitted yet.
+ // In all cases, we can check the hot region start address to determine whether the rest of the
+ // the information is valid.
+ BOOL IsValid() { return (m_rgCodeRegions[kHot].pAddress != NULL); }
+ void Clear();
+
+ // data members
+ // start addresses and sizes of hot & cold regions
+ TargetBuffer m_rgCodeRegions[MAX_REGIONS];
+
+ // indicates whether the function is a generic function, or a method inside a generic class (or both).
+ BOOL isInstantiatedGeneric;
+
+ // MethodDesc for the function
+ VMPTR_MethodDesc vmNativeCodeMethodDescToken;
+
+ // EnC version number of the function
+ SIZE_T encVersion;
+};
+
+//----------------------------------------------------------------------------------
+// declarations needed for getting type information
+//----------------------------------------------------------------------------------
+
+// FieldData holds data for each field within a class or type. This data
+// is passed from the DAC to the DI in response to a request for class info.
+// This type is also used by CordbClass and CordbType to hold the list of fields for the
+// class.
+class MSLAYOUT FieldData
+{
+public:
+#ifndef RIGHT_SIDE_COMPILE
+ // initialize various fields of an instance of FieldData from information in a FieldDesc
+ void Initialize(BOOL fIsStatic, BOOL fIsPrimitive, mdFieldDef mdToken);
+#else
+ HRESULT GetFieldSignature(class CordbModule * pModule, /*OUT*/ SigParser * pSigParser);
+#endif
+
+ // clear various fields for a new instance of FieldData
+ void ClearFields();
+
+ // Make sure it's okay to get or set an instance field offset.
+ BOOL OkToGetOrSetInstanceOffset();
+
+ // Make sure it's okay to get or set a static field address.
+ BOOL OkToGetOrSetStaticAddress();
+
+ // If this is an instance field, store its offset
+ void SetInstanceOffset( SIZE_T offset );
+
+ // If this is a "normal" static, store its absolute address
+ void SetStaticAddress( TADDR addr );
+
+ // If this is an instance field, return its offset
+ // Note that this offset is allways a real offset (possibly larger than 22 bits), which isn't
+ // necessarily the same as the overloaded FieldDesc.dwOffset field which can have
+ // some special FIELD_OFFSET tokens.
+ SIZE_T GetInstanceOffset();
+
+ // If this is a "normal" static, get its absolute address
+ // TLS and context-specific statics are "special".
+ TADDR GetStaticAddress();
+
+//
+// Data members
+//
+ mdFieldDef m_fldMetadataToken;
+ // m_fFldStorageAvailable is true whenever the storage for this field is available.
+ // If this is a field that is newly added with EnC and hasn't had any storage
+ // allocated yet, then fldEnCAvailable will be false.
+ BOOL m_fFldStorageAvailable;
+
+ // Bits that specify what type of field this is
+ bool m_fFldIsStatic; // true if static field, false if instance field
+ bool m_fFldIsRVA; // true if static relative to module address
+ bool m_fFldIsTLS; // true if thread-specific static
+ bool m_fFldIsContextStatic; // true if context-specific static
+ bool m_fFldIsPrimitive; // Only true if this is a value type masquerading as a primitive.
+ bool m_fFldIsCollectibleStatic; // true if this is a static field on a collectible type
+
+private:
+ // The m_fldInstanceOffset and m_pFldStaticAddress are mutually exclusive. Only one is ever set at a time.
+ SIZE_T m_fldInstanceOffset; // The offset of a field within an object instance
+ // For EnC fields, this isn't actually within the object instance,
+ // but has been cooked to still be relative to the beginning of
+ // the object.
+ TADDR m_pFldStaticAddress; // The absolute target address of a static field
+
+ PCCOR_SIGNATURE m_fldSignatureCache; // This is passed across as null. It is a RS-only cache, and SHOULD
+ // NEVER BE ACCESSED DIRECTLY!
+ ULONG m_fldSignatureCacheSize; // This is passed across as 0. It is a RS-only cache, and SHOULD
+ // NEVER BE ACCESSED DIRECTLY!
+public:
+ VMPTR_FieldDesc m_vmFieldDesc;
+
+}; // class FieldData
+
+
+// ClassInfo holds information about a type (class or other structured type), including a list of its fields
+class MSLAYOUT ClassInfo
+{
+public:
+ ClassInfo();
+
+ ~ClassInfo();
+
+ void Clear();
+
+ // Size of object in bytes, for non-generic types. Note: this is NOT valid for constructed value types,
+ // e.g. value type Pair<DateTime,int>. Use CordbType::m_objectSize instead.
+ SIZE_T m_objectSize;
+
+ // list of structs containing information about all the fields in this Class, along with the number of entries
+ // in the list. Does not include inherited fields. DON'T KEEP POINTERS TO ELEMENTS OF m_fieldList AROUND!!
+ // This may be deleted if the class gets EnC'd.
+ DacDbiArrayList<FieldData> m_fieldList;
+}; // class ClassInfo
+
+// EnCHangingFieldInfo holds information describing a field added with Edit And Continue. This data
+// is passed from the DAC to the DI in response to a request for EnC field info.
+class MSLAYOUT EnCHangingFieldInfo
+{
+public:
+ // Init will initialize fields, taking into account whether the field is static or not.
+ void Init(VMPTR_Object pObject,
+ SIZE_T offset,
+ mdFieldDef fieldToken,
+ CorElementType elementType,
+ mdTypeDef metadataToken,
+ VMPTR_DomainFile vmDomainFile);
+
+ DebuggerIPCE_BasicTypeData GetObjectTypeData() const { return m_objectTypeData; };
+ mdFieldDef GetFieldToken() const { return m_fldToken; };
+ VMPTR_Object GetVmObject() const { return m_vmObject; };
+ SIZE_T GetOffsetToVars() const { return m_offsetToVars; };
+
+private:
+ DebuggerIPCE_BasicTypeData m_objectTypeData; // type data for the EnC field
+ VMPTR_Object m_vmObject; // object instance to which the field has been added--if the field is
+ // static, this will be NULL instead of pointing to an instance
+ SIZE_T m_offsetToVars; // offset to the beginning of variable storage in the object
+ mdFieldDef m_fldToken; // metadata token for the added field
+
+}; // EnCHangingFieldInfo
+
+// TypeHandleToExpandedTypeInfo returns different DebuggerIPCE_ExpandedTypeData objects
+// depending on whether the object value that the TypeData corresponds to is
+// boxed or not. Different parts of the API transfer objects in slightly different ways.
+// AllBoxed:
+// For GetAndSendObjectData all values are boxed,
+//
+// OnlyPrimitivesUnboxed:
+// When returning results from FuncEval only "true" structs
+// get boxed, i.e. primitives are unboxed.
+//
+// NoValueTypeBoxing:
+// TypeHandleToExpandedTypeInfo is also used to report type parameters,
+// and in this case none of the types are considered boxed (
+enum AreValueTypesBoxed { NoValueTypeBoxing, OnlyPrimitivesUnboxed, AllBoxed };
+
+// TypeRefData is used for resolving a type reference (see code:CordbModule::ResolveTypeRef and
+// code:DacDbiInterfaceImpl::ResolveTypeReference) to store relevant information about the type
+typedef struct MSLAYOUT
+{
+ // domain file for the type
+ VMPTR_DomainFile vmDomainFile;
+ // metadata token for the type. This may be a typeRef (for requests) or a typeDef (for responses).
+ mdToken typeToken;
+} TypeRefData;
+
+// @dbgtodo Microsoft inspection: get rid of IPCE type.
+// TypeInfoList encapsulates a list of type data instances and the length of the list.
+typedef DacDbiArrayList<DebuggerIPCE_TypeArgData> TypeInfoList;
+
+// ArgInfoList encapsulates a list of type data instances for arguments for a top-level
+// type and the length of the list.
+typedef DacDbiArrayList<DebuggerIPCE_BasicTypeData> ArgInfoList;
+
+// TypeParamsList encapsulate a list of type parameters and the length of the list
+typedef DacDbiArrayList<DebuggerIPCE_ExpandedTypeData> TypeParamsList;
+
+// A struct for passing version information from DBI to DAC.
+// See code:CordbProcess::CordbProcess#DBIVersionChecking for more information.
+const DWORD kCurrentDacDbiProtocolBreakingChangeCounter = 1;
+
+struct DbiVersion
+{
+ DWORD m_dwFormat; // the format of this DbiVersion instance
+ DWORD m_dwDbiVersionMS; // version of the DBI DLL, in the convention used by VS_FIXEDFILEINFO
+ DWORD m_dwDbiVersionLS;
+ DWORD m_dwProtocolBreakingChangeCounter; // initially this was reserved and always set to 0
+ // Now we use it as a counter to explicitly introduce breaking changes
+ // between DBI and DAC when we have our IPC transport in the middle
+ // If DBI and DAC don't agree on the same value CheckDbiVersion will return CORDBG_E_INCOMPATIBLE_PROTOCOL
+ // Please document every time this value changes
+ // 0 - initial value
+ // 1 - Indicates that the protocol now supports the GetRemoteInterfaceHashAndTimestamp message
+ // The message must have ID 2, with signature:
+ // OUT DWORD & hash1, OUT DWORD & hash2, OUT DWORD & hash3, OUT DWORD & hash4, OUT DWORD & timestamp1, OUT DWORD & timestamp2
+ // The hash can be used as an indicator of many other breaking changes providing
+ // easier automated enforcement during development. It is NOT recommended to use
+ // the hash as a release versioning mechanism however.
+ DWORD m_dwReservedMustBeZero1; // reserved for future use
+};
+
+// The way in which a thread is blocking on an object
+enum DacBlockingReason
+{
+ DacBlockReason_MonitorCriticalSection,
+ DacBlockReason_MonitorEvent
+};
+
+// Information about an object which is blocking a managed thread
+struct DacBlockingObject
+{
+ VMPTR_Object vmBlockingObject;
+ VMPTR_AppDomain vmAppDomain;
+ DWORD dwTimeout;
+ DacBlockingReason blockingReason;
+};
+
+// Opaque user defined data used in callbacks
+typedef void* CALLBACK_DATA;
+
+struct MonitorLockInfo
+{
+ VMPTR_Thread lockOwner;
+ DWORD acquisitionCount;
+};
+
+struct MSLAYOUT DacGcReference
+{
+ VMPTR_AppDomain vmDomain; // The AppDomain of the handle/object, may be null.
+ union
+ {
+ CORDB_ADDRESS pObject; // A managed object, with the low bit set.
+ VMPTR_OBJECTHANDLE objHnd; // A reference to the object, valid if (pAddress & 1) == 0
+ };
+ DWORD dwType; // Where the root came from.
+
+ /*
+ DependentSource - for HandleDependent
+ RefCount - for HandleStrongRefCount
+ Size - for HandleSizedByref
+ */
+ UINT64 i64ExtraData;
+}; // struct DacGcReference
+
+struct MSLAYOUT DacExceptionCallStackData
+{
+ VMPTR_AppDomain vmAppDomain;
+ VMPTR_DomainFile vmDomainFile;
+ CORDB_ADDRESS ip;
+ mdMethodDef methodDef;
+ BOOL isLastForeignExceptionFrame;
+};
+
+// These represent the various states a SharedReJitInfo can be in.
+enum DacSharedReJitInfoState
+{
+ // The profiler has requested a ReJit, so we've allocated stuff, but we haven't
+ // called back to the profiler to get any info or indicate that the ReJit has
+ // started. (This Info can be 'reused' for a new ReJit if the
+ // profiler calls RequestReJit again before we transition to the next state.)
+ kStateRequested = 0x00000000,
+
+ // We have asked the profiler about this method via ICorProfilerFunctionControl,
+ // and have thus stored the IL and codegen flags the profiler specified. Can only
+ // transition to kStateReverted from this state.
+ kStateActive = 0x00000001,
+
+ // The methoddef has been reverted, but not freed yet. It (or its instantiations
+ // for generics) *MAY* still be active on the stack someplace or have outstanding
+ // memory references.
+ kStateReverted = 0x00000002,
+
+
+ kStateMask = 0x0000000F,
+};
+
+struct MSLAYOUT DacSharedReJitInfo
+{
+ DWORD m_state;
+ CORDB_ADDRESS m_pbIL;
+ DWORD m_dwCodegenFlags;
+ ULONG m_cInstrumentedMapEntries;
+ CORDB_ADDRESS m_rgInstrumentedMapEntries;
+};
+
+#include "dacdbistructures.inl"
+#endif // DACDBISTRUCTURES_H_
diff --git a/src/debug/inc/dacdbistructures.inl b/src/debug/inc/dacdbistructures.inl
new file mode 100644
index 0000000000..48749135f0
--- /dev/null
+++ b/src/debug/inc/dacdbistructures.inl
@@ -0,0 +1,732 @@
+// 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.
+//*****************************************************************************
+
+//
+// File: DacDbiInterface.inl
+//
+// Inline functions for DacDbiStructures.h
+//
+//*****************************************************************************
+
+#ifndef DACDBISTRUCTURES_INL_
+#define DACDBISTRUCTURES_INL_
+
+//-----------------------------------------------------------------------------------
+// DacDbiArrayList member function implementations
+//-----------------------------------------------------------------------------------
+
+// constructor--sets list to empty state
+// Arguments: none
+// Notes: this allocates no memory, so the list will not be ready to use
+template<class T>
+inline
+DacDbiArrayList<T>::DacDbiArrayList():
+ m_pList(NULL),
+ m_nEntries(0)
+ {
+ }
+
+// conversion constructor--takes a list of type T and a count and converts to a
+// DacDbiArrayList
+// Arguments:
+// input: list - a consecutive list (array) of elements of type T
+// count - the number of elements in list
+// Notes: - Allocates memory and copies the elements of list into "this"
+// - It is assumed that the list does NOT already have memory allocated; if it does,
+// calling Init will cause a leak.
+// - the element copy relies on the assignment operator for T
+// - may throw OOM
+template<class T>
+inline
+DacDbiArrayList<T>::DacDbiArrayList(const T * pList, int count):
+ m_pList(NULL),
+ m_nEntries(0)
+{
+ Init(pList, count);
+}
+
+// destructor: deallocates memory and sets list back to empty state
+// Arguments: none
+template<class T>
+inline
+DacDbiArrayList<T>::~DacDbiArrayList()
+{
+ Dealloc();
+}
+
+// explicitly deallocate the list and set it back to the empty state
+// Arguments: none
+// Notes: - Dealloc can be called multiple times without danger, since it
+// checks first that memory has been allocated
+template<class T>
+inline
+void DacDbiArrayList<T>::Dealloc()
+{
+ CONTRACT_VOID
+ {
+ NOTHROW;
+ }
+ CONTRACT_END;
+
+ if (m_pList != NULL)
+ {
+ delete [] m_pList;
+ m_pList = NULL;
+ }
+ m_nEntries = 0;
+ RETURN;
+}
+
+// Alloc and Init are very similar. Both preallocate the array; but Alloc leaves the
+// contents unintialized while Init provides initial values. The array contents are always
+// mutable.
+
+// allocate space for the list--in some instances, we'll know the count first, and then
+// we'll compute the elements one at a time. This (along with the array access operator
+// overload) allows us to handle that situation
+// Arguments:
+// input: nElements - number of elements of type T for which we need space
+// Notes:
+// - Alloc can be called multiple times and will free previous arrays.
+// - May throw OOM
+// - The array is not expandable, so you must allocate for all the elements at once.
+// - requesting an allocation of 0 or fewer bytes will not cause an error, but no memory is
+// allocated
+template<class T>
+inline
+void DacDbiArrayList<T>::Alloc(int nElements)
+{
+ Dealloc();
+ if (nElements > 0)
+ {
+ m_pList = new(forDbi) T[(size_t)nElements];
+ m_nEntries = nElements;
+ }
+}
+
+// allocate and initialize a DacDbiArrayList from a list of type T and a count
+// Arguments:
+// input: list - consecutive list (array) of elements of type T to be copied into
+// "this"
+// count - number of elements in list
+// Notes:
+// - May throw OOM
+// - Can be called multiple times with different lists, since this will deallocate
+// previous arrays.
+template<class T>
+inline
+void DacDbiArrayList<T>::Init(const T * pList, int count)
+{
+ _ASSERTE((m_pList == NULL) && (m_nEntries == 0));
+ if (count > 0)
+ {
+ Alloc(count);
+ m_nEntries = count;
+ for (int index = 0; index < count; ++index)
+ {
+ m_pList[index] = pList[index];
+ }
+ }
+}
+
+// read-only list element access
+template<class T>
+inline
+const T & DacDbiArrayList<T>::operator [](int i) const
+{
+ _ASSERTE(m_pList != NULL);
+ _ASSERTE((i >= 0) && (i < m_nEntries));
+ return m_pList[i];
+}
+
+// writeable list element access
+template<class T>
+inline
+T & DacDbiArrayList<T>::operator [](int i)
+{
+ _ASSERTE(m_pList != NULL);
+ _ASSERTE((i >= 0) && (i < m_nEntries));
+ return m_pList[i];
+}
+
+// get the number of elements in the list
+template<class T>
+inline
+int DacDbiArrayList<T>::Count() const
+{
+ return m_nEntries;
+}
+
+//-----------------------------------------------------------------------------
+// Target Buffer functions
+//-----------------------------------------------------------------------------
+
+// Default ctor
+inline
+TargetBuffer::TargetBuffer()
+{
+ this->pAddress = NULL;
+ this->cbSize = 0;
+}
+
+// Convenience Ctor to initialize around an (Address, size).
+inline
+TargetBuffer::TargetBuffer(CORDB_ADDRESS pBuffer, ULONG cbSizeInput)
+{
+ this->pAddress = pBuffer;
+ this->cbSize = cbSizeInput;
+}
+
+// Convenience Ctor to initialize around an (Address, size).
+inline
+TargetBuffer::TargetBuffer(void * pBuffer, ULONG cbSizeInput)
+{
+ this->pAddress = PTR_TO_CORDB_ADDRESS(pBuffer);
+ this->cbSize = cbSizeInput;
+}
+
+// Return a sub-buffer that's starts at byteOffset within this buffer and runs to the end.
+//
+// Arguments:
+// byteOffset - offset in bytes within this buffer that the new buffer starts at.
+//
+// Returns:
+// A new buffer that's a subset of the existing buffer.
+inline
+TargetBuffer TargetBuffer::SubBuffer(ULONG byteOffset) const
+{
+ _ASSERTE(byteOffset <= cbSize);
+ return TargetBuffer(pAddress + byteOffset, cbSize - byteOffset);
+}
+
+// Return a sub-buffer that starts at byteOffset within this buffer and is byteLength long.
+//
+// Arguments:
+// byteOffset - offset in bytes within this buffer that the new buffer starts at.
+// byteLength - length in bytes of the new buffer.
+//
+// Returns:
+// A new buffer that's a subset of the existing buffer.
+inline
+TargetBuffer TargetBuffer::SubBuffer(ULONG byteOffset, ULONG byteLength) const
+{
+ _ASSERTE(byteOffset + byteLength <= cbSize);
+ return TargetBuffer(pAddress + byteOffset, byteLength);
+}
+
+// Sets address to NULL and size to 0
+inline
+void TargetBuffer::Clear()
+{
+ pAddress = NULL;
+ cbSize = 0;
+}
+
+// Initialize fields
+inline
+void TargetBuffer::Init(CORDB_ADDRESS address, ULONG size)
+{
+ pAddress = address;
+ cbSize = size;
+}
+
+
+// Returns true iff the buffer is empty.
+inline
+bool TargetBuffer::IsEmpty() const
+{
+ return (this->cbSize == 0);
+}
+
+//-----------------------------------------------------------------------------
+// NativeVarData member function implementations
+//-----------------------------------------------------------------------------
+
+// Initialize a new instance of NativeVarData
+inline NativeVarData::NativeVarData() :
+ m_allArgsCount(0),
+ m_fInitialized(false)
+{
+}
+
+// destructor
+inline NativeVarData::~NativeVarData()
+{
+ m_fInitialized = false;
+ }
+
+// initialize the list of native var information structures, including the starting address of the list, the number of
+// entries and the number of fixed args.
+inline void NativeVarData::InitVarDataList(ICorDebugInfo::NativeVarInfo * pListStart,
+ int fixedArgCount,
+ int entryCount)
+{
+ m_offsetInfo.Init(pListStart, entryCount);
+ m_fixedArgsCount = fixedArgCount;
+ m_fInitialized = true;
+}
+
+//-----------------------------------------------------------------------------
+// SequencePoints member function implementations
+//-----------------------------------------------------------------------------
+
+// initializing constructor
+inline SequencePoints::SequencePoints() :
+ m_mapCount(0),
+ m_lastILOffset(0),
+ m_fInitialized(false)
+{
+}
+
+// destructor
+inline SequencePoints::~SequencePoints()
+{
+ m_fInitialized = false;
+ }
+
+// Initialize the m_pMap data member to the address of an allocated chunk
+// of memory (or to NULL if the count is zero). Set m_count as the
+// number of entries in the map.
+inline void SequencePoints::InitSequencePoints(ULONG32 count)
+{
+ m_map.Alloc(count),
+ m_fInitialized = true;
+}
+
+//
+// Map the given native offset to IL offset and return the mapping type.
+//
+// Arguments:
+// dwNativeOffset - the native offset to be mapped
+// pMapType - out parameter; return the mapping type
+//
+// Return Value:
+// Return the IL offset corresponding to the given native offset.
+// For a prolog, return 0.
+// For an epilog, return the IL offset of the last sequence point before the epilog.
+// If we can't map to an IL offset, then return 0, with a mapping type of MAPPING_NO_INFO.
+//
+// Assumptions:
+// The sequence points are sorted.
+//
+
+inline
+DWORD SequencePoints::MapNativeOffsetToIL(DWORD dwNativeOffset,
+ CorDebugMappingResult *pMapType)
+{
+ //_ASSERTE(IsInitialized());
+ if (!IsInitialized())
+ {
+ (*pMapType) = MAPPING_NO_INFO;
+ return 0;
+ }
+
+ _ASSERTE(pMapType != NULL);
+
+ int i;
+
+ for (i = 0; i < (int)m_mapCount; ++i)
+ {
+ // Check to determine if dwNativeOffset is within this sequence point. Checking the lower bound is trivial--
+ // we just make sure that dwNativeOffset >= m_map[i].nativeStartOffset.
+ // Checking to be sure it's before the end of the range is a little trickier. We can have
+ // m_map[i].nativeEndOffset = 0 for two reasons:
+ // 1. We use an end offset of 0 to signify that this end offset is also the end of the method.
+ // 2. We could also have an end offset of 0 if the IL prologue doesn't translate to any native
+ // instructions. Thus, the first native instruction (which will not be in the prologue) is at an offset
+ // of 0. The end offset is always set to the start offset of the next sequence point, so this means
+ // that both the start and end offsets of the (non-existent) native instruction range for the
+ // prologue is also 0.
+ // If the end offset is 0, we want to check if we're in the prologue before concluding that the
+ // value of dwNativeOffset is out of range.
+ if ((dwNativeOffset >= m_map[i].nativeStartOffset) &&
+ (((m_map[i].nativeEndOffset == 0) && (m_map[i].ilOffset != (ULONG)ICorDebugInfo::PROLOG)) ||
+ (dwNativeOffset < m_map[i].nativeEndOffset)))
+ {
+ ULONG uILOffset = m_map[i].ilOffset;
+
+ if (m_map[i].ilOffset == (ULONG)ICorDebugInfo::PROLOG)
+ {
+ uILOffset = 0;
+ (*pMapType) = MAPPING_PROLOG;
+ }
+ else if (m_map[i].ilOffset == (ULONG)ICorDebugInfo::NO_MAPPING)
+ {
+ uILOffset = 0;
+ (*pMapType) = MAPPING_UNMAPPED_ADDRESS;
+ }
+ else if (m_map[i].ilOffset == (ULONG)ICorDebugInfo::EPILOG)
+ {
+ uILOffset = m_lastILOffset;
+ (*pMapType) = MAPPING_EPILOG;
+ }
+ else if (dwNativeOffset == m_map[i].nativeStartOffset)
+ {
+ (*pMapType) = MAPPING_EXACT;
+ }
+ else
+ {
+ (*pMapType) = MAPPING_APPROXIMATE;
+ }
+ return uILOffset;
+ }
+ }
+
+ (*pMapType) = MAPPING_NO_INFO;
+ return 0;
+}
+
+//
+// Copy data from the VM map data to our own map structure and sort. The
+// information comes to us in a data structure that differs slightly from the
+// one we use out of process, so we have to copy it to the right-side struct.
+// Arguments
+// input
+// mapCopy sequence points
+// output
+// pSeqPoints.m_map is initialized with the correct right side representation of sequence points
+
+inline
+void SequencePoints::CopyAndSortSequencePoints(const ICorDebugInfo::OffsetMapping mapCopy[])
+{
+ // copy information to pSeqPoint and set end offsets
+ int i;
+
+ ULONG32 lastILOffset = 0;
+
+ const DWORD call_inst = (DWORD)ICorDebugInfo::CALL_INSTRUCTION;
+ for (i = 0; i < m_map.Count(); i++)
+ {
+ m_map[i].ilOffset = mapCopy[i].ilOffset;
+ m_map[i].nativeStartOffset = mapCopy[i].nativeOffset;
+
+ if (i < m_map.Count() - 1)
+ {
+ // We need to not use CALL_INSTRUCTION's IL start offset.
+ int j = i + 1;
+ while ((mapCopy[j].source & call_inst) == call_inst && j < m_map.Count()-1)
+ j++;
+
+ m_map[i].nativeEndOffset = mapCopy[j].nativeOffset;
+ }
+
+ m_map[i].source = mapCopy[i].source;
+
+ // need to cast the offsets to signed values first because we do actually use
+ // special negative offsets such as ICorDebugInfo::PROLOG
+ if ((m_map[i].source & call_inst) != call_inst)
+ lastILOffset = max((int)lastILOffset, (int)m_map[i].ilOffset);
+ }
+
+ if (m_map.Count() >= 1)
+ {
+ m_map[i - 1].nativeEndOffset = 0;
+ m_map[i - 1].source =
+ (ICorDebugInfo::SourceTypes)(m_map[i - 1].source | ICorDebugInfo::NATIVE_END_OFFSET_UNKNOWN);
+ }
+
+ // sort the map
+ MapSortILMap mapSorter(&m_map[0], m_map.Count());
+ mapSorter.Sort();
+
+
+ m_mapCount = m_map.Count();
+ while (m_mapCount > 0 && (m_map[m_mapCount-1].source & (call_inst)) == call_inst)
+ m_mapCount--;
+
+ SetLastILOffset(lastILOffset);
+} // CopyAndSortSequencePoints
+
+//-----------------------------------------------------------------------------
+// member function implementations for MapSortILMap class to sort sequence points
+// by IL offset
+//-----------------------------------------------------------------------------
+
+// secondary key comparison--if two IL offsets are the same,
+// we determine order based on native offset
+
+inline
+int SequencePoints::MapSortILMap::CompareInternal(DebuggerILToNativeMap *first,
+ DebuggerILToNativeMap *second)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ if (first->nativeStartOffset == second->nativeStartOffset)
+ return 0;
+ else if (first->nativeStartOffset < second->nativeStartOffset)
+ return -1;
+ else
+ return 1;
+}
+
+//Comparison operator
+inline
+int SequencePoints::MapSortILMap::Compare(DebuggerILToNativeMap * first,
+ DebuggerILToNativeMap * second)
+{
+ LIMITED_METHOD_CONTRACT;
+ const DWORD call_inst = (DWORD)ICorDebugInfo::CALL_INSTRUCTION;
+
+ //PROLOGs go first
+ if (first->ilOffset == (ULONG) ICorDebugInfo::PROLOG &&
+ second->ilOffset == (ULONG) ICorDebugInfo::PROLOG)
+ {
+ return CompareInternal(first, second);
+ }
+ else if (first->ilOffset == (ULONG) ICorDebugInfo::PROLOG)
+ {
+ return -1;
+ }
+ else if (second->ilOffset == (ULONG) ICorDebugInfo::PROLOG)
+ {
+ return 1;
+ }
+ // call_instruction goes at the very very end of the table.
+ else if ((first->source & call_inst) == call_inst
+ && (second->source & call_inst) == call_inst)
+ {
+ return CompareInternal(first, second);
+ } else if ((first->source & call_inst) == call_inst)
+ {
+ return 1;
+ } else if ((second->source & call_inst) == call_inst)
+ {
+ return -1;
+ }
+ //NO_MAPPING go last
+ else if (first->ilOffset == (ULONG) ICorDebugInfo::NO_MAPPING &&
+ second->ilOffset == (ULONG) ICorDebugInfo::NO_MAPPING)
+ {
+ return CompareInternal(first, second);
+ }
+ else if (first->ilOffset == (ULONG) ICorDebugInfo::NO_MAPPING)
+ {
+ return 1;
+ }
+ else if (second->ilOffset == (ULONG) ICorDebugInfo::NO_MAPPING)
+ {
+ return -1;
+ }
+ //EPILOGs go next-to-last
+ else if (first->ilOffset == (ULONG) ICorDebugInfo::EPILOG &&
+ second->ilOffset == (ULONG) ICorDebugInfo::EPILOG)
+ {
+ return CompareInternal(first, second);
+ }
+ else if (first->ilOffset == (ULONG) ICorDebugInfo::EPILOG)
+ {
+ return 1;
+ }
+ else if (second->ilOffset == (ULONG) ICorDebugInfo::EPILOG)
+ {
+ return -1;
+ }
+ //normal offsets compared otherwise
+ else if (first->ilOffset < second->ilOffset)
+ {
+ return -1;
+ }
+ else if (first->ilOffset == second->ilOffset)
+ {
+ return CompareInternal(first, second);
+ }
+ else
+ {
+ return 1;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// NativeCodeFunctionData member function implementations
+// (for getting native code regions)
+//-----------------------------------------------------------------------------
+
+inline
+CodeBlobRegion & operator++(CodeBlobRegion & rs)
+{
+ return rs = CodeBlobRegion(rs + 1);
+}
+
+// Convert the data in an instance of DebuggerIPCE_JITFUncData to an instance of NativeCodeFunctionData.
+// We need to have this latter type to look up or create a new CordbNativeCode object, but the stack walker is
+// using the former type to gather information.
+// Arguments:
+// Input:
+// source - an initialized instance of DebuggerIPCE_JITFuncData containing the information to
+// be copied into this instance of NativeCodeFunctionData
+// @dbgtodo dlaw: Once CordbThread::RefreshStack is fully DAC-ized, we can change the data structure that it uses
+// to have a member of type NativeCodeFunctionData which we can pass without copying. At that point,
+// this method can disappear.
+inline
+NativeCodeFunctionData::NativeCodeFunctionData(DebuggerIPCE_JITFuncData * source)
+{
+ // copy the code region information
+ m_rgCodeRegions[kHot].Init(CORDB_ADDRESS(source->nativeStartAddressPtr), (ULONG)source->nativeHotSize);
+ m_rgCodeRegions[kCold].Init(CORDB_ADDRESS(source->nativeStartAddressColdPtr), (ULONG)source->nativeColdSize);
+
+ // copy the other function information
+ isInstantiatedGeneric = source->isInstantiatedGeneric;
+ vmNativeCodeMethodDescToken = source->vmNativeCodeMethodDescToken;
+ encVersion = source->enCVersion;
+}
+
+
+// set all fields to default values (NULL, FALSE, or zero as appropriate)
+inline
+NativeCodeFunctionData::NativeCodeFunctionData()
+{
+ Clear();
+}
+
+inline
+void NativeCodeFunctionData::Clear()
+{
+ isInstantiatedGeneric = FALSE;
+ encVersion = CorDB_DEFAULT_ENC_FUNCTION_VERSION;
+ for (CodeBlobRegion region = kHot; region < MAX_REGIONS; ++region)
+ {
+ m_rgCodeRegions[region].Clear();
+ }
+}
+
+//-----------------------------------------------------------------------------------
+// ClassInfo member functions
+//-----------------------------------------------------------------------------------
+
+inline
+ClassInfo::ClassInfo():
+ m_objectSize(0)
+ {}
+
+// clear all fields
+inline
+void ClassInfo::Clear()
+{
+ m_objectSize = 0;
+ m_fieldList.Dealloc();
+}
+
+inline
+ClassInfo::~ClassInfo()
+{
+ m_fieldList.Dealloc();
+}
+
+//-----------------------------------------------------------------------------------
+// FieldData member functions
+//-----------------------------------------------------------------------------------
+#ifndef RIGHT_SIDE_COMPILE
+
+// initialize various fields of an instance of FieldData from information retrieved from a FieldDesc
+inline
+void FieldData::Initialize(BOOL fIsStatic, BOOL fIsPrimitive, mdFieldDef mdToken)
+{
+ ClearFields();
+ m_fFldIsStatic = (fIsStatic == TRUE);
+ m_fFldIsPrimitive = (fIsPrimitive == TRUE);
+ // This is what tells the right side the field is unavailable due to EnC.
+ m_fldMetadataToken = mdToken;
+}
+#endif
+
+// clear various fields for a new instance of FieldData
+inline
+void FieldData::ClearFields()
+{
+ m_fldSignatureCache = NULL;
+ m_fldSignatureCacheSize = 0;
+ m_fldInstanceOffset = 0;
+ m_pFldStaticAddress = NULL;
+}
+
+typedef ULONG_PTR SIZE_T;
+
+inline
+BOOL FieldData::OkToGetOrSetInstanceOffset()
+{
+ return (!m_fFldIsStatic && !m_fFldIsRVA && !m_fFldIsTLS && !m_fFldIsContextStatic &&
+ m_fFldStorageAvailable && (m_pFldStaticAddress == NULL));
+}
+
+// If this is an instance field, store its offset
+inline
+void FieldData::SetInstanceOffset(SIZE_T offset)
+{
+ _ASSERTE(!m_fFldIsStatic);
+ _ASSERTE(!m_fFldIsRVA);
+ _ASSERTE(!m_fFldIsTLS);
+ _ASSERTE(!m_fFldIsContextStatic);
+ _ASSERTE(m_fFldStorageAvailable);
+ _ASSERTE(m_pFldStaticAddress == NULL);
+ m_fldInstanceOffset = offset;
+}
+
+inline
+BOOL FieldData::OkToGetOrSetStaticAddress()
+{
+ return (m_fFldIsStatic && !m_fFldIsTLS && !m_fFldIsContextStatic &&
+ m_fFldStorageAvailable && (m_fldInstanceOffset == 0));
+}
+
+// If this is a "normal" static, store its absolute address
+inline
+void FieldData::SetStaticAddress(TADDR addr)
+{
+ _ASSERTE(m_fFldIsStatic);
+ _ASSERTE(!m_fFldIsTLS);
+ _ASSERTE(!m_fFldIsContextStatic);
+ _ASSERTE(m_fFldStorageAvailable);
+ _ASSERTE(m_fldInstanceOffset == 0);
+ m_pFldStaticAddress = TADDR(addr);
+}
+
+// Get the offset of a field
+inline
+SIZE_T FieldData::GetInstanceOffset()
+{
+ _ASSERTE(!m_fFldIsStatic);
+ _ASSERTE(!m_fFldIsRVA);
+ _ASSERTE(!m_fFldIsTLS);
+ _ASSERTE(!m_fFldIsContextStatic);
+ _ASSERTE(m_fFldStorageAvailable);
+ _ASSERTE(m_pFldStaticAddress == NULL);
+ return m_fldInstanceOffset;
+}
+
+// Get the static address for a field
+inline
+TADDR FieldData::GetStaticAddress()
+{
+ _ASSERTE(m_fFldIsStatic);
+ _ASSERTE(!m_fFldIsTLS);
+ _ASSERTE(!m_fFldIsContextStatic);
+ _ASSERTE(m_fFldStorageAvailable || (m_pFldStaticAddress == NULL));
+ _ASSERTE(m_fldInstanceOffset == 0);
+ return m_pFldStaticAddress;
+}
+
+//-----------------------------------------------------------------------------------
+// EnCHangingFieldInfo member functions
+//-----------------------------------------------------------------------------------
+
+inline
+void EnCHangingFieldInfo::Init(VMPTR_Object pObject,
+ SIZE_T offset,
+ mdFieldDef fieldToken,
+ CorElementType elementType,
+ mdTypeDef metadataToken,
+ VMPTR_DomainFile vmDomainFile)
+ {
+ m_vmObject = pObject;
+ m_offsetToVars = offset;
+ m_fldToken = fieldToken;
+ m_objectTypeData.elementType = elementType;
+ m_objectTypeData.metadataToken = metadataToken;
+ m_objectTypeData.vmDomainFile = vmDomainFile;
+ }
+
+
+
+#endif // DACDBISTRUCTURES_INL_
diff --git a/src/debug/inc/dbgappdomain.h b/src/debug/inc/dbgappdomain.h
new file mode 100644
index 0000000000..70504c09ec
--- /dev/null
+++ b/src/debug/inc/dbgappdomain.h
@@ -0,0 +1,388 @@
+// 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 DbgAppDomain_H
+#define DbgAppDomain_H
+
+// Forward declaration
+class AppDomain;
+
+
+void BeginThreadAffinityHelper();
+void EndThreadAffinityHelper();
+
+
+// AppDomainInfo contains information about an AppDomain
+// All pointers are for the left side, and we do not own any of the memory
+struct AppDomainInfo
+{
+ ULONG m_id; // unique identifier
+ int m_iNameLengthInBytes;
+ LPCWSTR m_szAppDomainName;
+ AppDomain *m_pAppDomain; // only used by LS
+
+ // NOTE: These functions are just helpers and must not add a VTable
+ // to this struct (since we need to read this out-of-proc)
+
+ // Provide a clean definition of an empty entry
+ inline bool IsEmpty() const
+ {
+ return m_szAppDomainName == NULL;
+ }
+
+#ifndef RIGHT_SIDE_COMPILE
+ // Mark this entry as empty.
+ inline void FreeEntry()
+ {
+ m_szAppDomainName = NULL;
+ }
+
+ // Set the string name and length.
+ // If szName is null, it is adjusted to a global constant.
+ // This also causes the entry to be considered valid
+ inline void SetName(LPCWSTR szName)
+ {
+ if (szName != NULL)
+ m_szAppDomainName = szName;
+ else
+ m_szAppDomainName = W("<NoName>");
+
+ m_iNameLengthInBytes = (int) (wcslen(m_szAppDomainName) + 1) * sizeof(WCHAR);
+ }
+#endif
+};
+
+// Enforce the AppDomain IPC block binary layout doesn't change between versions.
+// Only an issue for x86 since that's the only platform w/ multiple versions.
+#if defined(DBG_TARGET_X86)
+static_assert_no_msg(offsetof(AppDomainInfo, m_id) == 0x0);
+static_assert_no_msg(offsetof(AppDomainInfo, m_iNameLengthInBytes) == 0x4);
+static_assert_no_msg(offsetof(AppDomainInfo, m_szAppDomainName) == 0x8);
+static_assert_no_msg(offsetof(AppDomainInfo, m_pAppDomain) == 0xc);
+#endif
+
+
+
+// The RemoteHANDLE encapsulates the PAL specific handling of handles to avoid PAL specific ifdefs
+// everywhere else in the code.
+// There are two common initialization patterns:
+//
+// 1. Publishing of local handle for other processes, the value of the wrapper is a local handle
+// in *this* process at the end:
+// - In this process, call SetLocal(hHandle) to initialize the handle.
+// - In the other processes, call DuplicateToLocalProcess to get a local copy of the handle.
+//
+// 2. Injecting of local handle into other process, the value of the wrapper is a local handle
+// in the *other* process at the end:
+// - In this process, call DuplicateToRemoteProcess(hProcess, hHandle) to initialize the handle.
+// - In the other process, call ImportToOtherProcess() to finish the initialization of the wrapper
+// with a local copy of the handle.
+//
+// Once initialized, the wrapper can be used the same way as a regular HANDLE in the process
+// it was initialized for. There is casting operator HANDLE to achieve that.
+
+struct RemoteHANDLE {
+ HANDLE m_hLocal;
+
+ operator HANDLE& ()
+ {
+ return m_hLocal;
+ }
+
+ void Close()
+ {
+ HANDLE hHandle = m_hLocal;
+ if (hHandle != NULL) {
+ m_hLocal = NULL;
+ CloseHandle(hHandle);
+ }
+ }
+
+ // Sets the local value of the handle. DuplicateToLocalProcess can be used later
+ // by the remote process to acquire the remote handle.
+ BOOL SetLocal(HANDLE hHandle)
+ {
+ m_hLocal = hHandle;
+ return TRUE;
+ }
+
+ // Duplicates the current handle value to remote process. ImportToLocalProcess
+ // should be called in the remote process before the handle is used in the remote process.
+ // NOTE: right now this is used for duplicating the debugger's process handle into the LS so
+ // that the LS can know when the RS has exited; thus we are only specifying SYNCHRONIZE
+ // access to mitigate any security concerns.
+ BOOL DuplicateToRemoteProcess(HANDLE hProcess, HANDLE hHandle)
+ {
+ return DuplicateHandle(GetCurrentProcess(), hHandle, hProcess, &m_hLocal,
+ SYNCHRONIZE, FALSE, 0);
+ }
+
+ // Duplicates the current handle value to local process. To be used in combination with SetLocal.
+ BOOL DuplicateToLocalProcess(HANDLE hProcess, HANDLE* pHandle)
+ {
+ return DuplicateHandle(hProcess, m_hLocal, GetCurrentProcess(), pHandle,
+ NULL, FALSE, DUPLICATE_SAME_ACCESS);
+ }
+
+ void CloseInRemoteProcess(HANDLE hProcess)
+ {
+ HANDLE hHandle = m_hLocal;
+ m_hLocal = NULL;
+
+ HANDLE hTmp;
+ if (DuplicateHandle(hProcess, hHandle, GetCurrentProcess(), &hTmp,
+ NULL, FALSE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE))
+ {
+ CloseHandle(hTmp);
+ }
+ }
+
+ // Imports the handle to local process. To be used in combination with DuplicateToRemoteProcess.
+ HANDLE ImportToLocalProcess()
+ {
+ return m_hLocal;
+ }
+};
+
+
+// AppDomain publishing server support:
+// Information about all appdomains in the process will be maintained
+// in the shared memory block for use by the debugger, etc.
+// This structure defines the layout of the information that will
+// be maintained.
+struct AppDomainEnumerationIPCBlock
+{
+ // !!! The binary format of this layout must remain the same across versions so that
+ // !!! a V2.0 publisher can inspect a v1.0 app.
+
+ // lock for serialization while manipulating AppDomain list.
+ RemoteHANDLE m_hMutex;
+
+ // Number of slots in AppDomainListElement array
+ int m_iTotalSlots;
+ int m_iNumOfUsedSlots;
+ int m_iLastFreedSlot;
+ int m_iSizeInBytes; // Size of AppDomainInfo in bytes
+
+ // We can use psapi!GetModuleFileNameEx to get the module name.
+ // This provides an alternative.
+ int m_iProcessNameLengthInBytes;
+ WCHAR *m_szProcessName;
+
+ AppDomainInfo *m_rgListOfAppDomains;
+ BOOL m_fLockInvalid;
+
+
+#ifndef RIGHT_SIDE_COMPILE
+ /*************************************************************************
+ * Locks the list
+ *************************************************************************/
+ BOOL Lock()
+ {
+ BeginThreadAffinityHelper();
+
+ DWORD dwRes = WaitForSingleObject(m_hMutex, 3000);
+ if (dwRes == WAIT_TIMEOUT)
+ {
+ // Nobody should get stuck holding this lock.
+ // If we timeout on the wait, then either:
+ // - it's a really bad race and somebody got preempted for a long time
+ // - perhaps somebody's doing a DOS attack and holding onto the mutex.
+ m_fLockInvalid = TRUE;
+ }
+
+
+ // The only time this can happen is if we're in shutdown and a thread
+ // that held this lock is killed. If this happens, assume that this
+ // IPC block is in an invalid state and return FALSE to indicate
+ // that people shouldn't do anything with the block anymore.
+ if (dwRes == WAIT_ABANDONED)
+ {
+ m_fLockInvalid = TRUE;
+ }
+
+ if (m_fLockInvalid)
+ {
+ Unlock();
+ }
+
+ return (dwRes == WAIT_OBJECT_0 && !m_fLockInvalid);
+ }
+
+ /*************************************************************************
+ * Unlocks the list
+ *************************************************************************/
+ void Unlock()
+ {
+ // Lock may or may not be valid at this point. Thus Release may fail,
+ // but we'll just ignore that.
+ ReleaseMutex(m_hMutex);
+ EndThreadAffinityHelper();
+
+ }
+
+ /*************************************************************************
+ * Gets a free AppDomainInfo entry, and will allocate room if there are
+ * no free slots left.
+ *************************************************************************/
+ AppDomainInfo *GetFreeEntry()
+ {
+ // first check to see if there is space available. If not, then realloc.
+ if (m_iNumOfUsedSlots == m_iTotalSlots)
+ {
+ // need to realloc
+ AppDomainInfo *pTemp =
+ new (nothrow) AppDomainInfo [m_iTotalSlots*2];
+
+ if (pTemp == NULL)
+ {
+ return (NULL);
+ }
+
+ memcpy (pTemp, m_rgListOfAppDomains, m_iSizeInBytes);
+
+ delete [] m_rgListOfAppDomains;
+
+ // Initialize the increased portion of the realloced memory
+ int iNewSlotSize = m_iTotalSlots * 2;
+
+ for (int iIndex = m_iTotalSlots; iIndex < iNewSlotSize; iIndex++)
+ pTemp[iIndex].FreeEntry();
+
+ m_rgListOfAppDomains = pTemp;
+ m_iTotalSlots = iNewSlotSize;
+ m_iSizeInBytes *= 2;
+ }
+
+ // Walk the list looking for an empty slot. Start from the last
+ // one which was freed.
+ {
+ int i = m_iLastFreedSlot;
+
+ do
+ {
+ // Pointer to the entry being examined
+ AppDomainInfo *pADInfo = &(m_rgListOfAppDomains[i]);
+
+ // is the slot available?
+ if (pADInfo->IsEmpty())
+ return (pADInfo);
+
+ i = (i + 1) % m_iTotalSlots;
+
+ } while (i != m_iLastFreedSlot);
+ }
+
+ _ASSERTE(!"ADInfo::GetFreeEntry: should never get here.");
+ return (NULL);
+ }
+
+ /*************************************************************************
+ * Returns an AppDomainInfo slot to the free list.
+ *************************************************************************/
+ void FreeEntry(AppDomainInfo *pADInfo)
+ {
+ _ASSERTE(pADInfo >= m_rgListOfAppDomains &&
+ pADInfo < m_rgListOfAppDomains + m_iSizeInBytes);
+ _ASSERTE(((size_t)pADInfo - (size_t)m_rgListOfAppDomains) %
+ sizeof(AppDomainInfo) == 0);
+
+ // Mark this slot as free
+ pADInfo->FreeEntry();
+
+#ifdef _DEBUG
+ memset(pADInfo, 0, sizeof(AppDomainInfo));
+#endif
+
+ // decrement the used slot count
+ m_iNumOfUsedSlots--;
+
+ // Save the last freed slot.
+ m_iLastFreedSlot = (int)((size_t)pADInfo - (size_t)m_rgListOfAppDomains) /
+ sizeof(AppDomainInfo);
+ }
+
+ /*************************************************************************
+ * Finds an AppDomainInfo entry corresponding to the AppDomain pointer.
+ * Returns NULL if no such entry exists.
+ *************************************************************************/
+ AppDomainInfo *FindEntry(AppDomain *pAD)
+ {
+ // Walk the list looking for a matching entry
+ for (int i = 0; i < m_iTotalSlots; i++)
+ {
+ AppDomainInfo *pADInfo = &(m_rgListOfAppDomains[i]);
+
+ if (!pADInfo->IsEmpty() &&
+ pADInfo->m_pAppDomain == pAD)
+ return pADInfo;
+ }
+
+ return (NULL);
+ }
+
+ /*************************************************************************
+ * Returns the first AppDomainInfo entry in the list. Returns NULL if
+ * no such entry exists.
+ *************************************************************************/
+ AppDomainInfo *FindFirst()
+ {
+ // Walk the list looking for a non-empty entry
+ for (int i = 0; i < m_iTotalSlots; i++)
+ {
+ AppDomainInfo *pADInfo = &(m_rgListOfAppDomains[i]);
+
+ if (!pADInfo->IsEmpty())
+ return pADInfo;
+ }
+
+ return (NULL);
+ }
+
+ /*************************************************************************
+ * Returns the next AppDomainInfo entry after pADInfo. Returns NULL if
+ * pADInfo was the last in the list.
+ *************************************************************************/
+ AppDomainInfo *FindNext(AppDomainInfo *pADInfo)
+ {
+ _ASSERTE(pADInfo >= m_rgListOfAppDomains &&
+ pADInfo < m_rgListOfAppDomains + m_iSizeInBytes);
+ _ASSERTE(((size_t)pADInfo - (size_t)m_rgListOfAppDomains) %
+ sizeof(AppDomainInfo) == 0);
+
+ // Walk the list looking for the next non-empty entry
+ for (int i = (int)((size_t)pADInfo - (size_t)m_rgListOfAppDomains)
+ / sizeof(AppDomainInfo) + 1;
+ i < m_iTotalSlots;
+ i++)
+ {
+ AppDomainInfo *pADInfoTemp = &(m_rgListOfAppDomains[i]);
+
+ if (!pADInfoTemp->IsEmpty())
+ return pADInfoTemp;
+ }
+
+ return (NULL);
+ }
+#endif // RIGHT_SIDE_COMPILE
+};
+
+// Enforce the AppDomain IPC block binary layout doesn't change between versions.
+// Only an issue for x86 since that's the only platform w/ multiple versions.
+#if defined(DBG_TARGET_X86)
+static_assert_no_msg(offsetof(AppDomainEnumerationIPCBlock, m_hMutex) == 0x0);
+static_assert_no_msg(offsetof(AppDomainEnumerationIPCBlock, m_iTotalSlots) == 0x4);
+static_assert_no_msg(offsetof(AppDomainEnumerationIPCBlock, m_iNumOfUsedSlots) == 0x8);
+static_assert_no_msg(offsetof(AppDomainEnumerationIPCBlock, m_iLastFreedSlot) == 0xc);
+static_assert_no_msg(offsetof(AppDomainEnumerationIPCBlock, m_iSizeInBytes) == 0x10);
+static_assert_no_msg(offsetof(AppDomainEnumerationIPCBlock, m_iProcessNameLengthInBytes) == 0x14);
+static_assert_no_msg(offsetof(AppDomainEnumerationIPCBlock, m_szProcessName) == 0x18);
+static_assert_no_msg(offsetof(AppDomainEnumerationIPCBlock, m_rgListOfAppDomains) == 0x1c);
+static_assert_no_msg(offsetof(AppDomainEnumerationIPCBlock, m_fLockInvalid) == 0x20);
+#endif
+
+#endif //DbgAppDomain_H
+
+
+
diff --git a/src/debug/inc/dbgipcevents.h b/src/debug/inc/dbgipcevents.h
new file mode 100644
index 0000000000..6ace36e011
--- /dev/null
+++ b/src/debug/inc/dbgipcevents.h
@@ -0,0 +1,2360 @@
+// 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.
+/* ------------------------------------------------------------------------- *
+ * DbgIPCEvents.h -- header file for private Debugger data shared by various
+//
+
+ * debugger components.
+ * ------------------------------------------------------------------------- */
+
+#ifndef _DbgIPCEvents_h_
+#define _DbgIPCEvents_h_
+
+#include <new.hpp>
+#include <cor.h>
+#include <cordebug.h>
+#include <corjit.h> // for ICorDebugInfo::VarLocType & VarLoc
+#include <specstrings.h>
+
+#include "dbgtargetcontext.h"
+
+
+// Get version numbers for IPCHeader stamp
+#include "ndpversion.h"
+
+#include "dbgappdomain.h"
+
+#include "./common.h"
+
+//-----------------------------------------------------------------------------
+// V3 additions to IPC protocol between LS and RS.
+//-----------------------------------------------------------------------------
+
+// Special Exception code for LS to communicate with RS.
+// LS will raise this exception to communicate managed debug events to the RS.
+// Exception codes can't use bit 0x10000000, that's reserved by OS.
+#define CLRDBG_NOTIFICATION_EXCEPTION_CODE ((DWORD) 0x04242420)
+
+// This is exception argument 0 included in debugger notification events.
+// The debugger uses this as a sanity check.
+// This could be very volatile data that changes between builds.
+#define CLRDBG_EXCEPTION_DATA_CHECKSUM ((DWORD) 0x31415927)
+
+
+// Reasons for hijack.
+namespace EHijackReason
+{
+ enum EHijackReason
+ {
+ kUnhandledException = 1,
+ kM2UHandoff = 2,
+ kFirstChanceSuspend = 3,
+ kGenericHijack = 4,
+ kMax
+ };
+ inline bool IsValid(EHijackReason value)
+ {
+ SUPPORTS_DAC;
+ return (value > 0) && (value < kMax);
+ }
+}
+
+
+
+#define MAX_LOG_SWITCH_NAME_LEN 256
+
+//-----------------------------------------------------------------------------
+// Versioning note:
+// This file describes the IPC communication protocol between the LS (mscorwks)
+// and the RS (mscordbi). For Desktop builds, it is private and can change on a
+// daily basis. The version of the LS will always match the version of the RS
+// (but see the discussion of CoreCLR below). They are like a single conceptual
+// DLL split across 2 processes.
+// The only restriction is that it should be flavor agnostic - so don't change
+// layout based off '#ifdef DEBUG'. This lets us drop a Debug flavor RS onto
+// a retail installation w/o any further installation woes. That's very useful
+// for debugging.
+//-----------------------------------------------------------------------------
+
+
+// We want this available for DbgInterface.h - put it here.
+typedef enum
+{
+ IPC_TARGET_OUTOFPROC,
+ IPC_TARGET_COUNT,
+} IpcTarget;
+
+//
+// Names of the setup sync event and shared memory used for IPC between the Left Side and the Right Side. NOTE: these
+// names must include a %d for the process id. The process id used is the process id of the debuggee.
+//
+
+#define CorDBIPCSetupSyncEventName W("CorDBIPCSetupSyncEvent_%d")
+
+//
+// This define controls whether we always pass first chance exceptions to the in-process first chance hijack filter
+// during interop debugging or if we try to short-circuit and make the decision out-of-process as much as possible.
+//
+#define CorDB_Short_Circuit_First_Chance_Ownership 1
+
+//
+// Defines for current version numbers for the left and right sides
+//
+#define CorDB_LeftSideProtocolCurrent 2
+#define CorDB_LeftSideProtocolMinSupported 2
+#define CorDB_RightSideProtocolCurrent 2
+#define CorDB_RightSideProtocolMinSupported 2
+
+//
+// The remaining data structures in this file can be shared between two processes and for network transport
+// based debugging this can mean two different platforms as well. The two platforms that can share these
+// data structures must have identical layouts for them (each field must lie at the same offset and have the
+// same length). The MSLAYOUT macro should be applied to each structure to avoid any compiler packing differences.
+//
+
+//
+// DebuggerIPCRuntimeOffsets contains addresses and offsets of important global variables, functions, and fields in
+// Runtime objects. This is populated during Left Side initialization and is read by the Right Side. This struct is
+// mostly to facilitate unmanaged debugging support, but it may have some small uses for managed debugging.
+//
+struct MSLAYOUT DebuggerIPCRuntimeOffsets
+{
+#ifdef FEATURE_INTEROP_DEBUGGING
+ void *m_genericHijackFuncAddr;
+ void *m_signalHijackStartedBPAddr;
+ void *m_excepForRuntimeHandoffStartBPAddr;
+ void *m_excepForRuntimeHandoffCompleteBPAddr;
+ void *m_signalHijackCompleteBPAddr;
+ void *m_excepNotForRuntimeBPAddr;
+ void *m_notifyRSOfSyncCompleteBPAddr;
+ void *m_raiseExceptionAddr; // The address of kernel32!RaiseException in the debuggee
+#endif // FEATURE_INTEROP_DEBUGGING
+ SIZE_T m_TLSIndex; // The TLS index the CLR is using to hold Thread objects
+ SIZE_T m_TLSIsSpecialIndex; // The index into the Predef block of the the "IsSpecial" status for a thread.
+ SIZE_T m_TLSCantStopIndex; // The index into the Predef block of the the Can't-Stop count.
+ SIZE_T m_TLSIndexOfPredefs; // The TLS index of the Predef block.
+ SIZE_T m_EEThreadStateOffset; // Offset of m_state in a Thread
+ SIZE_T m_EEThreadStateNCOffset; // Offset of m_stateNC in a Thread
+ SIZE_T m_EEThreadPGCDisabledOffset; // Offset of the bit for whether PGC is disabled or not in a Thread
+ DWORD m_EEThreadPGCDisabledValue; // Value at m_EEThreadPGCDisabledOffset that equals "PGC disabled".
+ SIZE_T m_EEThreadDebuggerWordOffset; // Offset of debugger word in a Thread
+ SIZE_T m_EEThreadFrameOffset; // Offset of the Frame ptr in a Thread
+ SIZE_T m_EEThreadMaxNeededSize; // Max memory to read to get what we need out of a Thread object
+ DWORD m_EEThreadSteppingStateMask; // Mask for Thread::TSNC_DebuggerIsStepping
+ DWORD m_EEMaxFrameValue; // The max Frame value
+ SIZE_T m_EEThreadDebuggerFilterContextOffset; // Offset of debugger's filter context within a Thread Object.
+ SIZE_T m_EEThreadCantStopOffset; // Offset of the can't stop count in a Thread
+ SIZE_T m_EEFrameNextOffset; // Offset of the next ptr in a Frame
+ DWORD m_EEIsManagedExceptionStateMask; // Mask for Thread::TSNC_DebuggerIsManagedException
+ void *m_pPatches; // Addr of patch table
+ BOOL *m_pPatchTableValid; // Addr of g_patchTableValid
+ SIZE_T m_offRgData; // Offset of m_pcEntries
+ SIZE_T m_offCData; // Offset of count of m_pcEntries
+ SIZE_T m_cbPatch; // Size per patch entry
+ SIZE_T m_offAddr; // Offset within patch of target addr
+ SIZE_T m_offOpcode; // Offset within patch of target opcode
+ SIZE_T m_cbOpcode; // Max size of opcode
+ SIZE_T m_offTraceType; // Offset of the trace.type within a patch
+ DWORD m_traceTypeUnmanaged; // TRACE_UNMANAGED
+
+ DebuggerIPCRuntimeOffsets()
+ {
+ ZeroMemory(this, sizeof(DebuggerIPCRuntimeOffsets));
+ }
+};
+
+//
+// The size of the send and receive IPC buffers.
+// These must be big enough to fit a DebuggerIPCEvent. Also, the bigger they are, the fewer events
+// it takes to send variable length stuff like the stack trace.
+// But for perf reasons, they need to be small enough to not just push us over a page boundary in an IPC block.
+// Unfortunately, there's a lot of other goo in the IPC block, so we can't use some clean formula. So we
+// have to resort to just tuning things.
+//
+
+// When using a network transport rather than shared memory buffers CorDBIPC_BUFFER_SIZE is the upper bound
+// for a single DebuggerIPCEvent structure. This now relates to the maximal size of a network message and is
+// orthogonal to the host's page size. Because of this we defer definition of CorDBIPC_BUFFER_SIZE until we've
+// declared DebuggerIPCEvent at the end of this header (and we can do so because in the transport case there
+// aren't any embedded buffers in the DebuggerIPCControlBlock).
+
+#if defined(DBG_TARGET_X86) || defined(DBG_TARGET_ARM)
+#define CorDBIPC_BUFFER_SIZE (2088) // hand tuned to ensure that ipc block in IPCHeader.h fits in 1 page.
+#else // !_TARGET_X86_ && !_TARGET_ARM_
+// This is the size of a DebuggerIPCEvent. You will hit an assert in Cordb::Initialize() (DI\process.cpp)
+// if this is not defined correctly. AMD64 actually has a page size of 0x1000, not 0x2000.
+#define CorDBIPC_BUFFER_SIZE 4016 // (4016 + 6) * 2 + 148 = 8192 (two (DebuggerIPCEvent + alignment padding) +
+ // other fields = page size)
+#endif // DBG_TARGET_X86 || DBG_TARGET_ARM
+
+//
+// DebuggerIPCControlBlock describes the layout of the shared memory shared between the Left Side and the Right
+// Side. This includes error information, handles for the IPC channel, and space for the send/receive buffers.
+//
+struct MSLAYOUT DebuggerIPCControlBlock
+{
+ // Version data should be first in the control block to ensure that we can read it even if the control block
+ // changes.
+ SIZE_T m_DCBSize; // note this field is used as a semaphore to indicate the DCB is initialized
+ ULONG m_verMajor; // CLR build number for the Left Side.
+ ULONG m_verMinor; // CLR build number for the Left Side.
+
+ // This next stuff fits in a DWORD.
+ bool m_checkedBuild; // CLR build type for the Left Side.
+ // using the first padding byte to indicate if hosted in fiber mode.
+ // We actually just need one bit. So if needed, can turn this to a bit.
+ // BYTE padding1;
+ bool m_bHostingInFiber;
+ BYTE padding2;
+ BYTE padding3;
+
+ ULONG m_leftSideProtocolCurrent; // Current protocol version for the Left Side.
+ ULONG m_leftSideProtocolMinSupported; // Minimum protocol the Left Side can support.
+
+ ULONG m_rightSideProtocolCurrent; // Current protocol version for the Right Side.
+ ULONG m_rightSideProtocolMinSupported; // Minimum protocol the Right Side requires.
+
+ HRESULT m_errorHR;
+ unsigned int m_errorCode;
+
+#if defined(DBG_TARGET_WIN64)
+ // 64-bit needs this padding to make the handles after this aligned.
+ // But x86 can't have this padding b/c it breaks binary compatibility between v1.1 and v2.0.
+ ULONG padding4;
+#endif // DBG_TARGET_WIN64
+
+
+ RemoteHANDLE m_rightSideEventAvailable;
+ RemoteHANDLE m_rightSideEventRead;
+
+ // @dbgtodo inspection - this is where LSEA and LSER used to be. We need to the padding to maintain binary compatibility.
+ // Eventually, we expect to remove this whole block.
+ RemoteHANDLE m_paddingObsoleteLSEA;
+ RemoteHANDLE m_paddingObsoleteLSER;
+
+ RemoteHANDLE m_rightSideProcessHandle;
+
+ //.............................................................................
+ // Everything above this point must have the exact same binary layout as v1.1.
+ // See protocol details below.
+ //.............................................................................
+
+ RemoteHANDLE m_leftSideUnmanagedWaitEvent;
+
+
+
+ // This is set immediately when the helper thread is created.
+ // This will be set even if there's a temporary helper thread or if the real helper
+ // thread is not yet pumping (eg, blocked on a loader lock).
+ DWORD m_realHelperThreadId;
+
+ // This is only published once the helper thread starts running in its main loop.
+ // Thus we can use this field to see if the real helper thread is actually pumping.
+ DWORD m_helperThreadId;
+
+ // This is non-zero if the LS has a temporary helper thread.
+ DWORD m_temporaryHelperThreadId;
+
+ // ID of the Helper's canary thread.
+ DWORD m_CanaryThreadId;
+
+ DebuggerIPCRuntimeOffsets *m_pRuntimeOffsets;
+ void *m_helperThreadStartAddr;
+ void *m_helperRemoteStartAddr;
+ DWORD *m_specialThreadList;
+
+ BYTE m_receiveBuffer[CorDBIPC_BUFFER_SIZE];
+ BYTE m_sendBuffer[CorDBIPC_BUFFER_SIZE];
+
+ DWORD m_specialThreadListLength;
+ bool m_shutdownBegun;
+ bool m_rightSideIsWin32Debugger; // RS status
+ bool m_specialThreadListDirty;
+
+ bool m_rightSideShouldCreateHelperThread;
+
+ // NOTE The Init method works since there are no virtual functions - don't add any virtual functions without
+ // changing this!
+ // Only initialized by the LS, opened by the RS.
+ HRESULT Init(
+ HANDLE rsea,
+ HANDLE rser,
+ HANDLE lsea,
+ HANDLE lser,
+ HANDLE lsuwe
+ );
+
+};
+
+#if defined(FEATURE_DBGIPC_TRANSPORT_VM) || defined(FEATURE_DBGIPC_TRANSPORT_DI)
+
+// We need an alternate definition for the control block if using the transport, because the control block has to be sent over the transport
+// In particular we can't nest the send/receive buffers inside of it and we don't use any of the remote handles
+
+struct MSLAYOUT DebuggerIPCControlBlockTransport
+{
+ // Version data should be first in the control block to ensure that we can read it even if the control block
+ // changes.
+ SIZE_T m_DCBSize; // note this field is used as a semaphore to indicate the DCB is initialized
+ ULONG m_verMajor; // CLR build number for the Left Side.
+ ULONG m_verMinor; // CLR build number for the Left Side.
+
+ // This next stuff fits in a DWORD.
+ bool m_checkedBuild; // CLR build type for the Left Side.
+ // using the first padding byte to indicate if hosted in fiber mode.
+ // We actually just need one bit. So if needed, can turn this to a bit.
+ // BYTE padding1;
+ bool m_bHostingInFiber;
+ BYTE padding2;
+ BYTE padding3;
+
+ ULONG m_leftSideProtocolCurrent; // Current protocol version for the Left Side.
+ ULONG m_leftSideProtocolMinSupported; // Minimum protocol the Left Side can support.
+
+ ULONG m_rightSideProtocolCurrent; // Current protocol version for the Right Side.
+ ULONG m_rightSideProtocolMinSupported; // Minimum protocol the Right Side requires.
+
+ HRESULT m_errorHR;
+ unsigned int m_errorCode;
+
+#if defined(DBG_TARGET_WIN64)
+ // 64-bit needs this padding to make the handles after this aligned.
+ // But x86 can't have this padding b/c it breaks binary compatibility between v1.1 and v2.0.
+ ULONG padding4;
+#endif // DBG_TARGET_WIN64
+
+ // This is set immediately when the helper thread is created.
+ // This will be set even if there's a temporary helper thread or if the real helper
+ // thread is not yet pumping (eg, blocked on a loader lock).
+ DWORD m_realHelperThreadId;
+
+ // This is only published once the helper thread starts running in its main loop.
+ // Thus we can use this field to see if the real helper thread is actually pumping.
+ DWORD m_helperThreadId;
+
+ // This is non-zero if the LS has a temporary helper thread.
+ DWORD m_temporaryHelperThreadId;
+
+ // ID of the Helper's canary thread.
+ DWORD m_CanaryThreadId;
+
+ DebuggerIPCRuntimeOffsets *m_pRuntimeOffsets;
+ void *m_helperThreadStartAddr;
+ void *m_helperRemoteStartAddr;
+ DWORD *m_specialThreadList;
+
+ DWORD m_specialThreadListLength;
+ bool m_shutdownBegun;
+ bool m_rightSideIsWin32Debugger; // RS status
+ bool m_specialThreadListDirty;
+
+ bool m_rightSideShouldCreateHelperThread;
+
+ // NOTE The Init method works since there are no virtual functions - don't add any virtual functions without
+ // changing this!
+ // Only initialized by the LS, opened by the RS.
+ HRESULT Init();
+
+};
+
+#endif // defined(FEATURE_DBGIPC_TRANSPORT_VM) || defined(FEATURE_DBGIPC_TRANSPORT_DI)
+
+#if defined(FEATURE_DBGIPC_TRANSPORT_VM) || defined(FEATURE_DBGIPC_TRANSPORT_DI)
+#include "dbgtransportsession.h"
+#endif // defined(FEATURE_DBGIPC_TRANSPORT_VM) || defined(FEATURE_DBGIPC_TRANSPORT_DI)
+
+#if defined(DBG_TARGET_X86) && !defined(FEATURE_CORESYSTEM)
+// We have an versioning requirement.
+// Certain portions of the v1.0 and v1.1 IPC block are shared. This is b/c a v1.1 debugger needs to be able
+// to look at a v2.0 app enough to recognize the version mismatch.
+// This check is only necessary for platforms that ran on v1.1 (Win32-x86)
+
+// Just to catch any potential illegal change in the IPC block, we assert the offsets against the offsets from v1.1.
+// The constants here are pulled from v1.1.
+// The RS will look at these versioning fields, so they absolutely must line up.
+static_assert_no_msg(offsetof(DebuggerIPCControlBlock, m_leftSideProtocolCurrent) == 0x10);
+static_assert_no_msg(offsetof(DebuggerIPCControlBlock, m_leftSideProtocolMinSupported) == 0x14);
+static_assert_no_msg(offsetof(DebuggerIPCControlBlock, m_rightSideProtocolCurrent) == 0x18);
+static_assert_no_msg(offsetof(DebuggerIPCControlBlock, m_rightSideProtocolMinSupported) == 0x1c);
+
+// Unfortunately, on detecting such failure, v1.1 will also null out LSEA, LSER and RSPH.
+// If these get adjusted, a version-mismatch attach will effectively null out random fields.
+static_assert_no_msg(offsetof(DebuggerIPCControlBlock, m_paddingObsoleteLSEA) == 0x30);
+static_assert_no_msg(offsetof(DebuggerIPCControlBlock, m_paddingObsoleteLSER) == 0x34);
+static_assert_no_msg(offsetof(DebuggerIPCControlBlock, m_rightSideProcessHandle) == 0x38);
+
+
+
+#endif
+
+#define INITIAL_APP_DOMAIN_INFO_LIST_SIZE 16
+
+
+//-----------------------------------------------------------------------------
+// Provide some Type-safety in the IPC block when we pass remote pointers around.
+//-----------------------------------------------------------------------------
+
+
+//-----------------------------------------------------------------------------
+// This is the same in both the LS & RS.
+// Definitions on the LS & RS should be binary compatible. So all storage is
+// declared in GeneralLsPointer, and then the Ls & RS each have their own
+// derived accessors.
+//-----------------------------------------------------------------------------
+class MSLAYOUT GeneralLsPointer
+{
+protected:
+ friend ULONG_PTR LsPtrToCookie(GeneralLsPointer p);
+ void * m_ptr;
+
+public:
+ bool IsNull() { return m_ptr == NULL; }
+};
+
+class MSLAYOUT GeneralRsPointer
+{
+protected:
+ UINT m_data;
+
+public:
+ bool IsNull() { return m_data == 0; }
+};
+
+// In some cases, we need to get a uuid from a pointer (ie, in a hash)
+inline ULONG_PTR LsPtrToCookie(GeneralLsPointer p) {
+ return (ULONG_PTR) p.m_ptr;
+}
+#define VmPtrToCookie(vm) LsPtrToCookie((vm).ToLsPtr())
+
+
+#ifdef RIGHT_SIDE_COMPILE
+//-----------------------------------------------------------------------------
+// Infrasturcture for RS Definitions
+//-----------------------------------------------------------------------------
+
+// On the RS, we don't have the LS classes defined, so we can't templatize that
+// in terms of <class T>, but we still want things to be unique.
+// So we create an empty enum for each LS type and then templatize it in terms
+// of the enum.
+template <typename T>
+class MSLAYOUT LsPointer : public GeneralLsPointer
+{
+public:
+ void Set(void * p)
+ {
+ m_ptr = p;
+ }
+ void * UnsafeGet()
+ {
+ return m_ptr;
+ }
+
+ static LsPointer<T> NullPtr()
+ {
+ return MakePtr(NULL);
+ }
+
+ static LsPointer<T> MakePtr(T* p)
+ {
+#ifdef _PREFAST_
+#pragma warning(push)
+#pragma warning(disable:6001) // PREfast warning: Using uninitialize memory 't'
+#endif // _PREFAST_
+
+ LsPointer<T> t;
+ t.Set(p);
+ return t;
+
+#ifdef _PREFAST_
+#pragma warning(pop)
+#endif // _PREFAST_
+ }
+
+ bool operator!= (void * p) { return m_ptr != p; }
+ bool operator== (void * p) { return m_ptr == p; }
+ bool operator==(LsPointer<T> p) { return p.m_ptr == this->m_ptr; }
+
+ // We should never UnWrap() them in the RS, so we don't define that here.
+};
+
+class CordbProcess;
+template <class T> UINT AllocCookie(CordbProcess * pProc, T * p);
+template <class T> T * UnwrapCookie(CordbProcess * pProc, UINT cookie);
+
+UINT AllocCookieCordbEval(CordbProcess * pProc, class CordbEval * p);
+class CordbEval * UnwrapCookieCordbEval(CordbProcess * pProc, UINT cookie);
+
+template <class CordbEval> UINT AllocCookie(CordbProcess * pProc, CordbEval * p)
+{
+ return AllocCookieCordbEval(pProc, p);
+}
+template <class CordbEval> CordbEval * UnwrapCookie(CordbProcess * pProc, UINT cookie)
+{
+ return UnwrapCookieCordbEval(pProc, cookie);
+}
+
+
+
+// This is how the RS sees the pointers in the IPC block.
+template<class T>
+class MSLAYOUT RsPointer : public GeneralRsPointer
+{
+public:
+ // Since we're being used inside a union, we can't have a ctor.
+
+ static RsPointer<T> NullPtr()
+ {
+ RsPointer<T> t;
+ t.m_data = 0;
+ return t;
+ }
+
+ bool AllocHandle(CordbProcess *pProc, T* p)
+ {
+ // This will force validation.
+ m_data = AllocCookie<T>(pProc, p);
+ return (m_data != 0);
+ }
+
+ bool operator==(RsPointer<T> p) { return p.m_data == this->m_data; }
+
+ T* UnWrapAndRemove(CordbProcess *pProc)
+ {
+ return UnwrapCookie<T>(pProc, m_data);
+ }
+
+protected:
+};
+
+// Forward declare a class so that each type of LS pointer can have
+// its own type. We use the real class name to be compatible with VMPTRs.
+#define DEFINE_LSPTR_TYPE(ls_type, ptr_name) \
+ ls_type; \
+ typedef LsPointer<ls_type> ptr_name;
+
+
+#define DEFINE_RSPTR_TYPE(rs_type, ptr_name) \
+ class rs_type; \
+ typedef RsPointer<rs_type> ptr_name;
+
+#else // !RIGHT_SIDE_COMPILE
+//-----------------------------------------------------------------------------
+// Infrastructure for LS Definitions
+//-----------------------------------------------------------------------------
+
+// This is how the LS sees the pointers in the IPC block.
+template<typename T>
+class MSLAYOUT LsPointer : public GeneralLsPointer
+{
+public:
+ // Since we're being used inside a union, we can't have a ctor.
+ //LsPointer() { }
+
+ static LsPointer<T> NullPtr()
+ {
+ return MakePtr(NULL);
+ }
+
+ static LsPointer<T> MakePtr(T * p)
+ {
+#ifdef _PREFAST_
+#pragma warning(push)
+#pragma warning(disable:6001) // PREfast warning: Using uninitialize memory 't'
+#endif // _PREFAST_
+
+ LsPointer<T> t;
+ t.Set(p);
+ return t;
+
+#ifdef _PREFAST_
+#pragma warning(pop)
+#endif // _PREFAST_
+ }
+
+ bool operator!= (void * p) { return m_ptr != p; }
+ bool operator== (void * p) { return m_ptr == p; }
+ bool operator==(LsPointer<T> p) { return p.m_ptr == this->m_ptr; }
+
+ // @todo - we want to be able to swap out Set + Unwrap functions
+ void Set(T * p)
+ {
+ SUPPORTS_DAC;
+ // We could validate the pointer here.
+ m_ptr = p;
+ }
+
+ T * UnWrap()
+ {
+ // If we wanted to validate the pointer, here's our chance.
+ return static_cast<T*>(m_ptr);
+ }
+};
+
+template <class n>
+class MSLAYOUT RsPointer : public GeneralRsPointer
+{
+public:
+ static RsPointer<n> NullPtr()
+ {
+ RsPointer<n> t;
+ t.m_data = 0;
+ return t;
+ }
+
+ bool operator==(RsPointer<n> p) { return p.m_data == this->m_data; }
+
+ // We should never UnWrap() them in the LS, so we don't define that here.
+};
+
+#define DEFINE_LSPTR_TYPE(ls_type, ptr_name) \
+ ls_type; \
+ typedef LsPointer<ls_type> ptr_name;
+
+#define DEFINE_RSPTR_TYPE(rs_type, ptr_name) \
+ enum __RS__##rs_type { }; \
+ typedef RsPointer<__RS__##rs_type> ptr_name;
+
+#endif // !RIGHT_SIDE_COMPILE
+
+// We must be binary compatible w/ a pointer.
+static_assert_no_msg(sizeof(LsPointer<void>) == sizeof(GeneralLsPointer));
+
+static_assert_no_msg(sizeof(void*) == sizeof(GeneralLsPointer));
+
+
+
+//-----------------------------------------------------------------------------
+// Definitions for Left-Side ptrs.
+// NOTE: Use VMPTR instead of LSPTR. Don't add new LSPTR types.
+//
+//-----------------------------------------------------------------------------
+
+
+
+DEFINE_LSPTR_TYPE(class Assembly, LSPTR_ASSEMBLY);
+DEFINE_LSPTR_TYPE(class DebuggerJitInfo, LSPTR_DJI);
+DEFINE_LSPTR_TYPE(class DebuggerMethodInfo, LSPTR_DMI);
+DEFINE_LSPTR_TYPE(class MethodDesc, LSPTR_METHODDESC);
+DEFINE_LSPTR_TYPE(class DebuggerBreakpoint, LSPTR_BREAKPOINT);
+DEFINE_LSPTR_TYPE(class DebuggerEval, LSPTR_DEBUGGEREVAL);
+DEFINE_LSPTR_TYPE(class DebuggerStepper, LSPTR_STEPPER);
+
+// Need to be careful not to annoy the compiler here since DT_CONTEXT is a typedef, not a struct.
+#if defined(RIGHT_SIDE_COMPILE)
+typedef LsPointer<DT_CONTEXT> LSPTR_CONTEXT;
+#else // RIGHT_SIDE_COMPILE
+typedef LsPointer<DT_CONTEXT> LSPTR_CONTEXT;
+#endif // RIGHT_SIDE_COMPILE
+
+DEFINE_LSPTR_TYPE(struct OBJECTHANDLE__, LSPTR_OBJECTHANDLE);
+DEFINE_LSPTR_TYPE(class TypeHandleDummyPtr, LSPTR_TYPEHANDLE); // TypeHandle in the LS is not a direct pointer.
+
+//-----------------------------------------------------------------------------
+// Definitions for Right-Side ptrs.
+//-----------------------------------------------------------------------------
+DEFINE_RSPTR_TYPE(CordbEval, RSPTR_CORDBEVAL);
+
+
+//---------------------------------------------------------------------------------------
+// VMPTR_Base is the base type for an abstraction over pointers into the VM so
+// that DBI can treat them as opaque handles. Classes will derive from it to
+// provide type-safe Target pointers, which ICD will view as opaque handles.
+//
+// Lifetimes:
+// VMPTR_ objects survive across flushing the DAC cache. Therefore, the underlying
+// storage must be a target-pointer (and not a marshalled host pointer).
+// The RS must ensure they're still in sync with the LS (eg, by
+// tracking unload events).
+//
+//
+// Assumptions:
+// These handles are TADDR pointers and must not require any cleanup from DAC/DBI.
+// For direct untyped pointers into the VM, use CORDB_ADDRESS.
+//
+// Notes:
+// 1. This helps enforce that DBI goes through the primitives interface
+// for all access (and that it doesn't accidentally start calling
+// dac-ized methods on the objects)
+// 2. This isolates DBI from VM headers.
+// 3. This isolates DBI from the dac implementation (of DAC_Ptr)
+// 4. This is distinct from LSPTR because LSPTRs are truly opaque handles, whereas VMPtrs
+// move across VM, DAC, and DBI, exposing proper functionality in each component.
+// 5. VMPTRs are blittable because they are Target Addresses which act as opaque
+// handles outside of the Target / Dac-marshaller.
+//
+//---------------------------------------------------------------------------------------
+
+
+template <typename TTargetPtr, typename TDacPtr>
+class MSLAYOUT VMPTR_Base
+{
+ // Underlying pointer into Target address space.
+ // Target pointers are blittable.
+ // - In Target: can be used as normal local pointers.
+ // - In DAC: must be marshalled to a host-pointer and then they can be used via DAC
+ // - In RS: opaque handles.
+private:
+ TADDR m_addr;
+
+public:
+ typedef VMPTR_Base<TTargetPtr,TDacPtr> VMPTR_This;
+
+ // For DBI, VMPTRs are opaque handles.
+ // But the DAC side is allowed to inspect the handles to get at the raw pointer.
+#if defined(ALLOW_VMPTR_ACCESS)
+ //
+ // Case 1: Using in DAcDbi implementation
+ //
+
+ // DAC accessor
+ TDacPtr GetDacPtr() const
+ {
+ SUPPORTS_DAC;
+ return TDacPtr(m_addr);
+ }
+
+
+ // This will initialize the handle to a given target-pointer.
+ // We choose TADDR to make it explicit that it's a target pointer and avoid the risk
+ // of it accidentally getting marshalled to a host pointer.
+ void SetDacTargetPtr(TADDR addr)
+ {
+ SUPPORTS_DAC;
+ m_addr = addr;
+ }
+
+ void SetHostPtr(const TTargetPtr * pObject)
+ {
+ SUPPORTS_DAC;
+ m_addr = PTR_HOST_TO_TADDR(pObject);
+ }
+
+
+#elif !defined(RIGHT_SIDE_COMPILE)
+ //
+ // Case 2: Used in Left-side. Can get/set from local pointers.
+ //
+
+ // This will set initialize from a Target pointer. Since this is happening in the
+ // Left-side (Target), the pointer is local.
+ // This is commonly used by the Left-side to create a VMPTR_ for a notification event.
+ void SetRawPtr(TTargetPtr * ptr)
+ {
+ m_addr = reinterpret_cast<TADDR>(ptr);
+ }
+
+ // This will get the raw underlying target pointer.
+ // This can be used by inproc Left-side code to unwrap a VMPTR (Eg, for a func-eval
+ // hijack or in-proc worker threads)
+ TTargetPtr * GetRawPtr()
+ {
+ return reinterpret_cast<TTargetPtr*>(m_addr);
+ }
+
+ // Convenience for converting TTargetPtr --> VMPTR
+ static VMPTR_This MakePtr(TTargetPtr * ptr)
+ {
+#ifdef _PREFAST_
+#pragma warning(push)
+#pragma warning(disable:6001) // PREfast warning: Using uninitialize memory 't'
+#endif // _PREFAST_
+
+ VMPTR_This t;
+ t.SetRawPtr(ptr);
+ return t;
+
+#ifdef _PREFAST_
+#pragma warning(pop)
+#endif // _PREFAST_
+ }
+
+
+#else
+ //
+ // Case 3: Used in RS. Opaque handles only.
+ //
+#endif
+
+
+#ifndef DACCESS_COMPILE
+ // For compatibility, these can be converted to LSPTRs on the RS or LS (case 2 and 3). We don't allow
+ // this in the DAC case because it's a cast between address spaces which we're trying to eliminate
+ // in the DAC code.
+ // @dbgtodo inspection: LSPTRs will go away entirely once we've moved completely over to DAC
+ LsPointer<TTargetPtr> ToLsPtr()
+ {
+ return LsPointer<TTargetPtr>::MakePtr( reinterpret_cast<TTargetPtr *>(m_addr));
+ }
+#endif
+
+ //
+ // Operators to emulate Pointer semantics.
+ //
+ bool IsNull() { SUPPORTS_DAC; return m_addr == NULL; }
+
+ static VMPTR_This NullPtr()
+ {
+ SUPPORTS_DAC;
+
+#ifdef _PREFAST_
+#pragma warning(push)
+#pragma warning(disable:6001) // PREfast warning: Using uninitialize memory 't'
+#endif // _PREFAST_
+
+ VMPTR_This dummy;
+ dummy.m_addr = NULL;
+ return dummy;
+
+#ifdef _PREFAST_
+#pragma warning(pop)
+#endif // _PREFAST_
+ }
+
+ bool operator!= (VMPTR_This vmOther) const { SUPPORTS_DAC; return this->m_addr != vmOther.m_addr; }
+ bool operator== (VMPTR_This vmOther) const { SUPPORTS_DAC; return this->m_addr == vmOther.m_addr; }
+};
+
+#if defined(ALLOW_VMPTR_ACCESS)
+// Helper macro to define a VMPTR.
+// This is used in the DAC case, so this definition connects the pointers up to their DAC values.
+#define DEFINE_VMPTR(ls_type, dac_ptr_type, ptr_name) \
+ ls_type; \
+ typedef VMPTR_Base<ls_type, dac_ptr_type> ptr_name;
+
+#else
+// Helper macro to define a VMPTR.
+// This is used in the Right-side and Left-side (but not DAC) case.
+// This definition explicitly ignores dac_ptr_type to prevent accidental DAC usage.
+#define DEFINE_VMPTR(ls_type, dac_ptr_type, ptr_name) \
+ ls_type; \
+ typedef VMPTR_Base<ls_type, void> ptr_name;
+
+#endif
+
+// Declare VMPTRs.
+// The naming convention for instantiating a VMPTR is a 'vm' prefix.
+//
+// VM definition, DAC definition, pretty name for VMPTR
+DEFINE_VMPTR(class AppDomain, PTR_AppDomain, VMPTR_AppDomain);
+
+// Need to be careful not to annoy the compiler here since DT_CONTEXT is a typedef, not a struct.
+// DEFINE_VMPTR(struct _CONTEXT, PTR_CONTEXT, VMPTR_CONTEXT);
+#if defined(ALLOW_VMPTR_ACCESS)
+typedef VMPTR_Base<DT_CONTEXT, PTR_CONTEXT> VMPTR_CONTEXT;
+#else
+typedef VMPTR_Base<DT_CONTEXT, void > VMPTR_CONTEXT;
+#endif
+
+// DomainFile is a base-class for a CLR module, with app-domain affinity.
+// For domain-neutral modules (like mscorlib), there is a DomainFile instance
+// for each appdomain the module lives in.
+// This is the canonical handle ICorDebug uses to a CLR module.
+DEFINE_VMPTR(class DomainFile, PTR_DomainFile, VMPTR_DomainFile);
+DEFINE_VMPTR(class Module, PTR_Module, VMPTR_Module);
+
+// DomainAssembly derives from DomainFile and represents a manifest module.
+DEFINE_VMPTR(class DomainAssembly, PTR_DomainAssembly, VMPTR_DomainAssembly);
+DEFINE_VMPTR(class Assembly, PTR_Assembly, VMPTR_Assembly);
+
+DEFINE_VMPTR(class PEFile, PTR_PEFile, VMPTR_PEFile);
+DEFINE_VMPTR(class MethodDesc, PTR_MethodDesc, VMPTR_MethodDesc);
+DEFINE_VMPTR(class FieldDesc, PTR_FieldDesc, VMPTR_FieldDesc);
+
+// ObjectHandle is a safe way to represent an object into the GC heap. It gets updated
+// when a GC occurs.
+DEFINE_VMPTR(struct OBJECTHANDLE__, TADDR, VMPTR_OBJECTHANDLE);
+
+DEFINE_VMPTR(class TypeHandle, PTR_TypeHandle, VMPTR_TypeHandle);
+
+// A VMPTR_Thread represents a thread that has entered the runtime at some point.
+// It may or may not have executed managed code yet; and it may or may not have managed code
+// on its callstack.
+DEFINE_VMPTR(class Thread, PTR_Thread, VMPTR_Thread);
+
+DEFINE_VMPTR(class Object, PTR_Object, VMPTR_Object);
+
+DEFINE_VMPTR(class CrstBase, PTR_Crst, VMPTR_Crst);
+DEFINE_VMPTR(class SimpleRWLock, PTR_SimpleRWLock, VMPTR_SimpleRWLock);
+DEFINE_VMPTR(class SimpleRWLock, PTR_SimpleRWLock, VMPTR_RWLock);
+DEFINE_VMPTR(struct ReJitInfo, PTR_ReJitInfo, VMPTR_ReJitInfo);
+DEFINE_VMPTR(struct SharedReJitInfo, PTR_SharedReJitInfo, VMPTR_SharedReJitInfo);
+
+
+typedef CORDB_ADDRESS GENERICS_TYPE_TOKEN;
+
+
+//-----------------------------------------------------------------------------
+// We pass some fixed size strings in the IPC block.
+// Helper class to wrap the buffer and protect against buffer overflows.
+// This should be binary compatible w/ a wchar[] array.
+//-----------------------------------------------------------------------------
+
+template <int nMaxLengthIncludingNull>
+class MSLAYOUT EmbeddedIPCString
+{
+public:
+ // Set, caller responsibility that wcslen(pData) < nMaxLengthIncludingNull
+ void SetString(const WCHAR * pData)
+ {
+ // If the string doesn't fit into the buffer, that's an issue (and so this is a real
+ // assert, not just a simplifying assumption). To fix it, either:
+ // - make the buffer larger
+ // - don't pass the string as an embedded string in the IPC block.
+ // This will truncate (rather than AV on the RS).
+ int ret;
+ ret = SafeCopy(pData);
+
+ // See comment above - caller should guarantee that buffer is large enough.
+ _ASSERTE(ret != STRUNCATE);
+ }
+
+ // Set a string from a substring. This will truncate if necessary.
+ void SetStringTruncate(const WCHAR * pData)
+ {
+ // ignore return value because truncation is ok.
+ SafeCopy(pData);
+ }
+
+ const WCHAR * GetString()
+ {
+ // For a null-termination just in case an issue in the debuggee process
+ // yields a malformed string.
+ m_data[nMaxLengthIncludingNull - 1] = W('\0');
+ return &m_data[0];
+ }
+ int GetMaxSize() const { return nMaxLengthIncludingNull; }
+
+private:
+ int SafeCopy(const WCHAR * pData)
+ {
+ return wcsncpy_s(
+ m_data, nMaxLengthIncludingNull,
+ pData, _TRUNCATE);
+ }
+ WCHAR m_data[nMaxLengthIncludingNull];
+};
+
+//
+// Types of events that can be sent between the Runtime Controller and
+// the Debugger Interface. Some of these events are one way only, while
+// others go both ways. The grouping of the event numbers is an attempt
+// to show this distinction and perhaps even allow generic operations
+// based on the type of the event.
+//
+enum DebuggerIPCEventType
+{
+#define IPC_EVENT_TYPE0(type, val) type = val,
+#define IPC_EVENT_TYPE1(type, val) type = val,
+#define IPC_EVENT_TYPE2(type, val) type = val,
+#include "dbgipceventtypes.h"
+#undef IPC_EVENT_TYPE2
+#undef IPC_EVENT_TYPE1
+#undef IPC_EVENT_TYPE0
+};
+
+#ifdef _DEBUG
+
+// This is a static debugging structure to help breaking at the right place.
+// Debug only. This is to track the number of events that have been happened so far.
+// User can choose to set break point base on the number of events.
+// Variables are named as the event name with prefix m_iDebugCount. For example
+// m_iDebugCount_DB_IPCE_BREAKPOINT if for event DB_IPCE_BREAKPOINT.
+struct MSLAYOUT DebugEventCounter
+{
+// we don't need the event type 0
+#define IPC_EVENT_TYPE0(type, val)
+#define IPC_EVENT_TYPE1(type, val) int m_iDebugCount_##type;
+#define IPC_EVENT_TYPE2(type, val) int m_iDebugCount_##type;
+#include "dbgipceventtypes.h"
+#undef IPC_EVENT_TYPE2
+#undef IPC_EVENT_TYPE1
+#undef IPC_EVENT_TYPE0
+};
+#endif // _DEBUG
+
+
+#if !defined(DACCESS_COMPILE)
+
+struct MSLAYOUT IPCEventTypeNameMapping
+ {
+ DebuggerIPCEventType eventType;
+ const char * eventName;
+};
+
+extern const IPCEventTypeNameMapping DECLSPEC_SELECTANY DbgIPCEventTypeNames[] =
+{
+ #define IPC_EVENT_TYPE0(type, val) { type, #type },
+ #define IPC_EVENT_TYPE1(type, val) { type, #type },
+ #define IPC_EVENT_TYPE2(type, val) { type, #type },
+ #include "dbgipceventtypes.h"
+ #undef IPC_EVENT_TYPE2
+ #undef IPC_EVENT_TYPE1
+ #undef IPC_EVENT_TYPE0
+ { DB_IPCE_INVALID_EVENT, "DB_IPCE_Error" }
+};
+
+const size_t nameCount = sizeof(DbgIPCEventTypeNames) / sizeof(DbgIPCEventTypeNames[0]);
+
+
+struct MSLAYOUT IPCENames // We use a class/struct so that the function can remain in a shared header file
+{
+ static const DebuggerIPCEventType GetEventType(__in_z char * strEventType)
+ {
+ // pass in the string of event name and find the matching enum value
+ // This is a linear search which is pretty slow. However, this is only used
+ // at startup time when debug assert is turn on and with registry key set. So it is not that bad.
+ //
+ for (size_t i = 0; i < nameCount; i++)
+ {
+ if (_stricmp(DbgIPCEventTypeNames[i].eventName, strEventType) == 0)
+ return DbgIPCEventTypeNames[i].eventType;
+ }
+ return DB_IPCE_INVALID_EVENT;
+ }
+ static const char * GetName(DebuggerIPCEventType eventType)
+ {
+
+ enum DbgIPCEventTypeNum
+ {
+ #define IPC_EVENT_TYPE0(type, val) type##_Num,
+ #define IPC_EVENT_TYPE1(type, val) type##_Num,
+ #define IPC_EVENT_TYPE2(type, val) type##_Num,
+ #include "dbgipceventtypes.h"
+ #undef IPC_EVENT_TYPE2
+ #undef IPC_EVENT_TYPE1
+ #undef IPC_EVENT_TYPE0
+ };
+
+ unsigned int i, lim;
+
+ if (eventType < DB_IPCE_DEBUGGER_FIRST)
+ {
+ i = DB_IPCE_RUNTIME_FIRST_Num + 1;
+ lim = DB_IPCE_DEBUGGER_FIRST_Num;
+ }
+ else
+ {
+ i = DB_IPCE_DEBUGGER_FIRST_Num + 1;
+ lim = nameCount;
+ }
+
+ for (/**/; i < lim; i++)
+ {
+ if (DbgIPCEventTypeNames[i].eventType == eventType)
+ return DbgIPCEventTypeNames[i].eventName;
+ }
+
+ return DbgIPCEventTypeNames[nameCount - 1].eventName;
+ }
+};
+
+#endif // !DACCESS_COMPILE
+
+//
+// NOTE: CPU-specific values below!
+//
+// DebuggerREGDISPLAY is very similar to the EE REGDISPLAY structure. It holds
+// register values that can be saved over calls for each frame in a stack
+// trace.
+//
+// DebuggerIPCE_FloatCount is the number of doubles in the processor's
+// floating point stack.
+//
+// <TODO>Note: We used to just pass the values of the registers for each frame to the Right Side, but I had to add in the
+// address of each register, too, to support using enregistered variables on non-leaf frames as args to a func eval. Its
+// very, very possible that we would rework the entire code base to just use the register's address instead of passing
+// both, but its way, way too late in V1 to undertake that, so I'm just using these addresses to suppport our one func
+// eval case. Clearly, this needs to be cleaned up post V1.
+//
+// -- Fri Feb 09 11:21:24 2001</TODO>
+//
+
+struct MSLAYOUT DebuggerREGDISPLAY
+{
+#if defined(DBG_TARGET_X86)
+ #define DebuggerIPCE_FloatCount 8
+
+ SIZE_T Edi;
+ void *pEdi;
+ SIZE_T Esi;
+ void *pEsi;
+ SIZE_T Ebx;
+ void *pEbx;
+ SIZE_T Edx;
+ void *pEdx;
+ SIZE_T Ecx;
+ void *pEcx;
+ SIZE_T Eax;
+ void *pEax;
+ SIZE_T FP;
+ void *pFP;
+ SIZE_T SP;
+ SIZE_T PC;
+
+#elif defined(DBG_TARGET_AMD64)
+ #define DebuggerIPCE_FloatCount 16
+
+ SIZE_T Rax;
+ void *pRax;
+ SIZE_T Rcx;
+ void *pRcx;
+ SIZE_T Rdx;
+ void *pRdx;
+ SIZE_T Rbx;
+ void *pRbx;
+ SIZE_T Rbp;
+ void *pRbp;
+ SIZE_T Rsi;
+ void *pRsi;
+ SIZE_T Rdi;
+ void *pRdi;
+
+ SIZE_T R8;
+ void *pR8;
+ SIZE_T R9;
+ void *pR9;
+ SIZE_T R10;
+ void *pR10;
+ SIZE_T R11;
+ void *pR11;
+ SIZE_T R12;
+ void *pR12;
+ SIZE_T R13;
+ void *pR13;
+ SIZE_T R14;
+ void *pR14;
+ SIZE_T R15;
+ void *pR15;
+
+ SIZE_T SP;
+ SIZE_T PC;
+#elif defined(DBG_TARGET_ARM)
+ #define DebuggerIPCE_FloatCount 32
+
+ SIZE_T R0;
+ void *pR0;
+ SIZE_T R1;
+ void *pR1;
+ SIZE_T R2;
+ void *pR2;
+ SIZE_T R3;
+ void *pR3;
+ SIZE_T R4;
+ void *pR4;
+ SIZE_T R5;
+ void *pR5;
+ SIZE_T R6;
+ void *pR6;
+ SIZE_T R7;
+ void *pR7;
+ SIZE_T R8;
+ void *pR8;
+ SIZE_T R9;
+ void *pR9;
+ SIZE_T R10;
+ void *pR10;
+ SIZE_T R11;
+ void *pR11;
+ SIZE_T R12;
+ void *pR12;
+ SIZE_T SP;
+ void *pSP;
+ SIZE_T LR;
+ void *pLR;
+ SIZE_T PC;
+ void *pPC;
+#elif defined(DBG_TARGET_ARM64)
+ #define DebuggerIPCE_FloatCount 32
+
+ SIZE_T X[29];
+ SIZE_T SP;
+ SIZE_T FP;
+ SIZE_T LR;
+ SIZE_T PC;
+#else
+ #define DebuggerIPCE_FloatCount 1
+
+ SIZE_T PC;
+ SIZE_T FP;
+ SIZE_T SP;
+ void *pFP;
+#endif
+};
+
+inline LPVOID GetSPAddress(const DebuggerREGDISPLAY * display)
+{
+ return (LPVOID)&display->SP;
+}
+
+#if !defined(DBG_TARGET_AMD64) && !defined(DBG_TARGET_ARM)
+inline LPVOID GetFPAddress(const DebuggerREGDISPLAY * display)
+{
+ return (LPVOID)&display->FP;
+}
+#endif // !DBG_TARGET_AMD64
+
+
+class MSLAYOUT FramePointer
+{
+friend bool IsCloserToLeaf(FramePointer fp1, FramePointer fp2);
+friend bool IsCloserToRoot(FramePointer fp1, FramePointer fp2);
+friend bool IsEqualOrCloserToLeaf(FramePointer fp1, FramePointer fp2);
+friend bool IsEqualOrCloserToRoot(FramePointer fp1, FramePointer fp2);
+
+public:
+
+ static FramePointer MakeFramePointer(LPVOID sp)
+ {
+ LIMITED_METHOD_DAC_CONTRACT;
+ FramePointer fp;
+ fp.m_sp = sp;
+ return fp;
+ }
+
+ static FramePointer MakeFramePointer(UINT_PTR sp)
+ {
+ SUPPORTS_DAC;
+ return MakeFramePointer((LPVOID)sp);
+ }
+
+ inline bool operator==(FramePointer fp)
+ {
+ return (m_sp == fp.m_sp);
+ }
+
+ inline bool operator!=(FramePointer fp)
+ {
+ return !(*this == fp);
+ }
+
+ // This is needed because on the RS, the m_id values of CordbFrame and
+ // CordbChain are really FramePointers.
+ LPVOID GetSPValue() const
+ {
+ return m_sp;
+ }
+
+
+private:
+ // Declare some private constructors which signatures matching common usage of FramePointer
+ // to prevent people from accidentally assigning a pointer to a FramePointer().
+ FramePointer &operator=(LPVOID sp);
+ FramePointer &operator=(BYTE* sp);
+ FramePointer &operator=(const BYTE* sp);
+
+ LPVOID m_sp;
+};
+
+// For non-IA64 platforms, we use stack pointers as frame pointers.
+// (Stack grows towards smaller address.)
+#define LEAF_MOST_FRAME FramePointer::MakeFramePointer((LPVOID)NULL)
+#define ROOT_MOST_FRAME FramePointer::MakeFramePointer((LPVOID)-1)
+
+static_assert_no_msg(sizeof(FramePointer) == sizeof(void*));
+
+
+inline bool IsCloserToLeaf(FramePointer fp1, FramePointer fp2)
+{
+ return (fp1.m_sp < fp2.m_sp);
+}
+
+inline bool IsCloserToRoot(FramePointer fp1, FramePointer fp2)
+{
+ return (fp1.m_sp > fp2.m_sp);
+}
+
+inline bool IsEqualOrCloserToLeaf(FramePointer fp1, FramePointer fp2)
+{
+ return !IsCloserToRoot(fp1, fp2);
+}
+
+inline bool IsEqualOrCloserToRoot(FramePointer fp1, FramePointer fp2)
+{
+ return !IsCloserToLeaf(fp1, fp2);
+}
+
+
+// struct DebuggerIPCE_FuncData: DebuggerIPCE_FuncData holds data
+// to describe a given function, its
+// class, and a little bit about the code for the function. This is used
+// in the stack trace result data to pass function information back that
+// may be needed. Its also used when getting data about a specific function.
+//
+// void* nativeStartAddressPtr: Ptr to CORDB_ADDRESS, which is
+// the address of the real start address of the native code.
+// This field will be NULL only if the method hasn't been JITted
+// yet (and thus no code is available). Otherwise, it will be
+// the adress of a CORDB_ADDRESS in the remote memory. This
+// CORDB_ADDRESS may be NULL, in which case the code is unavailable
+// has been pitched (return CORDBG_E_CODE_NOT_AVAILABLE)
+//
+// SIZE_T nVersion: The version of the code that this instance of the
+// function is using.
+struct MSLAYOUT DebuggerIPCE_FuncData
+{
+ mdMethodDef funcMetadataToken;
+ VMPTR_DomainFile vmDomainFile;
+
+ mdTypeDef classMetadataToken;
+
+ void* ilStartAddress;
+ SIZE_T ilSize;
+
+ SIZE_T currentEnCVersion;
+
+ mdSignature localVarSigToken;
+
+
+};
+
+// struct DebuggerIPCE_JITFuncData: DebuggerIPCE_JITFuncData holds
+// a little bit about the JITted code for the function.
+//
+// void* nativeStartAddressPtr: Ptr to CORDB_ADDRESS, which is
+// the address of the real start address of the native code.
+// This field will be NULL only if the method hasn't been JITted
+// yet (and thus no code is available). Otherwise, it will be
+// the address of a CORDB_ADDRESS in the remote memory. This
+// CORDB_ADDRESS may be NULL, in which case the code is unavailable
+// or has been pitched (return CORDBG_E_CODE_NOT_AVAILABLE)
+//
+// SIZE_T nativeSize: Size of the native code.
+//
+// SIZE_T nativeOffset: Offset from the beginning of the function,
+// in bytes. This may be non-zero even when nativeStartAddressPtr
+// is NULL
+// void * nativeCodeJITInfoToken: An opaque value to hand back to the left
+// side when fetching the JITInfo for the native code, i.e. the
+// IL->native maps for the variables. This may be NULL if no JITInfo is available.
+// void * nativeCodeMethodDescToken: An opaque value to hand back to the left
+// side when fetching the code. In addition this token can act as the
+// unique identity for the native code in the case where there are
+// multiple blobs of native code per IL method (i.e. if the method is
+// generic code of some kind)
+// BOOL isInstantiatedGeneric: Indicates if the method is
+// generic code of some kind.
+// BOOL jsutAfterILThrow: indicates that code just threw a software exception and
+// nativeOffset points to an instruction just after [call IL_Throw].
+// This is being used to figure out a real offset of the exception origin.
+// By subtracting STACKWALK_CONTROLPC_ADJUST_OFFSET from nativeOffset you can get
+// an address somewhere inside [call IL_Throw] instruction.
+// void *ilToNativeMapAddr etc.: If nativeCodeJITInfoToken is not NULL then these
+// specify the table giving the mapping of IPs.
+struct MSLAYOUT DebuggerIPCE_JITFuncData
+{
+ TADDR nativeStartAddressPtr;
+ SIZE_T nativeHotSize;
+
+ // If we have a cold region, need its size & the pointer to where starts.
+ TADDR nativeStartAddressColdPtr;
+ SIZE_T nativeColdSize;
+
+
+ SIZE_T nativeOffset;
+ LSPTR_DJI nativeCodeJITInfoToken;
+ VMPTR_MethodDesc vmNativeCodeMethodDescToken;
+
+#if defined(DBG_TARGET_WIN64) || defined(DBG_TARGET_ARM)
+ BOOL fIsFilterFrame;
+ SIZE_T parentNativeOffset;
+ FramePointer fpParentOrSelf;
+#endif // DBG_TARGET_WIN64 || DBG_TARGET_ARM
+
+ // indicates if the MethodDesc is a generic function or a method inside a generic class (or
+ // both!).
+ BOOL isInstantiatedGeneric;
+
+ // this is the version of the jitted code
+ SIZE_T enCVersion;
+
+ BOOL jsutAfterILThrow;
+};
+
+//
+// DebuggerIPCE_STRData holds data for each stack frame or chain. This data is passed
+// from the RC to the DI during a stack walk.
+//
+#if defined(_MSC_VER)
+#pragma warning( push )
+#pragma warning( disable:4324 ) // the compiler pads a structure to comply with alignment requirements
+#endif // ARM context structures have a 16-byte alignment requirement
+struct MSLAYOUT DebuggerIPCE_STRData
+{
+ FramePointer fp;
+ // @dbgtodo stackwalker/shim- Ideally we should be able to get rid of the DebuggerREGDISPLAY and just use the CONTEXT.
+ DT_CONTEXT ctx;
+ DebuggerREGDISPLAY rd;
+ bool quicklyUnwound;
+
+ VMPTR_AppDomain vmCurrentAppDomainToken;
+
+
+ enum EType
+ {
+ cMethodFrame = 0,
+ cChain,
+ cStubFrame,
+ cRuntimeNativeFrame
+ } eType;
+
+ union MSLAYOUT
+ {
+ // Data for a chain
+ struct MSLAYOUT
+ {
+ CorDebugChainReason chainReason;
+ bool managed;
+ } u;
+
+ // Data for a Method
+ struct MSLAYOUT
+ {
+ struct DebuggerIPCE_FuncData funcData;
+ struct DebuggerIPCE_JITFuncData jitFuncData;
+ SIZE_T ILOffset;
+ CorDebugMappingResult mapping;
+
+ bool fVarArgs;
+
+ // Indicates whether the managed method has any metadata.
+ // Some dynamic methods such as IL stubs and LCG methods don't have any metadata.
+ // This is used only by the V3 stackwalker, not the V2 one, because we only
+ // expose dynamic methods as real stack frames in V3.
+ bool fNoMetadata;
+
+ TADDR taAmbientESP;
+
+ GENERICS_TYPE_TOKEN exactGenericArgsToken;
+ DWORD dwExactGenericArgsTokenIndex;
+
+ } v;
+
+ // Data for an Stub Frame.
+ struct MSLAYOUT
+ {
+ mdMethodDef funcMetadataToken;
+ VMPTR_DomainFile vmDomainFile;
+ VMPTR_MethodDesc vmMethodDesc;
+ CorDebugInternalFrameType frameType;
+ } stubFrame;
+
+ };
+};
+#if defined(_MSC_VER)
+#pragma warning( pop )
+#endif
+
+//
+// DebuggerIPCE_BasicTypeData and DebuggerIPCE_ExpandedTypeData
+// hold data for each type sent across the
+// boundary, whether it be a constructed type List<String> or a non-constructed
+// type such as String, Foo or Object.
+//
+// Logically speaking DebuggerIPCE_BasicTypeData might just be "typeHandle", as
+// we could then send further events to ask what the elementtype, typeToken and moduleToken
+// are for the type handle. But as
+// nearly all types are non-generic we send across even the basic type information in
+// the slightly expanded form shown below, sending the element type and the
+// tokens with the type handle itself. The fields debuggerModuleToken, metadataToken and typeHandle
+// are only used as follows:
+// elementType debuggerModuleToken metadataToken typeHandle
+// E_T_INT8 : E_T_INT8 No No No
+// Boxed E_T_INT8: E_T_CLASS No No No
+// E_T_CLASS, non-generic class: E_T_CLASS Yes Yes No
+// E_T_VALUETYPE, non-generic: E_T_VALUETYPE Yes Yes No
+// E_T_CLASS, generic class: E_T_CLASS Yes Yes Yes
+// E_T_VALUETYPE, generic class: E_T_VALUETYPE Yes Yes Yes
+// E_T_BYREF : E_T_BYREF No No Yes
+// E_T_PTR : E_T_PTR No No Yes
+// E_T_ARRAY etc. : E_T_ARRAY No No Yes
+// E_T_FNPTR etc. : E_T_FNPTR No No Yes
+// This allows us to always set "typeHandle" to NULL except when dealing with highly nested
+// types or function-pointer types (the latter are too complexe to transfer over in one hit).
+//
+
+struct MSLAYOUT DebuggerIPCE_BasicTypeData
+{
+ CorElementType elementType;
+ mdTypeDef metadataToken;
+ VMPTR_Module vmModule;
+ VMPTR_DomainFile vmDomainFile;
+ VMPTR_TypeHandle vmTypeHandle;
+};
+
+// DebuggerIPCE_ExpandedTypeData contains more information showing further
+// details for array types, byref types etc.
+// Whenever you fetch type information from the left-side
+// you get back one of these. These in turn contain further
+// DebuggerIPCE_BasicTypeData's and typeHandles which you can
+// then query to get further information about the type parameters.
+// This copes with the nested cases, e.g. jagged arrays,
+// String ****, &(String*), Pair<String,Pair<String>>
+// and so on.
+//
+// So this type information is not "fully expanded", it's just a little
+// more detail then DebuggerIPCE_BasicTypeData. For type
+// instantiatons (e.g. List<int>) and
+// function pointer types you will need to make further requests for
+// information about the type parameters.
+// For array types there is always only one type parameter so
+// we include that as part of the expanded data.
+//
+//
+struct MSLAYOUT DebuggerIPCE_ExpandedTypeData
+{
+ CorElementType elementType; // Note this is _never_ E_T_VAR, E_T_WITH or E_T_MVAR
+ union MSLAYOUT
+ {
+ // used for E_T_CLASS and E_T_VALUECLASS, E_T_PTR, E_T_BYREF etc.
+ // For non-constructed E_T_CLASS or E_T_VALUECLASS the tokens will be set and the typeHandle will be NULL
+ // For constructed E_T_CLASS or E_T_VALUECLASS the tokens will be set and the typeHandle will be non-NULL
+ // For E_T_PTR etc. the tokens will be NULL and the typeHandle will be non-NULL.
+ struct MSLAYOUT
+ {
+ mdTypeDef metadataToken;
+ VMPTR_Module vmModule;
+ VMPTR_DomainFile vmDomainFile;
+ VMPTR_TypeHandle typeHandle; // if non-null then further fetches will be needed to get type arguments
+ } ClassTypeData;
+
+ // used for E_T_PTR, E_T_BYREF etc.
+ struct MSLAYOUT
+ {
+ DebuggerIPCE_BasicTypeData unaryTypeArg; // used only when sending back to debugger
+ } UnaryTypeData;
+
+
+ // used for E_T_ARRAY etc.
+ struct MSLAYOUT
+ {
+ DebuggerIPCE_BasicTypeData arrayTypeArg; // used only when sending back to debugger
+ DWORD arrayRank;
+ } ArrayTypeData;
+
+ // used for E_T_FNPTR
+ struct MSLAYOUT
+ {
+ VMPTR_TypeHandle typeHandle; // if non-null then further fetches needed to get type arguments
+ } NaryTypeData;
+
+ };
+};
+
+// DebuggerIPCE_TypeArgData is used when sending type arguments
+// across to a funceval. It contains the DebuggerIPCE_ExpandedTypeData describing the
+// essence of the type, but the typeHandle and other
+// BasicTypeData fields should be zero and will be ignored.
+// The DebuggerIPCE_ExpandedTypeData is then followed
+// by the required number of type arguments, each of which
+// will be a further DebuggerIPCE_TypeArgData record in the stream of
+// flattened type argument data.
+struct MSLAYOUT DebuggerIPCE_TypeArgData
+{
+ DebuggerIPCE_ExpandedTypeData data;
+ unsigned int numTypeArgs; // number of immediate children on the type tree
+};
+
+
+//
+// DebuggerIPCE_ObjectData holds the results of a
+// GetAndSendObjectInfo, i.e., all the info about an object that the
+// Right Side would need to access it. (This include array, string,
+// and nstruct info.)
+//
+struct MSLAYOUT DebuggerIPCE_ObjectData
+{
+ void *objRef;
+ bool objRefBad;
+ SIZE_T objSize;
+
+ // Offset from the beginning of the object to the beginning of the first field
+ SIZE_T objOffsetToVars;
+
+ // The type of the object....
+ struct DebuggerIPCE_ExpandedTypeData objTypeData;
+
+ union MSLAYOUT
+ {
+ struct MSLAYOUT
+ {
+ SIZE_T length;
+ SIZE_T offsetToStringBase;
+ } stringInfo;
+
+ struct MSLAYOUT
+ {
+ SIZE_T rank;
+ SIZE_T offsetToArrayBase;
+ SIZE_T offsetToLowerBounds; // 0 if not present
+ SIZE_T offsetToUpperBounds; // 0 if not present
+ SIZE_T componentCount;
+ SIZE_T elementSize;
+ } arrayInfo;
+
+ struct MSLAYOUT
+ {
+ struct DebuggerIPCE_BasicTypeData typedByrefType; // the type of the thing contained in a typedByref...
+ } typedByrefInfo;
+ };
+};
+
+//
+// Remote enregistered info used by CordbValues and for passing
+// variable homes between the left and right sides during a func eval.
+//
+
+enum RemoteAddressKind
+{
+ RAK_NONE = 0,
+ RAK_REG,
+ RAK_REGREG,
+ RAK_REGMEM,
+ RAK_MEMREG,
+ RAK_FLOAT,
+ RAK_END
+};
+
+const CORDB_ADDRESS kLeafFrameRegAddr = 0;
+const CORDB_ADDRESS kNonLeafFrameRegAddr = (CORDB_ADDRESS)(-1);
+
+struct MSLAYOUT RemoteAddress
+{
+ RemoteAddressKind kind;
+ void *frame;
+
+ CorDebugRegister reg1;
+ void *reg1Addr;
+ SIZE_T reg1Value; // this is the actual value of the register
+
+ union MSLAYOUT
+ {
+ struct MSLAYOUT
+ {
+ CorDebugRegister reg2;
+ void *reg2Addr;
+ SIZE_T reg2Value; // this is the actual value of the register
+ } u;
+
+ CORDB_ADDRESS addr;
+ DWORD floatIndex;
+ };
+};
+
+//
+// DebuggerIPCE_FuncEvalType specifies the type of a function
+// evaluation that will occur.
+//
+enum DebuggerIPCE_FuncEvalType
+{
+ DB_IPCE_FET_NORMAL,
+ DB_IPCE_FET_NEW_OBJECT,
+ DB_IPCE_FET_NEW_OBJECT_NC,
+ DB_IPCE_FET_NEW_STRING,
+ DB_IPCE_FET_NEW_ARRAY,
+ DB_IPCE_FET_RE_ABORT
+};
+
+
+enum NameChangeType
+{
+ APP_DOMAIN_NAME_CHANGE,
+ THREAD_NAME_CHANGE
+};
+
+//
+// DebuggerIPCE_FuncEvalArgData holds data for each argument to a
+// function evaluation.
+//
+struct MSLAYOUT DebuggerIPCE_FuncEvalArgData
+{
+ RemoteAddress argHome; // enregistered variable home
+ void *argAddr; // address if not enregistered
+ CorElementType argElementType;
+ unsigned int fullArgTypeNodeCount; // Pointer to LS (DebuggerIPCE_TypeArgData *) buffer holding full description of the argument type (if needed - only needed for struct types)
+ void *fullArgType; // Pointer to LS (DebuggerIPCE_TypeArgData *) buffer holding full description of the argument type (if needed - only needed for struct types)
+ BYTE argLiteralData[8]; // copy of generic value data
+ bool argIsLiteral; // true if value is in argLiteralData
+ bool argIsHandleValue; // true if argAddr is OBJECTHANDLE
+};
+
+
+//
+// DebuggerIPCE_FuncEvalInfo holds info necessary to setup a func eval
+// operation.
+//
+struct MSLAYOUT DebuggerIPCE_FuncEvalInfo
+{
+ VMPTR_Thread vmThreadToken;
+ DebuggerIPCE_FuncEvalType funcEvalType;
+ mdMethodDef funcMetadataToken;
+ mdTypeDef funcClassMetadataToken;
+ VMPTR_DomainFile vmDomainFile;
+ RSPTR_CORDBEVAL funcEvalKey;
+ bool evalDuringException;
+
+ unsigned int argCount;
+ unsigned int genericArgsCount;
+ unsigned int genericArgsNodeCount;
+
+ SIZE_T stringSize;
+
+ SIZE_T arrayRank;
+};
+
+
+//
+// Used in DebuggerIPCFirstChanceData. This tells the LS what action to take within the hijack
+//
+enum HijackAction
+{
+ HIJACK_ACTION_EXIT_UNHANDLED,
+ HIJACK_ACTION_EXIT_HANDLED,
+ HIJACK_ACTION_WAIT
+};
+
+//
+// DebuggerIPCFirstChanceData holds info communicated from the LS to the RS when signaling that an exception does not
+// belong to the runtime from a first chance hijack. This is used when Win32 debugging only.
+//
+struct MSLAYOUT DebuggerIPCFirstChanceData
+{
+ LSPTR_CONTEXT pLeftSideContext;
+ HijackAction action;
+ UINT debugCounter;
+};
+
+//
+// DebuggerIPCSecondChanceData holds info communicated from the RS
+// to the LS when setting up a second chance exception hijack. This is
+// used when Win32 debugging only.
+//
+struct MSLAYOUT DebuggerIPCSecondChanceData
+{
+ DT_CONTEXT threadContext;
+};
+
+
+
+//-----------------------------------------------------------------------------
+// This struct holds pointer from the LS and needs to copy to
+// the RS. We have to free the memory on the RS.
+// The transfer function is called when the RS first reads the event. At this point,
+// the LS is stopped while sending the event. Thus the LS pointers only need to be
+// valid while the LS is in SendIPCEvent.
+//
+// Since this data is in an IPC/Marshallable block, it can't have any Ctors (holders)
+// in it.
+//-----------------------------------------------------------------------------
+struct MSLAYOUT Ls_Rs_BaseBuffer
+{
+#ifdef RIGHT_SIDE_COMPILE
+protected:
+ // copy data can happen on both LS and RS. In LS case,
+ // ReadProcessMemory is really reading from its own process memory.
+ //
+ void CopyLSDataToRSWorker(ICorDebugDataTarget * pTargethProcess);
+
+ // retrieve the RS data and own it
+ BYTE *TransferRSDataWorker()
+ {
+ BYTE *pbRS = m_pbRS;
+ m_pbRS = NULL;
+ return pbRS;
+ }
+public:
+
+
+ void CleanUp()
+ {
+ if (m_pbRS != NULL)
+ {
+ delete [] m_pbRS;
+ m_pbRS = NULL;
+ }
+ }
+#else
+public:
+ // Only LS can call this API
+ void SetLsData(BYTE *pbLS, DWORD cbSize)
+ {
+ m_pbRS = NULL;
+ m_pbLS = pbLS;
+ m_cbSize = cbSize;
+ }
+#endif // RIGHT_SIDE_COMPILE
+
+public:
+ // Common APIs.
+ DWORD GetSize() { return m_cbSize; }
+
+
+
+protected:
+ // Size of data in bytes
+ DWORD m_cbSize;
+
+ // If this is non-null, pointer into LS for buffer.
+ // LS can free this after the debug event is continued.
+ BYTE *m_pbLS; // @dbgtodo cross-plat- for cross-platform purposes, this should be a TADDR
+
+ // If this is non-null, pointer into RS for buffer. RS must then free this.
+ // This buffer was copied from the LS (via CopyLSDataToRSWorker).
+ BYTE *m_pbRS;
+};
+
+//-----------------------------------------------------------------------------
+// Byte wrapper around the buffer.
+//-----------------------------------------------------------------------------
+struct MSLAYOUT Ls_Rs_ByteBuffer : public Ls_Rs_BaseBuffer
+{
+#ifdef RIGHT_SIDE_COMPILE
+ BYTE *GetRSPointer()
+ {
+ return m_pbRS;
+ }
+
+ void CopyLSDataToRS(ICorDebugDataTarget * pTarget);
+ BYTE *TransferRSData()
+ {
+ return TransferRSDataWorker();
+ }
+#endif
+};
+
+//-----------------------------------------------------------------------------
+// Wrapper around a Ls_rS_Buffer to get it as a string.
+// This can also do some sanity checking.
+//-----------------------------------------------------------------------------
+struct MSLAYOUT Ls_Rs_StringBuffer : public Ls_Rs_BaseBuffer
+{
+#ifdef RIGHT_SIDE_COMPILE
+ const WCHAR * GetString()
+ {
+ return reinterpret_cast<const WCHAR*> (m_pbRS);
+ }
+
+ // Copy over the string.
+ void CopyLSDataToRS(ICorDebugDataTarget * pTarget);
+
+ // Caller will pick up ownership.
+ // Since caller will delete this data, we can't give back a constant pointer.
+ WCHAR * TransferStringData()
+ {
+ return reinterpret_cast<WCHAR*> (TransferRSDataWorker());
+ }
+#endif
+};
+
+
+// Data for an Managed Debug Assistant Probe (MDA).
+struct MSLAYOUT DebuggerMDANotification
+{
+ Ls_Rs_StringBuffer szName;
+ Ls_Rs_StringBuffer szDescription;
+ Ls_Rs_StringBuffer szXml;
+ DWORD dwOSThreadId;
+ CorDebugMDAFlags flags;
+};
+
+
+// The only remaining problem is that register number mappings are different for each platform. It turns out
+// that the debugger only uses REGNUM_SP and REGNUM_AMBIENT_SP though, so we can just virtualize these two for
+// the target platform.
+// Keep this is sync with the definitions in inc/corinfo.h.
+#if defined(DBG_TARGET_X86)
+#define DBG_TARGET_REGNUM_SP 4
+#define DBG_TARGET_REGNUM_AMBIENT_SP 9
+#ifdef _TARGET_X86_
+static_assert_no_msg(DBG_TARGET_REGNUM_SP == ICorDebugInfo::REGNUM_SP);
+static_assert_no_msg(DBG_TARGET_REGNUM_AMBIENT_SP == ICorDebugInfo::REGNUM_AMBIENT_SP);
+#endif // _TARGET_X86_
+#elif defined(DBG_TARGET_AMD64)
+#define DBG_TARGET_REGNUM_SP 4
+#define DBG_TARGET_REGNUM_AMBIENT_SP 17
+#ifdef _TARGET_AMD64_
+static_assert_no_msg(DBG_TARGET_REGNUM_SP == ICorDebugInfo::REGNUM_SP);
+static_assert_no_msg(DBG_TARGET_REGNUM_AMBIENT_SP == ICorDebugInfo::REGNUM_AMBIENT_SP);
+#endif // _TARGET_AMD64_
+#elif defined(DBG_TARGET_ARM)
+#define DBG_TARGET_REGNUM_SP 13
+#define DBG_TARGET_REGNUM_AMBIENT_SP 17
+#ifdef _TARGET_ARM_
+C_ASSERT(DBG_TARGET_REGNUM_SP == ICorDebugInfo::REGNUM_SP);
+C_ASSERT(DBG_TARGET_REGNUM_AMBIENT_SP == ICorDebugInfo::REGNUM_AMBIENT_SP);
+#endif // _TARGET_ARM_
+#elif defined(DBG_TARGET_ARM64)
+#define DBG_TARGET_REGNUM_SP 31
+#define DBG_TARGET_REGNUM_AMBIENT_SP 34
+#ifdef _TARGET_ARM64_
+C_ASSERT(DBG_TARGET_REGNUM_SP == ICorDebugInfo::REGNUM_SP);
+C_ASSERT(DBG_TARGET_REGNUM_AMBIENT_SP == ICorDebugInfo::REGNUM_AMBIENT_SP);
+#endif // _TARGET_ARM64_
+#else
+#error Target registers are not defined for this platform
+#endif
+
+
+//
+// Event structure that is passed between the Runtime Controller and the
+// Debugger Interface. Some types of events are a fixed size and have
+// entries in the main union, while others are variable length and have
+// more specialized data structures that are attached to the end of this
+// structure.
+//
+struct MSLAYOUT DebuggerIPCEvent
+{
+ DebuggerIPCEvent* next;
+ DebuggerIPCEventType type;
+ DWORD processId;
+ VMPTR_AppDomain vmAppDomain;
+ VMPTR_Thread vmThread;
+
+ HRESULT hr;
+ bool replyRequired;
+ bool asyncSend;
+
+ union MSLAYOUT
+ {
+ struct MSLAYOUT
+ {
+ // Pointer to a BOOL in the target.
+ CORDB_ADDRESS pfBeingDebugged;
+ } LeftSideStartupData;
+
+ struct MSLAYOUT
+ {
+ // Module whos metadata is being updated
+ // This tells the RS that the metadata for that module has become invalid.
+ VMPTR_DomainFile vmDomainFile;
+
+ } MetadataUpdateData;
+
+ struct MSLAYOUT
+ {
+ // Handle to CLR's internal appdomain object.
+ VMPTR_AppDomain vmAppDomain;
+ } AppDomainData;
+
+ struct MSLAYOUT
+ {
+ VMPTR_DomainAssembly vmDomainAssembly;
+ } AssemblyData;
+
+#ifdef TEST_DATA_CONSISTENCY
+ // information necessary for testing whether the LS holds a lock on data
+ // the RS needs to inspect. See code:DataTest::TestDataSafety and
+ // code:IDacDbiInterface::TestCrst for more information
+ struct MSLAYOUT
+ {
+ // the lock to be tested
+ VMPTR_Crst vmCrst;
+ // indicates whether the LS holds the lock
+ bool fOkToTake;
+ } TestCrstData;
+
+ // information necessary for testing whether the LS holds a lock on data
+ // the RS needs to inspect. See code:DataTest::TestDataSafety and
+ // code:IDacDbiInterface::TestCrst for more information
+ struct MSLAYOUT
+ {
+ // the lock to be tested
+ VMPTR_SimpleRWLock vmRWLock;
+ // indicates whether the LS holds the lock
+ bool fOkToTake;
+ } TestRWLockData;
+#endif // TEST_DATA_CONSISTENCY
+
+ // Debug event that a module has been loaded
+ struct MSLAYOUT
+ {
+ // Module that was just loaded.
+ VMPTR_DomainFile vmDomainFile;
+ }LoadModuleData;
+
+
+ struct MSLAYOUT
+ {
+ VMPTR_DomainFile vmDomainFile;
+ LSPTR_ASSEMBLY debuggerAssemblyToken;
+ } UnloadModuleData;
+
+
+ // The given module's pdb has been updated.
+ // Queury PDB from OOP
+ struct MSLAYOUT
+ {
+ VMPTR_DomainFile vmDomainFile;
+ } UpdateModuleSymsData;
+
+ DebuggerMDANotification MDANotification;
+
+ struct MSLAYOUT
+ {
+ LSPTR_BREAKPOINT breakpointToken;
+ mdMethodDef funcMetadataToken;
+ VMPTR_DomainFile vmDomainFile;
+ bool isIL;
+ SIZE_T offset;
+ SIZE_T encVersion;
+ LSPTR_METHODDESC nativeCodeMethodDescToken; // points to the MethodDesc if !isIL
+ } BreakpointData;
+
+ struct MSLAYOUT
+ {
+ LSPTR_BREAKPOINT breakpointToken;
+ } BreakpointSetErrorData;
+
+ struct MSLAYOUT
+ {
+ LSPTR_STEPPER stepperToken;
+ VMPTR_Thread vmThreadToken;
+ FramePointer frameToken;
+ bool stepIn;
+ bool rangeIL;
+ bool IsJMCStop;
+ unsigned int totalRangeCount;
+ CorDebugStepReason reason;
+ CorDebugUnmappedStop rgfMappingStop;
+ CorDebugIntercept rgfInterceptStop;
+ unsigned int rangeCount;
+ COR_DEBUG_STEP_RANGE range; //note that this is an array
+ } StepData;
+
+ struct MSLAYOUT
+ {
+ // An unvalidated GC-handle
+ VMPTR_OBJECTHANDLE GCHandle;
+ } GetGCHandleInfo;
+
+ struct MSLAYOUT
+ {
+ // An unvalidated GC-handle for which we're returning the results
+ LSPTR_OBJECTHANDLE GCHandle;
+
+ // The following are initialized by the LS in response to our query:
+ VMPTR_AppDomain vmAppDomain; // AD that handle is in (only applicable if fValid).
+ bool fValid; // Did the LS determine the GC handle to be valid?
+ } GetGCHandleInfoResult;
+
+ // Allocate memory on the left-side
+ struct MSLAYOUT
+ {
+ ULONG bufSize; // number of bytes to allocate
+ } GetBuffer;
+
+ // Memory allocated on the left-side
+ struct MSLAYOUT
+ {
+ void *pBuffer; // LS pointer to the buffer allocated
+ HRESULT hr; // success / failure
+ } GetBufferResult;
+
+ // Free a buffer allocated on the left-side with GetBuffer
+ struct MSLAYOUT
+ {
+ void *pBuffer; // Pointer previously returned in GetBufferResult
+ } ReleaseBuffer;
+
+ struct MSLAYOUT
+ {
+ HRESULT hr;
+ } ReleaseBufferResult;
+
+ // Apply an EnC edit
+ struct MSLAYOUT
+ {
+ VMPTR_DomainFile vmDomainFile; // Module to edit
+ DWORD cbDeltaMetadata; // size of blob pointed to by pDeltaMetadata
+ CORDB_ADDRESS pDeltaMetadata; // pointer to delta metadata in debuggee
+ // it's the RS's responsibility to allocate and free
+ // this (and pDeltaIL) using GetBuffer / ReleaseBuffer
+ CORDB_ADDRESS pDeltaIL; // pointer to delta IL in debugee
+ DWORD cbDeltaIL; // size of blob pointed to by pDeltaIL
+ } ApplyChanges;
+
+ struct MSLAYOUT
+ {
+ HRESULT hr;
+ } ApplyChangesResult;
+
+ struct MSLAYOUT
+ {
+ mdTypeDef classMetadataToken;
+ VMPTR_DomainFile vmDomainFile;
+ LSPTR_ASSEMBLY classDebuggerAssemblyToken;
+ } LoadClass;
+
+ struct MSLAYOUT
+ {
+ mdTypeDef classMetadataToken;
+ VMPTR_DomainFile vmDomainFile;
+ LSPTR_ASSEMBLY classDebuggerAssemblyToken;
+ } UnloadClass;
+
+ struct MSLAYOUT
+ {
+ VMPTR_DomainFile vmDomainFile;
+ bool flag;
+ } SetClassLoad;
+
+ struct MSLAYOUT
+ {
+ VMPTR_OBJECTHANDLE vmExceptionHandle;
+ bool firstChance;
+ bool continuable;
+ } Exception;
+
+ struct MSLAYOUT
+ {
+ VMPTR_Thread vmThreadToken;
+ } ClearException;
+
+ struct MSLAYOUT
+ {
+ void *address;
+ } IsTransitionStub;
+
+ struct MSLAYOUT
+ {
+ bool isStub;
+ } IsTransitionStubResult;
+
+ struct MSLAYOUT
+ {
+ CORDB_ADDRESS startAddress;
+ bool fCanSetIPOnly;
+ VMPTR_Thread vmThreadToken;
+ VMPTR_DomainFile vmDomainFile;
+ mdMethodDef mdMethod;
+ VMPTR_MethodDesc vmMethodDesc;
+ SIZE_T offset;
+ bool fIsIL;
+ void * firstExceptionHandler;
+ } SetIP; // this is also used for CanSetIP
+
+ struct MSLAYOUT
+ {
+ int iLevel;
+
+ EmbeddedIPCString<MAX_LOG_SWITCH_NAME_LEN + 1> szCategory;
+ Ls_Rs_StringBuffer szContent;
+ } FirstLogMessage;
+
+ struct MSLAYOUT
+ {
+ int iLevel;
+ int iReason;
+
+ EmbeddedIPCString<MAX_LOG_SWITCH_NAME_LEN + 1> szSwitchName;
+ EmbeddedIPCString<MAX_LOG_SWITCH_NAME_LEN + 1> szParentSwitchName;
+ } LogSwitchSettingMessage;
+
+ // information needed to send to the RS as part of a custom notification from the target
+ struct MSLAYOUT
+ {
+ // Domain file for the domain in which the notification occurred
+ VMPTR_DomainFile vmDomainFile;
+
+ // metadata token for the type of the CustomNotification object's type
+ mdTypeDef classToken;
+ } CustomNotification;
+
+ struct MSLAYOUT
+ {
+ VMPTR_Thread vmThreadToken;
+ CorDebugThreadState debugState;
+ } SetAllDebugState;
+
+ DebuggerIPCE_FuncEvalInfo FuncEval;
+
+ struct MSLAYOUT
+ {
+ CORDB_ADDRESS argDataArea;
+ LSPTR_DEBUGGEREVAL debuggerEvalKey;
+ } FuncEvalSetupComplete;
+
+ struct MSLAYOUT
+ {
+ RSPTR_CORDBEVAL funcEvalKey;
+ bool successful;
+ bool aborted;
+ void *resultAddr;
+
+ // AppDomain that the result is in.
+ VMPTR_AppDomain vmAppDomain;
+
+ VMPTR_OBJECTHANDLE vmObjectHandle;
+ DebuggerIPCE_ExpandedTypeData resultType;
+ } FuncEvalComplete;
+
+ struct MSLAYOUT
+ {
+ LSPTR_DEBUGGEREVAL debuggerEvalKey;
+ } FuncEvalAbort;
+
+ struct MSLAYOUT
+ {
+ LSPTR_DEBUGGEREVAL debuggerEvalKey;
+ } FuncEvalRudeAbort;
+
+ struct MSLAYOUT
+ {
+ LSPTR_DEBUGGEREVAL debuggerEvalKey;
+ } FuncEvalCleanup;
+
+ struct MSLAYOUT
+ {
+ void *objectRefAddress;
+ VMPTR_OBJECTHANDLE vmObjectHandle;
+ void *newReference;
+ } SetReference;
+
+ struct MSLAYOUT
+ {
+ NameChangeType eventType;
+ VMPTR_AppDomain vmAppDomain;
+ VMPTR_Thread vmThread;
+ } NameChange;
+
+ struct MSLAYOUT
+ {
+ VMPTR_DomainFile vmDomainFile;
+ BOOL fAllowJitOpts;
+ BOOL fEnableEnC;
+ } JitDebugInfo;
+
+ // EnC Remap opportunity
+ struct MSLAYOUT
+ {
+ VMPTR_DomainFile vmDomainFile;
+ mdMethodDef funcMetadataToken ; // methodDef of function with remap opportunity
+ SIZE_T currentVersionNumber; // version currently executing
+ SIZE_T resumeVersionNumber; // latest version
+ SIZE_T currentILOffset; // the IL offset of the current IP
+ SIZE_T *resumeILOffset; // pointer into left-side where an offset to resume
+ // to should be written if remap is desired.
+ } EnCRemap;
+
+ // EnC Remap has taken place
+ struct MSLAYOUT
+ {
+ VMPTR_DomainFile vmDomainFile;
+ mdMethodDef funcMetadataToken; // methodDef of function that was remapped
+ } EnCRemapComplete;
+
+ // Notification that the LS is about to update a CLR data structure to account for a
+ // specific edit made by EnC (function add/update or field add).
+ struct MSLAYOUT
+ {
+ VMPTR_DomainFile vmDomainFile;
+ mdToken memberMetadataToken; // Either a methodDef token indicating the function that
+ // was updated/added, or a fieldDef token indicating the
+ // field which was added.
+ mdTypeDef classMetadataToken; // TypeDef token of the class in which the update was made
+ SIZE_T newVersionNumber; // The new function/module version
+ } EnCUpdate;
+
+ struct MSLAYOUT
+ {
+ void *oldData;
+ void *newData;
+ DebuggerIPCE_BasicTypeData type;
+ } SetValueClass;
+
+
+ // Event used to tell LS if a single function is user or non-user code.
+ // Same structure used to get function status.
+ // @todo - Perhaps we can bundle these up so we can set multiple funcs w/ 1 event?
+ struct MSLAYOUT
+ {
+ VMPTR_DomainFile vmDomainFile;
+ mdMethodDef funcMetadataToken;
+ DWORD dwStatus;
+ } SetJMCFunctionStatus;
+
+ struct MSLAYOUT
+ {
+ TASKID taskid;
+ } GetThreadForTaskId;
+
+ struct MSLAYOUT
+ {
+ VMPTR_Thread vmThreadToken;
+ } GetThreadForTaskIdResult;
+
+ struct MSLAYOUT
+ {
+ CONNID connectionId;
+ } ConnectionChange;
+
+ struct MSLAYOUT
+ {
+ CONNID connectionId;
+ EmbeddedIPCString<MAX_LONGPATH> wzConnectionName;
+ } CreateConnection;
+
+ struct MSLAYOUT
+ {
+ void *objectToken;
+ BOOL fStrong;
+ } CreateHandle;
+
+ struct MSLAYOUT
+ {
+ VMPTR_OBJECTHANDLE vmObjectHandle;
+ } CreateHandleResult;
+
+ // used in DB_IPCE_DISPOSE_HANDLE event
+ struct MSLAYOUT
+ {
+ VMPTR_OBJECTHANDLE vmObjectHandle;
+ BOOL fStrong;
+ } DisposeHandle;
+
+ struct MSLAYOUT
+ {
+ FramePointer framePointer;
+ SIZE_T nOffset;
+ CorDebugExceptionCallbackType eventType;
+ DWORD dwFlags;
+ VMPTR_OBJECTHANDLE vmExceptionHandle;
+ } ExceptionCallback2;
+
+ struct MSLAYOUT
+ {
+ CorDebugExceptionUnwindCallbackType eventType;
+ DWORD dwFlags;
+ } ExceptionUnwind;
+
+ struct MSLAYOUT
+ {
+ VMPTR_Thread vmThreadToken;
+ FramePointer frameToken;
+ } InterceptException;
+
+ struct MSLAYOUT
+ {
+ VMPTR_Module vmModule;
+ void * pMetadataStart;
+ ULONG nMetadataSize;
+ } MetadataUpdateRequest;
+
+ };
+};
+
+
+// When using a network transport rather than shared memory buffers CorDBIPC_BUFFER_SIZE is the upper bound
+// for a single DebuggerIPCEvent structure. This now relates to the maximal size of a network message and is
+// orthogonal to the host's page size. Round the buffer size up to a multiple of 8 since MSVC seems more
+// aggressive in this regard than gcc.
+#define CorDBIPC_TRANSPORT_BUFFER_SIZE (((sizeof(DebuggerIPCEvent) + 7) / 8) * 8)
+
+// A DebuggerIPCEvent must fit in the send & receive buffers, which are CorDBIPC_BUFFER_SIZE bytes.
+static_assert_no_msg(sizeof(DebuggerIPCEvent) <= CorDBIPC_BUFFER_SIZE);
+static_assert_no_msg(CorDBIPC_TRANSPORT_BUFFER_SIZE <= CorDBIPC_BUFFER_SIZE);
+
+// 2*sizeof(WCHAR) for the two string terminating characters in the FirstLogMessage
+#define LOG_MSG_PADDING 4
+
+#endif /* _DbgIPCEvents_h_ */
diff --git a/src/debug/inc/dbgipceventtypes.h b/src/debug/inc/dbgipceventtypes.h
new file mode 100644
index 0000000000..b538360e68
--- /dev/null
+++ b/src/debug/inc/dbgipceventtypes.h
@@ -0,0 +1,143 @@
+// 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.
+
+//
+// Events that go both ways
+IPC_EVENT_TYPE0(DB_IPCE_INVALID_EVENT ,0x0000)
+IPC_EVENT_TYPE0(DB_IPCE_TYPE_MASK ,0x0FFF)
+
+// Some rules:
+// 1. Type0 is for marking sections in the id range.
+// Type1 is for events that go L->R, Type2 is for events that go R<-L.
+// 2. All non-type 0 events should have a unique identifier & value
+// 3. All type 1 events values should be in range [DB_IPCE_RUNTIME_FIRST, DB_IPCE_RUNTIME_LAST)
+// All type 2 events values should be in range [DB_IPCE_DEBUGGER_FIRST, DB_IPCE_DEBUGGER_LAST)
+// 4. All event values should be monotonically increasing, though we can skip values.
+// 5. All values should be a subset of the bits specified by DB_IPCE_TYPE_MASK.
+//
+// These rules are enforced by a bunch of compile time checks (C_ASSERT) in
+// the function DoCompileTimeCheckOnDbgIpcEventTypes.
+// If you get compiler errors in this file, you are probably violating the rules above.
+
+// Events that travel from the RC to the DI (Left to Right)
+IPC_EVENT_TYPE0(DB_IPCE_RUNTIME_FIRST ,0x0100) // change to TYPE0 because it is not really an event
+IPC_EVENT_TYPE1(DB_IPCE_BREAKPOINT ,0x0100)
+IPC_EVENT_TYPE1(DB_IPCE_SYNC_COMPLETE ,0x0102)
+IPC_EVENT_TYPE1(DB_IPCE_THREAD_ATTACH ,0x0103)
+IPC_EVENT_TYPE1(DB_IPCE_THREAD_DETACH ,0x0104)
+IPC_EVENT_TYPE1(DB_IPCE_LOAD_MODULE ,0x0105)
+IPC_EVENT_TYPE1(DB_IPCE_UNLOAD_MODULE ,0x0106)
+IPC_EVENT_TYPE1(DB_IPCE_LOAD_CLASS ,0x0107)
+IPC_EVENT_TYPE1(DB_IPCE_UNLOAD_CLASS ,0x0108)
+IPC_EVENT_TYPE1(DB_IPCE_EXCEPTION ,0x0109)
+IPC_EVENT_TYPE1(DB_IPCE_UNHANDLED_EXCEPTION ,0x010A)
+IPC_EVENT_TYPE1(DB_IPCE_BREAKPOINT_ADD_RESULT ,0x010D)
+IPC_EVENT_TYPE1(DB_IPCE_STEP_RESULT ,0x010E)
+IPC_EVENT_TYPE1(DB_IPCE_STEP_COMPLETE ,0x010F)
+IPC_EVENT_TYPE1(DB_IPCE_BREAKPOINT_REMOVE_RESULT ,0x0111)
+IPC_EVENT_TYPE1(DB_IPCE_GET_BUFFER_RESULT ,0x0115)
+IPC_EVENT_TYPE1(DB_IPCE_RELEASE_BUFFER_RESULT ,0x0116)
+IPC_EVENT_TYPE1(DB_IPCE_ENC_ADD_FIELD ,0x0117)
+IPC_EVENT_TYPE1(DB_IPCE_APPLY_CHANGES_RESULT ,0x0118)
+IPC_EVENT_TYPE1(DB_IPCE_CUSTOM_NOTIFICATION ,0x011B)
+IPC_EVENT_TYPE1(DB_IPCE_USER_BREAKPOINT ,0x011C)
+IPC_EVENT_TYPE1(DB_IPCE_FIRST_LOG_MESSAGE ,0x011D)
+// DB_IPCE_CONTINUED_LOG_MESSAGE = 0x11E, used to be here in v1.1,
+// But we've removed that remove the v2.0 protocol
+IPC_EVENT_TYPE1(DB_IPCE_LOGSWITCH_SET_MESSAGE ,0x011F)
+IPC_EVENT_TYPE1(DB_IPCE_CREATE_APP_DOMAIN ,0x0120)
+IPC_EVENT_TYPE1(DB_IPCE_EXIT_APP_DOMAIN ,0x0121)
+IPC_EVENT_TYPE1(DB_IPCE_LOAD_ASSEMBLY ,0x0122)
+IPC_EVENT_TYPE1(DB_IPCE_UNLOAD_ASSEMBLY ,0x0123)
+IPC_EVENT_TYPE1(DB_IPCE_SET_DEBUG_STATE_RESULT ,0x0124)
+IPC_EVENT_TYPE1(DB_IPCE_FUNC_EVAL_SETUP_RESULT ,0x0125)
+IPC_EVENT_TYPE1(DB_IPCE_FUNC_EVAL_COMPLETE ,0x0126)
+IPC_EVENT_TYPE1(DB_IPCE_SET_REFERENCE_RESULT ,0x0127)
+IPC_EVENT_TYPE1(DB_IPCE_APP_DOMAIN_NAME_RESULT ,0x0128)
+IPC_EVENT_TYPE1(DB_IPCE_FUNC_EVAL_ABORT_RESULT ,0x0129)
+IPC_EVENT_TYPE1(DB_IPCE_NAME_CHANGE ,0x012a)
+IPC_EVENT_TYPE1(DB_IPCE_UPDATE_MODULE_SYMS ,0x012c)
+IPC_EVENT_TYPE1(DB_IPCE_CONTROL_C_EVENT ,0x012f)
+IPC_EVENT_TYPE1(DB_IPCE_FUNC_EVAL_CLEANUP_RESULT ,0x0130)
+IPC_EVENT_TYPE1(DB_IPCE_ENC_REMAP ,0x0131)
+IPC_EVENT_TYPE1(DB_IPCE_SET_VALUE_CLASS_RESULT ,0x0133)
+IPC_EVENT_TYPE1(DB_IPCE_BREAKPOINT_SET_ERROR ,0x0134)
+IPC_EVENT_TYPE1(DB_IPCE_ENC_UPDATE_FUNCTION ,0x0137)
+IPC_EVENT_TYPE1(DB_IPCE_SET_METHOD_JMC_STATUS_RESULT ,0x013a)
+IPC_EVENT_TYPE1(DB_IPCE_GET_METHOD_JMC_STATUS_RESULT ,0x013b)
+IPC_EVENT_TYPE1(DB_IPCE_SET_MODULE_JMC_STATUS_RESULT ,0x013c)
+IPC_EVENT_TYPE1(DB_IPCE_GET_THREAD_FOR_TASKID_RESULT ,0x013d)
+IPC_EVENT_TYPE1(DB_IPCE_CREATE_CONNECTION ,0x0141)
+IPC_EVENT_TYPE1(DB_IPCE_DESTROY_CONNECTION ,0x0142)
+IPC_EVENT_TYPE1(DB_IPCE_CHANGE_CONNECTION ,0x0143)
+IPC_EVENT_TYPE1(DB_IPCE_FUNC_EVAL_RUDE_ABORT_RESULT ,0x0144)
+IPC_EVENT_TYPE1(DB_IPCE_EXCEPTION_CALLBACK2 ,0x0147)
+IPC_EVENT_TYPE1(DB_IPCE_EXCEPTION_UNWIND ,0x0148)
+IPC_EVENT_TYPE1(DB_IPCE_INTERCEPT_EXCEPTION_RESULT ,0x0149)
+IPC_EVENT_TYPE1(DB_IPCE_CREATE_HANDLE_RESULT ,0x014A)
+IPC_EVENT_TYPE1(DB_IPCE_INTERCEPT_EXCEPTION_COMPLETE ,0x014B)
+IPC_EVENT_TYPE1(DB_IPCE_ENC_REMAP_COMPLETE ,0x014C)
+IPC_EVENT_TYPE1(DB_IPCE_CREATE_PROCESS ,0x014D)
+IPC_EVENT_TYPE1(DB_IPCE_ENC_ADD_FUNCTION ,0x014E)
+IPC_EVENT_TYPE1(DB_IPCE_GET_NGEN_COMPILER_FLAGS_RESULT,0x0151)
+IPC_EVENT_TYPE1(DB_IPCE_SET_NGEN_COMPILER_FLAGS_RESULT,0x0152)
+IPC_EVENT_TYPE1(DB_IPCE_MDA_NOTIFICATION ,0x0156)
+IPC_EVENT_TYPE1(DB_IPCE_GET_GCHANDLE_INFO_RESULT ,0x0157)
+IPC_EVENT_TYPE1(DB_IPCE_TEST_CRST ,0x0158)
+IPC_EVENT_TYPE1(DB_IPCE_TEST_RWLOCK ,0x0159)
+IPC_EVENT_TYPE1(DB_IPCE_LEFTSIDE_STARTUP ,0x015C)
+IPC_EVENT_TYPE1(DB_IPCE_METADATA_UPDATE ,0x015D)
+IPC_EVENT_TYPE1(DB_IPCE_RESOLVE_UPDATE_METADATA_1_RESULT,0x015E)
+IPC_EVENT_TYPE1(DB_IPCE_RESOLVE_UPDATE_METADATA_2_RESULT,0x015F)
+IPC_EVENT_TYPE0(DB_IPCE_RUNTIME_LAST ,0x0160) // The last event from runtime
+
+
+
+// Events that travel from the DI to the RC (Right to Left)
+IPC_EVENT_TYPE0(DB_IPCE_DEBUGGER_FIRST ,0x0200) // change to TYPE0 because it is not really an event
+IPC_EVENT_TYPE2(DB_IPCE_ASYNC_BREAK ,0x0200)
+IPC_EVENT_TYPE2(DB_IPCE_CONTINUE ,0x0201)
+IPC_EVENT_TYPE2(DB_IPCE_LIST_THREADS ,0x0202)
+IPC_EVENT_TYPE2(DB_IPCE_SET_IP ,0x0205)
+IPC_EVENT_TYPE2(DB_IPCE_SUSPEND_THREAD ,0x0206)
+IPC_EVENT_TYPE2(DB_IPCE_RESUME_THREAD ,0x0207)
+IPC_EVENT_TYPE2(DB_IPCE_BREAKPOINT_ADD ,0x0209)
+IPC_EVENT_TYPE2(DB_IPCE_BREAKPOINT_REMOVE ,0x020A)
+IPC_EVENT_TYPE2(DB_IPCE_STEP_CANCEL ,0x020B)
+IPC_EVENT_TYPE2(DB_IPCE_STEP ,0x020C)
+IPC_EVENT_TYPE2(DB_IPCE_STEP_OUT ,0x020D)
+IPC_EVENT_TYPE2(DB_IPCE_GET_BUFFER ,0x0211)
+IPC_EVENT_TYPE2(DB_IPCE_RELEASE_BUFFER ,0x0212)
+IPC_EVENT_TYPE2(DB_IPCE_SET_CLASS_LOAD_FLAG ,0x0217)
+IPC_EVENT_TYPE2(DB_IPCE_CONTINUE_EXCEPTION ,0x0219)
+IPC_EVENT_TYPE2(DB_IPCE_ATTACHING ,0x021A)
+IPC_EVENT_TYPE2(DB_IPCE_APPLY_CHANGES ,0x021B)
+IPC_EVENT_TYPE2(DB_IPCE_SET_NGEN_COMPILER_FLAGS ,0x021F)
+IPC_EVENT_TYPE2(DB_IPCE_GET_NGEN_COMPILER_FLAGS ,0x0220)
+IPC_EVENT_TYPE2(DB_IPCE_IS_TRANSITION_STUB ,0x0221)
+IPC_EVENT_TYPE2(DB_IPCE_IS_TRANSITION_STUB_RESULT ,0x0222)
+IPC_EVENT_TYPE2(DB_IPCE_MODIFY_LOGSWITCH ,0x0223)
+IPC_EVENT_TYPE2(DB_IPCE_ENABLE_LOG_MESSAGES ,0x0224)
+IPC_EVENT_TYPE2(DB_IPCE_FUNC_EVAL ,0x0225)
+IPC_EVENT_TYPE2(DB_IPCE_SET_REFERENCE ,0x0228)
+IPC_EVENT_TYPE2(DB_IPCE_FUNC_EVAL_ABORT ,0x022c)
+IPC_EVENT_TYPE2(DB_IPCE_DETACH_FROM_PROCESS ,0x022f)
+IPC_EVENT_TYPE2(DB_IPCE_CONTROL_C_EVENT_RESULT ,0x0230)
+IPC_EVENT_TYPE2(DB_IPCE_FUNC_EVAL_CLEANUP ,0x0231)
+IPC_EVENT_TYPE2(DB_IPCE_SET_ALL_DEBUG_STATE ,0x0232)
+IPC_EVENT_TYPE2(DB_IPCE_SET_VALUE_CLASS ,0x0234)
+IPC_EVENT_TYPE2(DB_IPCE_SET_METHOD_JMC_STATUS ,0x023a)
+IPC_EVENT_TYPE2(DB_IPCE_GET_METHOD_JMC_STATUS ,0x023b)
+IPC_EVENT_TYPE2(DB_IPCE_SET_MODULE_JMC_STATUS ,0x023c)
+IPC_EVENT_TYPE2(DB_IPCE_GET_THREAD_FOR_TASKID ,0x023d)
+IPC_EVENT_TYPE2(DB_IPCE_FUNC_EVAL_RUDE_ABORT ,0x0241)
+IPC_EVENT_TYPE2(DB_IPCE_CREATE_HANDLE ,0x0244)
+IPC_EVENT_TYPE2(DB_IPCE_DISPOSE_HANDLE ,0x0245)
+IPC_EVENT_TYPE2(DB_IPCE_INTERCEPT_EXCEPTION ,0x0246)
+IPC_EVENT_TYPE2(DB_IPCE_DEBUGGER_INVALID ,0x0249) // An invalid event type
+IPC_EVENT_TYPE2(DB_IPCE_GET_GCHANDLE_INFO ,0x0251)
+IPC_EVENT_TYPE2(DB_IPCE_RESOLVE_UPDATE_METADATA_1 ,0x0256)
+IPC_EVENT_TYPE2(DB_IPCE_RESOLVE_UPDATE_METADATA_2 ,0x0257)
+IPC_EVENT_TYPE0(DB_IPCE_DEBUGGER_LAST ,0x0258) // The last event from the debugger
+
diff --git a/src/debug/inc/dbgtargetcontext.h b/src/debug/inc/dbgtargetcontext.h
new file mode 100644
index 0000000000..22b1c84096
--- /dev/null
+++ b/src/debug/inc/dbgtargetcontext.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 __DBG_TARGET_CONTEXT_INCLUDED
+#define __DBG_TARGET_CONTEXT_INCLUDED
+
+#include <dbgportable.h>
+#include <stddef.h>
+#include "crosscomp.h"
+
+//
+// The right side of the debugger can now be built to target multiple platforms. This means it is no longer
+// safe to use the CONTEXT structure directly: the context of the platform we're building for might not match
+// that of the one the debugger is targetting. So now all right side code will use the DT_CONTEXT abstraction
+// instead. When the debugger target is the local platform this will just resolve back into CONTEXT, but cross
+// platform we'll provide a hand-rolled version.
+//
+
+//
+// For cross platform cases we also need to provide a helper function for byte-swapping a context structure
+// should the endian-ness of the debugger and debuggee platforms differ. This is called ByteSwapContext and is
+// obviously a no-op for those cases where the left and right sides agree on storage format.
+//
+// NOTE: Any changes to the field layout of DT_CONTEXT must be tracked in the associated definition of
+// ByteSwapContext.
+//
+
+// For now, the only cross-platform CONTEXTs we support are x86/PAL and ARM/Win. Look in
+// rotor/pal/inc/rotor_pal.h for the original PAL definitions.
+
+//
+// **** NOTE: Keep these in sync with rotor/pal/inc/rotor_pal.h ****
+//
+
+// This odd define pattern is needed because in DBI we set _TARGET_ to match the host and
+// DBG_TARGET to control our targeting. In x-plat DBI DBG_TARGET won't match _TARGET_ and
+// DBG_TARGET needs to take precedence
+#if defined(DBG_TARGET_X86)
+#define DTCONTEXT_IS_X86
+#elif defined (DBG_TARGET_AMD64)
+#define DTCONTEXT_IS_AMD64
+#elif defined (DBG_TARGET_ARM)
+#define DTCONTEXT_IS_ARM
+#elif defined (DBG_TARGET_ARM64)
+#define DTCONTEXT_IS_ARM64
+#elif defined (_TARGET_X86_)
+#define DTCONTEXT_IS_X86
+#elif defined (_TARGET_AMD64_)
+#define DTCONTEXT_IS_AMD64
+#elif defined (_TARGET_ARM_)
+#define DTCONTEXT_IS_ARM
+#elif defined (_TARGET_ARM64_)
+#define DTCONTEXT_IS_ARM64
+#endif
+
+#if defined(DTCONTEXT_IS_X86)
+
+#define DT_SIZE_OF_80387_REGISTERS 80
+
+#define DT_CONTEXT_i386 0x00010000
+#define DT_CONTEXT_CONTROL (DT_CONTEXT_i386 | 0x00000001L) // SS:SP, CS:IP, FLAGS, BP
+#define DT_CONTEXT_INTEGER (DT_CONTEXT_i386 | 0x00000002L) // AX, BX, CX, DX, SI, DI
+#define DT_CONTEXT_SEGMENTS (DT_CONTEXT_i386 | 0x00000004L)
+#define DT_CONTEXT_FLOATING_POINT (DT_CONTEXT_i386 | 0x00000008L) // 387 state
+#define DT_CONTEXT_DEBUG_REGISTERS (DT_CONTEXT_i386 | 0x00000010L)
+
+#define DT_CONTEXT_FULL (DT_CONTEXT_CONTROL | DT_CONTEXT_INTEGER | DT_CONTEXT_SEGMENTS)
+#define DT_CONTEXT_EXTENDED_REGISTERS (DT_CONTEXT_i386 | 0x00000020L)
+
+#define DT_MAXIMUM_SUPPORTED_EXTENSION 512
+
+typedef struct {
+ DWORD ControlWord;
+ DWORD StatusWord;
+ DWORD TagWord;
+ DWORD ErrorOffset;
+ DWORD ErrorSelector;
+ DWORD DataOffset;
+ DWORD DataSelector;
+ BYTE RegisterArea[DT_SIZE_OF_80387_REGISTERS];
+ DWORD Cr0NpxState;
+} DT_FLOATING_SAVE_AREA;
+
+typedef struct {
+ ULONG ContextFlags;
+
+ ULONG Dr0;
+ ULONG Dr1;
+ ULONG Dr2;
+ ULONG Dr3;
+ ULONG Dr6;
+ ULONG Dr7;
+
+ DT_FLOATING_SAVE_AREA FloatSave;
+
+ ULONG SegGs;
+ ULONG SegFs;
+ ULONG SegEs;
+ ULONG SegDs;
+
+ ULONG Edi;
+ ULONG Esi;
+ ULONG Ebx;
+ ULONG Edx;
+ ULONG Ecx;
+ ULONG Eax;
+
+ ULONG Ebp;
+ ULONG Eip;
+ ULONG SegCs;
+ ULONG EFlags;
+ ULONG Esp;
+ ULONG SegSs;
+
+ UCHAR ExtendedRegisters[DT_MAXIMUM_SUPPORTED_EXTENSION];
+
+} DT_CONTEXT;
+
+// Since the target is little endian in this case we only have to provide a real implementation of
+// ByteSwapContext if the platform we're building on is big-endian.
+#ifdef BIGENDIAN
+inline void ByteSwapContext(DT_CONTEXT *pContext)
+{
+ // Our job is simplified since the context has large contiguous ranges with fields of the same size. Keep
+ // the following logic in sync with the definition of DT_CONTEXT above.
+ BYTE *pbContext = (BYTE*)pContext;
+
+ // The first span consists of 4 byte fields.
+ DWORD cbFields = (offsetof(DT_CONTEXT, FloatSave) + offsetof(DT_FLOATING_SAVE_AREA, RegisterArea)) / 4;
+ for (DWORD i = 0; i < cbFields; i++)
+ {
+ ByteSwapPrimitive(pbContext, pbContext, 4);
+ pbContext += 4;
+ }
+
+ // Then there's a float save area containing 8 byte fields.
+ cbFields = sizeof(pContext->FloatSave.RegisterArea);
+ for (DWORD i = 0; i < cbFields; i++)
+ {
+ ByteSwapPrimitive(pbContext, pbContext, 8);
+ pbContext += 8;
+ }
+
+ // Back to 4 byte fields.
+ cbFields = (offsetof(DT_CONTEXT, ExtendedRegisters) - offsetof(DT_CONTEXT, SegGs)) / 4;
+ for (DWORD i = 0; i < cbFields; i++)
+ {
+ ByteSwapPrimitive(pbContext, pbContext, 4);
+ pbContext += 4;
+ }
+
+ // We don't know the formatting of the extended register area, but the debugger doesn't access this data
+ // on the left side, so just leave it in left-side format for now.
+
+ // Validate that we converted up to where we think we did as a hedge against DT_CONTEXT layout changes.
+ _PASSERT((pbContext - ((BYTE*)pContext)) == (sizeof(DT_CONTEXT) - sizeof(pContext->ExtendedRegisters)));
+}
+#else // BIGENDIAN
+inline void ByteSwapContext(DT_CONTEXT *pContext)
+{
+}
+#endif // BIGENDIAN
+
+#elif defined(DTCONTEXT_IS_AMD64)
+
+#define DT_CONTEXT_AMD64 0x00100000L
+
+#define DT_CONTEXT_CONTROL (DT_CONTEXT_AMD64 | 0x00000001L)
+#define DT_CONTEXT_INTEGER (DT_CONTEXT_AMD64 | 0x00000002L)
+#define DT_CONTEXT_SEGMENTS (DT_CONTEXT_AMD64 | 0x00000004L)
+#define DT_CONTEXT_FLOATING_POINT (DT_CONTEXT_AMD64 | 0x00000008L)
+#define DT_CONTEXT_DEBUG_REGISTERS (DT_CONTEXT_AMD64 | 0x00000010L)
+
+#define DT_CONTEXT_FULL (DT_CONTEXT_CONTROL | DT_CONTEXT_INTEGER | DT_CONTEXT_FLOATING_POINT)
+#define DT_CONTEXT_ALL (DT_CONTEXT_CONTROL | DT_CONTEXT_INTEGER | DT_CONTEXT_SEGMENTS | DT_CONTEXT_FLOATING_POINT | DT_CONTEXT_DEBUG_REGISTERS)
+
+typedef struct {
+ ULONGLONG Low;
+ LONGLONG High;
+} DT_M128A;
+
+typedef struct {
+ WORD ControlWord;
+ WORD StatusWord;
+ BYTE TagWord;
+ BYTE Reserved1;
+ WORD ErrorOpcode;
+ DWORD ErrorOffset;
+ WORD ErrorSelector;
+ WORD Reserved2;
+ DWORD DataOffset;
+ WORD DataSelector;
+ WORD Reserved3;
+ DWORD MxCsr;
+ DWORD MxCsr_Mask;
+ DT_M128A FloatRegisters[8];
+ DT_M128A XmmRegisters[16];
+ BYTE Reserved4[96];
+} DT_XMM_SAVE_AREA32;
+
+typedef struct DECLSPEC_ALIGN(16) {
+
+ DWORD64 P1Home;
+ DWORD64 P2Home;
+ DWORD64 P3Home;
+ DWORD64 P4Home;
+ DWORD64 P5Home;
+ DWORD64 P6Home;
+
+ DWORD ContextFlags;
+ DWORD MxCsr;
+
+ WORD SegCs;
+ WORD SegDs;
+ WORD SegEs;
+ WORD SegFs;
+ WORD SegGs;
+ WORD SegSs;
+ DWORD EFlags;
+
+ DWORD64 Dr0;
+ DWORD64 Dr1;
+ DWORD64 Dr2;
+ DWORD64 Dr3;
+ DWORD64 Dr6;
+ DWORD64 Dr7;
+
+ DWORD64 Rax;
+ DWORD64 Rcx;
+ DWORD64 Rdx;
+ DWORD64 Rbx;
+ DWORD64 Rsp;
+ DWORD64 Rbp;
+ DWORD64 Rsi;
+ DWORD64 Rdi;
+ DWORD64 R8;
+ DWORD64 R9;
+ DWORD64 R10;
+ DWORD64 R11;
+ DWORD64 R12;
+ DWORD64 R13;
+ DWORD64 R14;
+ DWORD64 R15;
+
+ DWORD64 Rip;
+
+ union {
+ DT_XMM_SAVE_AREA32 FltSave;
+ struct {
+ DT_M128A Header[2];
+ DT_M128A Legacy[8];
+ DT_M128A Xmm0;
+ DT_M128A Xmm1;
+ DT_M128A Xmm2;
+ DT_M128A Xmm3;
+ DT_M128A Xmm4;
+ DT_M128A Xmm5;
+ DT_M128A Xmm6;
+ DT_M128A Xmm7;
+ DT_M128A Xmm8;
+ DT_M128A Xmm9;
+ DT_M128A Xmm10;
+ DT_M128A Xmm11;
+ DT_M128A Xmm12;
+ DT_M128A Xmm13;
+ DT_M128A Xmm14;
+ DT_M128A Xmm15;
+ };
+ };
+
+ DT_M128A VectorRegister[26];
+ DWORD64 VectorControl;
+
+ DWORD64 DebugControl;
+ DWORD64 LastBranchToRip;
+ DWORD64 LastBranchFromRip;
+ DWORD64 LastExceptionToRip;
+ DWORD64 LastExceptionFromRip;
+} DT_CONTEXT;
+
+#elif defined(DTCONTEXT_IS_ARM)
+
+#define DT_CONTEXT_ARM 0x00200000L
+
+#define DT_CONTEXT_CONTROL (DT_CONTEXT_ARM | 0x1L)
+#define DT_CONTEXT_INTEGER (DT_CONTEXT_ARM | 0x2L)
+#define DT_CONTEXT_FLOATING_POINT (DT_CONTEXT_ARM | 0x4L)
+#define DT_CONTEXT_DEBUG_REGISTERS (DT_CONTEXT_ARM | 0x8L)
+
+#define DT_CONTEXT_FULL (DT_CONTEXT_CONTROL | DT_CONTEXT_INTEGER | DT_CONTEXT_FLOATING_POINT)
+#define DT_CONTEXT_ALL (DT_CONTEXT_CONTROL | DT_CONTEXT_INTEGER | DT_CONTEXT_FLOATING_POINT | DT_CONTEXT_DEBUG_REGISTERS)
+
+#define DT_ARM_MAX_BREAKPOINTS 8
+#define DT_ARM_MAX_WATCHPOINTS 1
+
+typedef struct {
+ ULONGLONG Low;
+ LONGLONG High;
+} DT_NEON128;
+
+typedef DECLSPEC_ALIGN(8) struct {
+
+ //
+ // Control flags.
+ //
+
+ DWORD ContextFlags;
+
+ //
+ // Integer registers
+ //
+
+ DWORD R0;
+ DWORD R1;
+ DWORD R2;
+ DWORD R3;
+ DWORD R4;
+ DWORD R5;
+ DWORD R6;
+ DWORD R7;
+ DWORD R8;
+ DWORD R9;
+ DWORD R10;
+ DWORD R11;
+ DWORD R12;
+
+ //
+ // Control Registers
+ //
+
+ DWORD Sp;
+ DWORD Lr;
+ DWORD Pc;
+ DWORD Cpsr;
+
+ //
+ // Floating Point/NEON Registers
+ //
+
+ DWORD Fpscr;
+ DWORD Padding;
+ union {
+ DT_NEON128 Q[16];
+ ULONGLONG D[32];
+ DWORD S[32];
+ } DUMMYUNIONNAME;
+
+ //
+ // Debug registers
+ //
+
+ DWORD Bvr[DT_ARM_MAX_BREAKPOINTS];
+ DWORD Bcr[DT_ARM_MAX_BREAKPOINTS];
+ DWORD Wvr[DT_ARM_MAX_WATCHPOINTS];
+ DWORD Wcr[DT_ARM_MAX_WATCHPOINTS];
+
+ DWORD Padding2[2];
+
+} DT_CONTEXT;
+
+#elif defined(DTCONTEXT_IS_ARM64)
+
+#define DT_CONTEXT_ARM64 0x00400000L
+
+#define DT_CONTEXT_CONTROL (DT_CONTEXT_ARM64 | 0x1L)
+#define DT_CONTEXT_INTEGER (DT_CONTEXT_ARM64 | 0x2L)
+#define DT_CONTEXT_FLOATING_POINT (DT_CONTEXT_ARM64 | 0x4L)
+#define DT_CONTEXT_DEBUG_REGISTERS (DT_CONTEXT_ARM64 | 0x8L)
+
+#define DT_CONTEXT_FULL (DT_CONTEXT_CONTROL | DT_CONTEXT_INTEGER | DT_CONTEXT_FLOATING_POINT)
+#define DT_CONTEXT_ALL (DT_CONTEXT_CONTROL | DT_CONTEXT_INTEGER | DT_CONTEXT_FLOATING_POINT | DT_CONTEXT_DEBUG_REGISTERS)
+
+typedef DECLSPEC_ALIGN(16) struct {
+ //
+ // Control flags.
+ //
+
+ /* +0x000 */ DWORD ContextFlags;
+
+ //
+ // Integer registers
+ //
+
+ /* +0x004 */ DWORD Cpsr; // NZVF + DAIF + CurrentEL + SPSel
+ /* +0x008 */ union {
+ struct {
+ DWORD64 X0;
+ DWORD64 X1;
+ DWORD64 X2;
+ DWORD64 X3;
+ DWORD64 X4;
+ DWORD64 X5;
+ DWORD64 X6;
+ DWORD64 X7;
+ DWORD64 X8;
+ DWORD64 X9;
+ DWORD64 X10;
+ DWORD64 X11;
+ DWORD64 X12;
+ DWORD64 X13;
+ DWORD64 X14;
+ DWORD64 X15;
+ DWORD64 X16;
+ DWORD64 X17;
+ DWORD64 X18;
+ DWORD64 X19;
+ DWORD64 X20;
+ DWORD64 X21;
+ DWORD64 X22;
+ DWORD64 X23;
+ DWORD64 X24;
+ DWORD64 X25;
+ DWORD64 X26;
+ DWORD64 X27;
+ DWORD64 X28;
+ };
+ DWORD64 X[29];
+ };
+ /* +0x0f0 */ DWORD64 Fp;
+ /* +0x0f8 */ DWORD64 Lr;
+ /* +0x100 */ DWORD64 Sp;
+ /* +0x108 */ DWORD64 Pc;
+
+ //
+ // Floating Point/NEON Registers
+ //
+
+ /* +0x110 */ NEON128 V[32];
+ /* +0x310 */ DWORD Fpcr;
+ /* +0x314 */ DWORD Fpsr;
+
+ //
+ // Debug registers
+ //
+
+ /* +0x318 */ DWORD Bcr[ARM64_MAX_BREAKPOINTS];
+ /* +0x338 */ DWORD64 Bvr[ARM64_MAX_BREAKPOINTS];
+ /* +0x378 */ DWORD Wcr[ARM64_MAX_WATCHPOINTS];
+ /* +0x380 */ DWORD64 Wvr[ARM64_MAX_WATCHPOINTS];
+ /* +0x390 */
+
+} DT_CONTEXT;
+
+#else
+#error Unsupported platform
+#endif
+
+
+#endif // __DBG_TARGET_CONTEXT_INCLUDED
diff --git a/src/debug/inc/dbgtransportsession.h b/src/debug/inc/dbgtransportsession.h
new file mode 100644
index 0000000000..5187202753
--- /dev/null
+++ b/src/debug/inc/dbgtransportsession.h
@@ -0,0 +1,849 @@
+// 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 __DBG_TRANSPORT_SESSION_INCLUDED
+#define __DBG_TRANSPORT_SESSION_INCLUDED
+
+#ifndef RIGHT_SIDE_COMPILE
+#include <utilcode.h>
+#include <crst.h>
+
+#endif // !RIGHT_SIDE_COMPILE
+
+#if defined(FEATURE_DBGIPC_TRANSPORT_VM) || defined(FEATURE_DBGIPC_TRANSPORT_DI)
+
+#include <twowaypipe.h>
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ DbgTransportSession was originally designed around cross-machine debugging via sockets and it is supposed to
+ handle network interruptions. Right now we use pipes (see TwoWaypipe) and don't expect to have connection issues.
+ But there seem to be no good reason to try hard to get rid of existing working protocol even if it's a bit
+ cautious about connection quality. So please KEEP IN MIND THAT SOME COMMENTS REFERING TO NETWORK AND SOCKETS
+ CAN BE OUTDATED.
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+//
+// Provides a robust and secure transport session between a debugger and a debuggee that are potentially on
+// different machines.
+//
+// The following terminology is used for the wire protocol. The smallest meaningful entity written to or read
+// from the connection is a "message". This consists of one or maybe two "blocks" where a block is a
+// contiguous region of memory in the host machine. The first block is always a "message header" which is
+// fixed size (allowing the receiver to know how many bytes to read off the stream oriented connection) and
+// has type codes and other fields which the receiver can use to determine if another block is part of the
+// message (and if so, exactly how large that block is). Many management messages consist only of a message
+// header block, while operations such as sending a debugger event structure involve a message header followed
+// by a block containing the actual event structure.
+//
+// Message acknowledgement (sometimes abbreviated to ack) refers to a system of marking all messages with an
+// ID and noting and reporting which IDs we've seen from our peer. We piggy back the highest seen ID on all
+// outgoing messages and this is used by the infrastructure to communicate the fact that a sender can release
+// its copy of an outbound message since it successfully made it across the communications channel and won't
+// need to be resent in the case of a network failure.
+//
+// This file uses the debugger conventions for naming the two endpoints of the session: the left side or LS is
+// the side with the runtime while the right side (RS) is the side with the debugger.
+//
+
+// The structure of this file necessitates a certain number of forward references (particularly in the
+// comments). If you see a term you don't understand please do a search for it further down the file, where
+// hopefully you will find a detailed definition (and if not, please add one).
+
+struct DebuggerIPCEvent;
+struct DbgEventBufferEntry;
+
+// Some simple ad-hoc debug only transport logging. This output is too chatty for an exisitng CLR logging
+// channel (and we've run out of bits for an additional channel) and is likely to be of limited use to anyone
+// besides the transport developer (and even then only occasionally).
+//
+// To enable use 'set|export COMPlus_DbgTransportLog=X' where X is 1 for RS logging, 2 for LS logging and 3
+// for both (default is disabled). Use 'set|export COMPlus_DbgTransportLogClass=X' where X is the hex
+// representation of one or more DbgTransportLogClass flags defined below (default is all classes enabled).
+// For instance, 'set COMPlus_DbgTransportLogClass=f' will enable only message send and receive logging (for
+// all message types).
+enum DbgTransportLogEnable
+{
+ LE_None = 0x00000000,
+ LE_LeftSide = 0x00000001,
+ LE_RightSide = 0x00000002,
+ LE_Unknown = 0xffffffff,
+};
+
+enum DbgTransportLogClass
+{
+ LC_None = 0x00000000,
+ LC_Events = 0x00000001, // Sending and receiving debugger events
+ LC_Session = 0x00000002, // Sending and receiving session messages
+ LC_Requests = 0x00000004, // Sending requests such as MT_GetDCB and receiving replies
+ LC_EventAcks = 0x00000008, // Sending and receiving debugger event acks (DEPRECATED)
+ LC_NetErrors = 0x00000010, // Network errors
+ LC_FaultInject = 0x00000020, // Artificially injected network faults
+ LC_Proxy = 0x00000040, // Proxy interactions
+ LC_All = 0xffffffff,
+ LC_Always = 0xffffffff, // Always log, regardless of class setting
+};
+
+// Status codes that can be returned by various APIs that indicate some conditions of the error that a caller
+// might usefully pass on to a user (environmental factors that the user might have some control over).
+enum ConnStatus
+{
+ SCS_Success, // The request succeeded
+ SCS_OutOfMemory, // The request failed due to a low memory situation
+ SCS_InvalidConfiguration, // Initialize() failed because the debugger settings were not configured or
+ // have become corrupt
+ SCS_UnknownTarget, // Connect() failed because the remote machine at the given address could not
+ // be found
+ SCS_NoListener, // Connect() failed because the remote machine was not listening for requests
+ // on the given port (most likely the remote machine is not configured for
+ // debugging)
+ SCS_NetworkFailure, // Connect() failed due to miscellaneous network error
+ SCS_MismatchedCerts, // Connect()/Accept() failed because the remote party was using a different
+ // cert
+};
+
+
+// Multiple clients can use a single DbgTransportSession, but only one can act as the debugger.
+// A valid DebugTicket is given to the client who is acting as the debugger.
+struct DebugTicket
+{
+friend class DbgTransportSession;
+
+public:
+ DebugTicket() { m_fValid = false; };
+
+ bool IsValid() { return m_fValid; };
+
+protected:
+ void SetValid() { m_fValid = true; };
+ void SetInvalid() { m_fValid = false; };
+
+private:
+ // Tickets can't be copied around. Hide these definitions so as to enforce that.
+ // We still need the Copy ctor so that it can be passed in as a parameter.
+ void operator=(DebugTicket & other);
+
+ bool m_fValid;
+};
+
+#ifdef RIGHT_SIDE_COMPILE
+#define DBG_TRANSPORT_LOG_THIS_SIDE LE_RightSide
+#define DBG_TRANSPORT_LOG_PREFIX "RS"
+#else // RIGHT_SIDE_COMPILE
+#define DBG_TRANSPORT_LOG_THIS_SIDE LE_LeftSide
+#define DBG_TRANSPORT_LOG_PREFIX "LS"
+#endif // RIGHT_SIDE_COMPILE
+
+// Method used to log an interesting event (of the given class). The message given will have any additional
+// arguments inserted following 'printf' formatiing conventions and will be automatically prepended with a
+// LS/RS indicator and suffixed with a newline.
+inline void DbgTransportLog(DbgTransportLogClass eClass, const char *szFormat, ...)
+{
+#ifdef _DEBUG
+ static DWORD s_dwLoggingEnabled = LE_Unknown;
+ static DWORD s_dwLoggingClass = LC_All;
+
+ if (s_dwLoggingEnabled == LE_Unknown)
+ {
+ s_dwLoggingEnabled = REGUTIL::GetConfigDWORD_DontUse_(CLRConfig::INTERNAL_DbgTransportLog, LE_None);
+ s_dwLoggingClass = REGUTIL::GetConfigDWORD_DontUse_(CLRConfig::INTERNAL_DbgTransportLogClass, LC_All);
+ }
+
+ if ((s_dwLoggingEnabled & DBG_TRANSPORT_LOG_THIS_SIDE) &&
+ ((s_dwLoggingClass & eClass) || eClass == LC_Always))
+ {
+ char szOutput[256];
+ va_list args;
+
+ va_start(args, szFormat);
+ vsprintf_s(szOutput, sizeof(szOutput), szFormat, args);
+ va_end(args);
+
+ printf("%s %04x: %s\n", DBG_TRANSPORT_LOG_PREFIX, GetCurrentThreadId(), szOutput);
+ fflush(stdout);
+
+ char szDebugOutput[512];
+ sprintf_s(szDebugOutput, sizeof(szDebugOutput), "%s: %s\n", DBG_TRANSPORT_LOG_PREFIX, szOutput);
+ OutputDebugStringA(szDebugOutput);
+ }
+#endif // _DEBUG
+}
+
+#ifdef _DEBUG
+//
+// Debug-only network fault injection (in order to help test the robust session code). Control is via a single
+// DWORD read from the environment (COMPlus_DbgTransportFaultInject). This DWORD is treated as a set of bit
+// fields as follows:
+//
+// +-------+-------+-------+----------------+-----------+
+// | Side | Op | State | Reserved | Frequency |
+// +-------+-------+-------+----------------+-----------+
+// 31<->28 27<->24 23<->20 19<----------->8 7<------->0
+//
+// The 'Side' field indicates whether the left or right side (or both) should have faults injected. See
+// DbgTransportFaultSide below for values.
+//
+// The 'Op' field indicates which connection methods should simulate failures. See DbgTransportFaultOp.
+//
+// The 'State' field indicates the session states in which faults will be injected. See
+// DbgTransportFaultState. Note that introducing too many failures into the Opening and Opening_NC states will
+// cause the debugger to timeout and fail.
+//
+// The 'Reserved' field has no current meaning and should be left as zero.
+//
+// The 'Frequency' field indicates a percentage failure rate. Valid values are between 0 and 99, values beyond
+// this range will be clamped to 99.
+//
+// For example:
+//
+// export COMPlus_DbgTransportFaultInject=1ff00001
+// --> Fail all network operations on the left side 1% of the time
+//
+// export COMPlus_DbgTransportFaultInject=34200063
+// --> Fail Send() calls on both sides while the session is Open 99% of the time
+//
+
+#define DBG_TRANSPORT_FAULT_RATE_MASK 0x000000ff
+
+// Whether to inject faults to the left, right or both sides.
+enum DbgTransportFaultSide
+{
+ FS_Left = 0x10000000,
+ FS_Right = 0x20000000,
+};
+
+// Network operations which are candiates for fault injection.
+enum DbgTransportFaultOp
+{
+ FO_Connect = 0x01000000,
+ FO_Accept = 0x02000000,
+ FO_Send = 0x04000000,
+ FO_Receive = 0x08000000,
+};
+
+// Session states into which faults should be injected.
+enum DbgTransportFaultState
+{
+ FS_Opening = 0x00100000, // Opening and Opening_NC
+ FS_Open = 0x00200000,
+ FS_Resync = 0x00400000, // Resync and Resync_NC
+};
+
+#ifdef RIGHT_SIDE_COMPILE
+#define DBG_TRANSPORT_FAULT_THIS_SIDE FS_Right
+#else // RIGHT_SIDE_COMPILE
+#define DBG_TRANSPORT_FAULT_THIS_SIDE FS_Left
+#endif // RIGHT_SIDE_COMPILE
+
+// Macro to determine whether a fault should be injected for the given operation.
+#define DBG_TRANSPORT_SHOULD_INJECT_FAULT(_op) DbgTransportShouldInjectFault(FO_##_op, #_op)
+
+#else // _DEBUG
+#define DBG_TRANSPORT_SHOULD_INJECT_FAULT(_op) false
+#endif // _DEBUG
+
+// The PAL doesn't define htons (host-to-network-short) and friends. So provide our own versions here.
+// winsock2.h defines BIGENDIAN to 0x0000 and LITTLEENDIAN to 0x0001, so we need to be careful with the
+// #ifdef.
+#if BIGENDIAN > 0
+#define DBGIPC_HTONS(x) (x)
+#define DBGIPC_NTOHS(x) (x)
+#define DBGIPC_HTONL(x) (x)
+#define DBGIPC_NTOHL(x) (x)
+#else
+inline UINT16 DBGIPC_HTONS(UINT16 x)
+{
+ return (x >> 8) | (x << 8);
+}
+#define DBGIPC_NTOHS(x) DBGIPC_HTONS(x)
+inline UINT32 DBGIPC_HTONL(UINT32 x)
+{
+ return (x >> 24) |
+ ((x >> 8) & 0x0000FF00L) |
+ ((x & 0x0000FF00L) << 8) |
+ (x << 24);
+}
+#define DBGIPC_NTOHL(x) DBGIPC_HTONL(x)
+
+#endif
+
+// Lock abstraction (we can't use the same lock implementation on LS and RS since we really want a Crst on the
+// LS and this isn't available in the RS environment).
+class DbgTransportLock
+{
+public:
+ void Init();
+ void Destroy();
+ void Enter();
+ void Leave();
+
+private:
+#ifdef RIGHT_SIDE_COMPILE
+ CRITICAL_SECTION m_sLock;
+#else // RIGHT_SIDE_COMPILE
+ CrstExplicitInit m_sLock;
+#endif // RIGHT_SIDE_COMPILE
+};
+
+// The transport has only one queue for IPC events, but each IPC event can be marked as one of two types.
+// The transport will signal the handle corresponding to the type of each IPC event. (See
+// code:DbgTransportSession::GetIPCEventReadyEvent and code:DbgTransportSession::GetDebugEventReadyEvent.)
+// This is effectively a basic multiplexing scheme. The old-style IPC event are for all RS-to-LS IPC events
+// and for all LS-to-RS replies. The other type is for LS-to-RS IPC events transported over the native
+// pipeline. For more information, see the comments for the interface code:IEventChannel.
+enum IPCEventType
+{
+ IPCET_OldStyle,
+ IPCET_DebugEvent,
+ IPCET_Max,
+};
+
+// The class that encapsulates all the state for a single session on either the right or left side. The left
+// side supports only one instance of this class for a given runtime. The right side can support several (all
+// connected to different LS instances of course).
+class DbgTransportSession
+{
+public:
+ // No real work done in the constructor. Use Init() instead.
+ DbgTransportSession();
+
+ // Cleanup what is allocated/created in Init()
+ ~DbgTransportSession();
+
+ // Allocates initial resources (including starting the transport thread). The session will start in the
+ // SS_Opening state. That is, the RS will immediately start trying to Connect() a connection while the
+ // LS will perform an Accept() to wait for a connection request. The RS needs an IP address and port
+ // number to initiate connections. These should be given in host byte order. The LS, on the other hand,
+ // requires the addresses of a couple of runtime data structures to service certain debugger requests that
+ // may be delivered once the session is established.
+#ifdef RIGHT_SIDE_COMPILE
+ HRESULT Init(DWORD pid, HANDLE hProcessExited);
+#else
+ HRESULT Init(DebuggerIPCControlBlock * pDCB, AppDomainEnumerationIPCBlock * pADB);
+#endif // RIGHT_SIDE_COMPILE
+
+ // Drive the session to the SS_Closed state, which will deallocate all remaining transport resources
+ // (including terminating the transport thread). If this is the RS and the session state is SS_Open at the
+ // time of this call a graceful disconnect will be attempted (which tells the LS to go back to SS_Opening
+ // to look for a new RS rather than interpreting the disconnection as a temporary error and going into
+ // SS_Resync). On either side the session will no longer be functional after this call returns (though
+ // Init() may be called again to start over from the beginning).
+ void Shutdown();
+
+#ifdef RIGHT_SIDE_COMPILE
+ // Used by debugger side (RS) to cleanup the target (LS) named pipes
+ // and semaphores when the debugger detects the debuggee process exited.
+ void CleanupTargetProcess();
+#else
+ // Cleans up the named pipe connection so no tmp files are left behind. Does only
+ // the minimum and must be safe to call at any time. Called during PAL ExitProcess,
+ // TerminateProcess and for unhandled native exceptions and asserts.
+ void AbortConnection();
+#endif // RIGHT_SIDE_COMPILE
+
+ LONG AddRef()
+ {
+ LONG ref = InterlockedIncrement(&m_ref);
+ return ref;
+ }
+
+ LONG Release()
+ {
+ _ASSERTE(m_ref > 0);
+ LONG ref = InterlockedDecrement(&m_ref);
+ if (ref == 0)
+ {
+ delete this;
+ }
+ return ref;
+ }
+
+#ifndef RIGHT_SIDE_COMPILE
+ // API used only by the LS to drive the transport into a state where it won't accept connections. This is
+ // used when no proxy is detected at startup but it's too late to shutdown all of the debugging system
+ // easily. It's mainly paranoia to increase the protection of your system when the proxy isn't started.
+ void Neuter();
+#endif // !RIGHT_SIDE_COMPILE
+
+#ifdef RIGHT_SIDE_COMPILE
+ // On the RS it may be useful to wait and see if the session can reach the SS_Open state. If the target
+ // runtime has terminated for some reason then we'll never reach the open state. So the method below gives
+ // the RS a way to try and establish a connection for a reasonable amount of time and to time out
+ // otherwise. They could then call Shutdown on the session and report an error back to the rest of the
+ // debugger. The method returns true if the session opened within the time given (in milliseconds) and
+ // false otherwise.
+ bool WaitForSessionToOpen(DWORD dwTimeout);
+
+ // A valid ticket is returned if no other client is currently acting as the debugger.
+ bool UseAsDebugger(DebugTicket * pTicket);
+
+ // A valid ticket is required in order for this function to succeed. After this function succeeds,
+ // another client can request to be the debugger.
+ bool StopUsingAsDebugger(DebugTicket * pTicket);
+#endif // RIGHT_SIDE_COMPILE
+
+ // Sends a pre-initialized event to the other side.
+ HRESULT SendEvent(DebuggerIPCEvent * pEvent);
+ HRESULT SendDebugEvent(DebuggerIPCEvent * pEvent);
+
+ // Retrieves the auto-reset handle which is signalled by the session each time a new event is received
+ // from the other side.
+ HANDLE GetIPCEventReadyEvent();
+ HANDLE GetDebugEventReadyEvent();
+
+ // Copies the last event received from the other side into the provided buffer. This should only be called
+ // (once) after the event returned from GetIPCEventReadyEvent()/GetDebugEventReadyEvent() has been signalled.
+ void GetNextEvent(DebuggerIPCEvent *pEvent, DWORD cbEvent);
+
+#ifdef RIGHT_SIDE_COMPILE
+ // Read and write memory on the LS from the RS.
+ HRESULT ReadMemory(PBYTE pbRemoteAddress, PBYTE pbBuffer, SIZE_T cbBuffer);
+ HRESULT WriteMemory(PBYTE pbRemoteAddress, PBYTE pbBuffer, SIZE_T cbBuffer);
+ HRESULT VirtualUnwind(DWORD threadId, ULONG32 contextSize, PBYTE context);
+
+ // Read and write the debugger control block on the LS from the RS.
+ HRESULT GetDCB(DebuggerIPCControlBlock *pDCB);
+ HRESULT SetDCB(DebuggerIPCControlBlock *pDCB);
+
+ // Read the AppDomain control block on the LS from the RS.
+ HRESULT GetAppDomainCB(AppDomainEnumerationIPCBlock *pADB);
+
+#endif // RIGHT_SIDE_COMPILE
+
+private:
+
+ // Highest protocol version supported by this side of the session. See the
+ // m_dwMajorVersion/m_dwMinorVersion fields for a detailed explanation and the actual version being used
+ // by the session (if it is formed).
+ static const DWORD kCurrentMajorVersion = 2;
+ static const DWORD kCurrentMinorVersion = 0;
+
+ // Session states. These determine which action is taken on a SendMessage (message is sent, queued or an
+ // error is raised) and which incoming messages are valid.
+ enum SessionState
+ {
+ SS_Closed, // No session and no attempt is being made to form one
+ SS_Opening_NC, // Session is being formed but no connection is established yet
+ SS_Opening, // Session is being formed, the low level connection is in place
+ SS_Open, // Session is fully formed and normal transport messages can be sent and received
+ SS_Resync_NC, // A low level connection error is occurred and we're attempting to re-form the link
+ SS_Resync, // We're trying to resynchronize high level state over the new connection
+ };
+
+ // Types of messages that can be sent over the transport connection.
+ enum MessageType
+ {
+ // Session management operations. These must come first and MT_SessionClose must be last in the group.
+ MT_SessionRequest, // RS -> LS : Request a new session be formed (optionally pass encrypted data key)
+ MT_SessionAccept, // LS -> RS : Accept new session
+ MT_SessionReject, // LS -> RS : Reject new session, give reason
+ MT_SessionResync, // RS <-> LS : Resync broken connection by informing other side which messages must be resent
+ MT_SessionClose, // RS -> LS : Gracefully terminate a session
+
+ // Debugger events.
+ MT_Event, // RS <-> LS : A debugger event is being sent as the data block of the message
+
+ // Misc management operations.
+ MT_ReadMemory, // RS <-> LS : RS wants to read LS memory block (or LS is replying to such a request)
+ MT_WriteMemory, // RS <-> LS : RS wants to write LS memory block (or LS is replying to such a request)
+ MT_VirtualUnwind, // RS <-> LS : RS wants to LS unwind a stack frame (or LS is replying to such a request)
+ MT_GetDCB, // RS <-> LS : RS wants to read LS DCB (or LS is replying to such a request)
+ MT_SetDCB, // RS <-> LS : RS wants to write LS DCB (or LS is replying to such a request)
+ MT_GetAppDomainCB, // RS <-> LS : RS wants to read LS AppDomainCB (or LS is replying to such a request)
+ };
+
+ // Reasons the LS can give for rejecting a session. These codes should *not* be changed other than by
+ // adding reasons to keep versioning possible.
+ enum RejectReason
+ {
+ RR_IncompatibleVersion, // LS doesn't support the major version asked for in the request.
+ RR_AlreadyAttached, // LS already has another session open (LS only supports one session at a time)
+ };
+
+ // Struct that defines the format of a message header block sent on the connection. Note that the size of
+ // this structure and the location/size of the m_eType field must *never* change to allow our versioning
+ // protocol to work properly (in particular any LS must be able to interpret at least the type and version
+ // number of an MT_SessionRequest and reply with a MT_SessionReject that any RS can interpret the type and
+ // version of). To help with this there is a padding field at the end for future expansion (this should be
+ // initialized to zero and not accessed in any other manner).
+ struct MessageHeader
+ {
+ Portable<MessageType> m_eType; // Type of message this is
+ Portable<DWORD> m_cbDataBlock; // Size of data block that immediately follows this header (can be zero)
+ Portable<DWORD> m_dwId; // Message ID assigned by the sender of this message
+ Portable<DWORD> m_dwReplyId; // Message ID that this is a reply to (used by messages such as MT_GetDCB)
+ Portable<DWORD> m_dwLastSeenId; // Message ID last seen by sender (receiver can discard up to here from send queue)
+ Portable<DWORD> m_dwReserved; // Reserved for future expansion (must be initialized to zero and
+ // never read)
+
+ // The rest of the header varies depending on the message type (keep the maximum size of this union
+ // small since all messages will pay the overhead, large message type specific data should go in the
+ // following data block).
+ union
+ {
+ // Used by MT_SessionRequest / MT_SessionAccept.
+ struct
+ {
+ Portable<DWORD> m_dwMajorVersion; // Protocol version requested/accepted
+ Portable<DWORD> m_dwMinorVersion;
+ } VersionInfo;
+
+ // Used by MT_SessionReject.
+ struct
+ {
+ Portable<RejectReason> m_eReason; // Reason for rejection.
+ Portable<DWORD> m_dwMajorVersion; // Highest protocol version the LS supports
+ Portable<DWORD> m_dwMinorVersion;
+ } SessionReject;
+
+ // Used by MT_ReadMemory and MT_WriteMemory.
+ struct
+ {
+ Portable<PBYTE> m_pbLeftSideBuffer; // Address of memory to read/write on the LS
+ Portable<DWORD> m_cbLeftSideBuffer; // Size in bytes of memory to read/write
+ Portable<HRESULT> m_hrResult; // Result from LS (access can fail due to unmapped memory etc.)
+ } MemoryAccess;
+
+ // Used by MT_Event.
+ struct
+ {
+ Portable<IPCEventType> m_eIPCEventType; // multiplexing type of this IPC event
+ Portable<DWORD> m_eType; // Event type (useful for debugging)
+ } Event;
+
+ } TypeSpecificData;
+
+ BYTE m_sMustBeZero[8]; // Set this to zero when initializing and never read the contents
+ };
+
+ // Struct defining the format of the data block sent with a SessionRequest.
+ struct SessionRequestData
+ {
+ GUID m_sSessionID; // Unique session ID. Treated as byte blob so no endian-ness
+ };
+
+ // Struct used to track a message that is being (or will soon be) sent but has not yet been acknowledged.
+ // These are usually found queued on the send queue.
+ struct Message
+ {
+ Message *m_pNext; // Next message in the queue
+ MessageHeader m_sHeader; // Inline message header
+ PBYTE m_pbDataBlock; // Pointer to optional message data block (or NULL)
+ DWORD m_cbDataBlock; // Count of bytes in above block if it's non-NULL
+ HANDLE m_hReplyEvent; // Optional event to signal if this message is replied to (or NULL)
+ PBYTE m_pbReplyBlock; // Optional buffer to place data block from reply into (or NULL)
+ DWORD m_cbReplyBlock; // Size in bytes of the above buffer if it is non-NULL
+ Message *m_pOrigMessage; // Used when we need to find the original message from a copy
+ bool m_fAborted; // True if this send was aborted due to session shutdown
+
+ // Common initialization for messages.
+ void Init(MessageType eType,
+ PBYTE pbBufferIn = NULL,
+ DWORD cbBufferIn = 0,
+ PBYTE pbBufferOut = NULL,
+ DWORD cbBufferOut = 0)
+ {
+ memset(this, 0, sizeof(*this));
+ m_sHeader.m_eType = eType;
+ m_sHeader.m_cbDataBlock = cbBufferIn;
+ m_pbDataBlock = pbBufferIn;
+ m_cbDataBlock = cbBufferIn;
+ m_pbReplyBlock = pbBufferOut;
+ m_cbReplyBlock = cbBufferOut;
+ }
+ };
+
+ // Holder class used to take a transport lock in a given scope and automatically release it once that
+ // scope is exited.
+ class TransportLockHolder
+ {
+ public:
+ TransportLockHolder(DbgTransportLock *pLock)
+ {
+ m_pLock = pLock;
+ m_pLock->Enter();
+ }
+
+ ~TransportLockHolder()
+ {
+ m_pLock->Leave();
+ }
+
+ private:
+ DbgTransportLock *m_pLock;
+ };
+
+#ifdef _DEBUG
+ // Store statistics for various session activities that will be useful for performance analysis and tracking
+ // down bugs.
+ struct DbgStats
+ {
+ // Message type counts for sends.
+ LONG m_cSentSessionRequest;
+ LONG m_cSentSessionAccept;
+ LONG m_cSentSessionReject;
+ LONG m_cSentSessionResync;
+ LONG m_cSentSessionClose;
+ LONG m_cSentEvent;
+ LONG m_cSentReadMemory;
+ LONG m_cSentWriteMemory;
+ LONG m_cSentVirtualUnwind;
+ LONG m_cSentGetDCB;
+ LONG m_cSentSetDCB;
+ LONG m_cSentGetAppDomainCB;
+ LONG m_cSentDDMessage;
+
+ // Message type counts for receives.
+ LONG m_cReceivedSessionRequest;
+ LONG m_cReceivedSessionAccept;
+ LONG m_cReceivedSessionReject;
+ LONG m_cReceivedSessionResync;
+ LONG m_cReceivedSessionClose;
+ LONG m_cReceivedEvent;
+ LONG m_cReceivedReadMemory;
+ LONG m_cReceivedWriteMemory;
+ LONG m_cReceivedVirtualUnwind;
+ LONG m_cReceivedGetDCB;
+ LONG m_cReceivedSetDCB;
+ LONG m_cReceivedGetAppDomainCB;
+ LONG m_cReceivedDDMessage;
+
+ // Low level block counts.
+ LONG m_cSentBlocks;
+ LONG m_cReceivedBlocks;
+
+ // Byte count summaries.
+ LONGLONG m_cbSentBytes;
+ LONGLONG m_cbReceivedBytes;
+
+ // Errors and recovery
+ LONG m_cSendErrors;
+ LONG m_cReceiveErrors;
+ LONG m_cMiscErrors;
+ LONG m_cConnections;
+ LONG m_cResends;
+
+ // Session counts.
+ LONG m_cSessions;
+ };
+
+ DbgStats m_sStats;
+
+ // Macros to update the statistics. The increment version is thread safe, but the add version is assumed to be
+ // externally serialized since the 64-bit Interlocked operations are not available on all platforms and these
+ // stats are used for send and receive byte counts which are updated at locations that are serialized anyway.
+#define DBG_TRANSPORT_INC_STAT(_name) InterlockedIncrement(&m_sStats.m_c##_name)
+#define DBG_TRANSPORT_ADD_STAT(_name, _amount) m_sStats.m_cb##_name += (_amount)
+
+#else // _DEBUG
+
+#define DBG_TRANSPORT_INC_STAT(_name)
+#define DBG_TRANSPORT_ADD_STAT(_name, _amount)
+
+#endif // _DEBUG
+
+ // Reference count
+ LONG m_ref;
+
+ // Some flags used to record how far we got in Init() (used for cleanup in Shutdown()).
+ bool m_fInitStateLock;
+#ifndef RIGHT_SIDE_COMPILE
+ bool m_fInitWSA;
+#endif // !RIGHT_SIDE_COMPILE
+
+ // Protocol version. This consists of two parts. The major version is incremented on incompatible protocol
+ // updates. That is, a session between left and right sides that cannot use a protocol with the exact same
+ // major version cannot be formed. The minor version number is incremented on compatible protocol updates.
+ // These are usually associated with optional extensions to the protocol (e.g. a V1.2 endpoint might set
+ // previously unused fields in a message header to indicate some optional hint about the message that a
+ // V1.1 client won't notice at all).
+ //
+ // The right side has a hard-coded version number it sends in the SessionRequest message. The left side
+ // must support the same major version or reply with a SessionReject message containing the highest
+ // version it does support. For this reason the format of a SessionReject message can never change at all.
+ // On a SessionAccept the left side sends back the version number and can choose to lower the minor
+ // version to the highest it knows about. This gives the right side a hint as to the capabilities of the
+ // left side (though it must be prepared to interact with a left side with any minor version number).
+ //
+ // If necessary (and the SessionReject message sent by an incompatible left side indicates a major version
+ // the right side can also support), the right side can re-attempt a SessionRequest with a lower major
+ // version.
+ DWORD m_dwMajorVersion;
+ DWORD m_dwMinorVersion;
+
+ // Session ID randomly allocated by the right side and sent over in the SessionRequest message. This
+ // serves to disambiguate a re-send of the SessionRequest due to a network error versus a SessionRequest
+ // from a different debugger.
+ GUID m_sSessionID;
+
+ // Lock used to synchronize sending messages and updating the session state. This ensures message bytes
+ // don't become interleaved on the transport connection, the send queue is updated consistently across
+ // multiple threads and that we never attempt to use a connection that is being deallocated on another
+ // thread due to a state change. Receives don't need this since they're performed only on the transport
+ // thread (which is also the only thread allowed to deallocate the connection).
+ DbgTransportLock m_sStateLock;
+
+ // Queue of messages that have been sent over the connection but not acknowledged yet or are waiting to be
+ // sent (because another message is using the connection or we're in a SessionResync state). You must hold
+ // m_sStateLock in order to access this queue.
+ Message *m_pSendQueueFirst;
+ Message *m_pSendQueueLast;
+
+ // Message IDs. These are monotonically increasing numbers starting from 0 that are used to stamp each
+ // non-session management message sent on this session. If a low-level network error occurs and we must
+ // abandon and re-form the underlying transport connection the left and right sides send SessionResync
+ // messages with the ID of the last message they received (and processed). This allows us to determine
+ // which messages we still have in our send queue must be re-sent over the new transport connection.
+ // Allocate a new message ID by post incrementing m_dwNextMessageId under the state lock.
+ DWORD m_dwNextMessageId; // Next ID we'll give to a message we're sending
+ DWORD m_dwLastMessageIdSeen; // Last ID we saw in an incoming, fully received message
+
+ // The current session state. This is updated atomically under m_sStateLock.
+ SessionState m_eState;
+
+#ifdef RIGHT_SIDE_COMPILE
+ // Manual reset event that is signalled whenever the session state is SS_Open or SS_Closed (after waiting
+ // on this event the caller should check to see which state it was).
+ HANDLE m_hSessionOpenEvent;
+#endif // RIGHT_SIDE_COMPILE
+
+ // Thread responsible for initial Connect()/Accept() on a low level transport connection and
+ // subsequently for all message reception on that connection. Any error will cause the thread to reset
+ // back into the Connect()/Accept() phase (along with the resulting session state change).
+ HANDLE m_hTransportThread;
+
+ TwoWayPipe m_pipe;
+
+#ifdef RIGHT_SIDE_COMPILE
+ // On the RS the transport thread needs to know the IP address and port number to Connect() to.
+ DWORD m_pid; // Id of a process we're talking to.
+
+ HANDLE m_hProcessExited; // event which will be signaled when the debuggee is terminated
+
+ bool m_fDebuggerAttached;
+#endif
+
+ // Debugger event handling. To improve performance we allow the debugger to send as many events as it
+ // likes without acknowledgement from its peer. While not strictly adhering to the semantic provided by
+ // the shared memory buffer transport (where the buffer could not be written again until the receiver had
+ // explicitly released it) it turns out that no debugging code relies on this. In particular, the most
+ // common scenario where this makes sense is the left side sending large scale update events (such as the
+ // groups of appdomain create, module load etc. events sent during an attach). Here the right hand side
+ // queues the events for later processing and releases the buffers right away.
+ // We gain performance since its no longer necessary to send (or wait on) event acknowledgment messages.
+ // This lowers both network bandwidth and latency (especially when one side is trying to send a continuous
+ // stream of events).
+ // From the transport standpoint this design mainly impacts event receipt. We maintain a dynamically sized
+ // pool of event receipt buffers (the size is determined by the maximum number of unread events we've seen
+ // at any one time). The buffer is a circular array: clients read from the buffer at head index which is
+ // followed by some number of valid buffers (wrapping around to the start of the array if necessary). New
+ // events are added after these (and grow the array if the tail would touch the head otherwise).
+ DbgEventBufferEntry * m_pEventBuffers; // Pointer to array of incoming debugger events
+ DWORD m_cEventBuffers; // Size of the array above (in events)
+ DWORD m_cValidEventBuffers; // Number of events that actually contain data
+ DWORD m_idxEventBufferHead; // Index of the first valid event
+ DWORD m_idxEventBufferTail; // Index of the first invalid event
+ HANDLE m_rghEventReadyEvent[IPCET_Max]; // The event signalled when a new event arrives
+
+#ifndef RIGHT_SIDE_COMPILE
+ // The LS requires the addresses of a couple of runtime data structures in order to service MT_GetDCB etc.
+ // These are provided by the runtime at intialization time.
+ DebuggerIPCControlBlock *m_pDCB;
+ AppDomainEnumerationIPCBlock *m_pADB;
+#endif // !RIGHT_SIDE_COMPILE
+
+ HRESULT SendEventWorker(DebuggerIPCEvent * pEvent, IPCEventType type);
+
+ // Sends a pre-formatted message (including the data block, if any). The fWaitsForReply indicates whether
+ // the caller is going to block until some sort of reply message is received (for instance an event that
+ // must be ack'd or a request such as MT_GetDCB that needs a reply). SendMessage() uses this to determine
+ // whether it needs to buffer the message before placing it on the send queue (since it may need to resend
+ // the message after a transitory network failure).
+ HRESULT SendMessage(Message *pMessage, bool fWaitsForReply);
+
+ // Helper method for sending messages requiring a reply (such as MT_GetDCB) and waiting on the result.
+ HRESULT SendRequestMessageAndWait(Message *pMessage);
+
+ // Sends a single contiguous buffer of host memory over the connection. The caller is responsible for
+ // holding the state lock and ensuring the session state is SS_Open. Returns false if the send failed (the
+ // error will have already caused the recovery logic to kick in, so handling it is not required, the
+ // boolean is just returned so that any further blocks in the message are not sent).
+ bool SendBlock(PBYTE pbBuffer, DWORD cbBuffer);
+
+ // Receives a single contiguous buffer of host memory over the connection. No state lock needs to be
+ // held (receives are serialized by the fact they're only performed on the transport thread). Returns
+ // false if a network error is encountered (which will automatically transition the session into the
+ // correct retry state).
+ bool ReceiveBlock(PBYTE pbBuffer, DWORD cbBuffer);
+
+ // Called upon encountering a network error (e.g. an error from Send() or Receive()). This handles pushing
+ // the session state into SS_Resync_NC in order to start the recovery process.
+ void HandleNetworkError(bool fCallerHoldsStateLock);
+
+ // Scan the send queue and discard any messages which have been processed by the other side according to
+ // the specified ID). Messages waiting on a reply message (e.g. MT_GetDCB) will be retained until that
+ // reply is processed. FlushSendQueue will take the state lock.
+ void FlushSendQueue(DWORD dwLastProcessedId);
+
+#ifdef RIGHT_SIDE_COMPILE
+ // Perform processing required to complete a request (such as MT_GetDCB) once a reply comes in. This
+ // includes reading data from the connection into the output buffer, removing the original message from
+ // the send queue and signalling the completion event. Returns true if no network error was encountered.
+ bool ProcessReply(MessageHeader *pHeader);
+
+ // Upon receiving a reply message, signal the event on the message to wake up the thread waiting for
+ // the reply message and close the handle to the event.
+ void SignalReplyEvent(Message * pMesssage);
+
+ // Given a message ID, find the matching message in the send queue. If there is no match, return NULL.
+ // If there is a match, remove the message from the send queue and return it.
+ Message * RemoveMessageFromSendQueue(DWORD dwMessageId);
+#endif
+
+#ifndef RIGHT_SIDE_COMPILE
+ // Check read and optionally write memory access to the specified range of bytes. Used to check
+ // ReadProcessMemory and WriteProcessMemory requests.
+ HRESULT CheckBufferAccess(PBYTE pbBuffer, DWORD cbBuffer, bool fWriteAccess);
+#endif // !RIGHT_SIDE_COMPILE
+
+ // Initialize all session state to correct starting values. Used during Init() and on the LS when we
+ // gracefully close one session and prepare for another.
+ void InitSessionState();
+
+ // The entry point of the transport worker thread. This one's static, so we immediately dispatch to an
+ // instance method version defined below for convenience in the implementation.
+ static DWORD WINAPI TransportWorkerStatic(LPVOID pvContext);
+ void TransportWorker();
+
+ // Given a fully initialized debugger event structure, return the size of the structure in bytes (this is
+ // not trivial since DebuggerIPCEvent contains a large union member which can cause the portion containing
+ // significant data to vary wildy from event to event).
+ DWORD GetEventSize(DebuggerIPCEvent *pEvent);
+
+#ifdef _DEBUG
+ // Debug helper which returns the name associated with a MessageType.
+ const char *MessageName(MessageType eType);
+
+ // Debug logging helper which logs an incoming message of any type (as long as logging for that message
+ // class is currently enabled).
+ void DbgTransportLogMessageReceived(MessageHeader *pHeader);
+
+ // Helper method used by the DBG_TRANSPORT_SHOULD_INJECT_FAULT macro.
+ bool DbgTransportShouldInjectFault(DbgTransportFaultOp eOp, const char *szOpName);
+#else // _DEBUG
+#define DbgTransportLogMessageReceived(x)
+#endif // _DEBUG
+};
+
+#ifndef RIGHT_SIDE_COMPILE
+// The one and only transport instance for the left side. Allocated and initialized during EE startup (from
+// Debugger::Startup() in debugger.cpp).
+extern DbgTransportSession *g_pDbgTransport;
+#endif // !RIGHT_SIDE_COMPILE
+
+#define DBG_GET_LAST_WSA_ERROR() WSAGetLastError()
+
+#endif // defined(FEATURE_DBGIPC_TRANSPORT_VM) || defined(FEATURE_DBGIPC_TRANSPORT_DI)
+
+#endif // __DBG_TRANSPORT_SESSION_INCLUDED
diff --git a/src/debug/inc/dbgutil.h b/src/debug/inc/dbgutil.h
new file mode 100644
index 0000000000..8dae6d32ab
--- /dev/null
+++ b/src/debug/inc/dbgutil.h
@@ -0,0 +1,93 @@
+// 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.h
+//
+
+//
+//*****************************************************************************
+
+#pragma once
+#include <cor.h>
+#include <cordebug.h>
+#include <metahost.h>
+
+//
+// Various common helpers used by multiple debug components.
+//
+
+// 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);
+
+HRESULT GetResourceRvaFromResourceSectionRva(ICorDebugDataTarget* pDataTarget,
+ ULONG64 moduleBaseAddress,
+ DWORD resourceSectionRva,
+ DWORD type,
+ DWORD name,
+ DWORD language,
+ DWORD* pResourceRva,
+ DWORD* pResourceSize);
+
+HRESULT GetResourceRvaFromResourceSectionRvaByName(ICorDebugDataTarget* pDataTarget,
+ ULONG64 moduleBaseAddress,
+ DWORD resourceSectionRva,
+ DWORD type,
+ LPCWSTR pwszName,
+ DWORD language,
+ DWORD* pResourceRva,
+ DWORD* pResourceSize);
+
+// 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);
+
+// 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);
+
+// A small wrapper that reads from the data target and throws on error
+HRESULT ReadFromDataTarget(ICorDebugDataTarget* pDataTarget,
+ ULONG64 addr,
+ BYTE* pBuffer,
+ ULONG32 bytesToRead);
diff --git a/src/debug/inc/ddmarshalutil.h b/src/debug/inc/ddmarshalutil.h
new file mode 100644
index 0000000000..be24d4c7d5
--- /dev/null
+++ b/src/debug/inc/ddmarshalutil.h
@@ -0,0 +1,394 @@
+// 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.
+
+// DDMarshalUtil.cpp
+
+
+#ifndef _DDMarshal_Util_h
+#define _DDMarshal_Util_h
+
+#include "dacdbiinterface.h"
+typedef IDacDbiInterface::StackWalkHandle StackWalkHandle;
+typedef IDacDbiInterface::HeapWalkHandle HeapWalkHandle;
+typedef IDacDbiInterface::IStringHolder IStringHolder;
+
+#include "stringcopyholder.h"
+
+// @dbgtodo Mac - cleanup the buffer classes here. (are there pre-existing classes we could use instead?)
+// These ultimately get included in the signature for IDacDbiMarshalStub::DoRequest.
+// Key is that this helps serialize remote sized structures like IStringHolder and DacDbiArrayList<T>.
+class BaseBuffer
+{
+public:
+ BaseBuffer()
+ {
+ m_idx = 0;
+ m_pBuffer = NULL;
+ m_size = 0;
+ }
+
+protected:
+ DWORD m_idx;
+ DWORD m_size;
+ BYTE * m_pBuffer;
+};
+
+class WriteBuffer : public BaseBuffer
+{
+public:
+ friend class ReadBuffer;
+
+ WriteBuffer()
+ {
+ // @dbgtodo Mac - do something smarter than always allocate 1000 bytes.
+ m_size = 1000;
+ m_pBuffer = new BYTE[m_size];
+ }
+ ~WriteBuffer()
+ {
+ if (m_pBuffer != NULL)
+ {
+ delete [] m_pBuffer;
+ m_pBuffer = NULL;
+ }
+ }
+
+ // Write to the buffer, and grow if needed.
+ void WriteBlob(const void * pData, DWORD cbLength)
+ {
+ _ASSERTE(m_pBuffer != NULL);
+ EnsureSize(cbLength);
+ memcpy(&m_pBuffer[m_idx], pData, cbLength);
+ m_idx += cbLength;
+ }
+
+ void EnsureSize(DWORD cbLength)
+ {
+ DWORD cbSizeNeeded = m_idx + cbLength;
+ _ASSERTE(cbSizeNeeded <= 128000); // sanity checking...
+ while(cbSizeNeeded >= m_size)
+ {
+ DWORD cbNewSize = (m_size * 3) / 2; // grow by 1.5
+ BYTE * pNewBuffer = new BYTE[cbNewSize];
+ memcpy(pNewBuffer, m_pBuffer, m_idx);
+
+ _ASSERTE(m_pBuffer != NULL);
+ delete [] m_pBuffer;
+
+ m_size = cbNewSize;
+ m_pBuffer = pNewBuffer;
+ }
+ }
+
+ void WriteString(const WCHAR * pString)
+ {
+ bool fIsNull = (pString == NULL ? true : false);
+ EnsureSize(sizeof(fIsNull));
+ memcpy(&m_pBuffer[m_idx], &fIsNull, sizeof(fIsNull));
+ m_idx += sizeof(fIsNull);
+
+ if (!fIsNull)
+ {
+ _ASSERTE(pString != NULL);
+ DWORD len = (DWORD) wcslen(pString);
+ DWORD cbCopy = (len + 1) * sizeof(WCHAR);
+
+ EnsureSize(cbCopy);
+ memcpy(&m_pBuffer[m_idx], pString, cbCopy);
+ m_idx += cbCopy;
+ }
+ }
+
+ // Gets access to the raw buffer - does not transfer ownership of the memory
+ void GetRawPtr(PBYTE * ppBuffer, DWORD * pcbUsed)
+ {
+ *ppBuffer = m_pBuffer;
+ *pcbUsed = m_idx;
+ }
+};
+
+// Read-only stream access to memory blob.
+class ReadBuffer : public BaseBuffer
+{
+public:
+ ReadBuffer()
+ {
+ m_fDeleteOnClose = false;
+ }
+ ~ReadBuffer()
+ {
+ if (m_fDeleteOnClose)
+ {
+ delete [] m_pBuffer;
+ }
+ }
+
+ // Create on existing stream
+ void Open(BYTE * pStream, DWORD cbLength)
+ {
+ _ASSERTE(m_pBuffer == NULL);
+ _ASSERTE(m_idx == 0);
+ m_pBuffer = pStream;
+ m_size = cbLength;
+ }
+ void OpenAndOwn(BYTE * pStream, DWORD cbLength)
+ {
+ Open(pStream, cbLength);
+ m_fDeleteOnClose = true;
+ }
+
+ // Get a reader for the range written by a Writer
+ // This steal's the writer's buffer. The Writer object is dead after this.
+ void Open(WriteBuffer * pBuffer)
+ {
+ _ASSERTE(m_pBuffer == NULL);
+ _ASSERTE(m_idx == 0);
+ m_size = pBuffer->m_idx;
+ m_pBuffer = pBuffer->m_pBuffer;
+
+
+ pBuffer->m_pBuffer = NULL;
+ m_fDeleteOnClose = true;
+ }
+
+ bool IsAtEnd()
+ {
+ return (m_idx == m_size);
+ }
+ void ReadBlob(void * pData, DWORD cbLength)
+ {
+ _ASSERTE(m_idx + cbLength <= m_size);
+ memcpy(pData, &m_pBuffer[m_idx], cbLength);
+ m_idx += cbLength;
+ }
+ DWORD Length()
+ {
+ return m_size;
+ }
+
+ // Return a pointer to the string and mvoe the index.
+ const WCHAR * ReadString()
+ {
+ bool fIsNull = *reinterpret_cast<bool *>(&m_pBuffer[m_idx]);
+ m_idx += sizeof(fIsNull);
+
+ if (fIsNull)
+ {
+ return NULL;
+ }
+ else
+ {
+ const WCHAR * pString = (WCHAR*) &m_pBuffer[m_idx];
+ DWORD len = (DWORD) wcslen(pString);
+ m_idx += (len + 1) * sizeof(WCHAR); // skip past null
+ _ASSERTE(m_idx <= m_size);
+ return pString;
+ }
+ }
+
+protected:
+ bool m_fDeleteOnClose;
+};
+
+
+
+//
+// Writers
+//
+template<class T> inline
+void WriteToBuffer(WriteBuffer * p, T & data)
+{
+ p->WriteBlob(&data, sizeof(T));
+}
+
+inline
+void WriteToBuffer(WriteBuffer * p, StackWalkHandle & h)
+{
+ p->WriteBlob(&h, sizeof(StackWalkHandle));
+}
+
+inline
+void WriteCookie(WriteBuffer * p, BYTE cookie)
+{
+#if _DEBUG
+ WriteToBuffer(p, cookie);
+#endif
+}
+
+
+template<class T> inline
+void WriteToBuffer(WriteBuffer * p, T * pData)
+{
+ p->WriteBlob(pData, sizeof(T));
+}
+
+inline
+void WriteToBuffer(WriteBuffer * p, StringCopyHolder * pString)
+{
+ const WCHAR * pData = NULL;
+ if (pString->IsSet())
+ {
+ pData = *pString; // gets raw data
+ }
+ p->WriteString(pData);
+ WriteCookie(p, 0x1F);
+}
+
+template<class T> inline
+void WriteToBuffer(WriteBuffer * p, DacDbiArrayList<T> * pList)
+{
+ _ASSERTE(pList != NULL);
+ WriteCookie(p, 0xCD);
+
+ int count = pList->Count();
+ WriteToBuffer(p, count);
+
+ if (count == 0) return;
+
+ // Write raw data.
+ for(int i = 0; i < count; i++)
+ {
+ const T * pElement = &((*pList)[i]);
+ WriteToBuffer(p, pElement);
+ }
+ WriteCookie(p, 0xAB);
+}
+
+template<class T> inline
+void WriteToBuffer(WriteBuffer * p, DacDbiArrayList<T> & list)
+{
+ WriteToBuffer(p, &list);
+}
+
+inline
+void WriteToBuffer(WriteBuffer * p, NativeVarData * pData)
+{
+ WriteCookie(p, 0xD1);
+ p->WriteBlob(pData, sizeof(NativeVarData));
+ WriteToBuffer(p, pData->m_offsetInfo);
+}
+
+inline
+void WriteToBuffer(WriteBuffer * p, SequencePoints * pData)
+{
+ WriteCookie(p, 0xD2);
+ p->WriteBlob(pData, sizeof(SequencePoints));
+ WriteToBuffer(p, pData->m_map);
+}
+inline
+void WriteToBuffer(WriteBuffer * p, ClassInfo * pData)
+{
+ WriteCookie(p, 0xD3);
+ p->WriteBlob(pData, sizeof(ClassInfo));
+ WriteToBuffer(p, pData->m_fieldList);
+}
+
+//-----------------------------------------------------------------------------
+//
+// Readers
+//
+template<class T> inline
+void ReadFromBuffer(ReadBuffer * p, T & data)
+{
+ p->ReadBlob(&data, sizeof(T));
+}
+
+inline
+void ReadCookie(ReadBuffer * p, BYTE cookieExpected)
+{
+#if _DEBUG
+ BYTE cookie;
+ ReadFromBuffer(p, cookie);
+ _ASSERTE(cookie = cookieExpected);
+#endif
+}
+
+
+inline
+void ReadFromBuffer(ReadBuffer * p, StackWalkHandle & h)
+{
+ p->ReadBlob(&h, sizeof(StackWalkHandle));
+}
+
+template<class T> inline
+void ReadFromBuffer(ReadBuffer * p, T * pData)
+{
+ // Used to copy-back a By-ref / out parameter
+ p->ReadBlob(pData, sizeof(T));
+}
+
+inline
+void ReadFromBuffer(ReadBuffer * p, IStringHolder * pString)
+{
+ const WCHAR *pData = p->ReadString();
+ // AssignCopy() can handle a NULL string.
+ pString->AssignCopy(pData);
+ ReadCookie(p, 0x1F);
+}
+
+template<class T> inline
+void ReadFromBuffer(ReadBuffer * p, DacDbiArrayList<T> * pList)
+{
+ _ASSERTE(pList != NULL);
+
+ ReadCookie(p, 0xCD);
+
+ // Alloc() will attempt to free the old pointer.
+ // if this was blit copied, the pointer is trashed. So we need to safely clear that
+ // pointer to prepare it to be copied.
+ pList->PrepareForDeserialize();
+
+ int count;
+ ReadFromBuffer(p, count);
+
+ pList->Alloc(count);
+ if (count == 0)
+ {
+ return;
+ }
+
+ // Read raw data.
+ for(int i = 0; i < count; i++)
+ {
+ T * pElement = &((*pList)[i]);
+ ReadFromBuffer(p, pElement);
+ }
+ ReadCookie(p, 0xAB);
+}
+
+template<class T> inline
+void ReadFromBuffer(ReadBuffer * p, DacDbiArrayList<T> & list)
+{
+ ReadFromBuffer(p, &list);
+}
+
+inline
+void ReadFromBuffer(ReadBuffer * p, NativeVarData * pData)
+{
+ ReadCookie(p, 0xD1);
+ p->ReadBlob(pData, sizeof(NativeVarData));
+ ReadFromBuffer(p, &pData->m_offsetInfo);
+}
+
+inline
+void ReadFromBuffer(ReadBuffer * p, SequencePoints * pData)
+{
+ ReadCookie(p, 0xD2);
+ p->ReadBlob(pData, sizeof(SequencePoints));
+ ReadFromBuffer(p, &pData->m_map);
+}
+
+inline
+void ReadFromBuffer(ReadBuffer * p, ClassInfo * pData)
+{
+ ReadCookie(p, 0xD3);
+ p->ReadBlob(pData, sizeof(ClassInfo));
+ ReadFromBuffer(p, &pData->m_fieldList);
+}
+
+
+
+
+#endif // _DDMarshal_Util_h
+
diff --git a/src/debug/inc/dump/.gitmirror b/src/debug/inc/dump/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/debug/inc/dump/.gitmirror
@@ -0,0 +1 @@
+Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror. \ No newline at end of file
diff --git a/src/debug/inc/dump/dumpcommon.h b/src/debug/inc/dump/dumpcommon.h
new file mode 100644
index 0000000000..3e197ce29b
--- /dev/null
+++ b/src/debug/inc/dump/dumpcommon.h
@@ -0,0 +1,108 @@
+// 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 DEBUGGER_DUMPCOMMON_H
+#define DEBUGGER_DUMPCOMMON_H
+
+#if defined(DACCESS_COMPILE) || defined(RIGHT_SIDE_COMPILE)
+
+// When debugging against minidumps, we frequently need to ignore errors
+// due to the dump not having memory content.
+// You should be VERY careful using these macros. Because our code does not
+// distinguish target types, when you allow memory to be missing because a dump
+// target may not have that memory content by-design you are also implicitly
+// allowing that same data to be missing from a live debugging target.
+// Also, be aware that these macros exist in code under vm\. You must be careful to
+// only allow them to change execution for DAC and DBI.
+// Be careful state is such that execution can continue if the target is missing
+// memory.
+// In general, there are two solutions to this problem:
+// a) add the memory to all minidumps
+// b) stop forcing the memory to always be present
+// All decisions between a & b focus on cost. For a, cost is adding the memory & a complete
+// path to locate it to the dump, both in terms of dump generation time and most
+// especially in terms of dump size (we cannot make MiniDumpNormal many MB for trivial
+// apps).
+// For b, cost is that we lose some of our validation when we have to turn off asserts
+// and other checks for targets that should always have the missing memory present
+// because we have no concept of allowing it to be missing only from a dump.
+
+// This seemingly awkward try block starting tag is so that when the macro is used over
+// multiple source lines we don't create a useless try/catch block. This is important
+// when using the macros in vm\ code.
+#define EX_TRY_ALLOW_DATATARGET_MISSING_MEMORY EX_TRY
+#define EX_END_CATCH_ALLOW_DATATARGET_MISSING_MEMORY \
+ EX_CATCH \
+ { \
+ if ((GET_EXCEPTION()->GetHR() != HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY)) && \
+ (GET_EXCEPTION()->GetHR() != CORDBG_E_READVIRTUAL_FAILURE) ) \
+ { \
+ EX_RETHROW; \
+ } \
+ } \
+ EX_END_CATCH(SwallowAllExceptions)
+
+#define EX_TRY_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER EX_TRY
+#define EX_CATCH_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER \
+ EX_CATCH \
+ { \
+ if ((GET_EXCEPTION()->GetHR() != HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY)) && \
+ (GET_EXCEPTION()->GetHR() != CORDBG_E_READVIRTUAL_FAILURE) ) \
+ { \
+ EX_RETHROW; \
+ } \
+ else \
+
+#define EX_TRY_ALLOW_DATATARGET_MISSING_OR_INCONSISTENT_MEMORY EX_TRY
+#define EX_END_CATCH_ALLOW_DATATARGET_MISSING_OR_INCONSISTENT_MEMORY \
+ EX_CATCH \
+ { \
+ if ((GET_EXCEPTION()->GetHR() != HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY)) && \
+ (GET_EXCEPTION()->GetHR() != CORDBG_E_READVIRTUAL_FAILURE) && \
+ (GET_EXCEPTION()->GetHR() != CORDBG_E_TARGET_INCONSISTENT)) \
+ { \
+ EX_RETHROW; \
+ } \
+ } \
+ EX_END_CATCH(SwallowAllExceptions)
+
+
+#define EX_END_CATCH_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER \
+ } \
+ EX_END_CATCH(SwallowAllExceptions)
+
+// Only use this version for wrapping single source lines, or you'll make debugging
+// painful.
+#define ALLOW_DATATARGET_MISSING_MEMORY(sourceCode) \
+ EX_TRY \
+ { \
+ sourceCode \
+ } \
+ EX_END_CATCH_ALLOW_DATATARGET_MISSING_MEMORY
+
+#define ALLOW_DATATARGET_MISSING_OR_INCONSISTENT_MEMORY(sourceCode) \
+ EX_TRY \
+ { \
+ sourceCode \
+ } \
+ EX_END_CATCH_ALLOW_DATATARGET_MISSING_OR_INCONSISTENT_MEMORY
+
+#else
+#define EX_TRY_ALLOW_DATATARGET_MISSING_MEMORY
+#define EX_END_CATCH_ALLOW_DATATARGET_MISSING_MEMORY
+#define EX_TRY_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER \
+ #error This macro is only intended for use in DAC code!
+#define EX_CATCH_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER \
+ #error This macro is only intended for use in DAC code!
+#define EX_END_CATCH_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER \
+ #error This macro is only intended for use in DAC code!
+
+
+#define ALLOW_DATATARGET_MISSING_MEMORY(sourceCode) \
+ sourceCode
+
+#endif // defined(DACCESS_COMPILE) || defined(RIGHT_SIDE_COMPILE)
+
+
+#endif //DEBUGGER_DUMPCOMMON_H
diff --git a/src/debug/inc/eventredirection.h b/src/debug/inc/eventredirection.h
new file mode 100644
index 0000000000..67ea271ea4
--- /dev/null
+++ b/src/debug/inc/eventredirection.h
@@ -0,0 +1,84 @@
+// 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.
+//*****************************************************************************
+
+//
+// NativePipeline.h
+//
+// define control block for redirecting events.
+//*****************************************************************************
+
+#ifndef _EVENTREDIRECTION_H
+#define _EVENTREDIRECTION_H
+
+//---------------------------------------------------------------------------------------
+// Control block for redirecting events.
+// Motivation here is that only 1 process can be the real OS debugger. So if we want a windbg
+// attached to an ICorDebug debuggee, then that windbg is the real debugger and it forwards events
+// to the mdbg process.
+//
+// Terminology:
+// Server: a windbg extension (StrikeRS) that is the real OS debugger, and it forwards native debug
+// events (just exceptions currently) to the client
+// Client: ICorDebug, which gets events via shimmed call to WaitForDebugEvent, etc.
+//
+// Control block lives in Client's process space. All handles are valid in client.
+// Sever does Read/WriteProcessMemory
+struct RedirectionBlock
+{
+ // Version of the control block. Initialized by client, verified by server.
+ // Latest value is EVENT_REDIRECTION_CURRENT_VERSION
+ DWORD m_versionCookie;
+
+ //
+ // Counters. After each WFDE/CDE pair, these counters should be in sync.
+ //
+
+ // increment after WFDE
+ DWORD m_counterAvailable;
+ DWORD m_counterConsumed;
+
+ //
+ // Data for WaitForDebugEvent. (Server writes; Client reads)
+ //
+ DWORD m_dwProcessId;
+ DWORD m_dwThreadId;
+
+ // Different sizes on different platforms
+ EXCEPTION_RECORD m_record;
+ BOOL m_dwFirstChance;
+
+ //
+ // Data for ContinueDebugEvent. (Client writes, server reads)
+ //
+
+ // Continuation status argument to ContinueDebugEvent
+ DWORD m_ContinuationStatus;
+
+
+ //
+ // Coordination events. These are handles in client space; server duplicates out.
+ //
+
+ // Server signals when WFDE Data is ready.
+ HANDLE m_hEventAvailable;
+
+ // Server signals when CDE data is ready.
+ HANDLE m_hEventConsumed;
+
+ // Client signals before it deletes this block. This corresponds to client calling DebugActiveProcessStop.
+ // Thus server can check if signalled to know if accessing this block (which lives in client space) is safe.
+ // This is Synchronized because client only detaches if the debuggee is stopped, in which case the server
+ // isn't in the middle of sending an event.
+ HANDLE m_hDetachEvent;
+};
+
+
+// Current version.
+#define EVENT_REDIRECTION_CURRENT_VERSION ((DWORD) 4)
+
+
+
+#endif // _EVENTREDIRECTION_H
+
diff --git a/src/debug/inc/i386/.gitmirror b/src/debug/inc/i386/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/debug/inc/i386/.gitmirror
@@ -0,0 +1 @@
+Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror. \ No newline at end of file
diff --git a/src/debug/inc/i386/primitives.h b/src/debug/inc/i386/primitives.h
new file mode 100644
index 0000000000..abad642bbd
--- /dev/null
+++ b/src/debug/inc/i386/primitives.h
@@ -0,0 +1,223 @@
+// 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.
+//*****************************************************************************
+// File: primitives.h
+//
+
+//
+// Platform-specific debugger primitives
+//
+//*****************************************************************************
+
+#ifndef PRIMITIVES_H_
+#define PRIMITIVES_H_
+
+
+typedef const BYTE CORDB_ADDRESS_TYPE;
+typedef DPTR(CORDB_ADDRESS_TYPE) PTR_CORDB_ADDRESS_TYPE;
+
+//This is an abstraction to keep x86/ia64 patch data separate
+#define PRD_TYPE DWORD_PTR
+
+#define MAX_INSTRUCTION_LENGTH 16
+
+// Given a return address retrieved during stackwalk,
+// this is the offset by which it should be decremented to lend somewhere in a call instruction.
+#define STACKWALK_CONTROLPC_ADJUST_OFFSET 1
+
+#define CORDbg_BREAK_INSTRUCTION_SIZE 1
+#define CORDbg_BREAK_INSTRUCTION (BYTE)0xCC
+
+inline CORDB_ADDRESS GetPatchEndAddr(CORDB_ADDRESS patchAddr)
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+ return patchAddr + CORDbg_BREAK_INSTRUCTION_SIZE;
+}
+
+
+#define InitializePRDToBreakInst(_pPRD) *(_pPRD) = CORDbg_BREAK_INSTRUCTION
+#define PRDIsBreakInst(_pPRD) (*(_pPRD) == CORDbg_BREAK_INSTRUCTION)
+
+#define CORDbgGetInstructionEx(_buffer, _requestedAddr, _patchAddr, _dummy1, _dummy2) \
+ CORDbgGetInstruction((CORDB_ADDRESS_TYPE *)((_buffer) + ((_patchAddr) - (_requestedAddr))));
+
+#define CORDbgSetInstructionEx(_buffer, _requestedAddr, _patchAddr, _opcode, _dummy2) \
+ CORDbgSetInstruction((CORDB_ADDRESS_TYPE *)((_buffer) + ((_patchAddr) - (_requestedAddr))), (_opcode));
+
+#define CORDbgInsertBreakpointEx(_buffer, _requestedAddr, _patchAddr, _dummy1, _dummy2) \
+ CORDbgInsertBreakpoint((CORDB_ADDRESS_TYPE *)((_buffer) + ((_patchAddr) - (_requestedAddr))));
+
+
+SELECTANY const CorDebugRegister g_JITToCorDbgReg[] =
+{
+ REGISTER_X86_EAX,
+ REGISTER_X86_ECX,
+ REGISTER_X86_EDX,
+ REGISTER_X86_EBX,
+ REGISTER_X86_ESP,
+ REGISTER_X86_EBP,
+ REGISTER_X86_ESI,
+ REGISTER_X86_EDI
+};
+
+//
+// Mapping from ICorDebugInfo register numbers to CorDebugRegister
+// numbers. Note: this must match the order in corinfo.h.
+//
+inline CorDebugRegister ConvertRegNumToCorDebugRegister(ICorDebugInfo::RegNum reg)
+{
+ return g_JITToCorDbgReg[reg];
+}
+
+
+//
+// inline function to access/modify the CONTEXT
+//
+inline LPVOID CORDbgGetIP(DT_CONTEXT *context) {
+ LIMITED_METHOD_CONTRACT;
+
+ return (LPVOID)(size_t)(context->Eip);
+}
+
+inline void CORDbgSetIP(DT_CONTEXT *context, LPVOID eip) {
+ LIMITED_METHOD_CONTRACT;
+
+ context->Eip = (UINT32)(size_t)eip;
+}
+
+inline LPVOID CORDbgGetSP(const DT_CONTEXT * context) {
+ LIMITED_METHOD_CONTRACT;
+
+ return (LPVOID)(size_t)(context->Esp);
+}
+
+inline void CORDbgSetSP(DT_CONTEXT *context, LPVOID esp) {
+ LIMITED_METHOD_CONTRACT;
+
+ context->Esp = (UINT32)(size_t)esp;
+}
+
+inline void CORDbgSetFP(DT_CONTEXT *context, LPVOID ebp) {
+ LIMITED_METHOD_CONTRACT;
+
+ context->Ebp = (UINT32)(size_t)ebp;
+}
+inline LPVOID CORDbgGetFP(DT_CONTEXT* context)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ return (LPVOID)(UINT_PTR)context->Ebp;
+}
+
+// compare the EIP, ESP, and EBP
+inline BOOL CompareControlRegisters(const DT_CONTEXT * pCtx1, const DT_CONTEXT * pCtx2)
+{
+ LIMITED_METHOD_DAC_CONTRACT;
+
+ if ((pCtx1->Eip == pCtx2->Eip) &&
+ (pCtx1->Esp == pCtx2->Esp) &&
+ (pCtx1->Ebp == pCtx2->Ebp))
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/* ========================================================================= */
+//
+// Routines used by debugger support functions such as codepatch.cpp or
+// exception handling code.
+//
+// GetInstruction, InsertBreakpoint, and SetInstruction all operate on
+// a _single_ byte of memory. This is really important. If you only
+// save one byte from the instruction stream before placing a breakpoint,
+// you need to make sure to only replace one byte later on.
+//
+
+
+inline PRD_TYPE CORDbgGetInstruction(UNALIGNED CORDB_ADDRESS_TYPE* address)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ return *address; // retrieving only one byte is important
+
+}
+
+inline void CORDbgInsertBreakpoint(UNALIGNED CORDB_ADDRESS_TYPE *address)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ *((unsigned char*)address) = 0xCC; // int 3 (single byte patch)
+ FlushInstructionCache(GetCurrentProcess(), address, 1);
+}
+
+inline void CORDbgSetInstruction(CORDB_ADDRESS_TYPE* address,
+ DWORD instruction)
+{
+ // In a DAC build, this function assumes the input is an host address.
+ LIMITED_METHOD_DAC_CONTRACT;
+
+ *((unsigned char*)address)
+ = (unsigned char) instruction; // setting one byte is important
+ FlushInstructionCache(GetCurrentProcess(), address, 1);
+}
+
+// After a breakpoint exception, the CPU points to _after_ the break instruction.
+// Adjust the IP so that it points at the break instruction. This lets us patch that
+// opcode and re-excute what was underneath the bp.
+inline void CORDbgAdjustPCForBreakInstruction(DT_CONTEXT* pContext)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ pContext->Eip -= 1;
+}
+
+inline bool AddressIsBreakpoint(CORDB_ADDRESS_TYPE* address)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ return *address == CORDbg_BREAK_INSTRUCTION;
+}
+
+// Set the hardware trace flag.
+inline void SetSSFlag(DT_CONTEXT *context)
+{
+ _ASSERTE(context != NULL);
+ context->EFlags |= 0x100;
+}
+
+// Unset the hardware trace flag.
+inline void UnsetSSFlag(DT_CONTEXT *context)
+{
+ SUPPORTS_DAC;
+ _ASSERTE(context != NULL);
+ context->EFlags &= ~0x100;
+}
+
+// return true if the hardware trace flag applied.
+inline bool IsSSFlagEnabled(DT_CONTEXT * context)
+{
+ _ASSERTE(context != NULL);
+ return (context->EFlags & 0x100) != 0;
+}
+
+
+
+inline bool PRDIsEqual(PRD_TYPE p1, PRD_TYPE p2){
+ return p1 == p2;
+}
+
+// On x86 opcode 0 is an 8-bit version of ADD. Do we really want to use 0 to mean empty? (see issue 366221).
+inline void InitializePRD(PRD_TYPE *p1) {
+ *p1 = 0;
+}
+inline bool PRDIsEmpty(PRD_TYPE p1) {
+ LIMITED_METHOD_CONTRACT;
+
+ return p1 == 0;
+}
+
+
+#endif // PRIMITIVES_H_
diff --git a/src/debug/inc/readonlydatatargetfacade.h b/src/debug/inc/readonlydatatargetfacade.h
new file mode 100644
index 0000000000..95c12928c8
--- /dev/null
+++ b/src/debug/inc/readonlydatatargetfacade.h
@@ -0,0 +1,98 @@
+// 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.
+//*****************************************************************************
+// ReadOnlyDataTargetFacade.h
+//
+
+//
+//*****************************************************************************
+
+#ifndef READONLYDATATARGETFACADE_H_
+#define READONLYDATATARGETFACADE_H_
+
+#include <cordebug.h>
+
+//---------------------------------------------------------------------------------------
+// ReadOnlyDataTargetFacade
+//
+// This class is designed to be used as an ICorDebugMutableDataTarget when none is
+// supplied. All of the write APIs will fail with CORDBG_E_TARGET_READONLY as required
+// by the data target spec when a write operation is invoked on a read-only data target.
+// The desire here is to merge the error code paths for the case when a write fails,
+// and the case when a write is requested but the data target supplied doesn't
+// implement ICorDebugMutableDataTarget.
+//
+// Note that this is intended to be used only for the additional APIs defined by
+// ICorDebugMutableDataTarget. Calling any of the base ICorDebugDataTarget APIs
+// will ASSERT and fail. An alternative design would be to make this class a wrapper
+// class (similar to DataTargetAdapter) over an existing ICorDebugDataTarget interface.
+// In general, we'd like callers of the data target to differentiate between when they're
+// using read-only APIs and mutation APIs since they need to be aware that the latter often
+// won't be supported by the data target. Also, that design would have the draw-back
+// of incuring an extra virtual dispatch on every read API call (makaing debugging more
+// complex and possibly having a performance impact).
+//
+class ReadOnlyDataTargetFacade : public ICorDebugMutableDataTarget
+{
+public:
+ ReadOnlyDataTargetFacade();
+ virtual ~ReadOnlyDataTargetFacade() {}
+
+ //
+ // IUnknown.
+ //
+ virtual HRESULT STDMETHODCALLTYPE QueryInterface(
+ REFIID InterfaceId,
+ PVOID* Interface);
+
+ virtual ULONG STDMETHODCALLTYPE AddRef();
+
+ virtual ULONG STDMETHODCALLTYPE Release();
+
+ //
+ // ICorDebugDataTarget.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE GetPlatform(
+ CorDebugPlatform *pPlatform);
+
+ virtual HRESULT STDMETHODCALLTYPE ReadVirtual(
+ CORDB_ADDRESS address,
+ BYTE * pBuffer,
+ ULONG32 request,
+ ULONG32 * pcbRead);
+
+ virtual HRESULT STDMETHODCALLTYPE GetThreadContext(
+ DWORD dwThreadID,
+ ULONG32 contextFlags,
+ ULONG32 contextSize,
+ BYTE * context);
+
+ //
+ // ICorDebugMutableDataTarget.
+ //
+
+ virtual HRESULT STDMETHODCALLTYPE WriteVirtual(
+ CORDB_ADDRESS address,
+ const BYTE * pBuffer,
+ ULONG32 request);
+
+ virtual HRESULT STDMETHODCALLTYPE SetThreadContext(
+ DWORD dwThreadID,
+ ULONG32 contextSize,
+ const BYTE * context);
+
+ virtual HRESULT STDMETHODCALLTYPE ContinueStatusChanged(
+ DWORD dwThreadId,
+ CORDB_CONTINUE_STATUS dwContinueStatus);
+
+private:
+ // Reference count.
+ LONG m_ref;
+};
+
+#include "readonlydatatargetfacade.inl"
+
+#endif // READONLYDATATARGETFACADE_H_
+
diff --git a/src/debug/inc/readonlydatatargetfacade.inl b/src/debug/inc/readonlydatatargetfacade.inl
new file mode 100644
index 0000000000..df7e1d0e75
--- /dev/null
+++ b/src/debug/inc/readonlydatatargetfacade.inl
@@ -0,0 +1,139 @@
+// 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.
+//*****************************************************************************
+// File: ReadOnlyDataTargetFacade.inl
+//
+
+//
+//*****************************************************************************
+
+
+//---------------------------------------------------------------------------------------
+//
+// Ctor for ReadOnlyDataTargetFacade. Just initializes ref count to 0.
+//
+//---------------------------------------------------------------------------------------
+ReadOnlyDataTargetFacade::ReadOnlyDataTargetFacade()
+ : m_ref(0)
+{
+}
+
+// Standard impl of IUnknown::QueryInterface
+HRESULT STDMETHODCALLTYPE
+ReadOnlyDataTargetFacade::QueryInterface(
+ REFIID InterfaceId,
+ PVOID* pInterface
+ )
+{
+ if (InterfaceId == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown *>(static_cast<ICorDebugDataTarget *>(this));
+ }
+ else if (InterfaceId == IID_ICorDebugDataTarget)
+ {
+ *pInterface = static_cast<ICorDebugDataTarget *>(this);
+ }
+ else if (InterfaceId == IID_ICorDebugMutableDataTarget)
+ {
+ *pInterface = static_cast<ICorDebugMutableDataTarget *>(this);
+ }
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+// Standard impl of IUnknown::AddRef
+ULONG STDMETHODCALLTYPE
+ReadOnlyDataTargetFacade::AddRef()
+{
+ SUPPORTS_DAC;
+ LONG ref = InterlockedIncrement(&m_ref);
+ return ref;
+}
+
+// Standard impl of IUnknown::Release
+ULONG STDMETHODCALLTYPE
+ReadOnlyDataTargetFacade::Release()
+{
+ SUPPORTS_DAC;
+ LONG ref = InterlockedDecrement(&m_ref);
+ if (ref == 0)
+ {
+ delete this;
+ }
+ return ref;
+}
+
+// impl of interface method ICorDebugDataTarget::GetPlatform
+HRESULT STDMETHODCALLTYPE
+ReadOnlyDataTargetFacade::GetPlatform(
+ CorDebugPlatform *pPlatform)
+{
+ SUPPORTS_DAC;
+ _ASSERTE_MSG(false, "Unexpected call to read-API on read-only DataTarget facade");
+ return E_UNEXPECTED;
+}
+
+// impl of interface method ICorDebugDataTarget::ReadVirtual
+HRESULT STDMETHODCALLTYPE
+ReadOnlyDataTargetFacade::ReadVirtual(
+ CORDB_ADDRESS address,
+ PBYTE pBuffer,
+ ULONG32 cbRequestSize,
+ ULONG32 *pcbRead)
+{
+ SUPPORTS_DAC;
+ _ASSERTE_MSG(false, "Unexpected call to read-API on read-only DataTarget facade");
+ return E_UNEXPECTED;
+}
+
+// impl of interface method ICorDebugDataTarget::GetThreadContext
+HRESULT STDMETHODCALLTYPE
+ReadOnlyDataTargetFacade::GetThreadContext(
+ DWORD dwThreadID,
+ ULONG32 contextFlags,
+ ULONG32 contextSize,
+ BYTE * pContext)
+{
+ SUPPORTS_DAC;
+ _ASSERTE_MSG(false, "Unexpected call to read-API on read-only DataTarget facade");
+ return E_UNEXPECTED;
+}
+
+// impl of interface method ICorDebugMutableDataTarget::WriteVirtual
+HRESULT STDMETHODCALLTYPE
+ReadOnlyDataTargetFacade::WriteVirtual(
+ CORDB_ADDRESS pAddress,
+ const BYTE * pBuffer,
+ ULONG32 cbRequestSize)
+{
+ SUPPORTS_DAC;
+ return CORDBG_E_TARGET_READONLY;
+}
+
+// impl of interface method ICorDebugMutableDataTarget::SetThreadContext
+HRESULT STDMETHODCALLTYPE
+ReadOnlyDataTargetFacade::SetThreadContext(
+ DWORD dwThreadID,
+ ULONG32 contextSize,
+ const BYTE * pContext)
+{
+ SUPPORTS_DAC;
+ return CORDBG_E_TARGET_READONLY;
+}
+
+// Public implementation of ICorDebugMutableDataTarget::ContinueStatusChanged
+HRESULT STDMETHODCALLTYPE
+ReadOnlyDataTargetFacade::ContinueStatusChanged(
+ DWORD dwThreadId,
+ CORDB_CONTINUE_STATUS dwContinueStatus)
+{
+ SUPPORTS_DAC;
+ return CORDBG_E_TARGET_READONLY;
+}
diff --git a/src/debug/inc/stringcopyholder.h b/src/debug/inc/stringcopyholder.h
new file mode 100644
index 0000000000..13a89d87fc
--- /dev/null
+++ b/src/debug/inc/stringcopyholder.h
@@ -0,0 +1,59 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+
+#ifndef _StringCopyHolder_h_
+#define _StringCopyHolder_h_
+
+
+//-----------------------------------------------------------------------------
+// Simple holder to keep a copy of a string.
+// Implements IStringHolder so we can pass instances through IDacDbiInterface
+// and have it fill in the contents.
+//-----------------------------------------------------------------------------
+class StringCopyHolder : public IDacDbiInterface::IStringHolder
+{
+public:
+ StringCopyHolder();
+
+ // Free the memory allocated for the string contents
+ ~StringCopyHolder();
+
+ // Make a copy of the provided null-terminated unicode string
+ virtual HRESULT AssignCopy(const WCHAR * pCopy);
+
+ // Reset the string to NULL and free memory
+ void Clear();
+
+ // Returns true if the string has been set to a non-NULL value
+ bool IsSet()
+ {
+ return (m_szData != NULL);
+ }
+
+ // Returns true if an empty string is stored. IsSet must be true to call this.
+ bool IsEmpty()
+ {
+ _ASSERTE(m_szData != NULL);
+ return m_szData[0] == W('\0');
+ }
+
+ // Returns the pointer to the string contents
+ operator WCHAR* () const
+ {
+ return m_szData;
+ }
+
+private:
+ // Disallow copying (to prevent double-free) - no implementation
+ StringCopyHolder( const StringCopyHolder& rhs );
+ StringCopyHolder& operator=( const StringCopyHolder& rhs );
+
+ WCHAR * m_szData;
+
+};
+
+
+#endif // StringCopyHolder
diff --git a/src/debug/inc/twowaypipe.h b/src/debug/inc/twowaypipe.h
new file mode 100644
index 0000000000..6bc0f9f39e
--- /dev/null
+++ b/src/debug/inc/twowaypipe.h
@@ -0,0 +1,104 @@
+// 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 TwoWayPipe_H
+#define TwoWayPipe_H
+
+#ifdef FEATURE_PAL
+#define INVALID_PIPE -1
+#else
+#define INVALID_PIPE INVALID_HANDLE_VALUE
+#endif
+
+// This file contains definition of a simple IPC mechanism - bidirectional named pipe.
+// It is implemented on top of two one-directional names pipes (fifos on UNIX)
+
+// One Windows it is possible to ask OS to create a bidirectional pipe, but it is not the case on UNIX.
+// In order to unify implementation we use two pipes on all systems.
+
+// This all methods of this class are *NOT* thread safe: it is assumed the caller provides synchronization at a higher level.
+class TwoWayPipe
+{
+public:
+ enum State
+ {
+ NotInitialized, // Object didn't create or connect to any pipes.
+ Created, // Server side of the pipe has been created, but didn't bind it to a client.
+ ServerConnected, // Server side of the pipe is connected to a client
+ ClientConnected, // Client side of the pipe is connected to a server.
+ };
+
+ TwoWayPipe()
+ :m_state(NotInitialized),
+ m_inboundPipe(INVALID_PIPE),
+ m_outboundPipe(INVALID_PIPE)
+ {}
+
+
+ ~TwoWayPipe()
+ {
+ Disconnect();
+ }
+
+ // Creates a server side of the pipe.
+ // Id is used to create pipes names and uniquely identify the pipe on the machine.
+ // true - success, false - failure (use GetLastError() for more details)
+ bool CreateServer(DWORD id);
+
+ // Connects to a previously opened server side of the pipe.
+ // Id is used to locate the pipe on the machine.
+ // true - success, false - failure (use GetLastError() for more details)
+ bool Connect(DWORD id);
+
+ // Waits for incoming client connections, assumes GetState() == Created
+ // true - success, false - failure (use GetLastError() for more details)
+ bool WaitForConnection();
+
+ // Reads data from pipe. Returns number of bytes read or a negative number in case of an error.
+ // use GetLastError() for more details
+ int Read(void *buffer, DWORD bufferSize);
+
+ // Writes data to pipe. Returns number of bytes written or a negative number in case of an error.
+ // use GetLastError() for more details
+ int Write(const void *data, DWORD dataSize);
+
+ // Disconnects server or client side of the pipe.
+ // true - success, false - failure (use GetLastError() for more details)
+ bool Disconnect();
+
+ State GetState()
+ {
+ return m_state;
+ }
+
+ // Used by debugger side (RS) to cleanup the target (LS) named pipes
+ // and semaphores when the debugger detects the debuggee process exited.
+ void CleanupTargetProcess();
+
+private:
+
+ State m_state;
+
+#ifdef FEATURE_PAL
+
+ int m_id; // id that was passed to CreateServer() or Connect()
+ int m_inboundPipe, m_outboundPipe; // two one sided pipes used for communication
+ char m_inPipeName[MAX_DEBUGGER_TRANSPORT_PIPE_NAME_LENGTH]; // filename of the inbound pipe
+ char m_outPipeName[MAX_DEBUGGER_TRANSPORT_PIPE_NAME_LENGTH]; // filename of the outbound pipe
+
+#else
+ // Connects to a one sided pipe previously created by CreateOneWayPipe.
+ // In order to successfully connect id and inbound flag should be the same.
+ HANDLE OpenOneWayPipe(DWORD id, bool inbound);
+
+ // Creates a one way pipe, id and inboud flag are used for naming.
+ // Created pipe is supposed to be connected to by OpenOneWayPipe.
+ HANDLE CreateOneWayPipe(DWORD id, bool inbound);
+
+ HANDLE m_inboundPipe, m_outboundPipe; //two one sided pipes used for communication
+#endif //FEATURE_PAL
+};
+
+#endif //TwoWayPipe_H
diff --git a/src/debug/shared/.gitmirror b/src/debug/shared/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/debug/shared/.gitmirror
@@ -0,0 +1 @@
+Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror. \ No newline at end of file
diff --git a/src/debug/shared/amd64/.gitmirror b/src/debug/shared/amd64/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/debug/shared/amd64/.gitmirror
@@ -0,0 +1 @@
+Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror. \ No newline at end of file
diff --git a/src/debug/shared/amd64/primitives.cpp b/src/debug/shared/amd64/primitives.cpp
new file mode 100644
index 0000000000..fb5d95b0d6
--- /dev/null
+++ b/src/debug/shared/amd64/primitives.cpp
@@ -0,0 +1,178 @@
+// 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.
+//*****************************************************************************
+// File: primitives.cpp
+//
+
+//
+// Platform-specific debugger primitives
+//
+//*****************************************************************************
+
+#include "primitives.h"
+
+
+//
+// CopyThreadContext() does an intelligent copy from pSrc to pDst,
+// respecting the ContextFlags of both contexts.
+//
+void CORDbgCopyThreadContext(DT_CONTEXT* pDst, const DT_CONTEXT* pSrc)
+{
+ ULONG dstFlags = pDst->ContextFlags;
+ ULONG srcFlags = pSrc->ContextFlags;
+ LOG((LF_CORDB, LL_INFO1000000,
+ "CP::CTC: pDst=0x%p dstFlags=0x%x, pSrc=0x%px srcFlags=0x%x\n",
+ pDst, dstFlags, pSrc, srcFlags));
+
+ // Unlike on X86 the AMD64 CONTEXT struct isn't nicely defined
+ // to facilitate copying sections based on the CONTEXT flags.
+ if ((dstFlags & srcFlags & CONTEXT_CONTROL) == CONTEXT_CONTROL)
+ {
+ // SegCs
+ CopyContextChunk(&(pDst->SegCs), &(pSrc->SegCs), &(pDst->SegDs),
+ CONTEXT_CONTROL);
+ // SegSs, EFlags
+ CopyContextChunk(&(pDst->SegSs), &(pSrc->SegSs), &(pDst->Dr0),
+ CONTEXT_CONTROL);
+ // Rsp
+ CopyContextChunk(&(pDst->Rsp), &(pSrc->Rsp), &(pDst->Rbp),
+ CONTEXT_CONTROL);
+ // Rip
+ CopyContextChunk(&(pDst->Rip), &(pSrc->Rip), &(pDst->Xmm0),
+ CONTEXT_CONTROL);
+ }
+
+ if ((dstFlags & srcFlags & CONTEXT_INTEGER) == CONTEXT_INTEGER)
+ {
+ // Rax, Rcx, Rdx, Rbx
+ CopyContextChunk(&(pDst->Rax), &(pSrc->Rax), &(pDst->Rsp),
+ CONTEXT_INTEGER);
+ // Rbp, Rsi, Rdi, R8-R15
+ CopyContextChunk(&(pDst->Rbp), &(pSrc->Rbp), &(pDst->Rip),
+ CONTEXT_INTEGET);
+ }
+
+ if ((dstFlags & srcFlags & CONTEXT_SEGMENTS) == CONTEXT_SEGMENTS)
+ {
+ // SegDs, SegEs, SegFs, SegGs
+ CopyContextChunk(&(pDst->SegDs), &(pSrc->SegDs), &(pDst->SegSs),
+ CONTEXT_SEGMENTS);
+ }
+
+ if ((dstFlags & srcFlags & CONTEXT_FLOATING_POINT) == CONTEXT_FLOATING_POINT)
+ {
+ // Xmm0-Xmm15
+ CopyContextChunk(&(pDst->Xmm0), &(pSrc->Xmm0), &(pDst->Xmm15) + sizeof(M128A),
+ CONTEXT_FLOATING_POINT);
+
+ // MxCsr
+ CopyContextChunk(&(pDst->MxCsr), &(pSrc->MxCsr), &(pDst->SegCs),
+ CONTEXT_FLOATING_POINT);
+ }
+
+ if ((dstFlags & srcFlags & CONTEXT_DEBUG_REGISTERS) == CONTEXT_DEBUG_REGISTERS)
+ {
+ // Dr0-Dr3, Dr6-Dr7
+ CopyContextChunk(&(pDst->Dr0), &(pSrc->Dr0), &(pDst->Rax),
+ CONTEXT_DEBUG_REGISTERS);
+ }
+}
+
+void CORDbgSetDebuggerREGDISPLAYFromContext(DebuggerREGDISPLAY* pDRD,
+ DT_CONTEXT* pContext)
+{
+ DWORD flags = pContext->ContextFlags;
+ if ((flags & DT_CONTEXT_CONTROL) == DT_CONTEXT_CONTROL)
+ {
+ pDRD->PC = (SIZE_T)CORDbgGetIP(pContext);
+ pDRD->SP = (SIZE_T)CORDbgGetSP(pContext);
+ }
+
+ if ((flags & DT_CONTEXT_INTEGER) == DT_CONTEXT_INTEGER)
+ {
+ pDRD->Rax = pContext->Rax;
+ pDRD->Rcx = pContext->Rcx;
+ pDRD->Rdx = pContext->Rdx;
+ pDRD->Rbx = pContext->Rbx;
+ pDRD->Rbp = pContext->Rbp;
+ pDRD->Rsi = pContext->Rsi;
+ pDRD->Rdi = pContext->Rdi;
+ pDRD->R8 = pContext->R8;
+ pDRD->R9 = pContext->R9;
+ pDRD->R10 = pContext->R10;
+ pDRD->R11 = pContext->R11;
+ pDRD->R12 = pContext->R12;
+ pDRD->R13 = pContext->R13;
+ pDRD->R14 = pContext->R14;
+ pDRD->R15 = pContext->R15;
+ }
+}
+
+#if defined(ALLOW_VMPTR_ACCESS) || !defined(RIGHT_SIDE_COMPILE)
+void SetDebuggerREGDISPLAYFromREGDISPLAY(DebuggerREGDISPLAY* pDRD, REGDISPLAY* pRD)
+{
+ SUPPORTS_DAC_HOST_ONLY;
+ // CORDbgSetDebuggerREGDISPLAYFromContext() checks the context flags. In cases where we don't have a filter
+ // context from the thread, we initialize a CONTEXT on the stack and use that to do our stack walking. We never
+ // initialize the context flags in such cases. Since this function is called from the stackwalker, we can
+ // guarantee that the integer, control, and floating point sections are valid. So we set the flags here and
+ // restore them afterwards.
+ DWORD contextFlags = pRD->pCurrentContext->ContextFlags;
+ pRD->pCurrentContext->ContextFlags = CONTEXT_FULL;
+ // This doesn't set the pointers, only the values
+ CORDbgSetDebuggerREGDISPLAYFromContext(pDRD, reinterpret_cast<DT_CONTEXT *>(pRD->pCurrentContext)); // MACTODO: KLUDGE UNDO Microsoft
+ pRD->pCurrentContext->ContextFlags = contextFlags;
+
+ // These pointers are always valid so we don't need to test them using PushedRegAddr
+#if defined(USE_REMOTE_REGISTER_ADDRESS)
+ pDRD->pRax = pRD->pCurrentContextPointers->Integer.Register.Rax;
+ pDRD->pRcx = pRD->pCurrentContextPointers->Integer.Register.Rcx;
+ pDRD->pRdx = pRD->pCurrentContextPointers->Integer.Register.Rdx;
+ pDRD->pRbx = pRD->pCurrentContextPointers->Integer.Register.Rbx;
+ pDRD->pRbp = pRD->pCurrentContextPointers->Integer.Register.Rbp;
+ pDRD->pRsi = pRD->pCurrentContextPointers->Integer.Register.Rsi;
+ pDRD->pRdi = pRD->pCurrentContextPointers->Integer.Register.Rdi;
+ pDRD->pR8 = pRD->pCurrentContextPointers->Integer.Register.R8;
+ pDRD->pR9 = pRD->pCurrentContextPointers->Integer.Register.R9;
+ pDRD->pR10 = pRD->pCurrentContextPointers->Integer.Register.R10;
+ pDRD->pR11 = pRD->pCurrentContextPointers->Integer.Register.R11;
+ pDRD->pR12 = pRD->pCurrentContextPointers->Integer.Register.R12;
+ pDRD->pR13 = pRD->pCurrentContextPointers->Integer.Register.R13;
+ pDRD->pR14 = pRD->pCurrentContextPointers->Integer.Register.R14;
+ pDRD->pR15 = pRD->pCurrentContextPointers->Integer.Register.R15;
+#else // !USE_REMOTE_REGISTER_ADDRESS
+ pDRD->pRax = NULL;
+ pDRD->pRcx = NULL;
+ pDRD->pRdx = NULL;
+ pDRD->pRbx = NULL;
+ pDRD->pRbp = NULL;
+ pDRD->pRsi = NULL;
+ pDRD->pRdi = NULL;
+ pDRD->pR8 = NULL;
+ pDRD->pR9 = NULL;
+ pDRD->pR10 = NULL;
+ pDRD->pR11 = NULL;
+ pDRD->pR12 = NULL;
+ pDRD->pR13 = NULL;
+ pDRD->pR14 = NULL;
+ pDRD->pR15 = NULL;
+#endif // USE_REMOTE_REGISTER_ADDRESS
+
+ pDRD->PC = pRD->ControlPC;
+ pDRD->SP = pRD->SP;
+
+ // Please leave RSP, RIP at the front so I don't have to scroll
+ // left to see the most important registers. Thanks!
+ LOG( (LF_CORDB, LL_INFO1000, "SDRFR:Registers:"
+ "Rsp = %p Rip = %p Rbp = %p Rdi = %p "
+ "Rsi = %p Rbx = %p Rdx = %p Rcx = %p Rax = %p"
+ "R8 = %p R9 = %p R10 = %p R11 = %p"
+ "R12 = %p R13 = %p R14 = %p R15 = %p\n",
+ pDRD->SP, pDRD->PC, pDRD->Rbp, pDRD->Rdi,
+ pDRD->Rsi, pDRD->Rbx, pDRD->Rdx, pDRD->Rcx, pDRD->Rax,
+ pDRD->R8, pDRD->R9, pDRD->R10, pDRD->R11, pDRD->R12,
+ pDRD->R13, pDRD->R14, pDRD->R15) );
+
+}
+#endif // ALLOW_VMPTR_ACCESS || !RIGHT_SIDE_COMPILE
diff --git a/src/debug/shared/arm/.gitmirror b/src/debug/shared/arm/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/debug/shared/arm/.gitmirror
@@ -0,0 +1 @@
+Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror. \ No newline at end of file
diff --git a/src/debug/shared/arm/primitives.cpp b/src/debug/shared/arm/primitives.cpp
new file mode 100644
index 0000000000..e9d0bbd59b
--- /dev/null
+++ b/src/debug/shared/arm/primitives.cpp
@@ -0,0 +1,93 @@
+// 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.
+//*****************************************************************************
+// File: primitives.cpp
+//
+
+//
+// Platform-specific debugger primitives
+//
+//*****************************************************************************
+
+#include "primitives.h"
+
+
+//
+// CopyThreadContext() does an intelligent copy from pSrc to pDst,
+// respecting the ContextFlags of both contexts.
+//
+void CORDbgCopyThreadContext(DT_CONTEXT* pDst, const DT_CONTEXT* pSrc)
+{
+ DWORD dstFlags = pDst->ContextFlags;
+ DWORD srcFlags = pSrc->ContextFlags;
+ LOG((LF_CORDB, LL_INFO1000000,
+ "CP::CTC: pDst=0x%08x dstFlags=0x%x, pSrc=0x%08x srcFlags=0x%x\n",
+ pDst, dstFlags, pSrc, srcFlags));
+
+ if ((dstFlags & srcFlags & DT_CONTEXT_CONTROL) == DT_CONTEXT_CONTROL)
+ CopyContextChunk(&(pDst->Sp), &(pSrc->Sp), &(pDst->Fpscr),
+ DT_CONTEXT_CONTROL);
+
+ if ((dstFlags & srcFlags & DT_CONTEXT_INTEGER) == DT_CONTEXT_INTEGER)
+ CopyContextChunk(&(pDst->R0), &(pSrc->R0), &(pDst->Sp),
+ DT_CONTEXT_INTEGER);
+
+ if ((dstFlags & srcFlags & DT_CONTEXT_FLOATING_POINT) == DT_CONTEXT_FLOATING_POINT)
+ CopyContextChunk(&(pDst->Fpscr), &(pSrc->Fpscr), &(pDst->Bvr[0]),
+ DT_CONTEXT_FLOATING_POINT);
+
+ if ((dstFlags & srcFlags & DT_CONTEXT_DEBUG_REGISTERS) ==
+ DT_CONTEXT_DEBUG_REGISTERS)
+ CopyContextChunk(&(pDst->Bvr[0]), &(pSrc->Bvr[0]), &(pDst->Wcr[DT_ARM_MAX_WATCHPOINTS]),
+ DT_CONTEXT_DEBUG_REGISTERS);
+}
+
+
+// Update the regdisplay from a given context.
+void CORDbgSetDebuggerREGDISPLAYFromContext(DebuggerREGDISPLAY *pDRD,
+ DT_CONTEXT* pContext)
+{
+ // We must pay attention to the context flags so that we only use valid portions
+ // of the context.
+ DWORD flags = pContext->ContextFlags;
+ if ((flags & DT_CONTEXT_CONTROL) == DT_CONTEXT_CONTROL)
+ {
+ pDRD->PC = (SIZE_T)CORDbgGetIP(pContext);
+ pDRD->SP = (SIZE_T)CORDbgGetSP(pContext);
+ pDRD->LR = (SIZE_T)pContext->Lr;
+ }
+
+ if ((flags & DT_CONTEXT_INTEGER) == DT_CONTEXT_INTEGER)
+ {
+ pDRD->R0 = (SIZE_T)pContext->R0;
+ pDRD->R1 = (SIZE_T)pContext->R1;
+ pDRD->R2 = (SIZE_T)pContext->R2;
+ pDRD->R3 = (SIZE_T)pContext->R3;
+ pDRD->R4 = (SIZE_T)pContext->R4;
+ pDRD->R5 = (SIZE_T)pContext->R5;
+ pDRD->R6 = (SIZE_T)pContext->R6;
+ pDRD->R7 = (SIZE_T)pContext->R7;
+ pDRD->R8 = (SIZE_T)pContext->R8;
+ pDRD->R9 = (SIZE_T)pContext->R9;
+ pDRD->R10 = (SIZE_T)pContext->R10;
+ pDRD->R11 = (SIZE_T)pContext->R11;
+ pDRD->R12 = (SIZE_T)pContext->R12;
+ }
+}
+
+#if defined(ALLOW_VMPTR_ACCESS) || !defined(RIGHT_SIDE_COMPILE)
+void SetDebuggerREGDISPLAYFromREGDISPLAY(DebuggerREGDISPLAY* pDRD, REGDISPLAY* pRD)
+{
+ SUPPORTS_DAC_HOST_ONLY;
+
+ CORDbgSetDebuggerREGDISPLAYFromContext(pDRD, reinterpret_cast<DT_CONTEXT*>(pRD->pCurrentContext));
+
+ pDRD->SP = pRD->SP;
+ pDRD->PC = (SIZE_T)*(pRD->pPC);
+
+ LOG( (LF_CORDB, LL_INFO1000, "DT::TASSC:Registers:"
+ "SP = %x PC = %x",
+ pDRD->SP, pDRD->PC) );
+}
+#endif // ALLOW_VMPTR_ACCESS || !RIGHT_SIDE_COMPILE
diff --git a/src/debug/shared/arm64/.gitmirror b/src/debug/shared/arm64/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/debug/shared/arm64/.gitmirror
@@ -0,0 +1 @@
+Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror. \ No newline at end of file
diff --git a/src/debug/shared/arm64/primitives.cpp b/src/debug/shared/arm64/primitives.cpp
new file mode 100644
index 0000000000..1e351d676d
--- /dev/null
+++ b/src/debug/shared/arm64/primitives.cpp
@@ -0,0 +1,83 @@
+// 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.
+//*****************************************************************************
+// File: primitives.cpp
+//
+
+//
+// Platform-specific debugger primitives
+//
+//*****************************************************************************
+
+#include "primitives.h"
+
+
+//
+// CopyThreadContext() does an intelligent copy from pSrc to pDst,
+// respecting the ContextFlags of both contexts.
+//
+void CORDbgCopyThreadContext(DT_CONTEXT* pDst, const DT_CONTEXT* pSrc)
+{
+ DWORD dstFlags = pDst->ContextFlags;
+ DWORD srcFlags = pSrc->ContextFlags;
+ LOG((LF_CORDB, LL_INFO1000000,
+ "CP::CTC: pDst=0x%08x dstFlags=0x%x, pSrc=0x%08x srcFlags=0x%x\n",
+ pDst, dstFlags, pSrc, srcFlags));
+
+ if ((dstFlags & srcFlags & DT_CONTEXT_CONTROL) == DT_CONTEXT_CONTROL)
+ {
+ CopyContextChunk(&(pDst->Fp), &(pSrc->Fp), &(pDst->V),
+ DT_CONTEXT_CONTROL);
+ CopyContextChunk(&(pDst->Cpsr), &(pSrc->Cpsr), &(pDst->X),
+ DT_CONTEXT_CONTROL);
+ }
+
+ if ((dstFlags & srcFlags & DT_CONTEXT_INTEGER) == DT_CONTEXT_INTEGER)
+ CopyContextChunk(&(pDst->X[0]), &(pSrc->X[0]), &(pDst->Fp),
+ DT_CONTEXT_INTEGER);
+
+ if ((dstFlags & srcFlags & DT_CONTEXT_FLOATING_POINT) == DT_CONTEXT_FLOATING_POINT)
+ CopyContextChunk(&(pDst->V[0]), &(pSrc->V[0]), &(pDst->Bcr[0]),
+ DT_CONTEXT_FLOATING_POINT);
+
+ if ((dstFlags & srcFlags & DT_CONTEXT_DEBUG_REGISTERS) ==
+ DT_CONTEXT_DEBUG_REGISTERS)
+ CopyContextChunk(&(pDst->Bcr[0]), &(pSrc->Bcr[0]), &(pDst->Wvr[ARM64_MAX_WATCHPOINTS]),
+ DT_CONTEXT_DEBUG_REGISTERS);
+}
+
+#if defined(ALLOW_VMPTR_ACCESS) || !defined(RIGHT_SIDE_COMPILE)
+void SetDebuggerREGDISPLAYFromREGDISPLAY(DebuggerREGDISPLAY* pDRD, REGDISPLAY* pRD)
+{
+ SUPPORTS_DAC_HOST_ONLY;
+
+ DT_CONTEXT* pContext = reinterpret_cast<DT_CONTEXT*>(pRD->pCurrentContext);
+
+ // We must pay attention to the context flags so that we only use valid portions
+ // of the context.
+ DWORD flags = pContext->ContextFlags;
+ if ((flags & DT_CONTEXT_CONTROL) == DT_CONTEXT_CONTROL)
+ {
+ pDRD->FP = (SIZE_T)CORDbgGetFP(pContext);
+ pDRD->LR = (SIZE_T)pContext->Lr;
+ pDRD->PC = (SIZE_T)pContext->Pc;
+ }
+
+ if ((flags & DT_CONTEXT_INTEGER) == DT_CONTEXT_INTEGER)
+ {
+ for(int i = 0 ; i < 29 ; i++)
+ {
+ pDRD->X[i] = (SIZE_T)pContext->X[i];
+ }
+ }
+
+ pDRD->SP = pRD->SP;
+
+ LOG( (LF_CORDB, LL_INFO1000, "DT::TASSC:Registers:"
+ "SP = %x",
+ pDRD->SP) );
+}
+#endif // ALLOW_VMPTR_ACCESS || !RIGHT_SIDE_COMPILE
+
+
diff --git a/src/debug/shared/dbgtransportsession.cpp b/src/debug/shared/dbgtransportsession.cpp
new file mode 100644
index 0000000000..14b509aa9c
--- /dev/null
+++ b/src/debug/shared/dbgtransportsession.cpp
@@ -0,0 +1,2786 @@
+// 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 "dbgtransportsession.h"
+
+#if (!defined(RIGHT_SIDE_COMPILE) && defined(FEATURE_DBGIPC_TRANSPORT_VM)) || (defined(RIGHT_SIDE_COMPILE) && defined(FEATURE_DBGIPC_TRANSPORT_DI))
+
+// This is the entry type for the IPC event queue owned by the transport.
+// Each entry contains the multiplexing type of the IPC event plus the
+// IPC event itself.
+struct DbgEventBufferEntry
+{
+public:
+ IPCEventType m_type;
+ BYTE m_event[CorDBIPC_BUFFER_SIZE]; // buffer for the IPC event
+};
+
+//
+// Provides a robust and secure transport session between a debugger and a debuggee that are potentially on
+// different machines.
+//
+// See DbgTransportSession.h for further detailed comments.
+//
+
+#ifndef RIGHT_SIDE_COMPILE
+// The one and only transport instance for the left side. Allocated and initialized during EE startup (from
+// Debugger::Startup() in debugger.cpp).
+DbgTransportSession *g_pDbgTransport = NULL;
+
+#include "ddmarshalutil.h"
+#endif // !RIGHT_SIDE_COMPILE
+
+// No real work done in the constructor. Use Init() instead.
+DbgTransportSession::DbgTransportSession()
+{
+ m_ref = 1;
+ m_eState = SS_Closed;
+}
+
+DbgTransportSession::~DbgTransportSession()
+{
+ DbgTransportLog(LC_Proxy, "DbgTransportSession::~DbgTransportSession() called");
+
+ // No other threads are now using session resources. We're free to deallocate them as we wish (if they
+ // were allocated in the first place).
+ if (m_hTransportThread)
+ CloseHandle(m_hTransportThread);
+ if (m_rghEventReadyEvent[IPCET_OldStyle])
+ CloseHandle(m_rghEventReadyEvent[IPCET_OldStyle]);
+ if (m_rghEventReadyEvent[IPCET_DebugEvent])
+ CloseHandle(m_rghEventReadyEvent[IPCET_DebugEvent]);
+ if (m_pEventBuffers)
+ delete [] m_pEventBuffers;
+
+#ifdef RIGHT_SIDE_COMPILE
+ if (m_hSessionOpenEvent)
+ CloseHandle(m_hSessionOpenEvent);
+
+ if (m_hProcessExited)
+ CloseHandle(m_hProcessExited);
+#endif // RIGHT_SIDE_COMPILE
+
+ if (m_fInitStateLock)
+ m_sStateLock.Destroy();
+}
+
+// Allocates initial resources (including starting the transport thread). The session will start in the
+// SS_Opening state. That is, the RS will immediately start trying to Connect() a connection while the LS will
+// perform an accept()/Accept() to wait for a connection request. The RS needs an IP address and port number
+// to initiate connections. These should be given in host byte order. The LS, on the other hand, requires the
+// addresses of a couple of runtime data structures to service certain debugger requests that may be delivered
+// once the session is established.
+#ifdef RIGHT_SIDE_COMPILE
+HRESULT DbgTransportSession::Init(DWORD pid, HANDLE hProcessExited)
+#else // RIGHT_SIDE_COMPILE
+HRESULT DbgTransportSession::Init(DebuggerIPCControlBlock *pDCB, AppDomainEnumerationIPCBlock *pADB)
+#endif // RIGHT_SIDE_COMPILE
+{
+ _ASSERTE(m_eState == SS_Closed);
+
+ // Start with a blank slate so that Shutdown() on a partially initialized instance will only do the
+ // cleanup necessary.
+ memset(this, 0, sizeof(*this));
+
+ // Because of the above memset the embeded classes/structs need to be reinitialized especially
+ // the two way pipe; it expects the in/out handles to be -1 instead of 0.
+ m_ref = 1;
+ m_pipe = TwoWayPipe();
+ m_sStateLock = DbgTransportLock();
+
+ // Initialize all per-session state variables.
+ InitSessionState();
+
+#ifdef RIGHT_SIDE_COMPILE
+ // The RS randomly allocates a session ID which is sent to the LS in the SessionRequest message. In the
+ // case of network errors during session formation this allows the LS to tell SessionRequest re-sends from
+ // a new request from a different RS.
+ HRESULT hr = CoCreateGuid(&m_sSessionID);
+ if (FAILED(hr))
+ return hr;
+#endif // RIGHT_SIDE_COMPILE
+
+
+#ifdef RIGHT_SIDE_COMPILE
+ m_pid = pid;
+
+ if (!DuplicateHandle(GetCurrentProcess(),
+ hProcessExited,
+ GetCurrentProcess(),
+ &m_hProcessExited,
+ 0, // ignored since we are going to pass DUPLICATE_SAME_ACCESS
+ FALSE,
+ DUPLICATE_SAME_ACCESS))
+ {
+ return HRESULT_FROM_GetLastError();
+ }
+
+ m_fDebuggerAttached = false;
+#else // RIGHT_SIDE_COMPILE
+ m_pDCB = pDCB;
+ m_pADB = pADB;
+#endif // RIGHT_SIDE_COMPILE
+
+ m_sStateLock.Init();
+ m_fInitStateLock = true;
+
+#ifdef RIGHT_SIDE_COMPILE
+ m_hSessionOpenEvent = WszCreateEvent(NULL, TRUE, FALSE, NULL); // Manual reset, not signalled
+ if (m_hSessionOpenEvent == NULL)
+ return E_OUTOFMEMORY;
+#else // RIGHT_SIDE_COMPILE
+ DWORD pid = GetCurrentProcessId();
+ if (!m_pipe.CreateServer(pid)) {
+ return E_OUTOFMEMORY;
+ }
+#endif // RIGHT_SIDE_COMPILE
+
+ // Allocate some buffers to receive incoming events. The initial number is chosen arbitrarily, tune as
+ // necessary. This array will need to grow if it fills with unread events (it takes our client a little
+ // time to process each incoming receive). In general, however, one side will not send an unbounded stream
+ // of events to the other without waiting for some kind of response. More usual are small bursts of events
+ // to represent variable sized data (such as a stack trace).
+ m_cEventBuffers = 10;
+ m_pEventBuffers = (DbgEventBufferEntry *)new (nothrow) BYTE[m_cEventBuffers * sizeof(DbgEventBufferEntry)];
+ if (m_pEventBuffers == NULL)
+ return E_OUTOFMEMORY;
+
+ m_rghEventReadyEvent[IPCET_OldStyle] = WszCreateEvent(NULL, FALSE, FALSE, NULL); // Auto reset, not signalled
+ if (m_rghEventReadyEvent[IPCET_OldStyle] == NULL)
+ return E_OUTOFMEMORY;
+
+ m_rghEventReadyEvent[IPCET_DebugEvent] = WszCreateEvent(NULL, FALSE, FALSE, NULL); // Auto reset, not signalled
+ if (m_rghEventReadyEvent[IPCET_DebugEvent] == NULL)
+ return E_OUTOFMEMORY;
+
+ // Start the transport thread which handles forming and re-forming connections, driving the session
+ // state to SS_Open and receiving and initially processing all incoming traffic.
+ AddRef();
+ m_hTransportThread = CreateThread(NULL, 0, TransportWorkerStatic, this, 0, NULL);
+ if (m_hTransportThread == NULL)
+ {
+ Release();
+ return E_OUTOFMEMORY;
+ }
+
+ return S_OK;
+}
+
+// Drive the session to the SS_Closed state, which will deallocate all remaining transport resources
+// (including terminating the transport thread). If this is the RS and the session state is SS_Open at the
+// time of this call a graceful disconnect will be attempted (which tells the LS to go back to SS_Opening to
+// look for a new RS rather than interpreting the disconnection as a temporary error and going into
+// SS_Resync). On either side the session will no longer be functional after this call returns (though Init()
+// may be called again to start over from the beginning).
+void DbgTransportSession::Shutdown()
+{
+ DbgTransportLog(LC_Proxy, "DbgTransportSession::Shutdown() called");
+
+ // The transport thread is allocated last in Init() (since it uses all the other resources that Init()
+ // prepares). Don't do any transport related stuff unless this was allocated (which can happen if
+ // Shutdown() is called after an Init() failure).
+
+ if (m_hTransportThread)
+ {
+ // From SS_Open state try a graceful disconnect.
+ if (m_eState == SS_Open)
+ {
+ DbgTransportLog(LC_Session, "Sending 'SessionClose'");
+ DBG_TRANSPORT_INC_STAT(SentSessionClose);
+ Message sMessage;
+ sMessage.Init(MT_SessionClose);
+ SendMessage(&sMessage, false);
+ }
+
+ // Must take the state lock to make a state transition.
+ {
+ TransportLockHolder sLockHolder(&m_sStateLock);
+
+ // Remember previous state and transition to SS_Closed.
+ SessionState ePreviousState = m_eState;
+ m_eState = SS_Closed;
+
+ if (ePreviousState != SS_Closed)
+ {
+ m_pipe.Disconnect();
+ }
+
+ } // Leave m_sStateLock
+
+#ifdef RIGHT_SIDE_COMPILE
+ // Signal the m_hSessionOpenEvent now to quickly error out any callers of WaitForSessionToOpen().
+ SetEvent(m_hSessionOpenEvent);
+#endif // RIGHT_SIDE_COMPILE
+ }
+
+ // The transport instance is no longer valid
+ Release();
+}
+
+#ifndef RIGHT_SIDE_COMPILE
+
+// Cleans up the named pipe connection so no tmp files are left behind. Does only
+// the minimum and must be safe to call at any time. Called during PAL ExitProcess,
+// TerminateProcess and for unhandled native exceptions and asserts.
+void DbgTransportSession::AbortConnection()
+{
+ m_pipe.Disconnect();
+}
+
+// API used only by the LS to drive the transport into a state where it won't accept connections. This is used
+// when no proxy is detected at startup but it's too late to shutdown all of the debugging system easily. It's
+// mainly paranoia to increase the protection of your system when the proxy isn't started.
+void DbgTransportSession::Neuter()
+{
+ // Simply set the session state to SS_Closed. The transport thread will switch itself off if it ever gets
+ // a connection but the rest of the transport resources remain valid (so the debugger helper thread won't
+ // AV on a deallocated handle, which might happen if we simply called Shutdown()).
+ m_eState = SS_Closed;
+}
+
+#else // RIGHT_SIDE_COMPILE
+
+// Used by debugger side (RS) to cleanup the target (LS) named pipes
+// and semaphores when the debugger detects the debuggee process exited.
+void DbgTransportSession::CleanupTargetProcess()
+{
+ m_pipe.CleanupTargetProcess();
+}
+
+// On the RS it may be useful to wait and see if the session can reach the SS_Open state. If the target
+// runtime has terminated for some reason then we'll never reach the open state. So the method below gives the
+// RS a way to try and establish a connection for a reasonable amount of time and to time out otherwise. They
+// could then call Shutdown on the session and report an error back to the rest of the debugger. The method
+// returns true if the session opened within the time given (in milliseconds) and false otherwise.
+bool DbgTransportSession::WaitForSessionToOpen(DWORD dwTimeout)
+{
+ DWORD dwRet = WaitForSingleObject(m_hSessionOpenEvent, dwTimeout);
+ if (m_eState == SS_Closed)
+ return false;
+
+ if (dwRet == WAIT_TIMEOUT)
+ DbgTransportLog(LC_Proxy, "DbgTransportSession::WaitForSessionToOpen(%u) timed out", dwTimeout);
+
+ return dwRet == WAIT_OBJECT_0;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// A valid ticket is returned if no other client is currently acting as the debugger.
+// If the caller passes in a valid ticket, this function will return true without invalidating the ticket.
+//
+// Arguments:
+// pTicket - out parameter; set to a valid ticket if the client has successfully registered as the debugger
+//
+// Return Value:
+// Return true if the client has successfully registered as the debugger.
+//
+
+bool DbgTransportSession::UseAsDebugger(DebugTicket * pTicket)
+{
+ TransportLockHolder sLockHolder(&m_sStateLock);
+ if (m_fDebuggerAttached)
+ {
+ if (pTicket->IsValid())
+ {
+ // The client already holds a valid ticket.
+ return true;
+ }
+ else
+ {
+ // Another client of this session has already indicated that it's using this session to debug.
+ _ASSERTE(!pTicket->IsValid());
+ return false;
+ }
+ }
+ else
+ {
+ m_fDebuggerAttached = true;
+ pTicket->SetValid();
+ return true;
+ }
+}
+
+//---------------------------------------------------------------------------------------
+//
+// A valid ticket is required in order for this function to succeed. After this function succeeds,
+// another client can request to be the debugger.
+//
+// Arguments:
+// pTicket - the client's ticket; must be valid for this function to succeed
+//
+// Return Value:
+// Return true if the client has successfully unregistered as the debugger.
+// Return false if no client is currently acting as the debugger or if the client's ticket is invalid.
+//
+
+bool DbgTransportSession::StopUsingAsDebugger(DebugTicket * pTicket)
+{
+ TransportLockHolder sLockHolder(&m_sStateLock);
+ if (m_fDebuggerAttached && pTicket->IsValid())
+ {
+ // The caller is indeed the owner of the debug ticket.
+ m_fDebuggerAttached = false;
+ pTicket->SetInvalid();
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+#endif // RIGHT_SIDE_COMPILE
+
+// Sends a pre-initialized event to the other side.
+HRESULT DbgTransportSession::SendEvent(DebuggerIPCEvent *pEvent)
+{
+ DbgTransportLog(LC_Events, "Sending '%s'", IPCENames::GetName(pEvent->type));
+ DBG_TRANSPORT_INC_STAT(SentEvent);
+
+ return SendEventWorker(pEvent, IPCET_OldStyle);
+}
+
+// Sends a pre-initialized event to the other side, but pretend that this is coming from the native pipeline.
+// See code:IPCEventType for more information.
+HRESULT DbgTransportSession::SendDebugEvent(DebuggerIPCEvent * pEvent)
+{
+ DbgTransportLog(LC_Events, "Sending '%s' as DEBUG_EVENT", IPCENames::GetName(pEvent->type));
+ DBG_TRANSPORT_INC_STAT(SentEvent);
+
+ return SendEventWorker(pEvent, IPCET_DebugEvent);
+}
+
+// Retrieves the auto-reset handle which is signalled by the session each time a new event is received from
+// the other side.
+HANDLE DbgTransportSession::GetIPCEventReadyEvent()
+{
+ return m_rghEventReadyEvent[IPCET_OldStyle];
+}
+
+// Retrieves the auto-reset handle which is signalled by the session each time a new event (disguised as a
+// debug event) is received from the other side.
+HANDLE DbgTransportSession::GetDebugEventReadyEvent()
+{
+ return m_rghEventReadyEvent[IPCET_DebugEvent];
+}
+
+// Copies the last event received from the other side into the provided buffer. This should only be called
+// (once) after the event returned from GetIPCEEventReadyEvent()/GetDebugEventReadyEvent() has been signalled.
+void DbgTransportSession::GetNextEvent(DebuggerIPCEvent *pEvent, DWORD cbEvent)
+{
+ _ASSERTE(cbEvent <= CorDBIPC_BUFFER_SIZE);
+
+ // Must acquire the state lock to synchronize us wrt to the transport thread (clients already guarantee
+ // they serialize calls to this and waiting on m_rghEventReadyEvent).
+ TransportLockHolder sLockHolder(&m_sStateLock);
+
+ // There must be at least one valid event waiting (this call does not block).
+ _ASSERTE(m_cValidEventBuffers);
+
+ // Copy the first valid event into the client's buffer.
+ memcpy(pEvent, &m_pEventBuffers[m_idxEventBufferHead].m_event, cbEvent);
+
+ // Move the index of the head of the valid list forward (which may in fact move it back to the start of
+ // the array since the list is circular). This reduces the number of valid entries by one. Note that these
+ // two adjustments do not affect the tail of the list in any way. In the limit case the head will end up
+ // pointing to the same event as the tail (and m_cValidEventBuffers will be zero).
+ m_idxEventBufferHead = (m_idxEventBufferHead + 1) % m_cEventBuffers;
+ m_cValidEventBuffers--;
+ _ASSERTE(((m_idxEventBufferHead + m_cValidEventBuffers) % m_cEventBuffers) == m_idxEventBufferTail);
+
+ // If there's at least one more valid event we can signal event ready now.
+ if (m_cValidEventBuffers)
+ {
+ SetEvent(m_rghEventReadyEvent[m_pEventBuffers[m_idxEventBufferHead].m_type]);
+ }
+}
+
+
+
+void MarshalDCBTransportToDCB(DebuggerIPCControlBlockTransport* pIn, DebuggerIPCControlBlock* pOut)
+{
+ pOut->m_DCBSize = pIn->m_DCBSize;
+ pOut->m_verMajor = pIn->m_verMajor;
+ pOut->m_verMinor = pIn->m_verMinor;
+ pOut->m_checkedBuild = pIn->m_checkedBuild;
+ pOut->m_bHostingInFiber = pIn->m_bHostingInFiber;
+ pOut->padding2 = pIn->padding2;
+ pOut->padding3 = pIn->padding3;
+
+ pOut->m_leftSideProtocolCurrent = pIn->m_leftSideProtocolCurrent;
+ pOut->m_leftSideProtocolMinSupported = pIn->m_leftSideProtocolMinSupported;
+
+ pOut->m_rightSideProtocolCurrent = pIn->m_rightSideProtocolCurrent;
+ pOut->m_rightSideProtocolMinSupported = pIn->m_rightSideProtocolMinSupported;
+
+ pOut->m_errorHR = pIn->m_errorHR;
+ pOut->m_errorCode = pIn->m_errorCode;
+
+#if defined(DBG_TARGET_WIN64)
+ pOut->padding4 = pIn->padding4;
+#endif // DBG_TARGET_WIN64
+
+
+ //
+ //pOut->m_rightSideEventAvailable
+ //pOut->m_rightSideEventRead
+ //pOut->m_paddingObsoleteLSEA
+ //pOut->m_paddingObsoleteLSER
+ //pOut->m_rightSideProcessHandle
+ //pOut->m_leftSideUnmanagedWaitEvent
+
+ pOut->m_realHelperThreadId = pIn->m_realHelperThreadId;
+ pOut->m_helperThreadId = pIn->m_helperThreadId;
+ pOut->m_temporaryHelperThreadId = pIn->m_temporaryHelperThreadId;
+ pOut->m_CanaryThreadId = pIn->m_CanaryThreadId;
+ pOut->m_pRuntimeOffsets = pIn->m_pRuntimeOffsets;
+ pOut->m_helperThreadStartAddr = pIn->m_helperThreadStartAddr;
+ pOut->m_helperRemoteStartAddr = pIn->m_helperRemoteStartAddr;
+ pOut->m_specialThreadList = pIn->m_specialThreadList;
+
+ //
+ //pOut->m_receiveBuffer
+ //pOut->m_sendBuffer
+
+ pOut->m_specialThreadListLength = pIn->m_specialThreadListLength;
+ pOut->m_shutdownBegun = pIn->m_shutdownBegun;
+ pOut->m_rightSideIsWin32Debugger = pIn->m_rightSideIsWin32Debugger;
+ pOut->m_specialThreadListDirty = pIn->m_specialThreadListDirty;
+
+ pOut->m_rightSideShouldCreateHelperThread = pIn->m_rightSideShouldCreateHelperThread;
+
+}
+
+void MarshalDCBToDCBTransport(DebuggerIPCControlBlock* pIn, DebuggerIPCControlBlockTransport* pOut)
+{
+ pOut->m_DCBSize = pIn->m_DCBSize;
+ pOut->m_verMajor = pIn->m_verMajor;
+ pOut->m_verMinor = pIn->m_verMinor;
+ pOut->m_checkedBuild = pIn->m_checkedBuild;
+ pOut->m_bHostingInFiber = pIn->m_bHostingInFiber;
+ pOut->padding2 = pIn->padding2;
+ pOut->padding3 = pIn->padding3;
+
+ pOut->m_leftSideProtocolCurrent = pIn->m_leftSideProtocolCurrent;
+ pOut->m_leftSideProtocolMinSupported = pIn->m_leftSideProtocolMinSupported;
+
+ pOut->m_rightSideProtocolCurrent = pIn->m_rightSideProtocolCurrent;
+ pOut->m_rightSideProtocolMinSupported = pIn->m_rightSideProtocolMinSupported;
+
+ pOut->m_errorHR = pIn->m_errorHR;
+ pOut->m_errorCode = pIn->m_errorCode;
+
+#if defined(DBG_TARGET_WIN64)
+ pOut->padding4 = pIn->padding4;
+#endif // DBG_TARGET_WIN64
+
+ pOut->m_realHelperThreadId = pIn->m_realHelperThreadId;
+ pOut->m_helperThreadId = pIn->m_helperThreadId;
+ pOut->m_temporaryHelperThreadId = pIn->m_temporaryHelperThreadId;
+ pOut->m_CanaryThreadId = pIn->m_CanaryThreadId;
+ pOut->m_pRuntimeOffsets = pIn->m_pRuntimeOffsets;
+ pOut->m_helperThreadStartAddr = pIn->m_helperThreadStartAddr;
+ pOut->m_helperRemoteStartAddr = pIn->m_helperRemoteStartAddr;
+ pOut->m_specialThreadList = pIn->m_specialThreadList;
+
+ pOut->m_specialThreadListLength = pIn->m_specialThreadListLength;
+ pOut->m_shutdownBegun = pIn->m_shutdownBegun;
+ pOut->m_rightSideIsWin32Debugger = pIn->m_rightSideIsWin32Debugger;
+ pOut->m_specialThreadListDirty = pIn->m_specialThreadListDirty;
+
+ pOut->m_rightSideShouldCreateHelperThread = pIn->m_rightSideShouldCreateHelperThread;
+}
+
+
+
+#ifdef RIGHT_SIDE_COMPILE
+// Read and write memory on the LS from the RS.
+HRESULT DbgTransportSession::ReadMemory(PBYTE pbRemoteAddress, PBYTE pbBuffer, SIZE_T cbBuffer)
+{
+ DbgTransportLog(LC_Requests, "Sending 'ReadMemory(0x%08X, %u)'", pbRemoteAddress, cbBuffer);
+ DBG_TRANSPORT_INC_STAT(SentReadMemory);
+
+ Message sMessage;
+ sMessage.Init(MT_ReadMemory, NULL, 0, pbBuffer, (DWORD)cbBuffer);
+ sMessage.m_sHeader.TypeSpecificData.MemoryAccess.m_pbLeftSideBuffer = pbRemoteAddress;
+ sMessage.m_sHeader.TypeSpecificData.MemoryAccess.m_cbLeftSideBuffer = (DWORD)cbBuffer;
+
+ HRESULT hr = SendRequestMessageAndWait(&sMessage);
+ if (FAILED(hr))
+ return hr;
+
+ // If we reached here the send was successful but the actual memory operation may not have been (due to
+ // unmapped memory or page protections etc.). So the final result comes back to us in the reply.
+ return sMessage.m_sHeader.TypeSpecificData.MemoryAccess.m_hrResult;
+}
+
+HRESULT DbgTransportSession::WriteMemory(PBYTE pbRemoteAddress, PBYTE pbBuffer, SIZE_T cbBuffer)
+{
+ DbgTransportLog(LC_Requests, "Sending 'WriteMemory(0x%08X, %u)'", pbRemoteAddress, cbBuffer);
+ DBG_TRANSPORT_INC_STAT(SentWriteMemory);
+
+ Message sMessage;
+ sMessage.Init(MT_WriteMemory, pbBuffer, (DWORD)cbBuffer);
+ sMessage.m_sHeader.TypeSpecificData.MemoryAccess.m_pbLeftSideBuffer = pbRemoteAddress;
+ sMessage.m_sHeader.TypeSpecificData.MemoryAccess.m_cbLeftSideBuffer = (DWORD)cbBuffer;
+
+ HRESULT hr = SendRequestMessageAndWait(&sMessage);
+ if (FAILED(hr))
+ return hr;
+
+ // If we reached here the send was successful but the actual memory operation may not have been (due to
+ // unmapped memory or page protections etc.). So the final result comes back to us in the reply.
+ return sMessage.m_sHeader.TypeSpecificData.MemoryAccess.m_hrResult;
+}
+
+HRESULT DbgTransportSession::VirtualUnwind(DWORD threadId, ULONG32 contextSize, PBYTE context)
+{
+ DbgTransportLog(LC_Requests, "Sending 'VirtualUnwind'");
+ DBG_TRANSPORT_INC_STAT(SentVirtualUnwind);
+
+ Message sMessage;
+ sMessage.Init(MT_VirtualUnwind, context, contextSize, context, contextSize);
+ return SendRequestMessageAndWait(&sMessage);
+}
+
+// Read and write the debugger control block on the LS from the RS.
+HRESULT DbgTransportSession::GetDCB(DebuggerIPCControlBlock *pDCB)
+{
+ DbgTransportLog(LC_Requests, "Sending 'GetDCB'");
+ DBG_TRANSPORT_INC_STAT(SentGetDCB);
+
+ Message sMessage;
+ DebuggerIPCControlBlockTransport dcbt;
+ sMessage.Init(MT_GetDCB, NULL, 0, (PBYTE)&dcbt, sizeof(DebuggerIPCControlBlockTransport));
+ HRESULT ret = SendRequestMessageAndWait(&sMessage);
+
+ MarshalDCBTransportToDCB(&dcbt, pDCB);
+ return ret;
+}
+
+HRESULT DbgTransportSession::SetDCB(DebuggerIPCControlBlock *pDCB)
+{
+ DbgTransportLog(LC_Requests, "Sending 'SetDCB'");
+ DBG_TRANSPORT_INC_STAT(SentSetDCB);
+
+ DebuggerIPCControlBlockTransport dcbt;
+ MarshalDCBToDCBTransport(pDCB, &dcbt);
+
+ Message sMessage;
+ sMessage.Init(MT_SetDCB, (PBYTE)&dcbt, sizeof(DebuggerIPCControlBlockTransport));
+ return SendRequestMessageAndWait(&sMessage);
+
+}
+
+// Read the AppDomain control block on the LS from the RS.
+HRESULT DbgTransportSession::GetAppDomainCB(AppDomainEnumerationIPCBlock *pADB)
+{
+ DbgTransportLog(LC_Requests, "Sending 'GetAppDomainCB'");
+ DBG_TRANSPORT_INC_STAT(SentGetAppDomainCB);
+
+ Message sMessage;
+ sMessage.Init(MT_GetAppDomainCB, NULL, 0, (PBYTE)pADB, sizeof(AppDomainEnumerationIPCBlock));
+ return SendRequestMessageAndWait(&sMessage);
+}
+
+#endif // RIGHT_SIDE_COMPILE
+
+// Worker function for code:DbgTransportSession::SendEvent and code:DbgTransportSession::SendDebugEvent.
+HRESULT DbgTransportSession::SendEventWorker(DebuggerIPCEvent * pEvent, IPCEventType type)
+{
+ DWORD cbEvent = GetEventSize(pEvent);
+ _ASSERTE(cbEvent <= CorDBIPC_BUFFER_SIZE);
+
+ Message sMessage;
+ sMessage.Init(MT_Event, (PBYTE)pEvent, cbEvent);
+
+ // Store the event type in the header as well, it's sometimes useful for debugging.
+ sMessage.m_sHeader.TypeSpecificData.Event.m_eIPCEventType = type;
+ sMessage.m_sHeader.TypeSpecificData.Event.m_eType = pEvent->type;
+
+ return SendMessage(&sMessage, false);
+}
+
+// Sends a pre-formatted message (including the data block, if any). The fWaitsForReply indicates whether the
+// caller is going to block until some sort of reply message is received (for instance an event that must be
+// ack'd or a request such as MT_GetDCB that needs a reply). SendMessage() uses this to determine whether it
+// needs to buffer the message before placing it on the send queue (since it may need to resend the message
+// after a transitory network failure).
+HRESULT DbgTransportSession::SendMessage(Message *pMessage, bool fWaitsForReply)
+{
+ // Serialize the whole operation under the state lock. In particular we need to make allocating the
+ // message ID atomic wrt placing the message on the connection (to ensure our IDs are seen in order by the
+ // other side). We also need to hold the lock while manipulating the send queue (to prevent corruption)
+ // and while determining whether to send immediately or not depending on the session state (to avoid
+ // posting a send on a closed and possibly recycled socket).
+ {
+ TransportLockHolder sLockHolder(&m_sStateLock);
+
+ // Perform any last updates to the header or data block here since we might be about to encrypt them.
+
+ // Give this message a unique ID (useful both to track which messages need to be resent on a network
+ // failure and to match replies to the original message).
+ pMessage->m_sHeader.m_dwId = m_dwNextMessageId++;
+
+ // Use this message send to piggyback an acknowledgement of the last message we processed from the
+ // other side (this will allow the other side to discard one or more buffered messages from its send
+ // queue).
+ pMessage->m_sHeader.m_dwLastSeenId = m_dwLastMessageIdSeen;
+
+ // If the caller isn't waiting around for a reply we must make a copy of the message to place on the
+ // send queue.
+ pMessage->m_pOrigMessage = pMessage;
+ Message *pMessageCopy = NULL;
+ PBYTE pDataBlockCopy = NULL;
+ if (!fWaitsForReply)
+ {
+ // Allocate a new message (includes an embedded message header).
+ pMessageCopy = new (nothrow) Message();
+ if (pMessageCopy == NULL)
+ return E_OUTOFMEMORY;
+
+ // Allocate a new data block if one is being used.
+ if (pMessage->m_pbDataBlock)
+ {
+ pDataBlockCopy = new (nothrow) BYTE[pMessage->m_cbDataBlock];
+ if (pDataBlockCopy == NULL)
+ {
+ delete pMessageCopy;
+ return E_OUTOFMEMORY;
+ }
+ }
+
+ // Copy the message descriptor over.
+ memcpy(pMessageCopy, pMessage, sizeof(Message));
+
+ // And the data block if applicable.
+ if (pDataBlockCopy)
+ memcpy(pDataBlockCopy, pMessage->m_pbDataBlock, pMessage->m_cbDataBlock);
+
+ // The message copy still points to the wrong data block (if there is one).
+ pMessageCopy->m_pbDataBlock = pDataBlockCopy;
+
+ // Point the copy back to the original message.
+ pMessageCopy->m_pOrigMessage = pMessage;
+
+ // From now on we'll use the copy.
+ pMessage = pMessageCopy;
+ }
+
+ // Check the session state.
+ if (m_eState == SS_Closed)
+ {
+ // SS_Closed is bad news, we'll never recover from that so error the send immediately.
+ if (pMessageCopy)
+ delete pMessageCopy;
+ if (pDataBlockCopy)
+ delete [] pDataBlockCopy;
+
+ return E_ABORT;
+ }
+
+ // Don't queue session management messages. We always recreate these if we need to re-send them.
+ if (pMessage->m_sHeader.m_eType > MT_SessionClose)
+ {
+ // Regardless of session state we always queue the message for at least as long as it takes us to
+ // be sure the other side has received the message.
+ if (m_pSendQueueLast == NULL)
+ {
+ // Queue is currently empty.
+ m_pSendQueueFirst = pMessage;
+ m_pSendQueueLast = pMessage;
+ pMessage->m_pNext = NULL;
+ }
+ else
+ {
+ // Place on end of queue.
+ m_pSendQueueLast->m_pNext = pMessage;
+ m_pSendQueueLast = pMessage;
+ pMessage->m_pNext = NULL;
+ }
+ }
+
+ // If the state is SS_Open we can send the message now.
+ if (m_eState == SS_Open)
+ {
+ // Send the message header block followed by the data block if it's provided. Any network error will
+ // be reported internally by SendBlock and result in a transition to the SS_Resync_NC state (and an
+ // eventual resend of the data).
+ if (SendBlock((PBYTE)&pMessage->m_sHeader, sizeof(MessageHeader)) && pMessage->m_pbDataBlock)
+ SendBlock(pMessage->m_pbDataBlock, pMessage->m_cbDataBlock);
+ }
+
+ // If the state wasn't open there's nothing more to be done. The state will eventually transition to
+ // either SS_Open (in which case the transport thread will send all pending messages for us at the
+ // transition point) or SS_Closed (where the transport thread will drain the queue and discard each
+ // message, setting m_fAborted if necessary).
+
+ } // Leave m_sStateLock
+
+ return S_OK;
+}
+
+// Helper method for sending messages requiring a reply (such as MT_GetDCB) and waiting on the result.
+HRESULT DbgTransportSession::SendRequestMessageAndWait(Message *pMessage)
+{
+ // Allocate event to wait for reply on.
+ pMessage->m_hReplyEvent = WszCreateEvent(NULL, FALSE, FALSE, NULL); // Auto-reset, not signalled
+ if (pMessage->m_hReplyEvent == NULL)
+ return E_OUTOFMEMORY;
+
+ // Duplicate the handle to the event. It's necessary to have two handles to the same event because
+ // both this thread and the message pumping thread may be trying to access the handle at the same
+ // time (e.g. closing the handle). So we make a duplicate handle. This thread is responsible for
+ // closing hReplyEvent (the local variable) whereas the message pumping thread is responsible for
+ // closing the handle on the message.
+ HANDLE hReplyEvent = NULL;
+ if (!DuplicateHandle(GetCurrentProcess(),
+ pMessage->m_hReplyEvent,
+ GetCurrentProcess(),
+ &hReplyEvent,
+ 0, // ignored since we are going to pass DUPLICATE_SAME_ACCESS
+ FALSE,
+ DUPLICATE_SAME_ACCESS))
+ {
+ return HRESULT_FROM_GetLastError();
+ }
+
+ // Send the request.
+ HRESULT hr = SendMessage(pMessage, true);
+ if (FAILED(hr))
+ {
+ // In this case, we need to close both handles since the message is never put into the send queue.
+ // This thread is the only one who has access to the message.
+ CloseHandle(pMessage->m_hReplyEvent);
+ CloseHandle(hReplyEvent);
+ return hr;
+ }
+
+ // At this point, the message pumping thread may receive the reply any time. It may even receive the
+ // reply message even before we wait on the event. Keep this in mind.
+
+ // Wait for a reply (by the time this event is signalled the message header will have been overwritten by
+ // the reply and any output buffer provided will have been filled in).
+#if defined(RIGHT_SIDE_COMPILE)
+ HANDLE rgEvents[] = { hReplyEvent, m_hProcessExited };
+#else // !RIGHT_SIDE_COMPILE
+ HANDLE rgEvents[] = { hReplyEvent };
+#endif // RIGHT_SIDE_COMPILE
+
+ DWORD dwResult = WaitForMultipleObjectsEx(sizeof(rgEvents)/sizeof(rgEvents[0]), rgEvents, FALSE, INFINITE, FALSE);
+
+ if (dwResult == WAIT_OBJECT_0)
+ {
+ // This is the normal case. The message pumping thread receives a reply from the debuggee process.
+ // It signals the event to wake up this thread.
+ CloseHandle(hReplyEvent);
+
+ // Check whether the session aborted us due to a Shutdown().
+ if (pMessage->m_fAborted)
+ return E_ABORT;
+ }
+#if defined(RIGHT_SIDE_COMPILE)
+ else if (dwResult == (WAIT_OBJECT_0 + 1))
+ {
+ // This is the complicated case. This thread wakes up because the debuggee process is terminated.
+ // At the same time, the message pumping thread may be in the process of handling the reply message.
+ // We need to be careful here because there is a race condition.
+
+ // Remove the original message from the send queue. This is because in the case of a blocking message,
+ // the message can be allocated on the stack. Thus, the message becomes invalid when we return from
+ // this function. The message pumping thread may have beaten this thread to it. That's ok since
+ // RemoveMessageFromSendQueue() takes the state lock.
+ Message * pOriginalMessage = RemoveMessageFromSendQueue(pMessage->m_sHeader.m_dwId);
+ _ASSERTE((pOriginalMessage == NULL) || (pOriginalMessage == pMessage));
+
+ // If the message pumping thread has beaten this thread to removing the original message, then this
+ // thread must wait until the message pumping thread is done with the message before returning.
+ // Otherwise, the message may become invalid when the message pumping thread is accessing it.
+ // Fortunately, in this case, we know the message pumping thread is going to signal the event.
+ if (pOriginalMessage == NULL)
+ {
+ WaitForSingleObject(hReplyEvent, INFINITE);
+ }
+
+ CloseHandle(hReplyEvent);
+ return CORDBG_E_PROCESS_TERMINATED;
+ }
+#endif // RIGHT_SIDE_COMPILE
+ else
+ {
+ // Should never get here.
+ CloseHandle(hReplyEvent);
+ UNREACHABLE();
+ }
+
+ return S_OK;
+}
+
+// Sends a single contiguous buffer of host memory over the connection. The caller is responsible for holding
+// the state lock and ensuring the session state is SS_Open. Returns false if the send failed (the error will
+// have already caused the recovery logic to kick in, so handling it is not required, the boolean is just
+// returned so that any further blocks in the message are not sent).
+bool DbgTransportSession::SendBlock(PBYTE pbBuffer, DWORD cbBuffer)
+{
+ _ASSERTE(m_eState == SS_Opening || m_eState == SS_Resync || m_eState == SS_Open);
+ _ASSERTE(m_pipe.GetState() == TwoWayPipe::ServerConnected || m_pipe.GetState() == TwoWayPipe::ClientConnected);
+ _ASSERTE(cbBuffer > 0);
+
+ DBG_TRANSPORT_INC_STAT(SentBlocks);
+ DBG_TRANSPORT_ADD_STAT(SentBytes, cbBuffer);
+
+ //DbgTransportLog(LC_Proxy, "SendBlock(%08X, %u)", pbBuffer, cbBuffer);
+ bool fSuccess;
+ if (DBG_TRANSPORT_SHOULD_INJECT_FAULT(Send))
+ fSuccess = false;
+ else
+ fSuccess = (m_pipe.Write(pbBuffer, cbBuffer) == cbBuffer);
+
+ if (!fSuccess)
+ {
+ DbgTransportLog(LC_NetErrors, "Network error on Send()");
+ DBG_TRANSPORT_INC_STAT(SendErrors);
+ HandleNetworkError(true);
+ return false;
+ }
+
+ return true;
+}
+
+// Receives a single contiguous buffer of host memory over the connection. No state lock needs to be held
+// (receives are serialized by the fact they're only performed on the transport thread). Returns false if a
+// network error is encountered (which will automatically transition the session into the correct retry
+// state).
+bool DbgTransportSession::ReceiveBlock(PBYTE pbBuffer, DWORD cbBuffer)
+{
+ _ASSERTE(m_pipe.GetState() == TwoWayPipe::ServerConnected || m_pipe.GetState() == TwoWayPipe::ClientConnected);
+ _ASSERTE(cbBuffer > 0);
+
+ DBG_TRANSPORT_INC_STAT(ReceivedBlocks);
+ DBG_TRANSPORT_ADD_STAT(ReceivedBytes, cbBuffer);
+
+ //DbgTransportLog(LC_Proxy, "ReceiveBlock(%08X, %u)", pbBuffer, cbBuffer);
+
+ bool fSuccess;
+ if (DBG_TRANSPORT_SHOULD_INJECT_FAULT(Receive))
+ fSuccess = false;
+ else
+ fSuccess = (m_pipe.Read(pbBuffer, cbBuffer) == cbBuffer);
+
+ if (!fSuccess)
+ {
+ DbgTransportLog(LC_NetErrors, "Network error on Receive()");
+ DBG_TRANSPORT_INC_STAT(ReceiveErrors);
+ HandleNetworkError(false);
+ return false;
+ }
+
+ return true;
+}
+
+// Called upon encountering a network error (e.g. an error from Send() or Receive()). This handles pushing the
+// session state into SS_Resync_NC or SS_Opening_NC in order to start the recovery process.
+void DbgTransportSession::HandleNetworkError(bool fCallerHoldsStateLock)
+{
+ _ASSERTE(m_eState == SS_Open || m_eState == SS_Opening || m_eState == SS_Resync || !fCallerHoldsStateLock);
+
+ // Check the easy cases first which don't require us to take the lock (because we don't transition the
+ // state). These are the SS_Closed state (a network error doesn't matter when we're closing down the
+ // session anyway) and the SS_*_NC states (which indicate someone else beat us to it, closed the
+ // connection and has started recovery).
+ if (m_eState == SS_Closed ||
+ m_eState == SS_Opening_NC ||
+ m_eState == SS_Resync_NC)
+ return;
+
+ // We need the state lock to perform a state transition.
+ if (!fCallerHoldsStateLock)
+ m_sStateLock.Enter();
+
+ switch (m_eState)
+ {
+ case SS_Closed:
+ case SS_Opening_NC:
+ case SS_Resync_NC:
+ // Still need to cope with the no-op states handled above since we could have transitioned into them
+ // before we took the lock.
+ break;
+
+ case SS_Opening:
+ // All work to transition SS_Opening to SS_Open is performed by the transport thread, so we know we're
+ // on that thread. Consequently it's just enough to set the state to SS_Opening_NC and the thread will
+ // notice the change when the SendMessage() or ReceiveBlock() call completes.
+ m_eState = SS_Opening_NC;
+ break;
+
+ case SS_Resync:
+ // Likewise, all the work to transition SS_Resync to SS_Open is performed by the transport thread, so
+ // we know we're on that thread.
+ m_eState = SS_Resync_NC;
+ break;
+
+ case SS_Open:
+ // The state change to SS_Resync_NC will prompt the transport thread (which might be this thread) that
+ // it should discard the current connection and reform a new one. It will also cause sends to be
+ // queued instead of sent. In case we're not the transport thread and instead it is currently stuck in
+ // a Receive (I don't entirely trust the connection to immediately fail these on a network problem)
+ // we'll call CancelReceive() to abort the operation. The transport thread itself will handle the
+ // actual Destroy() (having one thread do this management greatly simplifies things).
+ m_eState = SS_Resync_NC;
+ m_pipe.Disconnect();
+ break;
+
+ default:
+ _ASSERTE(!"Unknown session state");
+ }
+
+ if (!fCallerHoldsStateLock)
+ m_sStateLock.Leave();
+}
+
+// Scan the send queue and discard any messages which have been processed by the other side according to the
+// specified ID). Messages waiting on a reply message (e.g. MT_GetDCB) will be retained until that reply is
+// processed. FlushSendQueue will take the state lock.
+void DbgTransportSession::FlushSendQueue(DWORD dwLastProcessedId)
+{
+ // Must access the send queue under the state lock.
+ TransportLockHolder sLockHolder(&m_sStateLock);
+
+ // Note that message headers (and data blocks) may be encrypted. Use the cached fields in the Message
+ // structure to compare message IDs and types.
+
+ Message *pMsg = m_pSendQueueFirst;
+ Message *pLastMsg = NULL;
+ while (pMsg)
+ {
+ if (pMsg->m_sHeader.m_dwId <= dwLastProcessedId)
+ {
+ // Message has been seen and processed by other side.
+ // Check if we can discard it (i.e. it's not waiting on a reply message that needs the original
+ // request to hang around).
+#ifdef RIGHT_SIDE_COMPILE
+ MessageType eType = pMsg->m_sHeader.m_eType;
+ if (eType != MT_ReadMemory &&
+ eType != MT_WriteMemory &&
+ eType != MT_VirtualUnwind &&
+ eType != MT_GetDCB &&
+ eType != MT_SetDCB &&
+ eType != MT_GetAppDomainCB)
+#endif // RIGHT_SIDE_COMPILE
+ {
+#ifdef RIGHT_SIDE_COMPILE
+ _ASSERTE(eType == MT_Event);
+#endif // RIGHT_SIDE_COMPILE
+
+ // We can discard this message.
+
+ // Unlink it from the queue.
+ if (pLastMsg == NULL)
+ m_pSendQueueFirst = pMsg->m_pNext;
+ else
+ pLastMsg->m_pNext = pMsg->m_pNext;
+ if (m_pSendQueueLast == pMsg)
+ m_pSendQueueLast = pLastMsg;
+
+ Message *pDiscardMsg = pMsg;
+ pMsg = pMsg->m_pNext;
+
+ // If the message is a copy deallocate it (and the data block associated with it).
+ if (pDiscardMsg->m_pOrigMessage != pDiscardMsg)
+ {
+ if (pDiscardMsg->m_pbDataBlock)
+ delete [] pDiscardMsg->m_pbDataBlock;
+ delete pDiscardMsg;
+ }
+
+ continue;
+ }
+ }
+
+ pLastMsg = pMsg;
+ pMsg = pMsg->m_pNext;
+ }
+}
+
+#ifdef RIGHT_SIDE_COMPILE
+// Perform processing required to complete a request (such as MT_GetDCB) once a reply comes in. This includes
+// reading data from the connection into the output buffer, removing the original message from the send queue
+// and signalling the completion event. Returns true if no network error was encountered.
+bool DbgTransportSession::ProcessReply(MessageHeader *pHeader)
+{
+ // Locate original message on the send queue.
+ Message *pMsg = RemoveMessageFromSendQueue(pHeader->m_dwReplyId);
+
+ // This can happen if the thread blocked waiting for the replyl message has waken up because the debuggee
+ // process has terminated. See code:DbgTransportSession::SendRequestMessageAndWait() for more info.
+ if (pMsg == NULL)
+ {
+ return true;
+ }
+
+ // If there is a reply block but the caller hasn't specified a reply buffer.
+ // This combination is not used any more.
+ _ASSERTE(! ((pHeader->m_cbDataBlock != (DWORD)0) && (pMsg->m_pbReplyBlock == (PBYTE)NULL)) );
+
+ // If there was an output buffer provided then we copy the data block in the reply into it (perhaps
+ // decrypting it first). If the reply header indicates there is no data block then presumably the request
+ // failed (which should be indicated in the TypeSpecificData of the reply, ala MT_ReadMemory).
+ if (pMsg->m_pbReplyBlock && pHeader->m_cbDataBlock)
+ {
+ _ASSERTE(pHeader->m_cbDataBlock == pMsg->m_cbReplyBlock);
+ if (!ReceiveBlock(pMsg->m_pbReplyBlock, pMsg->m_cbReplyBlock))
+ {
+ // Whoops. We hit an error trying to read the reply data. We need to push the original message
+ // back on the queue and await a retry. Since this message must have been seen by the other side
+ // we don't need to put it on the queue in order (it will never be resent). Easiest just to put it
+ // on the head.
+ {
+ TransportLockHolder sLockHolder(&m_sStateLock);
+ pMsg->m_pNext = m_pSendQueueFirst;
+ m_pSendQueueFirst = pMsg;
+ if (m_pSendQueueLast == NULL)
+ m_pSendQueueLast = pMsg;
+ return false;
+ } // Leave m_sStateLock
+ }
+ }
+
+ // Copy TypeSpecificData from the reply back into the original message (it can contain additional status).
+ // Be careful to update the real original message (the version on the queue will be a copy if we're using
+ // a secure session).
+ pMsg->m_pOrigMessage->m_sHeader.TypeSpecificData = pHeader->TypeSpecificData;
+
+ // **** IMPORTANT NOTE ****
+ // We're about to cause a side-effect visible to our client. From here on out (until we update the
+ // session's idea of the last incoming message we processed back in the transport thread's main loop) we
+ // must avoid any failures. If we fail before the update the other side will re-send the message which is
+ // bad if we've already processed it. See the comment near the start of the SS_Open message dispatch logic
+ // for more details.
+ // **** IMPORTANT NOTE ****
+
+ // Signal the completion event.
+ SignalReplyEvent(pMsg);
+
+ return true;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Upon receiving a reply message, signal the event on the message to wake up the thread waiting for
+// the reply message and close the handle to the event.
+//
+// Arguments:
+// pMessage - the reply message to be processed
+//
+
+void DbgTransportSession::SignalReplyEvent(Message * pMessage)
+{
+ // Make a local copy of the event handle. As soon as we signal the event, the thread blocked waiting on
+ // the reply may wake up and trash the message. See code:DbgTransportSession::SendRequestMessageAndWait()
+ // for more info.
+ HANDLE hReplyEvent = pMessage->m_hReplyEvent;
+ _ASSERTE(hReplyEvent != NULL);
+
+ SetEvent(hReplyEvent);
+ CloseHandle(hReplyEvent);
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Given a message ID, find the matching message in the send queue. If there is no match, return NULL.
+// If there is a match, remove the message from the send queue and return it.
+//
+// Arguments:
+// dwMessageId - the ID of the message to retrieve
+//
+// Return Value:
+// NULL if the specified message cannot be found.
+// Otherwise return the specified message with the side effect that it's also removed from the send queue.
+//
+// Notes:
+// The caller is NOT responsible for taking the state lock. This function will do that.
+//
+
+DbgTransportSession::Message * DbgTransportSession::RemoveMessageFromSendQueue(DWORD dwMessageId)
+{
+ // Locate original message on the send queue.
+ Message *pMsg = NULL;
+ {
+ TransportLockHolder sLockHolder(&m_sStateLock);
+
+ pMsg = m_pSendQueueFirst;
+ Message *pLastMsg = NULL;
+
+ while (pMsg)
+ {
+ if (dwMessageId == pMsg->m_sHeader.m_dwId)
+ {
+ // Found the original message that this is a reply to. Unlink it.
+ if (pLastMsg == NULL)
+ m_pSendQueueFirst = pMsg->m_pNext;
+ else
+ pLastMsg->m_pNext = pMsg->m_pNext;
+
+ if (m_pSendQueueLast == pMsg)
+ m_pSendQueueLast = pLastMsg;
+ break;
+ }
+
+ pLastMsg = pMsg;
+ pMsg = pMsg->m_pNext;
+ }
+ } // Leave m_sStateLock
+
+ // could be NULL
+ return pMsg;
+}
+#endif
+
+#ifndef RIGHT_SIDE_COMPILE
+
+#ifdef FEATURE_PAL
+__attribute__((noinline))
+__attribute__((optnone))
+static void
+ProbeMemory(__in_ecount(cbBuffer) volatile PBYTE pbBuffer, DWORD cbBuffer, bool fWriteAccess)
+{
+ // Need an throw in this function to fool the C++ runtime into handling the
+ // possible h/w exception below.
+ if (pbBuffer == NULL)
+ {
+ throw PAL_SEHException();
+ }
+
+ // Simple one byte at a time probing
+ while (cbBuffer > 0)
+ {
+ volatile BYTE read = *pbBuffer;
+ if (fWriteAccess)
+ {
+ *pbBuffer = read;
+ }
+ ++pbBuffer;
+ --cbBuffer;
+ }
+}
+#endif // FEATURE_PAL
+
+// Check read and optionally write memory access to the specified range of bytes. Used to check
+// ReadProcessMemory and WriteProcessMemory requests.
+HRESULT DbgTransportSession::CheckBufferAccess(__in_ecount(cbBuffer) PBYTE pbBuffer, DWORD cbBuffer, bool fWriteAccess)
+{
+ // check for integer overflow
+ if ((pbBuffer + cbBuffer) < pbBuffer)
+ {
+ return HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW);
+ }
+
+ // VirtualQuery doesn't know much about memory allocated outside of PAL's VirtualAlloc
+ // that's why on Unix we can't rely on in to detect invalid memory reads
+#ifndef FEATURE_PAL
+ do
+ {
+ // Find the attributes of the largest set of pages with common attributes starting from our base address.
+ MEMORY_BASIC_INFORMATION sMemInfo;
+ VirtualQuery(pbBuffer, &sMemInfo, sizeof(sMemInfo));
+
+ DbgTransportLog(LC_Proxy, "CBA(%08X,%08X): State:%08X Protect:%08X BA:%08X RS:%08X",
+ pbBuffer, cbBuffer, sMemInfo.State, sMemInfo.Protect, sMemInfo.BaseAddress, sMemInfo.RegionSize);
+
+ // The memory must be committed (i.e. have physical pages or backing store).
+ if (sMemInfo.State != MEM_COMMIT)
+ return HRESULT_FROM_WIN32(ERROR_INVALID_ADDRESS);
+
+ // Check for compatible page protections. Lower byte of Protect has these (upper bytes have options we're
+ // not interested in, cache modes and the like.
+ DWORD dwProtect = sMemInfo.Protect & 0xff;
+
+ if (fWriteAccess &&
+ ((dwProtect & (PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY | PAGE_READWRITE | PAGE_WRITECOPY)) == 0))
+ return HRESULT_FROM_WIN32(ERROR_NOACCESS);
+ else if (!fWriteAccess &&
+ ((dwProtect & (PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY | PAGE_READONLY | PAGE_READWRITE | PAGE_WRITECOPY)) == 0))
+ return HRESULT_FROM_WIN32(ERROR_NOACCESS);
+
+ // If the requested range is bigger than the region we have queried,
+ // we need to continue on to check the next region.
+ if ((pbBuffer + cbBuffer) > ((PBYTE)sMemInfo.BaseAddress + sMemInfo.RegionSize))
+ {
+ PBYTE pbRegionEnd = reinterpret_cast<PBYTE>(sMemInfo.BaseAddress) + sMemInfo.RegionSize;
+ cbBuffer = (DWORD)((pbBuffer + cbBuffer) - pbRegionEnd);
+ pbBuffer = pbRegionEnd;
+ }
+ else
+ {
+ // We are done. Set cbBuffer to 0 to exit this loop.
+ cbBuffer = 0;
+ }
+ }
+ while (cbBuffer > 0);
+#else
+ try
+ {
+ // Need to explicit h/w exception holder so to catch them in ProbeMemory
+ CatchHardwareExceptionHolder __catchHardwareException;
+
+ ProbeMemory(pbBuffer, cbBuffer, fWriteAccess);
+ }
+ catch(...)
+ {
+ return HRESULT_FROM_WIN32(ERROR_INVALID_ADDRESS);
+ }
+#endif
+
+ // The specified region has passed all of our checks.
+ return S_OK;
+}
+
+#endif // !RIGHT_SIDE_COMPILE
+
+// Initialize all session state to correct starting values. Used during Init() and on the LS when we
+// gracefully close one session and prepare for another.
+void DbgTransportSession::InitSessionState()
+{
+ DBG_TRANSPORT_INC_STAT(Sessions);
+
+ m_dwMajorVersion = kCurrentMajorVersion;
+ m_dwMinorVersion = kCurrentMinorVersion;
+
+ memset(&m_sSessionID, 0, sizeof(m_sSessionID));
+
+ m_pSendQueueFirst = NULL;
+ m_pSendQueueLast = NULL;
+
+ m_dwNextMessageId = 1;
+ m_dwLastMessageIdSeen = 0;
+
+ m_eState = SS_Opening_NC;
+
+ m_cValidEventBuffers = 0;
+ m_idxEventBufferHead = 0;
+ m_idxEventBufferTail = 0;
+}
+
+// The entry point of the transport worker thread. This one's static, so we immediately dispatch to an
+// instance method version defined below for convenience in the implementation.
+DWORD WINAPI DbgTransportSession::TransportWorkerStatic(LPVOID pvContext)
+{
+ ((DbgTransportSession*)pvContext)->TransportWorker();
+
+ // Nobody looks at this result, the choice of 0 is arbitrary.
+ return 0;
+}
+
+// Macros used to simplify error and state transition handling within the transport worker loop. Errors are
+// classified as either transient or critical. Transient errors (typically those from network operations)
+// result in the connection being closed and rebuilt: we should eventually recover from them. Critical errors
+// are those that cause a transition to the SS_Closed state, which the session never recovers from. These are
+// normally due to protocol errors where we want to shut the transport down in case they are of malicious
+// origin.
+#define HANDLE_TRANSIENT_ERROR() do { \
+ HandleNetworkError(false); \
+ m_pipe.Disconnect(); \
+ goto ResetConnection; \
+} while (false)
+
+#define HANDLE_CRITICAL_ERROR() do { \
+ m_eState = SS_Closed; \
+ goto Shutdown; \
+} while (false)
+
+#ifdef _PREFAST_
+#pragma warning(push)
+#pragma warning(disable:21000) // Suppress PREFast warning about overly large function
+#endif
+void DbgTransportSession::TransportWorker()
+{
+ _ASSERTE(m_eState == SS_Opening_NC);
+
+ // Loop until shutdown. Each loop iteration involves forming a connection (or waiting for one to form)
+ // followed by processing incoming messages on that connection until there's a failure (either here of
+ // from a send on another thread) or the session shuts down. The connection is then closed and discarded
+ // and we either go round the loop again (to recover our previous session state) or exit the method as
+ // part of shutdown.
+ ResetConnection:
+ while (m_eState != SS_Closed)
+ {
+ _ASSERTE(m_eState == SS_Opening_NC || m_eState == SS_Resync_NC || m_eState == SS_Closed);
+
+ DbgTransportLog(LC_Proxy, "Forming new connection");
+
+#ifdef RIGHT_SIDE_COMPILE
+ // The session is definitely not open at this point.
+ ResetEvent(m_hSessionOpenEvent);
+
+ // On the right side we initiate the connection via Connect(). A failure is dealt with by waiting a
+ // little while and retrying (the LS may take a little while to set up). If there's nobody listening
+ // the debugger will eventually get bored waiting for us and shutdown the session, which will
+ // terminate this loop.
+ ConnStatus eStatus;
+ if (DBG_TRANSPORT_SHOULD_INJECT_FAULT(Connect))
+ eStatus = SCS_NetworkFailure;
+ else
+ {
+ if (m_pipe.Connect(m_pid))
+ {
+ eStatus = SCS_Success;
+ }
+ else
+ {
+ //not really sure that this is the real failure
+ //TODO: we probably need to analyse GetErrorCode() here
+ eStatus = SCS_NoListener;
+ }
+ }
+
+ if (eStatus != SCS_Success)
+ {
+ DbgTransportLog(LC_Proxy, "AllocateConnection() failed with %u\n", eStatus);
+ DBG_TRANSPORT_INC_STAT(MiscErrors);
+ _ASSERTE(m_pipe.GetState() != TwoWayPipe::ClientConnected);
+ Sleep(1000);
+ continue;
+ }
+#else // RIGHT_SIDE_COMPILE
+ ConnStatus eStatus;
+ if (DBG_TRANSPORT_SHOULD_INJECT_FAULT(Accept))
+ eStatus = SCS_NetworkFailure;
+ else
+ {
+ DWORD pid = GetCurrentProcessId();
+ if ((m_pipe.GetState() == TwoWayPipe::Created || m_pipe.CreateServer(pid)) &&
+ m_pipe.WaitForConnection())
+ {
+ eStatus = SCS_Success;
+ }
+ else
+ {
+ //not really sure that this is the real failure
+ //TODO: we probably need to analyse GetErrorCode() here
+ eStatus = SCS_NoListener;
+ }
+ }
+
+ if (eStatus != SCS_Success)
+ {
+ DbgTransportLog(LC_Proxy, "Accept() failed with %u\n", eStatus);
+ DBG_TRANSPORT_INC_STAT(MiscErrors);
+ _ASSERTE(m_pipe.GetState() != TwoWayPipe::ServerConnected);
+ Sleep(1000);
+ continue;
+ }
+
+ // Note that when resynching a session we may let in a connection from a different debugger. That's
+ // OK, we'll reject his SessionRequest message in due course and drop the connection.
+#endif // RIGHT_SIDE_COMPILE
+
+ DBG_TRANSPORT_INC_STAT(Connections);
+
+ // We now have a connection. Transition to the next state (either SS_Opening or SS_Resync). The
+ // primary purpose of this state transition is to let other threads know that this thread might now be
+ // blocked on a Receive() on the newly formed connection (important if they want to transition the state
+ // to SS_Closed).
+ {
+ TransportLockHolder sLockHolder(&m_sStateLock);
+
+ if (m_eState == SS_Closed)
+ break;
+ else if (m_eState == SS_Opening_NC)
+ m_eState = SS_Opening;
+ else if (m_eState == SS_Resync_NC)
+ m_eState = SS_Resync;
+ else
+ _ASSERTE(!"Bad session state");
+ } // Leave m_sStateLock
+
+
+ // Now we have a connection in place. Start reading messages and processing them. Which messages are
+ // valid depends on whether we're in SS_Opening or SS_Resync (the state can change at any time
+ // asynchronously to us to either SS_Closed or SS_Resync_NC but we're guaranteed the connection stays
+ // valid (though not necessarily useful) until we notice this state change and Destroy() it ourself).
+ // We check the state after each network operation.
+
+ // During the SS_Opening and SS_Resync states we're guarantee to be the only thread posting sends, so
+ // we can break the rules and use SendBlock without acquiring the state lock. (We use SendBlock a lot
+ // during these phases because we're using simple Session* messages which don't require the extra
+ // processing SendMessage gives us such as encryption or placement on the send queue).
+
+ MessageHeader sSendHeader;
+ MessageHeader sReceiveHeader;
+
+ memset(&sSendHeader, 0, sizeof(MessageHeader));
+
+ if (m_eState == SS_Opening)
+ {
+#ifdef RIGHT_SIDE_COMPILE
+ // The right side actually starts things off by sending a SessionRequest message.
+
+ SessionRequestData sDataBlock;
+
+ sSendHeader.m_eType = MT_SessionRequest;
+ sSendHeader.TypeSpecificData.VersionInfo.m_dwMajorVersion = kCurrentMajorVersion;
+ sSendHeader.TypeSpecificData.VersionInfo.m_dwMinorVersion = kCurrentMinorVersion;
+
+ // The start of the data block always contains a session ID. This is a GUID randomly generated at
+ // Init() time.
+ sSendHeader.m_cbDataBlock = sizeof(SessionRequestData);
+ memcpy(&sDataBlock.m_sSessionID, &m_sSessionID, sizeof(m_sSessionID));
+
+ // Send the header block followed by the data block. For failures during SS_Opening we just close
+ // the connection and retry from the beginning (the failing send will already have caused a
+ // transition into SS_Opening_NC. No need to use the same resend logic that SS_Resync does, since
+ // no user messages have been sent and we can simply recreate the SessionRequest.
+ DbgTransportLog(LC_Session, "Sending 'SessionRequest'");
+ DBG_TRANSPORT_INC_STAT(SentSessionRequest);
+ if (!SendBlock((PBYTE)&sSendHeader, sizeof(MessageHeader)) ||
+ !SendBlock((PBYTE)&sDataBlock, sSendHeader.m_cbDataBlock))
+ HANDLE_TRANSIENT_ERROR();
+
+ // Wait for a reply.
+ if (!ReceiveBlock((PBYTE)&sReceiveHeader, sizeof(MessageHeader)))
+ HANDLE_TRANSIENT_ERROR();
+
+ DbgTransportLogMessageReceived(&sReceiveHeader);
+
+ // This should be either a SessionAccept or SessionReject. Any other message type will be treated
+ // as a SessionReject (i.e. an unrecoverable failure that will leave the session in SS_Closed
+ // permanently).
+ if (sReceiveHeader.m_eType != MT_SessionAccept)
+ {
+ _ASSERTE(!"Unexpected response to SessionRequest");
+ HANDLE_CRITICAL_ERROR();
+ }
+
+ // Validate the SessionAccept.
+ if (sReceiveHeader.TypeSpecificData.VersionInfo.m_dwMajorVersion != kCurrentMajorVersion ||
+ sReceiveHeader.m_cbDataBlock != (DWORD)0)
+ {
+ _ASSERTE(!"Malformed SessionAccept received");
+ HANDLE_CRITICAL_ERROR();
+ }
+
+ // The LS might have negotiated the minor protocol version down.
+ m_dwMinorVersion = sReceiveHeader.TypeSpecificData.VersionInfo.m_dwMinorVersion;
+#else // RIGHT_SIDE_COMPILE
+
+ // On the left side we wait for a SessionRequest first.
+ if (!ReceiveBlock((PBYTE)&sReceiveHeader, sizeof(MessageHeader)))
+ HANDLE_TRANSIENT_ERROR();
+
+ DbgTransportLogMessageReceived(&sReceiveHeader);
+
+ if (sReceiveHeader.m_eType != MT_SessionRequest)
+ {
+ _ASSERTE(!"Unexpected message type");
+ HANDLE_CRITICAL_ERROR();
+ }
+
+ // Validate the SessionRequest.
+ if (sReceiveHeader.TypeSpecificData.VersionInfo.m_dwMajorVersion != kCurrentMajorVersion ||
+ sReceiveHeader.m_cbDataBlock != (DWORD)sizeof(SessionRequestData))
+ {
+ // Send a SessionReject message with the reason for rejection.
+ sSendHeader.m_eType = MT_SessionReject;
+ sSendHeader.TypeSpecificData.SessionReject.m_eReason = RR_IncompatibleVersion;
+ sSendHeader.TypeSpecificData.SessionReject.m_dwMajorVersion = kCurrentMajorVersion;
+ sSendHeader.TypeSpecificData.SessionReject.m_dwMinorVersion = kCurrentMinorVersion;
+
+ DbgTransportLog(LC_Session, "Sending 'SessionReject(RR_IncompatibleVersion)'");
+ DBG_TRANSPORT_INC_STAT(SentSessionReject);
+
+ SendBlock((PBYTE)&sSendHeader, sizeof(MessageHeader));
+
+ // Go back into the opening state rather than closed because we want to give the RS a chance
+ // to correct the problem and try again.
+ HANDLE_TRANSIENT_ERROR();
+ }
+
+ // Read the data block.
+ SessionRequestData sDataBlock;
+ if (!ReceiveBlock((PBYTE)&sDataBlock, sizeof(SessionRequestData)))
+ HANDLE_TRANSIENT_ERROR();
+
+ // If the RS only understands a lower minor protocol version than us then remember that fact.
+ if (sReceiveHeader.TypeSpecificData.VersionInfo.m_dwMinorVersion < m_dwMinorVersion)
+ m_dwMinorVersion = sReceiveHeader.TypeSpecificData.VersionInfo.m_dwMinorVersion;
+
+ // Send a SessionAccept message back.
+ sSendHeader.m_eType = MT_SessionAccept;
+ sSendHeader.m_cbDataBlock = 0;
+ sSendHeader.TypeSpecificData.VersionInfo.m_dwMajorVersion = kCurrentMajorVersion;
+ sSendHeader.TypeSpecificData.VersionInfo.m_dwMinorVersion = m_dwMinorVersion;
+
+ DbgTransportLog(LC_Session, "Sending 'SessionAccept'");
+ DBG_TRANSPORT_INC_STAT(SentSessionAccept);
+
+ if (!SendBlock((PBYTE)&sSendHeader, sizeof(MessageHeader)))
+ HANDLE_TRANSIENT_ERROR();
+#endif // RIGHT_SIDE_COMPILE
+
+ // Everything pans out, we have a session formed. But we must send messages that queued up
+ // before transitioning the state to open (otherwise a racing send could sneak in ahead).
+
+ // Must access the send queue under the state lock.
+ {
+ TransportLockHolder sLockHolder(&m_sStateLock);
+ Message *pMsg = m_pSendQueueFirst;
+ while (pMsg)
+ {
+ if (SendBlock((PBYTE)&pMsg->m_sHeader, sizeof(MessageHeader)) && pMsg->m_pbDataBlock)
+ SendBlock(pMsg->m_pbDataBlock, pMsg->m_cbDataBlock);
+ pMsg = pMsg->m_pNext;
+ }
+
+ // Check none of the sends failed.
+ if (m_eState != SS_Opening)
+ {
+ m_pipe.Disconnect();
+ continue;
+ }
+ } // Leave m_sStateLock
+
+ // Finally we can transition to SS_Open.
+ {
+ TransportLockHolder sLockHolder(&m_sStateLock);
+ if (m_eState == SS_Closed)
+ break;
+ else if (m_eState == SS_Opening)
+ m_eState = SS_Open;
+ else
+ _ASSERTE(!"Bad session state");
+ } // Leave m_sStateLock
+
+#ifdef RIGHT_SIDE_COMPILE
+ // Signal any WaitForSessionToOpen() waiters that we've gotten to SS_Open.
+ SetEvent(m_hSessionOpenEvent);
+#endif // RIGHT_SIDE_COMPILE
+
+ // We're ready to begin receiving normal incoming messages now.
+ }
+ else
+ {
+ // The SS_Resync case. Send a message indicating the last message we saw from the other side and
+ // wait for a similar message to arrive for us.
+
+ sSendHeader.m_eType = MT_SessionResync;
+ sSendHeader.m_dwLastSeenId = m_dwLastMessageIdSeen;
+
+ DbgTransportLog(LC_Session, "Sending 'SessionResync'");
+ DBG_TRANSPORT_INC_STAT(SentSessionResync);
+
+ if (!SendBlock((PBYTE)&sSendHeader, sizeof(MessageHeader)))
+ HANDLE_TRANSIENT_ERROR();
+
+ if (!ReceiveBlock((PBYTE)&sReceiveHeader, sizeof(MessageHeader)))
+ HANDLE_TRANSIENT_ERROR();
+
+#ifndef RIGHT_SIDE_COMPILE
+ if (sReceiveHeader.m_eType == MT_SessionRequest)
+ {
+ DbgTransportLogMessageReceived(&sReceiveHeader);
+
+ // This SessionRequest could be from a different debugger. In this case we should send a
+ // SessionReject to let them know we're not available and close the connection so we can
+ // re-listen for the original debugger.
+ // Or it could be the original debugger re-sending the SessionRequest because the connection
+ // died as we sent the SessionAccept.
+ // We distinguish the two cases by looking at the session ID in the request.
+ bool fRequestResend = false;
+
+ // Only read the data block if it matches our expectations of its size.
+ if (sReceiveHeader.m_cbDataBlock == (DWORD)sizeof(SessionRequestData))
+ {
+ SessionRequestData sDataBlock;
+ if (!ReceiveBlock((PBYTE)&sDataBlock, sizeof(SessionRequestData)))
+ HANDLE_TRANSIENT_ERROR();
+
+ // Check the session ID for a match.
+ if (memcmp(&sDataBlock.m_sSessionID, &m_sSessionID, sizeof(m_sSessionID)) == 0)
+ // OK, everything checks out and this is a valid re-send of a SessionRequest.
+ fRequestResend = true;
+ }
+
+ if (fRequestResend)
+ {
+ // The RS never got our SessionAccept. We must resend it.
+ memset(&sSendHeader, 0, sizeof(MessageHeader));
+ sSendHeader.m_eType = MT_SessionAccept;
+ sSendHeader.m_cbDataBlock = 0;
+ sSendHeader.TypeSpecificData.VersionInfo.m_dwMajorVersion = kCurrentMajorVersion;
+ sSendHeader.TypeSpecificData.VersionInfo.m_dwMinorVersion = m_dwMinorVersion;
+
+ DbgTransportLog(LC_Session, "Sending 'SessionAccept'");
+ DBG_TRANSPORT_INC_STAT(SentSessionAccept);
+
+ if (!SendBlock((PBYTE)&sSendHeader, sizeof(MessageHeader)))
+ HANDLE_TRANSIENT_ERROR();
+
+ // Now simply reset the connection. The RS should get the SessionAccept and transition to
+ // SS_Open then detect the connection loss and transition to SS_Resync_NC, which will
+ // finally sync the two sides.
+ HANDLE_TRANSIENT_ERROR();
+ }
+ else
+ {
+ // This is the case where we must reject the request.
+ memset(&sSendHeader, 0, sizeof(MessageHeader));
+ sSendHeader.m_eType = MT_SessionReject;
+ sSendHeader.TypeSpecificData.SessionReject.m_eReason = RR_AlreadyAttached;
+ sSendHeader.TypeSpecificData.SessionReject.m_dwMajorVersion = kCurrentMajorVersion;
+ sSendHeader.TypeSpecificData.SessionReject.m_dwMinorVersion = kCurrentMinorVersion;
+
+ DbgTransportLog(LC_Session, "Sending 'SessionReject(RR_AlreadyAttached)'");
+ DBG_TRANSPORT_INC_STAT(SentSessionReject);
+
+ SendBlock((PBYTE)&sSendHeader, sizeof(MessageHeader));
+
+ HANDLE_TRANSIENT_ERROR();
+ }
+ }
+#endif // !RIGHT_SIDE_COMPILE
+
+ DbgTransportLogMessageReceived(&sReceiveHeader);
+
+ // Handle all other invalid message types by shutting down (it may be an attempt to subvert the
+ // protocol).
+ if (sReceiveHeader.m_eType != MT_SessionResync)
+ {
+ _ASSERTE(!"Unexpected message type during SS_Resync");
+ HANDLE_CRITICAL_ERROR();
+ }
+
+ // We've got our resync message. Go through the send queue and resend any messages that haven't
+ // been processed by the other side. Those that have been processed can be discarded (unless
+ // they're waiting for another form of higher level acknowledgement, such as a reply message).
+
+ // Discard unneeded messages first.
+ FlushSendQueue(sReceiveHeader.m_dwLastSeenId);
+
+ // Must access the send queue under the state lock.
+ {
+ TransportLockHolder sLockHolder(&m_sStateLock);
+
+ Message *pMsg = m_pSendQueueFirst;
+ while (pMsg)
+ {
+ if (pMsg->m_sHeader.m_dwId > sReceiveHeader.m_dwLastSeenId)
+ {
+ // The other side never saw this message, re-send it.
+ DBG_TRANSPORT_INC_STAT(Resends);
+ if (SendBlock((PBYTE)&pMsg->m_sHeader, sizeof(MessageHeader)) && pMsg->m_pbDataBlock)
+ SendBlock(pMsg->m_pbDataBlock, pMsg->m_cbDataBlock);
+ }
+ pMsg = pMsg->m_pNext;
+ }
+
+ // Finished processing queued sends. We can transition to the SS_Open state now as long as there
+ // wasn't a send failure or an asynchronous Shutdown().
+ if (m_eState == SS_Resync)
+ m_eState = SS_Open;
+ else if (m_eState == SS_Closed)
+ break;
+ else if (m_eState == SS_Resync_NC)
+ {
+ m_pipe.Disconnect();
+ continue;
+ }
+ else
+ _ASSERTE(!"Bad session state");
+ } // Leave m_sStateLock
+ }
+
+ // Once we get here we should be in SS_Open (can't assert this because Shutdown() can throw the state
+ // into SS_Closed and we've just released SendMessage() calls on other threads that can transition us
+ // into SS_Resync).
+
+ // We now loop receiving messages and processing them until the state changes.
+ while (m_eState == SS_Open)
+ {
+ // temporary data block used in DCB messages
+ DebuggerIPCControlBlockTransport dcbt;
+
+ // temporary virtual stack unwind context buffer
+ CONTEXT frameContext;
+
+ // Read a message header block.
+ if (!ReceiveBlock((PBYTE)&sReceiveHeader, sizeof(MessageHeader)))
+ HANDLE_TRANSIENT_ERROR();
+
+ // Since we care about security here, perform some additional validation checks that make it
+ // harder for a malicious sender to attack with random message data.
+ if (sReceiveHeader.m_eType > MT_GetAppDomainCB ||
+ (sReceiveHeader.m_dwId <= m_dwLastMessageIdSeen &&
+ sReceiveHeader.m_dwId != (DWORD)0) ||
+ (sReceiveHeader.m_dwReplyId >= m_dwNextMessageId &&
+ sReceiveHeader.m_dwReplyId != (DWORD)0) ||
+ (sReceiveHeader.m_dwLastSeenId >= m_dwNextMessageId &&
+ sReceiveHeader.m_dwLastSeenId != (DWORD)0))
+ {
+ _ASSERTE(!"Incoming message header looks bogus");
+ HANDLE_CRITICAL_ERROR();
+ }
+
+ DbgTransportLogMessageReceived(&sReceiveHeader);
+
+ // Flush any entries in our send queue for messages that the other side has just confirmed
+ // processed with this message.
+ FlushSendQueue(sReceiveHeader.m_dwLastSeenId);
+
+#ifndef RIGHT_SIDE_COMPILE
+ // State variables to track whether this message needs a reply and if so whether it consists of a
+ // header only or a header and an optional data block.
+ bool fReplyRequired = false;
+ PBYTE pbOptReplyData = NULL;
+ DWORD cbOptReplyData = 0;
+ HRESULT hr = E_FAIL;
+
+ // if you change the lifetime of resultBuffer, make sure you change pbOptReplyData to match.
+ // In some cases pbOptReplyData will point at the memory held alive in resultBuffer
+ WriteBuffer resultBuffer;
+ ReadBuffer receiveBuffer;
+
+#endif // RIGHT_SIDE_COMPILE
+
+ // Dispatch based on message type.
+ //
+ // **** IMPORTANT NOTE ****
+ //
+ // We must be very careful wrt to updating m_dwLastMessageIdSeen here. If we update it too soon
+ // (we haven't finished receiving the entire message, for instance) then the other side won't
+ // re-send the message on failure and we'll lose it. If we update it too late we might have
+ // reported the message to our caller or produced any other side-effect we can't take back such as
+ // sending a reply and then hit an error and reset the connection before we had a chance to record
+ // the message as seen. In this case the other side will re-send the original message and we'll
+ // repeat our actions, which is also very bad.
+ //
+ // So we must be very disciplined here.
+ //
+ // First we must read the message in its entirety (i.e. receive the data block if there is one)
+ // without causing any side-effects. This ensures that any failure at this point will be handled
+ // correctly (by the other side re-sending us the same message).
+ //
+ // Then we process the message. At this point we are committed. The processing must always
+ // succeed, or have no side-effect (that we care about) or we must have an additional scheme to
+ // handle resynchronization in the event of failure. This ensures that we don't have the tricky
+ // situation where we can't cope with a re-send of the message (because we've started processing
+ // it) but can't report a failure to the other side (because we don't know how).
+ //
+ // Finally we must ensure that there is no error path between the completion of processing and
+ // updating the m_dwLastMessageIdSeen field. This ensures we don't accidently get re-sent a
+ // message we've processed completely (it's really just a sub-case of the rule above, but it's
+ // worth pointing out explicitly since it can be a subtle problem).
+ //
+ // Request messages (such as MT_GetDCB) are an interesting case in point here. They all require a
+ // reply and we can fail on the reply because we run out of system resources. This breaks the
+ // second rule above (we fail halfway through processing). We should really preallocate enough
+ // resources to send the reply before we begin processing of it but for now we don't since (a) the
+ // SendMessage system isn't currently set up to make this easy and (b) we happen to know that all
+ // the request types are effectively idempotent (even ReadMemory and WriteMemory since the RS is
+ // holding the LS still while it does these). So instead we must carefully distinguish the case
+ // where SendMessage fails without possibility of message transmission (e.g. out of memory) and
+ // those where it fails for a transient network failure (where it will re-send the reply on
+ // resync). This is easy enough to do since SendMessage returns a failure hresult for the first
+ // case and success (and a state transition) for the second. In the first case we don't update
+ // m_dwLastMessageIdSeen and instead wait for the request to be resent. In the second we make the
+ // update because we know the reply will get through eventually.
+ //
+ // **** IMPORTANT NOTE ****
+ switch (sReceiveHeader.m_eType)
+ {
+ case MT_SessionRequest:
+ case MT_SessionAccept:
+ case MT_SessionReject:
+ case MT_SessionResync:
+ // Illegal messages at this time, fail the transport entirely.
+ m_eState = SS_Closed;
+ break;
+
+ case MT_SessionClose:
+ // Close is legal on the LS and transitions to the SS_Opening_NC state. It's illegal on the RS
+ // and should shutdown the transport.
+#ifdef RIGHT_SIDE_COMPILE
+ m_eState = SS_Closed;
+ break;
+#else // RIGHT_SIDE_COMPILE
+ // We need to do some state cleanup here, since when we reform a connection (if ever, it will
+ // be with a new session).
+ {
+ TransportLockHolder sLockHolder(&m_sStateLock);
+
+ // Check we're still in a good state before a clean restart.
+ if (m_eState != SS_Open)
+ {
+ m_eState = SS_Closed;
+ break;
+ }
+
+ m_pipe.Disconnect();
+
+ // We could add code to drain the send queue here (like we have for SS_Closed at the end of
+ // this method) but I'm pretty sure we can only get a graceful session close with no
+ // outstanding sends. So just assert the queue is empty instead. If the assert fires and it's
+ // not due to an issue we can add the logic here).
+ _ASSERTE(m_pSendQueueFirst == NULL);
+ _ASSERTE(m_pSendQueueLast == NULL);
+
+ // This will reset all session specific state and transition us to SS_Opening_NC.
+ InitSessionState();
+ } // Leave m_sStateLock
+
+ goto ResetConnection;
+#endif // RIGHT_SIDE_COMPILE
+
+ case MT_Event:
+ {
+ // Incoming debugger event.
+
+ if (sReceiveHeader.m_cbDataBlock > CorDBIPC_BUFFER_SIZE)
+ {
+ _ASSERTE(!"Oversized Event");
+ HANDLE_CRITICAL_ERROR();
+ }
+
+ // See if our array of buffered events has filled up. If so we'll need to re-allocate the
+ // array to expand it.
+ if (m_cValidEventBuffers == m_cEventBuffers)
+ {
+ // Allocate a larger array.
+ DWORD cNewEntries = m_cEventBuffers + 4;
+ DbgEventBufferEntry * pNewBuffers = (DbgEventBufferEntry *)new (nothrow) BYTE[cNewEntries * sizeof(DbgEventBufferEntry)];
+ if (pNewBuffers == NULL)
+ HANDLE_TRANSIENT_ERROR();
+
+ // We must take the lock to swap the new array in. Although this thread is the only one
+ // that can expand the array, a client thread may be in GetNextEvent() reading from the
+ // old version.
+ {
+ TransportLockHolder sLockHolder(&m_sStateLock);
+
+ // When we copy old array contents over we place the head of the list at the start of
+ // the new array for simplicity. If the head happened to be at the start of the old
+ // array anyway, this is even simpler.
+ if (m_idxEventBufferHead == 0)
+ memcpy(pNewBuffers, m_pEventBuffers, m_cEventBuffers * sizeof(DbgEventBufferEntry));
+ else
+ {
+ // Otherwise we need to perform the copy in two segments: first we copy the head
+ // of the list (starts at a non-zero index and runs to the end of the old array)
+ // into the start of the new array.
+ DWORD cHeadEntries = m_cEventBuffers - m_idxEventBufferHead;
+
+ memcpy(pNewBuffers,
+ &m_pEventBuffers[m_idxEventBufferHead],
+ cHeadEntries * sizeof(DbgEventBufferEntry));
+
+ // Then we copy the remaining portion from the beginning of the old array upto to
+ // the index of the head.
+ memcpy(&pNewBuffers[cHeadEntries],
+ m_pEventBuffers,
+ m_idxEventBufferHead * sizeof(DbgEventBufferEntry));
+ }
+
+ // Delete the old array.
+ delete [] m_pEventBuffers;
+
+ // Swap the new array in.
+ m_pEventBuffers = pNewBuffers;
+ m_cEventBuffers = cNewEntries;
+
+ // The new array now has the head at index zero and the tail at the start of the
+ // new entries.
+ m_idxEventBufferHead = 0;
+ m_idxEventBufferTail = m_cValidEventBuffers;
+ }
+ }
+
+ // We have at least one free buffer at this point (no threading issues, the only thread that
+ // can add entries is this one).
+
+ // Receive event data into the tail buffer (we want to do this without holding the state lock
+ // and can do so safely since this is the only thread that can receive data and clients can do
+ // nothing that impacts the location of the tail of the buffer list).
+ if (!ReceiveBlock((PBYTE)&m_pEventBuffers[m_idxEventBufferTail].m_event, sReceiveHeader.m_cbDataBlock))
+ HANDLE_TRANSIENT_ERROR();
+
+ {
+ m_pEventBuffers[m_idxEventBufferTail].m_type = sReceiveHeader.TypeSpecificData.Event.m_eIPCEventType;
+
+ // We must take the lock to update the count of valid entries though, since clients can
+ // touch this field as well.
+ TransportLockHolder sLockHolder(&m_sStateLock);
+
+ m_cValidEventBuffers++;
+ DWORD idxCurrentEvent = m_idxEventBufferTail;
+
+ // Update tail of the list (strictly speaking this needn't be done under the lock, but the
+ // code in GetNextEvent() does read it for an assert.
+ m_idxEventBufferTail = (m_idxEventBufferTail + 1) % m_cEventBuffers;
+
+ // If we just added the first valid event then wake up the client so they can call
+ // GetNextEvent().
+ if (m_cValidEventBuffers == 1)
+ SetEvent(m_rghEventReadyEvent[m_pEventBuffers[idxCurrentEvent].m_type]);
+ }
+ }
+ break;
+
+ case MT_ReadMemory:
+#ifdef RIGHT_SIDE_COMPILE
+ if (!ProcessReply(&sReceiveHeader))
+ HANDLE_TRANSIENT_ERROR();
+#else // RIGHT_SIDE_COMPILE
+ // The RS wants to read our memory. First check the range requested is both committed and
+ // readable. If that succeeds we simply set the optional reply block to match the request region
+ // (i.e. we send the memory directly).
+ fReplyRequired = true;
+
+ hr = CheckBufferAccess(sReceiveHeader.TypeSpecificData.MemoryAccess.m_pbLeftSideBuffer,
+ sReceiveHeader.TypeSpecificData.MemoryAccess.m_cbLeftSideBuffer,
+ false);
+ sReceiveHeader.TypeSpecificData.MemoryAccess.m_hrResult = hr;
+ if (SUCCEEDED(hr))
+ {
+ pbOptReplyData = sReceiveHeader.TypeSpecificData.MemoryAccess.m_pbLeftSideBuffer;
+ cbOptReplyData = sReceiveHeader.TypeSpecificData.MemoryAccess.m_cbLeftSideBuffer;
+ }
+#endif // RIGHT_SIDE_COMPILE
+ break;
+
+ case MT_WriteMemory:
+#ifdef RIGHT_SIDE_COMPILE
+ if (!ProcessReply(&sReceiveHeader))
+ HANDLE_TRANSIENT_ERROR();
+#else // RIGHT_SIDE_COMPILE
+ // The RS wants to write our memory.
+ if (sReceiveHeader.m_cbDataBlock != sReceiveHeader.TypeSpecificData.MemoryAccess.m_cbLeftSideBuffer)
+ {
+ _ASSERTE(!"Inconsistent WriteMemory request");
+ HANDLE_CRITICAL_ERROR();
+ }
+
+ fReplyRequired = true;
+
+ // Check the range requested is both committed and writeable. If that succeeds we simply read
+ // the next incoming block into the destination buffer.
+ hr = CheckBufferAccess(sReceiveHeader.TypeSpecificData.MemoryAccess.m_pbLeftSideBuffer,
+ sReceiveHeader.TypeSpecificData.MemoryAccess.m_cbLeftSideBuffer,
+ true);
+ if (SUCCEEDED(hr))
+ {
+ if (!ReceiveBlock(sReceiveHeader.TypeSpecificData.MemoryAccess.m_pbLeftSideBuffer,
+ sReceiveHeader.TypeSpecificData.MemoryAccess.m_cbLeftSideBuffer))
+ HANDLE_TRANSIENT_ERROR();
+ }
+ else
+ {
+ sReceiveHeader.TypeSpecificData.MemoryAccess.m_hrResult = hr;
+
+ // We might be failing the write attempt but we still need to read the update data to
+ // drain it from the connection or we'll become unsynchronized (i.e. we'll treat the start
+ // of the write data as the next message header). So read and discard the data into a
+ // dummy buffer.
+ BYTE rgDummy[256];
+ DWORD cbBytesToRead = sReceiveHeader.TypeSpecificData.MemoryAccess.m_cbLeftSideBuffer;
+ while (cbBytesToRead)
+ {
+ DWORD cbTransfer = min(cbBytesToRead, sizeof(rgDummy));
+ if (!ReceiveBlock(rgDummy, cbTransfer))
+ HANDLE_TRANSIENT_ERROR();
+ cbBytesToRead -= cbTransfer;
+ }
+ }
+#endif // RIGHT_SIDE_COMPILE
+ break;
+
+ case MT_VirtualUnwind:
+#ifdef RIGHT_SIDE_COMPILE
+ if (!ProcessReply(&sReceiveHeader))
+ HANDLE_TRANSIENT_ERROR();
+#else // RIGHT_SIDE_COMPILE
+ if (sReceiveHeader.m_cbDataBlock != (DWORD)sizeof(frameContext))
+ {
+ _ASSERTE(!"Inconsistent VirtualUnwind request");
+ HANDLE_CRITICAL_ERROR();
+ }
+
+ if (!ReceiveBlock((PBYTE)&frameContext, sizeof(frameContext)))
+ {
+ HANDLE_TRANSIENT_ERROR();
+ }
+
+ if (!PAL_VirtualUnwind(&frameContext, NULL))
+ {
+ HANDLE_TRANSIENT_ERROR();
+ }
+
+ fReplyRequired = true;
+ pbOptReplyData = (PBYTE)&frameContext;
+ cbOptReplyData = sizeof(frameContext);
+#endif // RIGHT_SIDE_COMPILE
+ break;
+
+ case MT_GetDCB:
+#ifdef RIGHT_SIDE_COMPILE
+ if (!ProcessReply(&sReceiveHeader))
+ HANDLE_TRANSIENT_ERROR();
+#else // RIGHT_SIDE_COMPILE
+ fReplyRequired = true;
+ MarshalDCBToDCBTransport(m_pDCB, &dcbt);
+ pbOptReplyData = (PBYTE)&dcbt;
+ cbOptReplyData = sizeof(DebuggerIPCControlBlockTransport);
+#endif // RIGHT_SIDE_COMPILE
+ break;
+
+ case MT_SetDCB:
+#ifdef RIGHT_SIDE_COMPILE
+ if (!ProcessReply(&sReceiveHeader))
+ HANDLE_TRANSIENT_ERROR();
+#else // RIGHT_SIDE_COMPILE
+ if (sReceiveHeader.m_cbDataBlock != (DWORD)sizeof(DebuggerIPCControlBlockTransport))
+ {
+ _ASSERTE(!"Inconsistent SetDCB request");
+ HANDLE_CRITICAL_ERROR();
+ }
+
+ fReplyRequired = true;
+
+ if (!ReceiveBlock((PBYTE)&dcbt, sizeof(DebuggerIPCControlBlockTransport)))
+ HANDLE_TRANSIENT_ERROR();
+
+ MarshalDCBTransportToDCB(&dcbt, m_pDCB);
+#endif // RIGHT_SIDE_COMPILE
+ break;
+
+ case MT_GetAppDomainCB:
+#ifdef RIGHT_SIDE_COMPILE
+ if (!ProcessReply(&sReceiveHeader))
+ HANDLE_TRANSIENT_ERROR();
+#else // RIGHT_SIDE_COMPILE
+ fReplyRequired = true;
+ pbOptReplyData = (PBYTE)m_pADB;
+ cbOptReplyData = sizeof(AppDomainEnumerationIPCBlock);
+#endif // RIGHT_SIDE_COMPILE
+ break;
+
+ default:
+ _ASSERTE(!"Unknown message type");
+ HANDLE_CRITICAL_ERROR();
+ }
+
+#ifndef RIGHT_SIDE_COMPILE
+ // On the left side we may need to send a reply back.
+ if (fReplyRequired)
+ {
+ Message sReply;
+ sReply.Init(sReceiveHeader.m_eType, pbOptReplyData, cbOptReplyData);
+ sReply.m_sHeader.m_dwReplyId = sReceiveHeader.m_dwId;
+ sReply.m_sHeader.TypeSpecificData = sReceiveHeader.TypeSpecificData;
+
+#ifdef _DEBUG
+ DbgTransportLog(LC_Requests, "Sending '%s' reply", MessageName(sReceiveHeader.m_eType));
+#endif // _DEBUG
+
+ // We must be careful with the failure mode of SendMessage here to avoid the same request
+ // being processed too many or too few times. See the comment above starting with 'IMPORTANT
+ // NOTE' for more details. The upshot is that on SendMessage hresult failures (which indicate
+ // the message will never be sent), we don't update m_dwLastMessageIdSeen and simply wait for
+ // the request to be made again. When we get success, however, we must be careful to ensure
+ // that m_dwLastMessageIdSeen gets updated even if a network error is reported. Otherwise on
+ // the resync we'll both reprocess the request and re-send the original reply which is very
+ // very bad.
+ hr = SendMessage(&sReply, false);
+
+ if (FAILED(hr))
+ HANDLE_TRANSIENT_ERROR(); // Message will never be sent, other side will retry
+
+ // SendMessage doesn't report network errors (it simply queues the send and changes the
+ // session state). So check for a network error here specifically so we can get started on the
+ // resync. We must update m_dwLastMessageIdSeen first though, or the other side will retry the
+ // request.
+ if (m_eState != SS_Open)
+ {
+ _ASSERTE(sReceiveHeader.m_dwId > m_dwLastMessageIdSeen);
+ m_dwLastMessageIdSeen = sReceiveHeader.m_dwId;
+ HANDLE_TRANSIENT_ERROR();
+ }
+ }
+#endif // !RIGHT_SIDE_COMPILE
+
+ if (sReceiveHeader.m_dwId != (DWORD)0)
+ {
+ // We've now completed processing on the incoming message. Remember we've processed up to this
+ // message ID so that on a resync the other side doesn't send it to us again.
+ _ASSERTE(sReceiveHeader.m_dwId > m_dwLastMessageIdSeen);
+ m_dwLastMessageIdSeen = sReceiveHeader.m_dwId;
+ }
+ }
+ }
+
+ Shutdown:
+
+ _ASSERTE(m_eState == SS_Closed);
+
+#ifdef RIGHT_SIDE_COMPILE
+ // The session is definitely not open at this point.
+ ResetEvent(m_hSessionOpenEvent);
+#endif // RIGHT_SIDE_COMPILE
+
+ // Close the connection if we haven't done so already.
+ m_pipe.Disconnect();
+
+ // Drain any remaining entries in the send queue (aborting them when they need completions).
+ {
+ TransportLockHolder sLockHolder(&m_sStateLock);
+
+ Message *pMsg;
+ while ((pMsg = m_pSendQueueFirst) != NULL)
+ {
+ // Remove message from the queue.
+ m_pSendQueueFirst = pMsg->m_pNext;
+
+ // Determine whether the message needs to be deleted by us before we signal any completion (because
+ // once we signal the completion pMsg might become invalid immediately if it's not a copy).
+ bool fMustDelete = pMsg->m_pOrigMessage != pMsg;
+
+ // If there's a waiter (i.e. we don't own the message) it know that the operation didn't really
+ // complete, it was aborted.
+ if (!fMustDelete)
+ pMsg->m_pOrigMessage->m_fAborted = true;
+
+ // Determine how to complete the message.
+ switch (pMsg->m_sHeader.m_eType)
+ {
+ case MT_SessionRequest:
+ case MT_SessionAccept:
+ case MT_SessionReject:
+ case MT_SessionResync:
+ case MT_SessionClose:
+ _ASSERTE(!"Session management messages should not be on send queue");
+ break;
+
+ case MT_Event:
+ break;
+
+#ifdef RIGHT_SIDE_COMPILE
+ case MT_ReadMemory:
+ case MT_WriteMemory:
+ case MT_VirtualUnwind:
+ case MT_GetDCB:
+ case MT_SetDCB:
+ case MT_GetAppDomainCB:
+ // On the RS these are the original requests. Signal the completion event.
+ SignalReplyEvent(pMsg);
+ break;
+#else // RIGHT_SIDE_COMPILE
+ case MT_ReadMemory:
+ case MT_WriteMemory:
+ case MT_VirtualUnwind:
+ case MT_GetDCB:
+ case MT_SetDCB:
+ case MT_GetAppDomainCB:
+ // On the LS these are replies to the original request. Nobody's waiting on these.
+ break;
+#endif // RIGHT_SIDE_COMPILE
+
+ default:
+ _ASSERTE(!"Unknown message type");
+ }
+
+ // If the message was a copy, deallocate the resources now.
+ if (fMustDelete)
+ {
+ if (pMsg->m_pbDataBlock)
+ delete [] pMsg->m_pbDataBlock;
+ delete pMsg;
+ }
+ }
+ } // Leave m_sStateLock
+
+ // Now release all the resources allocated for the transport now that the
+ // worker thread isn't using them anymore.
+ Release();
+}
+
+// Given a fully initialized debugger event structure, return the size of the structure in bytes (this is not
+// trivial since DebuggerIPCEvent contains a large union member which can cause the portion containing
+// significant data to vary wildy from event to event).
+DWORD DbgTransportSession::GetEventSize(DebuggerIPCEvent *pEvent)
+{
+ DWORD cbBaseSize = offsetof(DebuggerIPCEvent, LeftSideStartupData);
+ DWORD cbAdditionalSize = 0;
+
+ switch (pEvent->type & DB_IPCE_TYPE_MASK)
+ {
+ case DB_IPCE_SYNC_COMPLETE:
+ case DB_IPCE_THREAD_ATTACH:
+ case DB_IPCE_THREAD_DETACH:
+ case DB_IPCE_USER_BREAKPOINT:
+ case DB_IPCE_EXIT_APP_DOMAIN:
+ case DB_IPCE_SET_DEBUG_STATE_RESULT:
+ case DB_IPCE_FUNC_EVAL_ABORT_RESULT:
+ case DB_IPCE_CONTROL_C_EVENT:
+ case DB_IPCE_FUNC_EVAL_CLEANUP_RESULT:
+ case DB_IPCE_SET_METHOD_JMC_STATUS_RESULT:
+ case DB_IPCE_SET_MODULE_JMC_STATUS_RESULT:
+ case DB_IPCE_FUNC_EVAL_RUDE_ABORT_RESULT:
+ case DB_IPCE_INTERCEPT_EXCEPTION_RESULT:
+ case DB_IPCE_INTERCEPT_EXCEPTION_COMPLETE:
+ case DB_IPCE_CREATE_PROCESS:
+ case DB_IPCE_SET_NGEN_COMPILER_FLAGS_RESULT:
+ case DB_IPCE_LEFTSIDE_STARTUP:
+ case DB_IPCE_ASYNC_BREAK:
+ case DB_IPCE_CONTINUE:
+ case DB_IPCE_ATTACHING:
+ case DB_IPCE_GET_NGEN_COMPILER_FLAGS:
+ case DB_IPCE_DETACH_FROM_PROCESS:
+ case DB_IPCE_CONTROL_C_EVENT_RESULT:
+ cbAdditionalSize = 0;
+ break;
+
+ case DB_IPCE_BREAKPOINT:
+ cbAdditionalSize = sizeof(pEvent->BreakpointData);
+ break;
+
+ case DB_IPCE_LOAD_MODULE:
+ cbAdditionalSize = sizeof(pEvent->LoadModuleData);
+ break;
+
+ case DB_IPCE_UNLOAD_MODULE:
+ cbAdditionalSize = sizeof(pEvent->UnloadModuleData);
+ break;
+
+ case DB_IPCE_LOAD_CLASS:
+ cbAdditionalSize = sizeof(pEvent->LoadClass);
+ break;
+
+ case DB_IPCE_UNLOAD_CLASS:
+ cbAdditionalSize = sizeof(pEvent->UnloadClass);
+ break;
+
+ case DB_IPCE_EXCEPTION:
+ cbAdditionalSize = sizeof(pEvent->Exception);
+ break;
+
+ case DB_IPCE_BREAKPOINT_ADD_RESULT:
+ cbAdditionalSize = sizeof(pEvent->BreakpointData);
+ break;
+
+ case DB_IPCE_STEP_RESULT:
+ cbAdditionalSize = sizeof(pEvent->StepData);
+ if (pEvent->StepData.rangeCount)
+ cbAdditionalSize += (pEvent->StepData.rangeCount - 1) * sizeof(COR_DEBUG_STEP_RANGE);
+ break;
+
+ case DB_IPCE_STEP_COMPLETE:
+ cbAdditionalSize = sizeof(pEvent->StepData);
+ break;
+
+ case DB_IPCE_GET_BUFFER_RESULT:
+ cbAdditionalSize = sizeof(pEvent->GetBufferResult);
+ break;
+
+ case DB_IPCE_RELEASE_BUFFER_RESULT:
+ cbAdditionalSize = sizeof(pEvent->ReleaseBufferResult);
+ break;
+
+ case DB_IPCE_ENC_ADD_FIELD:
+ cbAdditionalSize = sizeof(pEvent->EnCUpdate);
+ break;
+
+ case DB_IPCE_APPLY_CHANGES_RESULT:
+ cbAdditionalSize = sizeof(pEvent->ApplyChangesResult);
+ break;
+
+ case DB_IPCE_FIRST_LOG_MESSAGE:
+ cbAdditionalSize = sizeof(pEvent->FirstLogMessage);
+ break;
+
+ case DB_IPCE_LOGSWITCH_SET_MESSAGE:
+ cbAdditionalSize = sizeof(pEvent->LogSwitchSettingMessage);
+ break;
+
+ case DB_IPCE_CREATE_APP_DOMAIN:
+ cbAdditionalSize = sizeof(pEvent->AppDomainData);
+ break;
+
+ case DB_IPCE_LOAD_ASSEMBLY:
+ cbAdditionalSize = sizeof(pEvent->AssemblyData);
+ break;
+
+ case DB_IPCE_UNLOAD_ASSEMBLY:
+ cbAdditionalSize = sizeof(pEvent->AssemblyData);
+ break;
+
+ case DB_IPCE_FUNC_EVAL_SETUP_RESULT:
+ cbAdditionalSize = sizeof(pEvent->FuncEvalSetupComplete);
+ break;
+
+ case DB_IPCE_FUNC_EVAL_COMPLETE:
+ cbAdditionalSize = sizeof(pEvent->FuncEvalComplete);
+ break;
+
+ case DB_IPCE_SET_REFERENCE_RESULT:
+ cbAdditionalSize = sizeof(pEvent->SetReference);
+ break;
+
+ case DB_IPCE_NAME_CHANGE:
+ cbAdditionalSize = sizeof(pEvent->NameChange);
+ break;
+
+ case DB_IPCE_UPDATE_MODULE_SYMS:
+ cbAdditionalSize = sizeof(pEvent->UpdateModuleSymsData);
+ break;
+
+ case DB_IPCE_ENC_REMAP:
+ cbAdditionalSize = sizeof(pEvent->EnCRemap);
+ break;
+
+ case DB_IPCE_SET_VALUE_CLASS_RESULT:
+ cbAdditionalSize = sizeof(pEvent->SetValueClass);
+ break;
+
+ case DB_IPCE_BREAKPOINT_SET_ERROR:
+ cbAdditionalSize = sizeof(pEvent->BreakpointSetErrorData);
+ break;
+
+ case DB_IPCE_ENC_UPDATE_FUNCTION:
+ cbAdditionalSize = sizeof(pEvent->EnCUpdate);
+ break;
+
+ case DB_IPCE_GET_METHOD_JMC_STATUS_RESULT:
+ cbAdditionalSize = sizeof(pEvent->SetJMCFunctionStatus);
+ break;
+
+ case DB_IPCE_GET_THREAD_FOR_TASKID_RESULT:
+ cbAdditionalSize = sizeof(pEvent->GetThreadForTaskIdResult);
+ break;
+
+ case DB_IPCE_CREATE_CONNECTION:
+ cbAdditionalSize = sizeof(pEvent->CreateConnection);
+ break;
+
+ case DB_IPCE_DESTROY_CONNECTION:
+ cbAdditionalSize = sizeof(pEvent->ConnectionChange);
+ break;
+
+ case DB_IPCE_CHANGE_CONNECTION:
+ cbAdditionalSize = sizeof(pEvent->ConnectionChange);
+ break;
+
+ case DB_IPCE_EXCEPTION_CALLBACK2:
+ cbAdditionalSize = sizeof(pEvent->ExceptionCallback2);
+ break;
+
+ case DB_IPCE_EXCEPTION_UNWIND:
+ cbAdditionalSize = sizeof(pEvent->ExceptionUnwind);
+ break;
+
+ case DB_IPCE_CREATE_HANDLE_RESULT:
+ cbAdditionalSize = sizeof(pEvent->CreateHandleResult);
+ break;
+
+ case DB_IPCE_ENC_REMAP_COMPLETE:
+ cbAdditionalSize = sizeof(pEvent->EnCRemapComplete);
+ break;
+
+ case DB_IPCE_ENC_ADD_FUNCTION:
+ cbAdditionalSize = sizeof(pEvent->EnCUpdate);
+ break;
+
+ case DB_IPCE_GET_NGEN_COMPILER_FLAGS_RESULT:
+ cbAdditionalSize = sizeof(pEvent->JitDebugInfo);
+ break;
+
+ case DB_IPCE_MDA_NOTIFICATION:
+ cbAdditionalSize = sizeof(pEvent->MDANotification);
+ break;
+
+ case DB_IPCE_GET_GCHANDLE_INFO_RESULT:
+ cbAdditionalSize = sizeof(pEvent->GetGCHandleInfoResult);
+ break;
+
+ case DB_IPCE_SET_IP:
+ cbAdditionalSize = sizeof(pEvent->SetIP);
+ break;
+
+ case DB_IPCE_BREAKPOINT_ADD:
+ cbAdditionalSize = sizeof(pEvent->BreakpointData);
+ break;
+
+ case DB_IPCE_BREAKPOINT_REMOVE:
+ cbAdditionalSize = sizeof(pEvent->BreakpointData);
+ break;
+
+ case DB_IPCE_STEP_CANCEL:
+ cbAdditionalSize = sizeof(pEvent->StepData);
+ break;
+
+ case DB_IPCE_STEP:
+ cbAdditionalSize = sizeof(pEvent->StepData);
+ if (pEvent->StepData.rangeCount)
+ cbAdditionalSize += (pEvent->StepData.rangeCount - 1) * sizeof(COR_DEBUG_STEP_RANGE);
+ break;
+
+ case DB_IPCE_STEP_OUT:
+ cbAdditionalSize = sizeof(pEvent->StepData);
+ break;
+
+ case DB_IPCE_GET_BUFFER:
+ cbAdditionalSize = sizeof(pEvent->GetBuffer);
+ break;
+
+ case DB_IPCE_RELEASE_BUFFER:
+ cbAdditionalSize = sizeof(pEvent->ReleaseBuffer);
+ break;
+
+ case DB_IPCE_SET_CLASS_LOAD_FLAG:
+ cbAdditionalSize = sizeof(pEvent->SetClassLoad);
+ break;
+
+ case DB_IPCE_APPLY_CHANGES:
+ cbAdditionalSize = sizeof(pEvent->ApplyChanges);
+ break;
+
+ case DB_IPCE_SET_NGEN_COMPILER_FLAGS:
+ cbAdditionalSize = sizeof(pEvent->JitDebugInfo);
+ break;
+
+ case DB_IPCE_IS_TRANSITION_STUB:
+ cbAdditionalSize = sizeof(pEvent->IsTransitionStub);
+ break;
+
+ case DB_IPCE_IS_TRANSITION_STUB_RESULT:
+ cbAdditionalSize = sizeof(pEvent->IsTransitionStubResult);
+ break;
+
+ case DB_IPCE_MODIFY_LOGSWITCH:
+ cbAdditionalSize = sizeof(pEvent->LogSwitchSettingMessage);
+ break;
+
+ case DB_IPCE_ENABLE_LOG_MESSAGES:
+ cbAdditionalSize = sizeof(pEvent->LogSwitchSettingMessage);
+ break;
+
+ case DB_IPCE_FUNC_EVAL:
+ cbAdditionalSize = sizeof(pEvent->FuncEval);
+ break;
+
+ case DB_IPCE_SET_REFERENCE:
+ cbAdditionalSize = sizeof(pEvent->SetReference);
+ break;
+
+ case DB_IPCE_FUNC_EVAL_ABORT:
+ cbAdditionalSize = sizeof(pEvent->FuncEvalAbort);
+ break;
+
+ case DB_IPCE_FUNC_EVAL_CLEANUP:
+ cbAdditionalSize = sizeof(pEvent->FuncEvalCleanup);
+ break;
+
+ case DB_IPCE_SET_ALL_DEBUG_STATE:
+ cbAdditionalSize = sizeof(pEvent->SetAllDebugState);
+ break;
+
+ case DB_IPCE_SET_VALUE_CLASS:
+ cbAdditionalSize = sizeof(pEvent->SetValueClass);
+ break;
+
+ case DB_IPCE_SET_METHOD_JMC_STATUS:
+ cbAdditionalSize = sizeof(pEvent->SetJMCFunctionStatus);
+ break;
+
+ case DB_IPCE_GET_METHOD_JMC_STATUS:
+ cbAdditionalSize = sizeof(pEvent->SetJMCFunctionStatus);
+ break;
+
+ case DB_IPCE_SET_MODULE_JMC_STATUS:
+ cbAdditionalSize = sizeof(pEvent->SetJMCFunctionStatus);
+ break;
+
+ case DB_IPCE_GET_THREAD_FOR_TASKID:
+ cbAdditionalSize = sizeof(pEvent->GetThreadForTaskId);
+ break;
+
+ case DB_IPCE_FUNC_EVAL_RUDE_ABORT:
+ cbAdditionalSize = sizeof(pEvent->FuncEvalRudeAbort);
+ break;
+
+ case DB_IPCE_CREATE_HANDLE:
+ cbAdditionalSize = sizeof(pEvent->CreateHandle);
+ break;
+
+ case DB_IPCE_DISPOSE_HANDLE:
+ cbAdditionalSize = sizeof(pEvent->DisposeHandle);
+ break;
+
+ case DB_IPCE_INTERCEPT_EXCEPTION:
+ cbAdditionalSize = sizeof(pEvent->InterceptException);
+ break;
+
+ case DB_IPCE_GET_GCHANDLE_INFO:
+ cbAdditionalSize = sizeof(pEvent->GetGCHandleInfo);
+ break;
+
+ case DB_IPCE_CUSTOM_NOTIFICATION:
+ cbAdditionalSize = sizeof(pEvent->CustomNotification);
+ break;
+
+ default:
+ printf("Unknown debugger event type: 0x%x\n", (pEvent->type & DB_IPCE_TYPE_MASK));
+ _ASSERTE(!"Unknown debugger event type");
+ }
+
+ return cbBaseSize + cbAdditionalSize;
+}
+#ifdef _PREFAST_
+#pragma warning(pop)
+#endif
+
+#ifdef _DEBUG
+// Debug helper which returns the name associated with a MessageType.
+const char *DbgTransportSession::MessageName(MessageType eType)
+{
+ switch (eType)
+ {
+ case MT_SessionRequest:
+ return "SessionRequest";
+ case MT_SessionAccept:
+ return "SessionAccept";
+ case MT_SessionReject:
+ return "SessionReject";
+ case MT_SessionResync:
+ return "SessionResync";
+ case MT_SessionClose:
+ return "SessionClose";
+ case MT_Event:
+ return "Event";
+ case MT_ReadMemory:
+ return "ReadMemory";
+ case MT_WriteMemory:
+ return "WriteMemory";
+ case MT_VirtualUnwind:
+ return "VirtualUnwind";
+ case MT_GetDCB:
+ return "GetDCB";
+ case MT_SetDCB:
+ return "SetDCB";
+ case MT_GetAppDomainCB:
+ return "GetAppDomainCB";
+ default:
+ _ASSERTE(!"Unknown message type");
+ return NULL;
+ }
+}
+
+// Debug logging helper which logs an incoming message of any type (as long as logging for that message
+// class is currently enabled).
+void DbgTransportSession::DbgTransportLogMessageReceived(MessageHeader *pHeader)
+{
+ switch (pHeader->m_eType)
+ {
+ case MT_SessionRequest:
+ DbgTransportLog(LC_Session, "Received 'SessionRequest'");
+ DBG_TRANSPORT_INC_STAT(ReceivedSessionRequest);
+ return;
+ case MT_SessionAccept:
+ DbgTransportLog(LC_Session, "Received 'SessionAccept'");
+ DBG_TRANSPORT_INC_STAT(ReceivedSessionAccept);
+ return;
+ case MT_SessionReject:
+ DbgTransportLog(LC_Session, "Received 'SessionReject'");
+ DBG_TRANSPORT_INC_STAT(ReceivedSessionReject);
+ return;
+ case MT_SessionResync:
+ DbgTransportLog(LC_Session, "Received 'SessionResync'");
+ DBG_TRANSPORT_INC_STAT(ReceivedSessionResync);
+ return;
+ case MT_SessionClose:
+ DbgTransportLog(LC_Session, "Received 'SessionClose'");
+ DBG_TRANSPORT_INC_STAT(ReceivedSessionClose);
+ return;
+ case MT_Event:
+ DbgTransportLog(LC_Events, "Received '%s'",
+ IPCENames::GetName((DebuggerIPCEventType)(DWORD)pHeader->TypeSpecificData.Event.m_eType));
+ DBG_TRANSPORT_INC_STAT(ReceivedEvent);
+ return;
+#ifdef RIGHT_SIDE_COMPILE
+ case MT_ReadMemory:
+ DbgTransportLog(LC_Requests, "Received 'ReadMemory(0x%08X, %u)' reply",
+ (PBYTE)pHeader->TypeSpecificData.MemoryAccess.m_pbLeftSideBuffer,
+ (DWORD)pHeader->TypeSpecificData.MemoryAccess.m_cbLeftSideBuffer);
+ DBG_TRANSPORT_INC_STAT(ReceivedReadMemory);
+ return;
+ case MT_WriteMemory:
+ DbgTransportLog(LC_Requests, "Received 'WriteMemory(0x%08X, %u)' reply",
+ (PBYTE)pHeader->TypeSpecificData.MemoryAccess.m_pbLeftSideBuffer,
+ (DWORD)pHeader->TypeSpecificData.MemoryAccess.m_cbLeftSideBuffer);
+ DBG_TRANSPORT_INC_STAT(ReceivedWriteMemory);
+ return;
+ case MT_VirtualUnwind:
+ DbgTransportLog(LC_Requests, "Received 'VirtualUnwind' reply");
+ DBG_TRANSPORT_INC_STAT(ReceivedVirtualUnwind);
+ return;
+ case MT_GetDCB:
+ DbgTransportLog(LC_Requests, "Received 'GetDCB' reply");
+ DBG_TRANSPORT_INC_STAT(ReceivedGetDCB);
+ return;
+ case MT_SetDCB:
+ DbgTransportLog(LC_Requests, "Received 'SetDCB' reply");
+ DBG_TRANSPORT_INC_STAT(ReceivedSetDCB);
+ return;
+ case MT_GetAppDomainCB:
+ DbgTransportLog(LC_Requests, "Received 'GetAppDomainCB' reply");
+ DBG_TRANSPORT_INC_STAT(ReceivedGetAppDomainCB);
+ return;
+#else // RIGHT_SIDE_COMPILE
+ case MT_ReadMemory:
+ DbgTransportLog(LC_Requests, "Received 'ReadMemory(0x%08X, %u)'",
+ (PBYTE)pHeader->TypeSpecificData.MemoryAccess.m_pbLeftSideBuffer,
+ (DWORD)pHeader->TypeSpecificData.MemoryAccess.m_cbLeftSideBuffer);
+ DBG_TRANSPORT_INC_STAT(ReceivedReadMemory);
+ return;
+ case MT_WriteMemory:
+ DbgTransportLog(LC_Requests, "Received 'WriteMemory(0x%08X, %u)'",
+ (PBYTE)pHeader->TypeSpecificData.MemoryAccess.m_pbLeftSideBuffer,
+ (DWORD)pHeader->TypeSpecificData.MemoryAccess.m_cbLeftSideBuffer);
+ DBG_TRANSPORT_INC_STAT(ReceivedWriteMemory);
+ return;
+ case MT_VirtualUnwind:
+ DbgTransportLog(LC_Requests, "Received 'VirtualUnwind'");
+ DBG_TRANSPORT_INC_STAT(ReceivedVirtualUnwind);
+ return;
+ case MT_GetDCB:
+ DbgTransportLog(LC_Requests, "Received 'GetDCB'");
+ DBG_TRANSPORT_INC_STAT(ReceivedGetDCB);
+ return;
+ case MT_SetDCB:
+ DbgTransportLog(LC_Requests, "Received 'SetDCB'");
+ DBG_TRANSPORT_INC_STAT(ReceivedSetDCB);
+ return;
+ case MT_GetAppDomainCB:
+ DbgTransportLog(LC_Requests, "Received 'GetAppDomainCB'");
+ DBG_TRANSPORT_INC_STAT(ReceivedGetAppDomainCB);
+ return;
+#endif // RIGHT_SIDE_COMPILE
+ default:
+ _ASSERTE(!"Unknown message type");
+ return;
+ }
+}
+
+static CLRRandom s_faultInjectionRandom;
+
+// Helper method used by the DBG_TRANSPORT_SHOULD_INJECT_FAULT macro.
+bool DbgTransportSession::DbgTransportShouldInjectFault(DbgTransportFaultOp eOp, const char *szOpName)
+{
+ static DWORD s_dwFaultInjection = 0xffffffff;
+
+ // Init the fault injection system if that hasn't already happened.
+ if (s_dwFaultInjection == 0xffffffff)
+ {
+ s_dwFaultInjection = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgTransportFaultInject);
+
+ // Try for repeatable failures here by always initializing the random seed to a fixed value. But use
+ // different seeds for the left and right sides or they'll end up in lock step. The
+ // DBG_TRANSPORT_FAULT_THIS_SIDE macro is a convenient integer value that differs on each side.
+ s_faultInjectionRandom.Init(DBG_TRANSPORT_FAULT_THIS_SIDE);
+
+ // Clamp failure rate to a permissable value.
+ if ((s_dwFaultInjection & DBG_TRANSPORT_FAULT_RATE_MASK) > 99)
+ s_dwFaultInjection = (s_dwFaultInjection & ~DBG_TRANSPORT_FAULT_RATE_MASK) | 99;
+ }
+
+ // Map current session state into the bitmask format used for fault injection control.
+ DWORD dwState = 0;
+ switch (m_eState)
+ {
+ case SS_Opening_NC:
+ case SS_Opening:
+ dwState = FS_Opening;
+ break;
+ case SS_Resync_NC:
+ case SS_Resync:
+ dwState = FS_Resync;
+ break;
+ case SS_Open:
+ dwState = FS_Open;
+ break;
+ case SS_Closed:
+ break;
+ default:
+ _ASSERTE(!"Bad session state");
+ }
+
+ if ((s_dwFaultInjection & DBG_TRANSPORT_FAULT_THIS_SIDE) &&
+ (s_dwFaultInjection & eOp) &&
+ (s_dwFaultInjection & dwState))
+ {
+ // We're faulting this side, op and state. Roll the dice and see if this particular call should fail.
+ DWORD dwChance = s_faultInjectionRandom.Next(100);
+ if (dwChance < (s_dwFaultInjection & DBG_TRANSPORT_FAULT_RATE_MASK))
+ {
+ DbgTransportLog(LC_FaultInject, "Injected fault for %s operation", szOpName);
+#if defined(FEATURE_CORESYSTEM)
+ // not supported
+#else
+ WSASetLastError(WSAEFAULT);
+#endif // defined(FEATURE_CORESYSTEM)
+ return true;
+ }
+ }
+
+ return false;
+}
+#endif // _DEBUG
+
+// Lock abstraction code (hides difference in lock implementation between left and right side).
+#ifdef RIGHT_SIDE_COMPILE
+
+// On the right side we use a CRITICAL_SECTION.
+
+void DbgTransportLock::Init()
+{
+ InitializeCriticalSection(&m_sLock);
+}
+
+void DbgTransportLock::Destroy()
+{
+ DeleteCriticalSection(&m_sLock);
+}
+
+void DbgTransportLock::Enter()
+{
+ EnterCriticalSection(&m_sLock);
+}
+
+void DbgTransportLock::Leave()
+{
+ LeaveCriticalSection(&m_sLock);
+}
+#else // RIGHT_SIDE_COMPILE
+
+// On the left side we use a Crst.
+
+void DbgTransportLock::Init()
+{
+ m_sLock.Init(CrstDbgTransport, (CrstFlags)(CRST_UNSAFE_ANYMODE | CRST_DEBUGGER_THREAD | CRST_TAKEN_DURING_SHUTDOWN));
+}
+
+void DbgTransportLock::Destroy()
+{
+}
+
+void DbgTransportLock::Enter()
+{
+ m_sLock.Enter();
+}
+
+void DbgTransportLock::Leave()
+{
+ m_sLock.Leave();
+}
+#endif // RIGHT_SIDE_COMPILE
+
+#endif // (!defined(RIGHT_SIDE_COMPILE) && defined(FEATURE_DBGIPC_TRANSPORT_VM)) || (defined(RIGHT_SIDE_COMPILE) && defined(FEATURE_DBGIPC_TRANSPORT_DI))
diff --git a/src/debug/shared/i386/.gitmirror b/src/debug/shared/i386/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/debug/shared/i386/.gitmirror
@@ -0,0 +1 @@
+Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror. \ No newline at end of file
diff --git a/src/debug/shared/i386/primitives.cpp b/src/debug/shared/i386/primitives.cpp
new file mode 100644
index 0000000000..e47f3e731e
--- /dev/null
+++ b/src/debug/shared/i386/primitives.cpp
@@ -0,0 +1,127 @@
+// 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.
+//*****************************************************************************
+// File: primitives.cpp
+//
+
+//
+// Platform-specific debugger primitives
+//
+//*****************************************************************************
+
+#include "primitives.h"
+
+
+//
+// CopyThreadContext() does an intelligent copy from pSrc to pDst,
+// respecting the ContextFlags of both contexts.
+//
+void CORDbgCopyThreadContext(DT_CONTEXT* pDst, const DT_CONTEXT* pSrc)
+{
+ DWORD dstFlags = pDst->ContextFlags;
+ DWORD srcFlags = pSrc->ContextFlags;
+ LOG((LF_CORDB, LL_INFO1000000,
+ "CP::CTC: pDst=0x%08x dstFlags=0x%x, pSrc=0x%08x srcFlags=0x%x\n",
+ pDst, dstFlags, pSrc, srcFlags));
+
+ if ((dstFlags & srcFlags & DT_CONTEXT_CONTROL) == DT_CONTEXT_CONTROL)
+ CopyContextChunk(&(pDst->Ebp), &(pSrc->Ebp), pDst->ExtendedRegisters,
+ DT_CONTEXT_CONTROL);
+
+ if ((dstFlags & srcFlags & DT_CONTEXT_INTEGER) == DT_CONTEXT_INTEGER)
+ CopyContextChunk(&(pDst->Edi), &(pSrc->Edi), &(pDst->Ebp),
+ DT_CONTEXT_INTEGER);
+
+ if ((dstFlags & srcFlags & DT_CONTEXT_SEGMENTS) == DT_CONTEXT_SEGMENTS)
+ CopyContextChunk(&(pDst->SegGs), &(pSrc->SegGs), &(pDst->Edi),
+ DT_CONTEXT_SEGMENTS);
+
+ if ((dstFlags & srcFlags & DT_CONTEXT_FLOATING_POINT) == DT_CONTEXT_FLOATING_POINT)
+ CopyContextChunk(&(pDst->FloatSave), &(pSrc->FloatSave),
+ (&pDst->FloatSave)+1,
+ DT_CONTEXT_FLOATING_POINT);
+
+ if ((dstFlags & srcFlags & DT_CONTEXT_DEBUG_REGISTERS) ==
+ DT_CONTEXT_DEBUG_REGISTERS)
+ CopyContextChunk(&(pDst->Dr0), &(pSrc->Dr0), &(pDst->FloatSave),
+ DT_CONTEXT_DEBUG_REGISTERS);
+
+ if ((dstFlags & srcFlags & DT_CONTEXT_EXTENDED_REGISTERS) ==
+ DT_CONTEXT_EXTENDED_REGISTERS)
+ CopyContextChunk(pDst->ExtendedRegisters,
+ pSrc->ExtendedRegisters,
+ &(pDst->ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION]),
+ DT_CONTEXT_EXTENDED_REGISTERS);
+}
+
+
+// Update the regdisplay from a given context.
+void CORDbgSetDebuggerREGDISPLAYFromContext(DebuggerREGDISPLAY *pDRD,
+ DT_CONTEXT* pContext)
+{
+ // We must pay attention to the context flags so that we only use valid portions
+ // of the context.
+ DWORD flags = pContext->ContextFlags;
+ if ((flags & DT_CONTEXT_CONTROL) == DT_CONTEXT_CONTROL)
+ {
+ pDRD->PC = (SIZE_T)CORDbgGetIP(pContext);
+ pDRD->SP = (SIZE_T)CORDbgGetSP(pContext);
+ pDRD->FP = (SIZE_T)CORDbgGetFP(pContext);
+ }
+
+ if ((flags & DT_CONTEXT_INTEGER) == DT_CONTEXT_INTEGER)
+ {
+ pDRD->Eax = pContext->Eax;
+ pDRD->Ebx = pContext->Ebx;
+ pDRD->Ecx = pContext->Ecx;
+ pDRD->Edx = pContext->Edx;
+ pDRD->Esi = pContext->Esi;
+ pDRD->Edi = pContext->Edi;
+ }
+}
+
+#if defined(ALLOW_VMPTR_ACCESS) || !defined(RIGHT_SIDE_COMPILE)
+void SetDebuggerREGDISPLAYFromREGDISPLAY(DebuggerREGDISPLAY* pDRD, REGDISPLAY* pRD)
+{
+ SUPPORTS_DAC_HOST_ONLY;
+ // Frame pointer
+ LPVOID FPAddress = GetRegdisplayFPAddress(pRD);
+ pDRD->FP = (FPAddress == NULL ? 0 : *((SIZE_T *)FPAddress));
+ pDRD->Edi = (pRD->pEdi == NULL ? 0 : *(pRD->pEdi));
+ pDRD->Esi = (pRD->pEsi == NULL ? 0 : *(pRD->pEsi));
+ pDRD->Ebx = (pRD->pEbx == NULL ? 0 : *(pRD->pEbx));
+ pDRD->Edx = (pRD->pEdx == NULL ? 0 : *(pRD->pEdx));
+ pDRD->Ecx = (pRD->pEcx == NULL ? 0 : *(pRD->pEcx));
+ pDRD->Eax = (pRD->pEax == NULL ? 0 : *(pRD->pEax));
+
+#if defined(USE_REMOTE_REGISTER_ADDRESS)
+ pDRD->pFP = PushedRegAddr(pRD, FPAddress);
+ pDRD->pEdi = PushedRegAddr(pRD, pRD->pEdi);
+ pDRD->pEsi = PushedRegAddr(pRD, pRD->pEsi);
+ pDRD->pEbx = PushedRegAddr(pRD, pRD->pEbx);
+ pDRD->pEdx = PushedRegAddr(pRD, pRD->pEdx);
+ pDRD->pEcx = PushedRegAddr(pRD, pRD->pEcx);
+ pDRD->pEax = PushedRegAddr(pRD, pRD->pEax);
+#else // !USE_REMOTE_REGISTER_ADDRESS
+ pDRD->pFP = NULL;
+ pDRD->pEdi = NULL;
+ pDRD->pEsi = NULL;
+ pDRD->pEbx = NULL;
+ pDRD->pEdx = NULL;
+ pDRD->pEcx = NULL;
+ pDRD->pEax = NULL;
+#endif // !USE_REMOTE_REGISTER_ADDRESS
+
+ pDRD->SP = pRD->Esp;
+ pDRD->PC = pRD->ControlPC;
+
+ // Please leave EBP, ESP, EIP at the front so I don't have to scroll
+ // left to see the most important registers. Thanks!
+ LOG( (LF_CORDB, LL_INFO1000, "DT::TASSC:Registers:"
+ "Ebp = %x Esp = %x Eip = %x Edi:%d "
+ "Esi = %x Ebx = %x Edx = %x Ecx = %x Eax = %x\n",
+ pDRD->FP, pDRD->SP, pDRD->PC, pDRD->Edi,
+ pDRD->Esi, pDRD->Ebx, pDRD->Edx, pDRD->Ecx, pDRD->Eax ) );
+}
+#endif // ALLOW_VMPTR_ACCESS || !RIGHT_SIDE_COMPILE
diff --git a/src/debug/shared/stringcopyholder.cpp b/src/debug/shared/stringcopyholder.cpp
new file mode 100644
index 0000000000..dfe55196a1
--- /dev/null
+++ b/src/debug/shared/stringcopyholder.cpp
@@ -0,0 +1,83 @@
+// 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.
+//
+// StringCopyHolder.cpp
+//
+
+//
+// This is the implementation of a simple holder for a copy of a string.
+//
+// ======================================================================================
+
+
+// Initialize to Null.
+StringCopyHolder::StringCopyHolder()
+{
+ m_szData = NULL;
+}
+
+// Dtor to free memory.
+StringCopyHolder::~StringCopyHolder()
+{
+ Clear();
+}
+
+// Reset the string to NULL and free memory
+void StringCopyHolder::Clear()
+{
+ if (m_szData != NULL)
+ {
+ delete [] m_szData;
+ m_szData = NULL;
+ }
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Allocate a copy of the incoming string and assign it to this holder.
+// pStringSrc can be NULL or a pointer to a null-terminated string.
+//
+// Arguments:
+// pStringSrc - string to be duplicated
+//
+// Returns:
+// S_OK on success. That means it succeeded in allocating and copying pStringSrc.
+// If the incoming string is NULL, then the underlying string will be NULL as welll.
+// Callers may want to assert that IsSet() is true if they don't expect a NULL string.
+//
+// E_OUTOFMEMORY on failure. Only happens in an OOM scenario.
+//
+// Notes:
+// Since this function is a callback from DAC, it must not take the process lock.
+// If it does, we may deadlock between the DD lock and the process lock.
+// If we really need to take the process lock for whatever reason, we must take it in the DBI functions
+// which call the DAC API that ends up calling this function.
+// See code:InternalDacCallbackHolder for more information.
+//
+
+HRESULT StringCopyHolder::AssignCopy(const WCHAR * pStringSrc)
+{
+ if (m_szData != NULL)
+ {
+ Clear();
+ }
+
+ if (pStringSrc == NULL)
+ {
+ m_szData = NULL;
+ }
+ else
+ {
+ SIZE_T cchLen = wcslen(pStringSrc) + 1;
+ m_szData = new (nothrow) WCHAR[cchLen];
+ if (m_szData == NULL)
+ {
+ _ASSERTE(!"Warning: Out-of-Memory in Right Side. This component is not robust in OOM sccenarios.");
+ return E_OUTOFMEMORY;
+ }
+
+ wcscpy_s(m_szData, cchLen, pStringSrc);
+ }
+ return S_OK;
+}
diff --git a/src/debug/shared/utils.cpp b/src/debug/shared/utils.cpp
new file mode 100644
index 0000000000..cc6727396d
--- /dev/null
+++ b/src/debug/shared/utils.cpp
@@ -0,0 +1,203 @@
+// 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.
+
+// Type-safe helper wrapper to get an EXCEPTION_RECORD slot as a CORDB_ADDRESS
+//
+// Arguments:
+// pRecord - exception record
+// idxSlot - slot to retrieve from.
+//
+// Returns:
+// contents of slot as a CordbAddress.
+CORDB_ADDRESS GetExceptionInfoAsAddress(const EXCEPTION_RECORD * pRecord, int idxSlot)
+{
+ _ASSERTE((idxSlot >= 0) && (idxSlot < EXCEPTION_MAXIMUM_PARAMETERS));
+
+ // ExceptionInformation is an array of ULONG_PTR. CORDB_ADDRESS is a 0-extended ULONG64.
+ // So the implicit cast will work here on x86. On 64-bit, it's basically a nop.
+ return pRecord->ExceptionInformation[idxSlot];
+}
+
+
+// Determine if an exception event is a Debug event for this flavor of the CLR.
+//
+// Arguments:
+// pRecord - exception record
+// pClrBaseAddress - clr Instance ID for which CLR in the target we're checking against.
+//
+// Returns:
+// NULL if the exception is not a CLR managed debug event for the given Clr instance.
+// Else, address in target process of managed debug event described by the exception (the payload).
+//
+// Notes:
+// This decodes events raised by code:Debugger.SendRawEvent
+// Anybody can spoof our exception, so this is not a reliably safe method.
+// With multiple CLRs in the same process, it's essential to use the proper pClrBaseAddress.
+CORDB_ADDRESS IsEventDebuggerNotification(
+ const EXCEPTION_RECORD * pRecord,
+ CORDB_ADDRESS pClrBaseAddress
+ )
+{
+ _ASSERTE(pRecord != NULL);
+
+ // Must specify a CLR instance.
+ _ASSERTE(pClrBaseAddress != NULL);
+
+ // If it's not even our exception code, then it's not ours.
+ if (pRecord->ExceptionCode != CLRDBG_NOTIFICATION_EXCEPTION_CODE)
+ {
+ return NULL;
+ }
+
+ //
+ // Format of an ExceptionInformation parameter is:
+ // 0: cookie (CLRDBG_EXCEPTION_DATA_CHECKSUM)
+ // 1: Base address of mscorwks. This identifies the instance of the CLR.
+ // 2: Target Address of DebuggerIPCEvent, which contains the "real" event.
+ //
+ if (pRecord->NumberParameters != 3)
+ {
+ return NULL;
+ }
+
+ // 1st argument should always be the cookie.
+ // If cookie doesn't match, very likely it's a stray exception that happens to be using
+ // our code.
+ DWORD cookie = (DWORD) pRecord->ExceptionInformation[0];
+ if (cookie != CLRDBG_EXCEPTION_DATA_CHECKSUM)
+ {
+ return NULL;
+ }
+
+ // TODO: We don't do this check in case of non-windows debugging now, because we don't support
+ // multi-instance debugging.
+#if !defined(FEATURE_DBGIPC_TRANSPORT_VM) && !defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ // If base-address doesn't match, then it's likely an event from another version of the CLR
+ // in the target.
+ // We need to be careful here. CORDB_ADDRESS is a ULONG64, whereas ExceptionInformation[1]
+ // is ULONG_PTR. So on 32-bit, their sizes don't match.
+ CORDB_ADDRESS pTargetBase = GetExceptionInfoAsAddress(pRecord, 1);
+ if (pTargetBase != pClrBaseAddress)
+ {
+ return NULL;
+ }
+#endif
+
+ // It passes all the format checks. So now get the payload.
+ CORDB_ADDRESS ptrRemoteManagedEvent = GetExceptionInfoAsAddress(pRecord, 2);
+
+ return ptrRemoteManagedEvent;
+}
+
+#if defined(FEATURE_DBGIPC_TRANSPORT_VM) || defined(FEATURE_DBGIPC_TRANSPORT_DI)
+void InitEventForDebuggerNotification(DEBUG_EVENT * pDebugEvent,
+ CORDB_ADDRESS pClrBaseAddress,
+ DebuggerIPCEvent * pIPCEvent)
+{
+ pDebugEvent->dwDebugEventCode = EXCEPTION_DEBUG_EVENT;
+
+ pDebugEvent->u.Exception.dwFirstChance = TRUE;
+ pDebugEvent->u.Exception.ExceptionRecord.ExceptionCode = CLRDBG_NOTIFICATION_EXCEPTION_CODE;
+ pDebugEvent->u.Exception.ExceptionRecord.ExceptionFlags = 0;
+ pDebugEvent->u.Exception.ExceptionRecord.ExceptionRecord = NULL;
+ pDebugEvent->u.Exception.ExceptionRecord.ExceptionAddress = 0;
+
+ //
+ // Format of an ExceptionInformation parameter is:
+ // 0: cookie (CLRDBG_EXCEPTION_DATA_CHECKSUM)
+ // 1: Base address of mscorwks. This identifies the instance of the CLR.
+ // 2: Target Address of DebuggerIPCEvent, which contains the "real" event.
+ //
+ pDebugEvent->u.Exception.ExceptionRecord.NumberParameters = 3;
+ pDebugEvent->u.Exception.ExceptionRecord.ExceptionInformation[0] = CLRDBG_EXCEPTION_DATA_CHECKSUM;
+ pDebugEvent->u.Exception.ExceptionRecord.ExceptionInformation[1] = (ULONG_PTR)CORDB_ADDRESS_TO_PTR(pClrBaseAddress);
+ pDebugEvent->u.Exception.ExceptionRecord.ExceptionInformation[2] = (ULONG_PTR)pIPCEvent;
+
+ _ASSERTE(IsEventDebuggerNotification(&(pDebugEvent->u.Exception.ExceptionRecord), pClrBaseAddress) ==
+ PTR_TO_CORDB_ADDRESS(pIPCEvent));
+}
+#endif // defined(FEATURE_DBGIPC_TRANSPORT_VM) || defined(FEATURE_DBGIPC_TRANSPORT_DI)
+
+//-----------------------------------------------------------------------------
+// Helper to get the proper decorated name
+// Caller ensures that pBufSize is large enough. We'll assert just to check,
+// but no runtime failure.
+// pBuf - the output buffer to write the decorated name in
+// cBufSizeInChars - the size of the buffer in characters, including the null.
+// pPrefx - The undecorated name of the event.
+//-----------------------------------------------------------------------------
+void GetPidDecoratedName(__out_z __out_ecount(cBufSizeInChars) WCHAR * pBuf, int cBufSizeInChars, const WCHAR * pPrefix, DWORD pid)
+{
+ const WCHAR szGlobal[] = W("Global\\");
+ int szGlobalLen;
+ szGlobalLen = NumItems(szGlobal) - 1;
+
+ // Caller should always give us a big enough buffer.
+ _ASSERTE(cBufSizeInChars > (int) wcslen(pPrefix) + szGlobalLen);
+
+ // PERF: We are no longer calling GetSystemMetrics in an effort to prevent
+ // superfluous DLL loading on startup. Instead, we're prepending
+ // "Global\" to named kernel objects if we are on NT5 or above. The
+ // only bad thing that results from this is that you can't debug
+ // cross-session on NT4. Big bloody deal.
+ wcscpy_s(pBuf, cBufSizeInChars, szGlobal);
+ pBuf += szGlobalLen;
+ cBufSizeInChars -= szGlobalLen;
+
+ int ret;
+ ret = _snwprintf_s(pBuf, cBufSizeInChars, _TRUNCATE, pPrefix, pid);
+
+ // Since this is all determined at compile time, we know we should have enough buffer.
+ _ASSERTE (ret != STRUNCATE);
+}
+
+//-----------------------------------------------------------------------------
+// The 'internal' version of our IL to Native map (the DebuggerILToNativeMap struct)
+// has an extra field - ICorDebugInfo::SourceTypes source. The 'external/user-visible'
+// version (COR_DEBUG_IL_TO_NATIVE_MAP) lacks that field, so we need to translate our
+// internal version to the external version.
+// "Export" seemed more succinct than "CopyInternalToExternalILToNativeMap" :)
+//-----------------------------------------------------------------------------
+void ExportILToNativeMap(ULONG32 cMap, // [in] Min size of mapExt, mapInt
+ COR_DEBUG_IL_TO_NATIVE_MAP mapExt[], // [in] Filled in here
+ struct DebuggerILToNativeMap mapInt[],// [in] Source of info
+ SIZE_T sizeOfCode) // [in] Total size of method (bytes)
+{
+ ULONG32 iMap;
+ _ASSERTE(mapExt != NULL);
+ _ASSERTE(mapInt != NULL);
+
+ for(iMap=0; iMap < cMap; iMap++)
+ {
+ mapExt[iMap].ilOffset = mapInt[iMap].ilOffset ;
+ mapExt[iMap].nativeStartOffset = mapInt[iMap].nativeStartOffset ;
+ mapExt[iMap].nativeEndOffset = mapInt[iMap].nativeEndOffset ;
+
+ // An element that has an end offset of zero, means "till the end of
+ // the method". Pretty this up so that customers don't have to care about
+ // this.
+ if ((DWORD)mapInt[iMap].source & (DWORD)ICorDebugInfo::NATIVE_END_OFFSET_UNKNOWN)
+ {
+ mapExt[iMap].nativeEndOffset = (ULONG32)sizeOfCode;
+ }
+
+#if defined(_DEBUG)
+ {
+ // UnsafeGetConfigDWORD
+ SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE;
+ static int fReturnSourceTypeForTesting = -1;
+ if (fReturnSourceTypeForTesting == -1)
+ fReturnSourceTypeForTesting = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ReturnSourceTypeForTesting);
+
+ if (fReturnSourceTypeForTesting)
+ {
+ // Steal the most significant four bits from the native end offset for the source type.
+ _ASSERTE( (mapExt[iMap].nativeEndOffset >> 28) == 0x0 );
+ _ASSERTE( (ULONG32)(mapInt[iMap].source) < 0xF );
+ mapExt[iMap].nativeEndOffset |= ((ULONG32)(mapInt[iMap].source) << 28);
+ }
+ }
+#endif // _DEBUG
+ }
+}
diff --git a/src/debug/shim/.gitmirror b/src/debug/shim/.gitmirror
new file mode 100644
index 0000000000..f507630f94
--- /dev/null
+++ b/src/debug/shim/.gitmirror
@@ -0,0 +1 @@
+Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror. \ No newline at end of file
diff --git a/src/debug/shim/CMakeLists.txt b/src/debug/shim/CMakeLists.txt
new file mode 100644
index 0000000000..8720eb7df2
--- /dev/null
+++ b/src/debug/shim/CMakeLists.txt
@@ -0,0 +1,15 @@
+if(WIN32)
+ #use static crt
+ add_definitions(-MT)
+ add_definitions(-DHOST_IS_WINDOWS_OS)
+endif(WIN32)
+
+if(CLR_CMAKE_PLATFORM_UNIX)
+ add_compile_options(-fPIC)
+endif(CLR_CMAKE_PLATFORM_UNIX)
+
+set(DEBUGSHIM_SOURCES
+ debugshim.cpp
+)
+
+add_library_clr(debugshim STATIC ${DEBUGSHIM_SOURCES}) \ No newline at end of file
diff --git a/src/debug/shim/debugshim.cpp b/src/debug/shim/debugshim.cpp
new file mode 100644
index 0000000000..03b9c5f550
--- /dev/null
+++ b/src/debug/shim/debugshim.cpp
@@ -0,0 +1,657 @@
+// 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.
+//*****************************************************************************
+// debugshim.cpp
+//
+
+//
+//*****************************************************************************
+
+#include "debugshim.h"
+#include "dbgutil.h"
+#include <crtdbg.h>
+#include <clrinternal.h> //has the CLR_ID_V4_DESKTOP guid in it
+#include "palclr.h"
+
+#ifndef IMAGE_FILE_MACHINE_ARMNT
+#define IMAGE_FILE_MACHINE_ARMNT 0x01c4 // ARM Thumb-2 Little-Endian
+#endif
+
+#ifndef IMAGE_FILE_MACHINE_ARM64
+#define IMAGE_FILE_MACHINE_ARM64 0xAA64 // ARM64 Little-Endian
+#endif
+
+// making the defines very clear, these represent the host architecture - aka
+// the arch on which this code is running
+#if defined(_X86_)
+#define _HOST_X86_
+#elif defined(_AMD64_)
+#define _HOST_AMD64_
+#elif defined(_ARM_)
+#define _HOST_ARM_
+#elif defined(_ARM64_)
+#define _HOST_ARM64_
+#endif
+
+//*****************************************************************************
+// CLRDebuggingImpl implementation (ICLRDebugging)
+//*****************************************************************************
+
+typedef HRESULT (__stdcall *OpenVirtualProcessImplFnPtr)(ULONG64 clrInstanceId,
+ IUnknown * pDataTarget,
+ HMODULE hDacDll,
+ CLR_DEBUGGING_VERSION * pMaxDebuggerSupportedVersion,
+ REFIID riid,
+ IUnknown ** ppInstance,
+ CLR_DEBUGGING_PROCESS_FLAGS * pdwFlags);
+
+typedef HRESULT (__stdcall *OpenVirtualProcess2FnPtr)(ULONG64 clrInstanceId,
+ IUnknown * pDataTarget,
+ HMODULE hDacDll,
+ REFIID riid,
+ IUnknown ** ppInstance,
+ CLR_DEBUGGING_PROCESS_FLAGS * pdwFlags);
+
+// Implementation of ICLRDebugging::OpenVirtualProcess
+//
+// Arguments:
+// moduleBaseAddress - the address of the module which might be a CLR
+// pDataTarget - the data target for inspecting the process
+// pLibraryProvider - a callback for locating DBI and DAC
+// pMaxDebuggerSupportedVersion - the max version of the CLR that this debugger will support debugging
+// riidProcess - the IID of the interface that should be passed back in ppProcess
+// ppProcess - output for the ICorDebugProcess# if this module is a CLR
+// pVersion - the CLR version if this module is a CLR
+// pFlags - output, see the CLR_DEBUGGING_PROCESS_FLAGS for more details. Right now this has only one possible
+// value which indicates this runtime had an unhandled exception
+STDMETHODIMP CLRDebuggingImpl::OpenVirtualProcess(
+ ULONG64 moduleBaseAddress,
+ IUnknown * pDataTarget,
+ ICLRDebuggingLibraryProvider * pLibraryProvider,
+ CLR_DEBUGGING_VERSION * pMaxDebuggerSupportedVersion,
+ REFIID riidProcess,
+ IUnknown ** ppProcess,
+ CLR_DEBUGGING_VERSION * pVersion,
+ CLR_DEBUGGING_PROCESS_FLAGS * pFlags)
+{
+ //PRECONDITION(CheckPointer(pDataTarget));
+
+ HRESULT hr = S_OK;
+ ICorDebugDataTarget * pDt = NULL;
+ HMODULE hDbi = NULL;
+ HMODULE hDac = NULL;
+ DWORD dbiTimestamp;
+ DWORD dbiSizeOfImage;
+ WCHAR dbiName[MAX_PATH_FNAME] = {0};
+ DWORD dacTimestamp;
+ DWORD dacSizeOfImage;
+ WCHAR dacName[MAX_PATH_FNAME] = {0};
+ CLR_DEBUGGING_VERSION version;
+ BOOL versionSupportedByCaller = FALSE;
+
+
+
+ // argument checking
+ if( (ppProcess != NULL || pFlags != NULL) && pLibraryProvider == NULL)
+ {
+ hr = E_POINTER; // the library provider must be specified if either
+ // ppProcess or pFlags is non-NULL
+ }
+ else if( (ppProcess != NULL || pFlags != NULL) && pMaxDebuggerSupportedVersion == NULL)
+ {
+ hr = E_POINTER; // the max supported version must be specified if either
+ // ppProcess or pFlags is non-NULL
+ }
+ else if(pVersion != NULL && pVersion->wStructVersion != 0)
+ {
+ hr = CORDBG_E_UNSUPPORTED_VERSION_STRUCT;
+ }
+ else if(FAILED(pDataTarget->QueryInterface(__uuidof(ICorDebugDataTarget), (void**) &pDt)))
+ {
+ hr = CORDBG_E_MISSING_DATA_TARGET_INTERFACE;
+ }
+
+ if(SUCCEEDED(hr))
+ {
+ // get CLR version
+ // The expectation is that new versions of the CLR will continue to use the same GUID
+ // (unless there's a reason to hide them from older shims), but debuggers will tell us the
+ // CLR version they're designed for and mscordbi.dll can decide whether or not to accept it.
+ version.wStructVersion = 0;
+ hr = GetCLRInfo(pDt,
+ moduleBaseAddress,
+ &version,
+ &dbiTimestamp,
+ &dbiSizeOfImage,
+ dbiName,
+ MAX_PATH_FNAME,
+ &dacTimestamp,
+ &dacSizeOfImage,
+ dacName,
+ MAX_PATH_FNAME);
+ }
+
+ // If we need to fetch either the process info or the flags info then we need to find
+ // mscordbi and DAC and do the version specific OVP work
+ if(SUCCEEDED(hr) && (ppProcess != NULL || pFlags != NULL))
+ {
+ // ask library provider for dbi
+ if(FAILED(pLibraryProvider->ProvideLibrary(dbiName, dbiTimestamp, dbiSizeOfImage, &hDbi)) ||
+ hDbi == NULL)
+ {
+ hr = CORDBG_E_LIBRARY_PROVIDER_ERROR;
+ }
+
+ if(SUCCEEDED(hr))
+ {
+ // Adjust the timestamp and size of image if this DAC is a known buggy version and needs to be retargeted
+ RetargetDacIfNeeded(&dacTimestamp, &dacSizeOfImage);
+
+ // ask library provider for dac
+ if(FAILED(pLibraryProvider->ProvideLibrary(dacName, dacTimestamp, dacSizeOfImage, &hDac)) ||
+ hDac == NULL)
+ {
+ hr = CORDBG_E_LIBRARY_PROVIDER_ERROR;
+ }
+ }
+
+ if(SUCCEEDED(hr))
+ {
+ // get access to OVP and call it
+ OpenVirtualProcessImplFnPtr ovpFn = (OpenVirtualProcessImplFnPtr) GetProcAddress(hDbi, "OpenVirtualProcessImpl");
+ if(ovpFn == NULL)
+ {
+ // Fallback to CLR v4 Beta1 path, but skip some of the checking we'd normally do (maxSupportedVersion, etc.)
+ OpenVirtualProcess2FnPtr ovp2Fn = (OpenVirtualProcess2FnPtr) GetProcAddress(hDbi, "OpenVirtualProcess2");
+ if (ovp2Fn == NULL)
+ {
+ hr = CORDBG_E_LIBRARY_PROVIDER_ERROR;
+ }
+ else
+ {
+ hr = ovp2Fn(moduleBaseAddress, pDataTarget, hDac, riidProcess, ppProcess, pFlags);
+ }
+ }
+ else
+ {
+ // Have a CLR v4 Beta2+ DBI, call it and let it do the version check
+ hr = ovpFn(moduleBaseAddress, pDataTarget, hDac, pMaxDebuggerSupportedVersion, riidProcess, ppProcess, pFlags);
+ if(FAILED(hr))
+ {
+ _ASSERTE(ppProcess == NULL || *ppProcess == NULL);
+ _ASSERTE(pFlags == NULL || *pFlags == 0);
+ }
+ }
+ }
+ }
+
+ //version is still valid in some failure cases
+ if(pVersion != NULL &&
+ (SUCCEEDED(hr) ||
+ (hr == CORDBG_E_UNSUPPORTED_DEBUGGING_MODEL) ||
+ (hr == CORDBG_E_UNSUPPORTED_FORWARD_COMPAT)))
+ {
+ memcpy(pVersion, &version, sizeof(CLR_DEBUGGING_VERSION));
+ }
+
+ // free the data target we QI'ed earlier
+ if(pDt != NULL)
+ {
+ pDt->Release();
+ }
+
+ return hr;
+}
+
+// Checks to see if this DAC is one of a known set of old DAC builds which contains an issue.
+// If so we retarget to a newer compatible version which has the bug fixed. This is done
+// by changing the PE information used to lookup the DAC.
+//
+// Arguments
+// pdwTimeStamp - on input, the timestamp of DAC as embedded in the CLR image
+// on output, a potentially new timestamp for an updated DAC to use
+// instead
+// pdwSizeOfImage - on input, the sizeOfImage of DAC as embedded in the CLR image
+// on output, a potentially new sizeOfImage for an updated DAC to use
+// instead
+VOID CLRDebuggingImpl::RetargetDacIfNeeded(DWORD* pdwTimeStamp,
+ DWORD* pdwSizeOfImage)
+{
+
+ // This code is auto generated by the CreateRetargetTable tool
+ // on 3/4/2011 6:35 PM
+ // and then copy-pasted here.
+ //
+ //
+ //
+ // Retarget the GDR1 amd64 build
+ if( (*pdwTimeStamp == 0x4d536868) && (*pdwSizeOfImage == 0x17b000))
+ {
+ *pdwTimeStamp = 0x4d71a160;
+ *pdwSizeOfImage = 0x17b000;
+ }
+ // Retarget the GDR1 x86 build
+ else if( (*pdwTimeStamp == 0x4d5368f2) && (*pdwSizeOfImage == 0x120000))
+ {
+ *pdwTimeStamp = 0x4d71a14f;
+ *pdwSizeOfImage = 0x120000;
+ }
+ // Retarget the RTM amd64 build
+ else if( (*pdwTimeStamp == 0x4ba21fa7) && (*pdwSizeOfImage == 0x17b000))
+ {
+ *pdwTimeStamp = 0x4d71a13c;
+ *pdwSizeOfImage = 0x17b000;
+ }
+ // Retarget the RTM x86 build
+ else if( (*pdwTimeStamp == 0x4ba1da25) && (*pdwSizeOfImage == 0x120000))
+ {
+ *pdwTimeStamp = 0x4d71a128;
+ *pdwSizeOfImage = 0x120000;
+ }
+ // This code is auto generated by the CreateRetargetTable tool
+ // on 8/17/2011 1:28 AM
+ // and then copy-pasted here.
+ //
+ //
+ //
+ // Retarget the GDR2 amd64 build
+ else if( (*pdwTimeStamp == 0x4da428c7) && (*pdwSizeOfImage == 0x17b000))
+ {
+ *pdwTimeStamp = 0x4e4b7bc2;
+ *pdwSizeOfImage = 0x17b000;
+ }
+ // Retarget the GDR2 x86 build
+ else if( (*pdwTimeStamp == 0x4da3fe52) && (*pdwSizeOfImage == 0x120000))
+ {
+ *pdwTimeStamp = 0x4e4b7bb1;
+ *pdwSizeOfImage = 0x120000;
+ }
+ // End auto-generated code
+}
+
+#define PE_FIXEDFILEINFO_SIGNATURE 0xFEEF04BD
+
+// The format of the special debugging resource we embed in CLRs starting in
+// v4
+struct CLR_DEBUG_RESOURCE
+{
+ DWORD dwVersion;
+ GUID signature;
+ DWORD dwDacTimeStamp;
+ DWORD dwDacSizeOfImage;
+ DWORD dwDbiTimeStamp;
+ DWORD dwDbiSizeOfImage;
+};
+
+// Checks to see if a module is a CLR and if so, fetches the debug data
+// from the embedded resource
+//
+// Arguments
+// pDataTarget - dataTarget for the process we are inspecting
+// moduleBaseAddress - base address of a module we should inspect
+// pVersion - output, the version of the CLR detected if this is a CLR
+// pdwDbiTimeStamp - the timestamp of DBI as embedded in the CLR image
+// pdwDbiSizeOfImage - the SizeOfImage of DBI as embedded in the CLR image
+// pDbiName - output, the filename of DBI (as calculated by this function but that might change)
+// dwDbiNameCharCount - input, the number of WCHARs in the buffer pointed to by pDbiName
+// pdwDacTimeStampe - the timestamp of DAC as embedded in the CLR image
+// pdwDacSizeOfImage - the SizeOfImage of DAC as embedded in the CLR image
+// pDacName - output, the filename of DAC (as calculated by this function but that might change)
+// dwDacNameCharCount - input, the number of WCHARs in the buffer pointed to by pDacName
+HRESULT CLRDebuggingImpl::GetCLRInfo(ICorDebugDataTarget* pDataTarget,
+ ULONG64 moduleBaseAddress,
+ CLR_DEBUGGING_VERSION* pVersion,
+ DWORD* pdwDbiTimeStamp,
+ DWORD* pdwDbiSizeOfImage,
+ __out_z __inout_ecount(dwDbiNameCharCount) WCHAR* pDbiName,
+ DWORD dwDbiNameCharCount,
+ DWORD* pdwDacTimeStamp,
+ DWORD* pdwDacSizeOfImage,
+ __out_z __inout_ecount(dwDacNameCharCount) WCHAR* pDacName,
+ DWORD dwDacNameCharCount)
+{
+#ifndef FEATURE_PAL
+ WORD imageFileMachine = 0;
+ DWORD resourceSectionRVA = 0;
+ HRESULT hr = GetMachineAndResourceSectionRVA(pDataTarget, moduleBaseAddress, &imageFileMachine, &resourceSectionRVA);
+
+ // We want the version resource which has type = RT_VERSION = 16, name = 1, language = 0x409
+ DWORD versionResourceRVA = 0;
+ DWORD versionResourceSize = 0;
+ if(SUCCEEDED(hr))
+ {
+ hr = GetResourceRvaFromResourceSectionRva(pDataTarget, moduleBaseAddress, resourceSectionRVA, 16, 1, 0x409,
+ &versionResourceRVA, &versionResourceSize);
+ }
+
+ // At last we get our version info
+ VS_FIXEDFILEINFO fixedFileInfo = {0};
+ if(SUCCEEDED(hr))
+ {
+ // The version resource has 3 words, then the unicode string "VS_VERSION_INFO"
+ // (16 WCHARS including the null terminator)
+ // then padding to a 32-bit boundary, then the VS_FIXEDFILEINFO struct
+ DWORD fixedFileInfoRVA = ((versionResourceRVA + 3*2 + 16*2 + 3)/4)*4;
+ hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + fixedFileInfoRVA, (BYTE*)&fixedFileInfo, sizeof(fixedFileInfo));
+ }
+
+ //Verify the signature on the version resource
+ if(SUCCEEDED(hr) && fixedFileInfo.dwSignature != PE_FIXEDFILEINFO_SIGNATURE)
+ {
+ hr = CORDBG_E_NOT_CLR;
+ }
+
+ // Record the version information
+ if(SUCCEEDED(hr))
+ {
+ pVersion->wMajor = (WORD) (fixedFileInfo.dwProductVersionMS >> 16);
+ pVersion->wMinor = (WORD) (fixedFileInfo.dwProductVersionMS & 0xFFFF);
+ pVersion->wBuild = (WORD) (fixedFileInfo.dwProductVersionLS >> 16);
+ pVersion->wRevision = (WORD) (fixedFileInfo.dwProductVersionLS & 0xFFFF);
+ }
+
+ // Now grab the special clr debug info resource
+ // We may need to scan a few different names searching though...
+ // 1) CLRDEBUGINFO<host_os><host_arch> where host_os = 'WINDOWS' or 'CORESYS' and host_arch = 'X86' or 'ARM' or 'AMD64'
+ // 2) For back-compat if the host os is windows and the host architecture matches the target then CLRDEBUGINFO is used with no suffix.
+ DWORD debugResourceRVA = 0;
+ DWORD debugResourceSize = 0;
+ BOOL useCrossPlatformNaming = FALSE;
+ if(SUCCEEDED(hr))
+ {
+ // the initial state is that we haven't found a proper resource
+ HRESULT hrGetResource = E_FAIL;
+
+ // First check for the resource which has type = RC_DATA = 10, name = "CLRDEBUGINFO<host_os><host_arch>", language = 0
+#if defined (HOST_IS_WINDOWS_OS) && defined(_HOST_X86_)
+ const WCHAR * resourceName = W("CLRDEBUGINFOWINDOWSX86");
+#endif
+
+#if !defined (HOST_IS_WINDOWS_OS) && defined(_HOST_X86_)
+ const WCHAR * resourceName = W("CLRDEBUGINFOCORESYSX86");
+#endif
+
+#if defined (HOST_IS_WINDOWS_OS) && defined(_HOST_AMD64_)
+ const WCHAR * resourceName = W("CLRDEBUGINFOWINDOWSAMD64");
+#endif
+
+#if !defined (HOST_IS_WINDOWS_OS) && defined(_HOST_AMD64_)
+ const WCHAR * resourceName = W("CLRDEBUGINFOCORESYSAMD64");
+#endif
+
+#if defined (HOST_IS_WINDOWS_OS) && defined(_HOST_ARM64_)
+ const WCHAR * resourceName = W("CLRDEBUGINFOWINDOWSARM64");
+#endif
+
+#if !defined (HOST_IS_WINDOWS_OS) && defined(_HOST_ARM64_)
+ const WCHAR * resourceName = W("CLRDEBUGINFOCORESYSARM64");
+#endif
+
+#if defined (HOST_IS_WINDOWS_OS) && defined(_HOST_ARM_)
+ const WCHAR * resourceName = W("CLRDEBUGINFOWINDOWSARM");
+#endif
+
+#if !defined (HOST_IS_WINDOWS_OS) && defined(_HOST_ARM_)
+ const WCHAR * resourceName = W("CLRDEBUGINFOCORESYSARM");
+#endif
+
+ hrGetResource = GetResourceRvaFromResourceSectionRvaByName(pDataTarget, moduleBaseAddress, resourceSectionRVA, 10, resourceName, 0,
+ &debugResourceRVA, &debugResourceSize);
+ useCrossPlatformNaming = SUCCEEDED(hrGetResource);
+
+
+#if defined(HOST_IS_WINDOWS_OS) && (defined(_HOST_X86_) || defined(_HOST_AMD64_) || defined(_HOST_ARM_))
+ #if defined(_HOST_X86_)
+ #define _HOST_MACHINE_TYPE IMAGE_FILE_MACHINE_I386
+ #elif defined(_HOST_AMD64_)
+ #define _HOST_MACHINE_TYPE IMAGE_FILE_MACHINE_AMD64
+ #elif defined(_HOST_ARM_)
+ #define _HOST_MACHINE_TYPE IMAGE_FILE_MACHINE_ARMNT
+ #endif
+
+ // if this is windows, and if host_arch matches target arch then we can fallback to searching for CLRDEBUGINFO on failure
+ if(FAILED(hrGetResource) && (imageFileMachine == _HOST_MACHINE_TYPE))
+ {
+ hrGetResource = GetResourceRvaFromResourceSectionRvaByName(pDataTarget, moduleBaseAddress, resourceSectionRVA, 10, W("CLRDEBUGINFO"), 0,
+ &debugResourceRVA, &debugResourceSize);
+ }
+
+ #undef _HOST_MACHINE_TYPE
+#endif
+ // if the search failed, we don't recognize the CLR
+ if(FAILED(hrGetResource))
+ hr = CORDBG_E_NOT_CLR;
+ }
+
+ CLR_DEBUG_RESOURCE debugResource;
+ if(SUCCEEDED(hr) && debugResourceSize != sizeof(debugResource))
+ {
+ hr = CORDBG_E_NOT_CLR;
+ }
+
+ // Get the special debug resource from the image and return the results
+ if(SUCCEEDED(hr))
+ {
+ hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + debugResourceRVA, (BYTE*)&debugResource, sizeof(debugResource));
+ }
+ if(SUCCEEDED(hr) && (debugResource.dwVersion != 0))
+ {
+ hr = CORDBG_E_NOT_CLR;
+ }
+
+ // The signature needs to match m_skuId exactly, except for m_skuId=CLR_ID_ONECORE_CLR which is
+ // also compatible with the older CLR_ID_PHONE_CLR signature.
+ if(SUCCEEDED(hr) &&
+ (debugResource.signature != m_skuId) &&
+ !( (debugResource.signature == CLR_ID_PHONE_CLR) && (m_skuId == CLR_ID_ONECORE_CLR) ))
+ {
+ hr = CORDBG_E_NOT_CLR;
+ }
+
+ if(SUCCEEDED(hr) &&
+ (debugResource.signature != CLR_ID_ONECORE_CLR) &&
+ useCrossPlatformNaming)
+ {
+ FormatLongDacModuleName(pDacName, dwDacNameCharCount, imageFileMachine, &fixedFileInfo);
+ swprintf_s(pDbiName, dwDbiNameCharCount, W("%s_%s.dll"), MAIN_DBI_MODULE_NAME_W, W("x86"));
+ }
+ else
+ {
+ if(m_skuId == CLR_ID_V4_DESKTOP)
+ swprintf_s(pDacName, dwDacNameCharCount, W("%s.dll"), CLR_DAC_MODULE_NAME_W);
+ else
+ swprintf_s(pDacName, dwDacNameCharCount, W("%s.dll"), CORECLR_DAC_MODULE_NAME_W);
+ swprintf_s(pDbiName, dwDbiNameCharCount, W("%s.dll"), MAIN_DBI_MODULE_NAME_W);
+ }
+
+ if(SUCCEEDED(hr))
+ {
+ *pdwDbiTimeStamp = debugResource.dwDbiTimeStamp;
+ *pdwDbiSizeOfImage = debugResource.dwDbiSizeOfImage;
+ *pdwDacTimeStamp = debugResource.dwDacTimeStamp;
+ *pdwDacSizeOfImage = debugResource.dwDacSizeOfImage;
+ }
+
+ // any failure should be interpreted as this module not being a CLR
+ if(FAILED(hr))
+ {
+ return CORDBG_E_NOT_CLR;
+ }
+ else
+ {
+ return S_OK;
+ }
+#else
+ swprintf_s(pDacName, dwDacNameCharCount, W("%s"), MAKEDLLNAME_W(CORECLR_DAC_MODULE_NAME_W));
+ swprintf_s(pDbiName, dwDbiNameCharCount, W("%s"), MAKEDLLNAME_W(MAIN_DBI_MODULE_NAME_W));
+
+ pVersion->wMajor = 0;
+ pVersion->wMinor = 0;
+ pVersion->wBuild = 0;
+ pVersion->wRevision = 0;
+
+ *pdwDbiTimeStamp = 0;
+ *pdwDbiSizeOfImage = 0;
+ *pdwDacTimeStamp = 0;
+ *pdwDacSizeOfImage = 0;
+
+ return S_OK;
+#endif // FEATURE_PAL
+}
+
+// Formats the long name for DAC
+HRESULT CLRDebuggingImpl::FormatLongDacModuleName(__out_z __inout_ecount(cchBuffer) WCHAR * pBuffer,
+ DWORD cchBuffer,
+ DWORD targetImageFileMachine,
+ VS_FIXEDFILEINFO * pVersion)
+{
+
+#ifndef HOST_IS_WINDOWS_OS
+ _ASSERTE(!"NYI");
+ return E_NOTIMPL;
+#endif
+
+#if defined(_HOST_X86_)
+ const WCHAR* pHostArch = W("x86");
+#elif defined(_HOST_AMD64_)
+ const WCHAR* pHostArch = W("amd64");
+#elif defined(_HOST_ARM_)
+ const WCHAR* pHostArch = W("arm");
+#elif defined(_HOST_ARM64_)
+ const WCHAR* pHostArch = W("arm64");
+#else
+ _ASSERTE(!"Unknown host arch");
+ return E_NOTIMPL;
+#endif
+
+ const WCHAR* pDacBaseName = NULL;
+ if(m_skuId == CLR_ID_V4_DESKTOP)
+ pDacBaseName = CLR_DAC_MODULE_NAME_W;
+ else if(m_skuId == CLR_ID_CORECLR || m_skuId == CLR_ID_PHONE_CLR || m_skuId == CLR_ID_ONECORE_CLR)
+ pDacBaseName = CORECLR_DAC_MODULE_NAME_W;
+ else
+ {
+ _ASSERTE(!"Unknown SKU id");
+ return E_UNEXPECTED;
+ }
+
+ const WCHAR* pTargetArch = NULL;
+ if(targetImageFileMachine == IMAGE_FILE_MACHINE_I386)
+ {
+ pTargetArch = W("x86");
+ }
+ else if(targetImageFileMachine == IMAGE_FILE_MACHINE_AMD64)
+ {
+ pTargetArch = W("amd64");
+ }
+ else if(targetImageFileMachine == IMAGE_FILE_MACHINE_ARMNT)
+ {
+ pTargetArch = W("arm");
+ }
+ else if(targetImageFileMachine == IMAGE_FILE_MACHINE_ARM64)
+ {
+ pTargetArch = W("arm64");
+ }
+ else
+ {
+ _ASSERTE(!"Unknown target image file machine type");
+ return E_INVALIDARG;
+ }
+
+ const WCHAR* pBuildFlavor = W("");
+ if(pVersion->dwFileFlags & VS_FF_DEBUG)
+ {
+ if(pVersion->dwFileFlags & VS_FF_SPECIALBUILD)
+ pBuildFlavor = W(".dbg");
+ else
+ pBuildFlavor = W(".chk");
+ }
+
+ // WARNING: if you change the formatting make sure you recalculate the maximum
+ // possible size string and verify callers pass a big enough buffer. This doesn't
+ // have to be a tight estimate, just make sure its >= the biggest possible DAC name
+ // and it can be calculated statically
+ DWORD minCchBuffer =
+ (DWORD) wcslen(CLR_DAC_MODULE_NAME_W) + (DWORD) wcslen(CORECLR_DAC_MODULE_NAME_W) + // max name
+ 10 + // max host arch
+ 10 + // max target arch
+ 40 + // max version
+ 10 + // max build flavor
+ (DWORD) wcslen(W("name_host_target_version.flavor.dll")) + // max intermediate formatting chars
+ 1; // null terminator
+
+ // validate the output buffer is larger than our estimate above
+ _ASSERTE(cchBuffer >= minCchBuffer);
+ if(!(cchBuffer >= minCchBuffer)) return E_INVALIDARG;
+
+ swprintf_s(pBuffer, cchBuffer, W("%s_%s_%s_%u.%u.%u.%02u%s.dll"),
+ pDacBaseName,
+ pHostArch,
+ pTargetArch,
+ pVersion->dwProductVersionMS >> 16,
+ pVersion->dwProductVersionMS & 0xFFFF,
+ pVersion->dwProductVersionLS >> 16,
+ pVersion->dwProductVersionLS & 0xFFFF,
+ pBuildFlavor);
+ return S_OK;
+}
+
+// An implementation of ICLRDebugging::CanUnloadNow
+//
+// Arguments:
+// hModule - a handle to a module provided earlier by ProvideLibrary
+//
+// Returns:
+// S_OK if the library is no longer in use and can be unloaded, S_FALSE otherwise
+//
+STDMETHODIMP CLRDebuggingImpl::CanUnloadNow(HMODULE hModule)
+{
+ // In V4 at least we don't support any unloading.
+ HRESULT hr = S_FALSE;
+
+ return hr;
+}
+
+
+
+STDMETHODIMP CLRDebuggingImpl::QueryInterface(REFIID riid, void **ppvObject)
+{
+ HRESULT hr = S_OK;
+
+ if (riid == __uuidof(IUnknown))
+ {
+ IUnknown *pItf = static_cast<IUnknown *>(this);
+ pItf->AddRef();
+ *ppvObject = pItf;
+ }
+ else if (riid == __uuidof(ICLRDebugging))
+ {
+ ICLRDebugging *pItf = static_cast<ICLRDebugging *>(this);
+ pItf->AddRef();
+ *ppvObject = pItf;
+ }
+ else
+ hr = E_NOINTERFACE;
+
+ return hr;
+}
+
+// Standard AddRef implementation
+ULONG CLRDebuggingImpl::AddRef()
+{
+ return InterlockedIncrement(&m_cRef);
+}
+
+// Standard Release implementation.
+ULONG CLRDebuggingImpl::Release()
+{
+ _ASSERTE(m_cRef > 0);
+
+ ULONG cRef = InterlockedDecrement(&m_cRef);
+
+ if (cRef == 0)
+ delete this; // Relies on virtual dtor to work properly.
+
+ return cRef;
+}
diff --git a/src/debug/shim/debugshim.h b/src/debug/shim/debugshim.h
new file mode 100644
index 0000000000..c1d9173879
--- /dev/null
+++ b/src/debug/shim/debugshim.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.
+//*****************************************************************************
+// debugshim.h
+//
+
+//
+//*****************************************************************************
+
+#ifndef _DEBUG_SHIM_
+#define _DEBUG_SHIM_
+
+#include "cor.h"
+#include "cordebug.h"
+#include <wchar.h>
+#include <metahost.h>
+
+#define CORECLR_DAC_MODULE_NAME_W W("mscordaccore")
+#define CLR_DAC_MODULE_NAME_W W("mscordacwks")
+#define MAIN_DBI_MODULE_NAME_W W("mscordbi")
+
+// forward declaration
+struct ICorDebugDataTarget;
+
+// ICLRDebugging implementation.
+class CLRDebuggingImpl : public ICLRDebugging
+{
+
+public:
+ CLRDebuggingImpl(GUID skuId) : m_cRef(0), m_skuId(skuId)
+ {
+ }
+
+ virtual ~CLRDebuggingImpl() {}
+
+public:
+ // ICLRDebugging methods:
+ STDMETHOD(OpenVirtualProcess(
+ ULONG64 moduleBaseAddress,
+ IUnknown * pDataTarget,
+ ICLRDebuggingLibraryProvider * pLibraryProvider,
+ CLR_DEBUGGING_VERSION * pMaxDebuggerSupportedVersion,
+ REFIID riidProcess,
+ IUnknown ** ppProcess,
+ CLR_DEBUGGING_VERSION * pVersion,
+ CLR_DEBUGGING_PROCESS_FLAGS * pFlags));
+
+ STDMETHOD(CanUnloadNow(HMODULE hModule));
+
+ //IUnknown methods:
+ STDMETHOD(QueryInterface(
+ REFIID riid,
+ void **ppvObject));
+
+ // Standard AddRef implementation
+ STDMETHOD_(ULONG, AddRef());
+
+ // Standard Release implementation.
+ STDMETHOD_(ULONG, Release());
+
+
+
+private:
+ VOID RetargetDacIfNeeded(DWORD* pdwTimeStamp,
+ DWORD* pdwSizeOfImage);
+
+ HRESULT GetCLRInfo(ICorDebugDataTarget * pDataTarget,
+ ULONG64 moduleBaseAddress,
+ CLR_DEBUGGING_VERSION * pVersion,
+ DWORD * pdwDbiTimeStamp,
+ DWORD * pdwDbiSizeOfImage,
+ __out_z __inout_ecount(dwDbiNameCharCount) WCHAR * pDbiName,
+ DWORD dwDbiNameCharCount,
+ DWORD * pdwDacTimeStamp,
+ DWORD * pdwDacSizeOfImage,
+ __out_z __inout_ecount(dwDacNameCharCount) WCHAR * pDacName,
+ DWORD dwDacNameCharCount);
+
+ HRESULT FormatLongDacModuleName(__out_z __inout_ecount(cchBuffer) WCHAR * pBuffer,
+ DWORD cchBuffer,
+ DWORD targetImageFileMachine,
+ VS_FIXEDFILEINFO * pVersion);
+
+ volatile LONG m_cRef;
+ GUID m_skuId;
+
+}; // class CLRDebuggingImpl
+
+#endif
diff --git a/src/debug/shim/debugshim.props b/src/debug/shim/debugshim.props
new file mode 100644
index 0000000000..8d3bb3fe65
--- /dev/null
+++ b/src/debug/shim/debugshim.props
@@ -0,0 +1,19 @@
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="dogfood">
+ <PropertyGroup>
+ <TargetType>LIBRARY</TargetType>
+ <OutputPath>$(ClrLibDest)</OutputPath>
+ <LinkSubsystem>windows</LinkSubsystem>
+ <UseMsvcrt />
+ <ExceptionHandling>$(Sehonly)</ExceptionHandling>
+ <UserIncludes>$(UserIncludes);
+ ..\;
+ ..\..\inc;
+ ..\..\..\inc;
+ </UserIncludes>
+ <CDefines>$(CDefines);UNICODE;_UNICODE</CDefines>
+ <CDefines Condition="$(HostMachineOS)=='windows'">$(CDefines);HOST_IS_WINDOWS_OS</CDefines>
+ </PropertyGroup>
+ <ItemGroup>
+ <CppCompile Include="..\debugshim.cpp" />
+ </ItemGroup>
+</Project>
diff --git a/src/debug/shim/dirs.proj b/src/debug/shim/dirs.proj
new file mode 100644
index 0000000000..45798d6237
--- /dev/null
+++ b/src/debug/shim/dirs.proj
@@ -0,0 +1,16 @@
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\clr.props" />
+
+ <PropertyGroup>
+ <BuildInPhase1>true</BuildInPhase1>
+ <BuildInPhaseDefault>false</BuildInPhaseDefault>
+ <BuildCoreBinaries>true</BuildCoreBinaries>
+ <BuildSysBinaries>true</BuildSysBinaries>
+ </PropertyGroup>
+
+ <ItemGroup Condition="'$(BuildExePhase)' == '1'">
+ <ProjectFile Include="HostLocal\debugshim.nativeproj" />
+ </ItemGroup>
+
+ <Import Project="$(_NTDRIVE)$(_NTROOT)\tools\Microsoft.DevDiv.Traversal.targets" />
+</Project>